Update React, fix null dates, add default route for missing season ID and move blurred area effect to table only
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2021-02-08 03:01:53 +01:00
parent a6ca23dd5d
commit d7208b773c
11 changed files with 77 additions and 162 deletions

View File

@@ -32,8 +32,9 @@ export class AppController {
return await this.sinusBotService.fetchStats(); return await this.sinusBotService.fetchStats();
} }
@Get('/stats/season/:id') @Get('/stats/season/:id?')
async getStats(@Param('id') id?: string): Promise<UserStatsResponse> { async getStats(@Param('id') id?: string): Promise<UserStatsResponse> {
logger.info(`Got id ${id}`)
return await this.databaseService return await this.databaseService
.fetchStats(id) .fetchStats(id)
.catch(err => { .catch(err => {

View File

@@ -7,6 +7,7 @@ import {
} from '../models/aliases'; } from '../models/aliases';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { seasons } from '@prisma/client'; import { seasons } from '@prisma/client';
import logger from 'src/logger/Logger';
@Injectable() @Injectable()
export class DatabaseService { export class DatabaseService {
@@ -25,7 +26,7 @@ export class DatabaseService {
}) })
.then(value => this.prismaClient.seasons.findUnique({ .then(value => this.prismaClient.seasons.findUnique({
where: { where: {
season_id: Number((!seasonId ? value : seasonId)) season_id: Number(seasonId ?? value)
} }
}) as Promise<seasons>) }) as Promise<seasons>)
.then((result: SeasonInfo) => { .then((result: SeasonInfo) => {

View File

@@ -14,8 +14,8 @@
"husky": "^4.3.7", "husky": "^4.3.7",
"lint-staged": "^10.5.3", "lint-staged": "^10.5.3",
"prettier": "^2.2.1", "prettier": "^2.2.1",
"react": "^16.13.1", "react": "17.0.0",
"react-dom": "^16.13.1", "react-dom": "17.0.0",
"react-router": "^5.2.0", "react-router": "^5.2.0",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "3.4.1", "react-scripts": "3.4.1",

View File

@@ -11,30 +11,10 @@ library.add(faArrowCircleLeft, faArrowCircleRight)
const App: FunctionComponent = () => ( const App: FunctionComponent = () => (
<Router> <Router>
<Switch> <Switch>
{/*<Route exact path={'/'} component={(props: IMainPageProps) => (<MainPage {...props}/>)} />*/} <Route path={'/season/:id?'} component={(props: IMainPageProps) => (<MainPage {...props}/>)} />
<Route path={'/season/:id'} component={(props: IMainPageProps) => (<MainPage {...props}/>)} />
<Redirect to={'/season'} /> <Redirect to={'/season'} />
</Switch> </Switch>
</Router> </Router>
); );
export default App; export default App;
// export default class App extends React.Component {
// componentDidMount() {
// this.setState({loading: false, mock: this.mockService.getStatsWithoutPromise()});
// this.apiService.getStats()
// .then(data => this.setState({
// isLoaded: true,
// users: data
// }))
// .catch(error => {
// error.response.json()
// .then((err: any) => this.setState({
// isLoaded: true,
// error: err.error
// }))
// });
// }

View File

@@ -7,17 +7,21 @@ const SeasonDetail: React.FC<IUserListProperties> = (props: IUserListProperties)
return ( return (
<span className="SeasonDetail" data-testid="SeasonDetail"> <span className="SeasonDetail" data-testid="SeasonDetail">
Season {seasonId} - Duration: {dates.start.toDateString()} - {dates.end.toDateString()} Season {seasonId} - Duration: {dateToString(dates.start)} - {dateToString(dates.end)}
</span> </span>
) )
} }
const dateToString = (date?: Date): string => {
return date ? date.toLocaleDateString('en-GB') : 'TBD';
}
export interface SeasonDetailProperties { export interface SeasonDetailProperties {
seasonId: string, seasonId?: string,
maxSeasonId: string maxSeasonId: string
dates: { dates: {
start: Date, start?: Date,
end: Date end?: Date
} }
} }

View File

@@ -10,14 +10,18 @@ const SeasonSwitch: React.FC<IUserListProperties> = (props: IUserListProperties)
const maxSeasonId = Number(props.userStats.maxSeasonId) const maxSeasonId = Number(props.userStats.maxSeasonId)
return ( return (
<div className={"SeasonSwitch " + (!props.enabled && " no-click")} data-testid="SeasonSwitch"> <div className={"SeasonSwitch"} data-testid="SeasonSwitch">
{ {
seasonId > 1 seasonId > 1
&& (props.enabled && (props.enabled
? <Link to={"/season/" + (seasonId - 1)} onClick={() => props.onSeasonIdChange('' + (seasonId - 1))}> && <Link to={"/season/" + (seasonId - 1)} onClick={() => {
console.log("nothing happens")
props.onSeasonIdChange('' + (seasonId - 1))}}>
<FontAwesomeIcon icon="arrow-circle-left"/> <FontAwesomeIcon icon="arrow-circle-left"/>
</Link> </Link>
: <Link to={""} onClick={(e) => e.preventDefault()}> || <Link to={""} onClick={(e) => {
console.log("nothing happens2")
e.preventDefault()}}>
<FontAwesomeIcon icon="arrow-circle-left"/> <FontAwesomeIcon icon="arrow-circle-left"/>
</Link>) </Link>)
} }

View File

@@ -24,7 +24,7 @@ const createTableEntries = (entries: TableEntry[]) =>
const UserList: React.FC<IUserListProperties> = (props: IUserListProperties) => ( const UserList: React.FC<IUserListProperties> = (props: IUserListProperties) => (
<div className="UserList" data-testid="UserList"> <div className="UserList" data-testid="UserList">
<SeasonSwitch {...props} /> <SeasonSwitch {...props} />
<table> <table className={!props.enabled ? "blurred" : ""}>
<thead> <thead>
<tr> <tr>
<th>Placement</th> <th>Placement</th>
@@ -43,7 +43,7 @@ const UserList: React.FC<IUserListProperties> = (props: IUserListProperties) =>
export interface IUserListProperties { export interface IUserListProperties {
userStats: UserStatsResponse userStats: UserStatsResponse
mocked?: boolean mocked?: boolean
onSeasonIdChange: any onSeasonIdChange: React.Dispatch<React.SetStateAction<string | undefined>>
enabled: boolean enabled: boolean
} }

View File

@@ -1,6 +1,7 @@
import UserStatsResponse from "../models/UserStatsResponse"; import UserStatsResponse from "../models/UserStatsResponse";
import UserStatsService from "../services/UserStatsService";
export default class UserStatsMockService { export default class UserStatsMockService extends UserStatsService {
private static readonly mocks: UserStatsResponse[] = [ private static readonly mocks: UserStatsResponse[] = [
{ {
seasonId: "1", seasonId: "1",
@@ -33,14 +34,30 @@ export default class UserStatsMockService {
{ name: "Humen", rank: "12", onlineTime: "0d 1h 0m 0s" }, { name: "Humen", rank: "12", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "ok", onlineTime: "0d 1h 0m 0s" } { name: "Humen", rank: "ok", onlineTime: "0d 1h 0m 0s" }
] ]
} },
{
seasonId: 'undefined',
maxSeasonId: "2",
dates: {
start: undefined,
end: undefined
},
stats: [
{ name: "Humen", rank: "Overwatch Noob", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "Random Rank 3", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "Kas is cool", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "Bremsspur", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "12", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "ok", onlineTime: "0d 1h 0m 0s" }
]
}
] ]
static async getStats(seasonId: string): Promise<UserStatsResponse> { public static async getStats(seasonId?: string): Promise<UserStatsResponse> {
return Promise.resolve(this.mocks[Number(seasonId) - 1]) return Promise.resolve(this.mocks[Number((seasonId ? seasonId : 2)) - 1])
} }
static getStatsWithoutPromise(seasonId: string): UserStatsResponse { static getStatsWithoutPromise(seasonId?: string): UserStatsResponse {
return this.mocks[Number(seasonId) - 1] return this.mocks[Number((seasonId ? seasonId : 2)) - 1]
} }
} }

View File

@@ -13,13 +13,15 @@ import {ClipLoader} from "react-spinners";
import UserStatsService from "../../services/UserStatsService"; import UserStatsService from "../../services/UserStatsService";
const MainPage: FC<IMainPageProps> = (props: IMainPageProps) => { const MainPage: FC<IMainPageProps> = (props: IMainPageProps) => {
const [seasonId, setSeasonId] = useState(props.match.params.id) const [seasonId, setSeasonId] = useState(props.match.params.id)
const [error, setError] = useState<RequestError | undefined>(undefined) const [error, setError] = useState<RequestError | undefined>(undefined)
const [loading, setLoadingState] = useState(true) const [loading, setLoadingState] = useState(true)
const [seasonStats, setSeasonStats] = useState<UserStatsResponse>(UserStatsMockService.getStatsWithoutPromise(seasonId)) const [seasonStats, setSeasonStats] = useState<UserStatsResponse>(UserStatsMockService.getStatsWithoutPromise(seasonId))
const [spinnerColor] = useState('#61dafb') const [spinnerColor] = useState('#61dafb')
useEffect(() => { useEffect(() => {
setLoadingState(true)
setSeasonStats(UserStatsMockService.getStatsWithoutPromise('3'))
UserStatsService.getStats(seasonId) UserStatsService.getStats(seasonId)
.then(res => { .then(res => {
setSeasonStats(res) setSeasonStats(res)
@@ -30,7 +32,7 @@ const MainPage: FC<IMainPageProps> = (props: IMainPageProps) => {
setLoadingState(false) setLoadingState(false)
setError(new RequestError(0, "Could not retrieve stats. Try again later.")) setError(new RequestError(0, "Could not retrieve stats. Try again later."))
}) })
}, [seasonId, setLoadingState]) }, [seasonId])
const spinnerCss = ` const spinnerCss = `
margin: 0; margin: 0;
@@ -40,22 +42,20 @@ const MainPage: FC<IMainPageProps> = (props: IMainPageProps) => {
return ( return (
<div className="MainPage"> <div className="MainPage">
<Header /> <Header />
{ {
error && <ErrorContainer message={error.message} /> error && <ErrorContainer message={error.message} />
} }
<ClipLoader color={spinnerColor} loading={loading} size={150} css={spinnerCss} /> <ClipLoader color={spinnerColor} loading={loading} size={150} css={spinnerCss} />
<div className={loading || error ? "blurred" : ""}> <UserList enabled={!loading && (error == null)} onSeasonIdChange={setSeasonId} userStats={seasonStats} />
<UserList enabled={loading || !(!error)} onSeasonIdChange={setSeasonId} userStats={seasonStats} />
</div>
<Footer /> <Footer />
</div> </div>
) )
} }
export interface IMainPageProps extends RouteComponentProps<{ id: string }> { export interface IMainPageProps extends RouteComponentProps<{ id?: string }> {
} }

View File

@@ -4,8 +4,7 @@ import UserStatsResponse from "../models/UserStatsResponse";
export default class UserStatsService { export default class UserStatsService {
private static apiURL = 'https://api.tsotr.humenius.me/stats' private static apiURL = 'https://api.tsotr.humenius.me/stats'
// private static apiURL = 'http://localhost:3500/stats' //private static apiURL = 'http://localhost:3500/stats'
//private static apiURL = 'https://mock.codes/501'
private static requestInit: RequestInit = { private static requestInit: RequestInit = {
method: 'GET', method: 'GET',
@@ -14,13 +13,13 @@ export default class UserStatsService {
}, },
}; };
public static async getStats(seasonId: string): Promise<UserStatsResponse> { public static async getStats(seasonId?: string): Promise<UserStatsResponse> {
return fetch(`${this.apiURL}/season/${seasonId}`, this.requestInit) return fetch(`${this.apiURL}/season/${seasonId ?? ''}`, this.requestInit)
.then(res => UserStatsService.checkResponse(res)) .then(res => UserStatsService.checkResponse(res))
.then(data => data.json()) .then(data => data.json())
.then(data => { .then(data => {
data.dates.start = new Date(data.dates.start) data.dates.start = data.dates.start ? new Date(data.dates.start) : undefined
data.dates.end = new Date(data.dates.end) data.dates.end = data.dates.end ? new Date(data.dates.end) : undefined
return data return data
}) })
} }
@@ -32,70 +31,4 @@ export default class UserStatsService {
} }
return response; return response;
} }
} }
// const checkResponse = <T extends Response>(response: T): T => {
// if (!response.ok) {
// console.log(response);
// let error = new RequestError(response.statusText);
// error.response = response;
// throw error;
// }
// return response;
// }
//
// const useApiService = <T>(url: string, method: string = 'GET'): { response: T | null; error: RequestError | null } => {
// const baseUrl = "https://api.tsotr.humenius.me"
// const [response, setResponse] = useState<T | null>(null)
// const [error, setError] = useState<RequestError | null>(null)
// const requestInit = {
// method: method,
// headers: {
// 'Content-Type': 'application/json'
// }
// }
//
// useEffect(() => {
// const fetchData = async (): Promise<void> => {
// const result = await fetch(`${baseUrl}${url}`, requestInit)
// .then(res => checkResponse(res))
// .then(data => data.json())
// .catch(err => setError(err))
// setResponse(result)
// }
//
// fetchData()
// }, [url])
//
// return { response, error }
// }
//
// // const seasonStatsQuery = (seasonId: number): { response: UserStatsResponse | null; error: RequestError | null } => {
// // const baseUrl = "https://api.tsotr.humenius.me/stats/season/"
// // const [response, setResponse] = useState<UserStatsResponse | null>(null)
// // const [error, setError] = useState<RequestError | null>(null)
// // const requestInit = {
// // method: 'GET',
// // headers: {
// // 'Content-Type': 'application/json'
// // }
// // }
// //
// // useEffect(() => {
// // const fetchData = async (): Promise<void> => {
// // const result = await fetch(baseUrl, requestInit)
// // .then(res => checkResponse(res))
// // .then(data => data.json())
// // .catch(err => setError(err))
// // setResponse(result)
// // }
// //
// // fetchData()
// // }, [seasonId])
// //
// // return { response, error }
// // }
//
// const currentSeasonStatsQuery = useApiService<UserStatsResponse>('/stats')
//
// export default useApiService;

View File

@@ -1667,11 +1667,6 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
"@types/isomorphic-fetch@^0.0.35":
version "0.0.35"
resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.35.tgz#c1c0d402daac324582b6186b91f8905340ea3361"
integrity sha512-DaZNUvLDCAnCTjgwxgiL1eQdxIKEpNLOlTNtAgnZc50bG2copGhRrFN9/PxPBuJe+tZVLCbQ7ls0xveXVRPkvw==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@@ -4806,11 +4801,6 @@ fb-watchman@^2.0.0:
dependencies: dependencies:
bser "2.1.1" bser "2.1.1"
fetch-retry-ts@^1.1.23:
version "1.1.23"
resolved "https://registry.yarnpkg.com/fetch-retry-ts/-/fetch-retry-ts-1.1.23.tgz#7659974215c7a66b9bb81c006e5acee34d932ca8"
integrity sha512-UwqrMGfDPRa60etXl1HkDgIhY9k2AoB/Qa41/U2thzHtA/NVHGSmn037dw4iOg62ccyX2imtkU8SFfSwxLLEYA==
figgy-pudding@^3.5.1: figgy-pudding@^3.5.1:
version "3.5.2" version "3.5.2"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
@@ -6179,14 +6169,6 @@ isobject@^3.0.0, isobject@^3.0.1:
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
isomorphic-fetch@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4"
integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==
dependencies:
node-fetch "^2.6.1"
whatwg-fetch "^3.4.1"
isstream@~0.1.2: isstream@~0.1.2:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@@ -7522,11 +7504,6 @@ no-case@^3.0.4:
lower-case "^2.0.2" lower-case "^2.0.2"
tslib "^2.0.3" tslib "^2.0.3"
node-fetch@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-forge@^0.10.0: node-forge@^0.10.0:
version "0.10.0" version "0.10.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
@@ -9241,15 +9218,14 @@ react-dev-utils@^10.2.1:
strip-ansi "6.0.0" strip-ansi "6.0.0"
text-table "0.2.0" text-table "0.2.0"
react-dom@^16.13.1: react-dom@17.0.0:
version "16.14.0" version "17.0.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.0.tgz#f8266e4d9861584553ccbd186d596a1c7dd8dcb4"
integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw== integrity sha512-OGnFbxCjI2TMAZYMVxi4hqheJiN8rCEVVrL7XIGzCB6beNc4Am8M47HtkvxODZw9QgjmAPKpLba9FTu4fC1byA==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1" object-assign "^4.1.1"
prop-types "^15.6.2" scheduler "^0.20.0"
scheduler "^0.19.1"
react-error-overlay@^6.0.7: react-error-overlay@^6.0.7:
version "6.0.8" version "6.0.8"
@@ -9362,14 +9338,13 @@ react-spinners@^0.10.4:
dependencies: dependencies:
"@emotion/core" "^10.0.35" "@emotion/core" "^10.0.35"
react@^16.13.1: react@17.0.0:
version "16.14.0" version "17.0.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" resolved "https://registry.yarnpkg.com/react/-/react-17.0.0.tgz#ad96d5fa1a33bb9b06d0cc52672f7992d84aa662"
integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== integrity sha512-rG9bqS3LMuetoSUKHN8G3fMNuQOePKDThK6+2yXFWtoeTDLVNh/QCaxT+Jr+rNf4lwNXpx+atdn3Aa0oi8/6eQ==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1" object-assign "^4.1.1"
prop-types "^15.6.2"
read-pkg-up@^2.0.0: read-pkg-up@^2.0.0:
version "2.0.0" version "2.0.0"
@@ -9864,10 +9839,10 @@ saxes@^3.1.9:
dependencies: dependencies:
xmlchars "^2.1.1" xmlchars "^2.1.1"
scheduler@^0.19.1: scheduler@^0.20.0:
version "0.19.1" version "0.20.1"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.1.tgz#da0b907e24026b01181ecbc75efdc7f27b5a000c"
integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== integrity sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1" object-assign "^4.1.1"
@@ -11335,7 +11310,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
dependencies: dependencies:
iconv-lite "0.4.24" iconv-lite "0.4.24"
whatwg-fetch@^3.0.0, whatwg-fetch@^3.4.1: whatwg-fetch@^3.0.0:
version "3.5.0" version "3.5.0"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz#605a2cd0a7146e5db141e29d1c62ab84c0c4c868" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz#605a2cd0a7146e5db141e29d1c62ab84c0c4c868"
integrity sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A== integrity sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A==