implemented request cancellation on component dismount
Signed-off-by: Raul Kele <raulkeleblk@gmail.com>
This commit is contained in:
parent
dce87afba9
commit
d1bf977871
@ -16,7 +16,6 @@ function App() {
|
||||
return localStorageToken ? true : false;
|
||||
};
|
||||
|
||||
const [data, setData] = useState(null);
|
||||
const [isAuthEnabled, setIsAuthEnabled] = useState(true);
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(isToken());
|
||||
|
||||
@ -26,7 +25,7 @@ function App() {
|
||||
<Routes>
|
||||
<Route element={<AuthWrapper isLoggedIn={isLoggedIn} redirect="/login" />}>
|
||||
<Route path="/" element={<Navigate to="/home" />} />
|
||||
<Route path="/home" element={<HomePage data={data} updateData={setData} />} />
|
||||
<Route path="/home" element={<HomePage />} />
|
||||
<Route path="/explore" element={<ExplorePage />} />
|
||||
<Route path="/image/:name" element={<RepoPage />} />
|
||||
<Route path="/image/:name/tag/:tag" element={<TagPage />} />
|
||||
|
@ -77,6 +77,13 @@ describe('Explore component', () => {
|
||||
expect(await screen.findByText(/nodeUnique/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays the no data message if no data is received', async () => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: { GlobalSearch: { Repos: [] } } } });
|
||||
render(<StateExploreWrapper />);
|
||||
expect(await screen.findByText(/Looks like/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should log an error when data can't be fetched", async () => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
|
||||
|
@ -15,7 +15,7 @@ it('renders the explore page component', () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="*" element={<ExplorePage data={[]} updateData={() => {}} />} />
|
||||
<Route path="*" element={<ExplorePage />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { api } from 'api';
|
||||
import Home from 'components/Home';
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
// useNavigate mock
|
||||
const mockedUsedNavigate = jest.fn();
|
||||
@ -11,10 +11,6 @@ jest.mock('react-router-dom', () => ({
|
||||
useNavigate: () => mockedUsedNavigate
|
||||
}));
|
||||
|
||||
const StateHomeWrapper = () => {
|
||||
const [data, useData] = useState([]);
|
||||
return <Home data={data} updateData={useData} />;
|
||||
};
|
||||
const mockImageList = {
|
||||
RepoListWithNewestImage: [
|
||||
{
|
||||
@ -55,6 +51,11 @@ const mockImageList = {
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.scrollTo = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// restore the spy created with spyOn
|
||||
jest.restoreAllMocks();
|
||||
@ -64,7 +65,7 @@ describe('Home component', () => {
|
||||
it('fetches image data and renders popular, bookmarks and recently updated', async () => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
|
||||
render(<StateHomeWrapper />);
|
||||
render(<Home />);
|
||||
await waitFor(() => expect(screen.getAllByText(/alpine/i)).toHaveLength(2));
|
||||
await waitFor(() => expect(screen.getAllByText(/mongo/i)).toHaveLength(2));
|
||||
await waitFor(() => expect(screen.getAllByText(/node/i)).toHaveLength(1));
|
||||
@ -74,7 +75,7 @@ describe('Home component', () => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
|
||||
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(<StateHomeWrapper />);
|
||||
render(<Home />);
|
||||
await waitFor(() => expect(error).toBeCalledTimes(1));
|
||||
});
|
||||
});
|
||||
|
@ -15,7 +15,7 @@ it('renders the homepage component', () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="*" element={<HomePage data={[]} updateData={() => {}} />} />
|
||||
<Route path="*" element={<HomePage />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
@ -38,7 +38,7 @@ describe('Sign in form', () => {
|
||||
beforeEach(() => {
|
||||
// mock auth check request
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 401, data: {} });
|
||||
jest.spyOn(api, 'get').mockRejectedValue({ status: 401, data: {} });
|
||||
});
|
||||
|
||||
it('should change username and password values on user input', async () => {
|
||||
@ -53,16 +53,16 @@ describe('Sign in form', () => {
|
||||
|
||||
it('should display error if username and password values are empty after change', async () => {
|
||||
render(<SignIn isAuthEnabled={true} setIsAuthEnabled={() => {}} isLoggedIn={false} setIsLoggedIn={() => {}} />);
|
||||
const usernameInput = screen.getByLabelText(/^Username/i);
|
||||
const passwordInput = screen.getByLabelText(/^Enter Password/i);
|
||||
const usernameInput = await screen.findByLabelText(/^Username/i);
|
||||
const passwordInput = await screen.findByLabelText(/^Enter Password/i);
|
||||
userEvent.click(usernameInput);
|
||||
userEvent.type(usernameInput, 't');
|
||||
userEvent.type(usernameInput, '{backspace}');
|
||||
userEvent.click(passwordInput);
|
||||
userEvent.type(passwordInput, 't');
|
||||
userEvent.type(passwordInput, '{backspace}');
|
||||
const usernameError = screen.getByText(/enter a username/i);
|
||||
const passwordError = screen.getByText(/enter a password/i);
|
||||
const usernameError = await screen.findByText(/enter a username/i);
|
||||
const passwordError = await screen.findByText(/enter a password/i);
|
||||
await waitFor(() => expect(usernameError).toBeInTheDocument());
|
||||
await waitFor(() => expect(passwordError).toBeInTheDocument());
|
||||
});
|
||||
|
@ -71,9 +71,9 @@ describe('Repo details component', () => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
|
||||
render(<RepoDetails />);
|
||||
expect(screen.getByTestId('overview-container')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('overview-container')).toBeInTheDocument();
|
||||
fireEvent.click(await screen.findByText(/tags/i));
|
||||
expect(screen.getByTestId('tags-container')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('tags-container')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('overview-container')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@ -81,7 +81,7 @@ describe('Repo details component', () => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
|
||||
render(<RepoDetails />);
|
||||
fireEvent.click(screen.getByTestId('pullcopy-btn'));
|
||||
fireEvent.click(await screen.findByTestId('pullcopy-btn'));
|
||||
await waitFor(() => expect(mockCopyToClipboard).toHaveBeenCalledWith('Pull test'));
|
||||
});
|
||||
});
|
||||
|
44
src/api.js
44
src/api.js
@ -26,36 +26,36 @@ const api = {
|
||||
};
|
||||
},
|
||||
|
||||
get(urli, cfg) {
|
||||
if (isEmpty(cfg)) {
|
||||
return axios.get(urli, this.getRequestCfg());
|
||||
} else {
|
||||
return axios.get(urli, cfg);
|
||||
get(urli, abortSignal, cfg) {
|
||||
let config = isEmpty(cfg) ? this.getRequestCfg() : cfg;
|
||||
if (!isEmpty(abortSignal) && isEmpty(config.signal)) {
|
||||
config = { ...config, signal: abortSignal };
|
||||
}
|
||||
return axios.get(urli, config);
|
||||
},
|
||||
|
||||
// This method creates the POST request with axios
|
||||
// If caller specifies the request configuration to be sent (@param cfg), it adds it to the request
|
||||
// If caller doesn't specfiy the request configuration, it adds the default config to the request
|
||||
// This allows caller to pass in any desired request configuration, based on the specifc need
|
||||
post(urli, payload, cfg) {
|
||||
// generic post - generate config for request
|
||||
if (isEmpty(cfg)) {
|
||||
return axios.post(urli, payload, this.getRequestCfg());
|
||||
// custom post - use passed in config
|
||||
// TODO:: validate config object before sending request
|
||||
} else {
|
||||
return axios.post(urli, payload, cfg);
|
||||
post(urli, payload, abortSignal, cfg) {
|
||||
let config = isEmpty(cfg) ? this.getRequestCfg() : cfg;
|
||||
if (!isEmpty(abortSignal) && isEmpty(config.signal)) {
|
||||
config = { ...config, signal: abortSignal };
|
||||
}
|
||||
return axios.post(urli, payload, config);
|
||||
},
|
||||
|
||||
put(urli, payload) {
|
||||
return axios.put(urli, payload, this.getRequestCfg());
|
||||
put(urli, payload, abortSignal, cfg) {
|
||||
let config = isEmpty(cfg) ? this.getRequestCfg() : cfg;
|
||||
if (!isEmpty(abortSignal) && isEmpty(config.signal)) {
|
||||
config = { ...config, signal: abortSignal };
|
||||
}
|
||||
return axios.put(urli, payload, config);
|
||||
},
|
||||
|
||||
delete(urli, cfg) {
|
||||
let requestCfg = isEmpty(cfg) ? this.getRequestCfg() : cfg;
|
||||
return axios.delete(urli, requestCfg);
|
||||
delete(urli, abortSignal, cfg) {
|
||||
let config = isEmpty(cfg) ? this.getRequestCfg() : cfg;
|
||||
if (!isEmpty(abortSignal) && isEmpty(config.signal)) {
|
||||
config = { ...config, signal: abortSignal };
|
||||
}
|
||||
return axios.delete(urli, config);
|
||||
}
|
||||
};
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.1 MiB |
Binary file not shown.
Before Width: | Height: | Size: 120 KiB |
Binary file not shown.
Before Width: | Height: | Size: 122 KiB |
Binary file not shown.
Before Width: | Height: | Size: 205 KiB |
Binary file not shown.
Before Width: | Height: | Size: 118 KiB |
Binary file not shown.
Before Width: | Height: | Size: 120 KiB |
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
|
||||
// utility
|
||||
import { api, endpoints } from '../api';
|
||||
@ -71,11 +71,12 @@ function DependsOn(props) {
|
||||
const { name } = props;
|
||||
const classes = useStyles();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
api
|
||||
.get(`${host()}${endpoints.dependsOnForImage(name)}`)
|
||||
.get(`${host()}${endpoints.dependsOnForImage(name)}`, abortController.signal)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
let images = response.data.data.BaseImageList;
|
||||
@ -87,6 +88,9 @@ function DependsOn(props) {
|
||||
console.error(e);
|
||||
setIsLoading(false);
|
||||
});
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const renderDependencies = () => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// react global
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
// components
|
||||
import RepoCard from './RepoCard.jsx';
|
||||
@ -58,6 +58,7 @@ function Explore() {
|
||||
const [imageFilters, setImageFilters] = useState(false);
|
||||
const [osFilters, setOSFilters] = useState('');
|
||||
const [archFilters, setArchFilters] = useState('');
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
const classes = useStyles();
|
||||
|
||||
const buildFilterQuery = () => {
|
||||
@ -74,7 +75,10 @@ function Explore() {
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
api
|
||||
.get(`${host()}${endpoints.globalSearch({ searchQuery: search, filter: buildFilterQuery() })}`)
|
||||
.get(
|
||||
`${host()}${endpoints.globalSearch({ searchQuery: search, filter: buildFilterQuery() })}`,
|
||||
abortController.signal
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
let repoList = response.data.data.GlobalSearch.Repos;
|
||||
@ -88,6 +92,9 @@ function Explore() {
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [search, queryParams, imageFilters, osFilters, archFilters]);
|
||||
|
||||
const renderRepoCards = () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import transform from 'utilities/transform';
|
||||
|
||||
// utility
|
||||
@ -118,6 +118,7 @@ function HistoryLayers(props) {
|
||||
const [historyData, setHistoryData] = useState([]);
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
const { name, history } = props;
|
||||
|
||||
useEffect(() => {
|
||||
@ -126,7 +127,7 @@ function HistoryLayers(props) {
|
||||
setIsLoaded(true);
|
||||
} else {
|
||||
api
|
||||
.get(`${host()}${endpoints.layersDetailsForImage(name)}`)
|
||||
.get(`${host()}${endpoints.layersDetailsForImage(name)}`, abortController.signal)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
let layersHistory = response.data.data.Image;
|
||||
@ -140,6 +141,9 @@ function HistoryLayers(props) {
|
||||
setIsLoaded(false);
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [name]);
|
||||
|
||||
const renderHistoryData = () => {
|
||||
|
@ -2,7 +2,7 @@ import { Grid, Stack, Typography } from '@mui/material';
|
||||
import { makeStyles } from '@mui/styles';
|
||||
import { api, endpoints } from 'api';
|
||||
import { host } from '../host';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import PreviewCard from './PreviewCard';
|
||||
import RepoCard from './RepoCard';
|
||||
import { mapToRepo } from 'utilities/objectModels';
|
||||
@ -61,34 +61,34 @@ const useStyles = makeStyles(() => ({
|
||||
}
|
||||
}));
|
||||
|
||||
function Home({ data, updateData }) {
|
||||
function Home() {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [homeData, setHomeData] = useState([]);
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
const classes = useStyles();
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
setIsLoading(true);
|
||||
api
|
||||
.get(`${host()}${endpoints.repoList}`)
|
||||
.get(`${host()}${endpoints.repoList}`, abortController.signal)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
let repoList = response.data.data.RepoListWithNewestImage;
|
||||
let repoData = repoList.map((responseRepo) => {
|
||||
return mapToRepo(responseRepo);
|
||||
});
|
||||
updateData(repoData);
|
||||
setHomeData(repoData);
|
||||
setIsLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}, [updateData]);
|
||||
|
||||
useEffect(() => {
|
||||
setHomeData(data);
|
||||
}, [data]);
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const renderPreviewCards = () => {
|
||||
return (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
// utility
|
||||
import { api, endpoints } from '../api';
|
||||
@ -71,11 +71,12 @@ function IsDependentOn(props) {
|
||||
const { name } = props;
|
||||
const classes = useStyles();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
api
|
||||
.get(`${host()}${endpoints.isDependentOnForImage(name)}`)
|
||||
.get(`${host()}${endpoints.isDependentOnForImage(name)}`, abortController.signal)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
let images = response.data.data.DerivedImageList;
|
||||
@ -87,6 +88,9 @@ function IsDependentOn(props) {
|
||||
console.error(e);
|
||||
setIsLoading(false);
|
||||
});
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const renderDependents = () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
// react global
|
||||
import { useParams } from 'react-router-dom';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
// utility
|
||||
import { api, endpoints } from '../api';
|
||||
@ -138,11 +138,12 @@ function RepoDetails() {
|
||||
|
||||
// get url param from <Route here (i.e. image name)
|
||||
const { name } = useParams();
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
const classes = useStyles();
|
||||
|
||||
useEffect(() => {
|
||||
api
|
||||
.get(`${host()}${endpoints.detailedRepoInfo(name)}`)
|
||||
.get(`${host()}${endpoints.detailedRepoInfo(name)}`, abortController.signal)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
let repoInfo = response.data.data.ExpandedRepoInfo;
|
||||
@ -169,6 +170,9 @@ function RepoDetails() {
|
||||
setRepoDetailData({});
|
||||
setIsLoading(false);
|
||||
});
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [name]);
|
||||
//function that returns a random element from an array
|
||||
// function getRandom(list) {
|
||||
|
@ -75,6 +75,7 @@ function SearchSuggestion() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [suggestionData, setSuggestionData] = useState([]);
|
||||
const navigate = useNavigate();
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
const classes = useStyles();
|
||||
|
||||
const handleSuggestionSelected = (event) => {
|
||||
@ -94,7 +95,10 @@ function SearchSuggestion() {
|
||||
setSearchQuery(value);
|
||||
if (value !== '' && value.length > 1) {
|
||||
api
|
||||
.get(`${host()}${endpoints.globalSearch({ searchQuery: value, pageNumber: 1, pageSize: 9 })}`)
|
||||
.get(
|
||||
`${host()}${endpoints.globalSearch({ searchQuery: value, pageNumber: 1, pageSize: 9 })}`,
|
||||
abortController.signal
|
||||
)
|
||||
.then((suggestionResponse) => {
|
||||
if (suggestionResponse.data.data.GlobalSearch.Repos) {
|
||||
const suggestionParsedData = suggestionResponse.data.data.GlobalSearch.Repos.map((el) => mapToRepo(el));
|
||||
@ -114,6 +118,7 @@ function SearchSuggestion() {
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
debounceSuggestions.cancel();
|
||||
abortController.abort();
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// react global
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { host } from '../host';
|
||||
// utility
|
||||
@ -107,6 +107,7 @@ export default function SignIn({ isAuthEnabled, setIsAuthEnabled, isLoggedIn, se
|
||||
const [requestProcessing, setRequestProcessing] = useState(false);
|
||||
const [requestError, setRequestError] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
const navigate = useNavigate();
|
||||
const classes = useStyles();
|
||||
|
||||
@ -117,7 +118,7 @@ export default function SignIn({ isAuthEnabled, setIsAuthEnabled, isLoggedIn, se
|
||||
navigate('/home');
|
||||
} else {
|
||||
api
|
||||
.get(`${host()}/v2/`)
|
||||
.get(`${host()}/v2/`, abortController.signal)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
setIsAuthEnabled(false);
|
||||
@ -131,6 +132,9 @@ export default function SignIn({ isAuthEnabled, setIsAuthEnabled, isLoggedIn, se
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleClick = (event) => {
|
||||
@ -146,7 +150,7 @@ export default function SignIn({ isAuthEnabled, setIsAuthEnabled, isLoggedIn, se
|
||||
};
|
||||
}
|
||||
api
|
||||
.get(`${host()}${endpoints.repoList}`, cfg)
|
||||
.get(`${host()}${endpoints.repoList}`, abortController.signal, cfg)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
if (isAuthEnabled) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
// react global
|
||||
import { useParams } from 'react-router-dom';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
// utility
|
||||
import { api, endpoints } from '../api';
|
||||
@ -123,6 +123,7 @@ function TagDetails() {
|
||||
//const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectedTab, setSelectedTab] = useState('Layers');
|
||||
const [fullName, setFullName] = useState('');
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
|
||||
// get url param from <Route here (i.e. image name)
|
||||
const { name, tag } = useParams();
|
||||
@ -134,7 +135,7 @@ function TagDetails() {
|
||||
setSelectedTab('Layers');
|
||||
window?.scrollTo(0, 0);
|
||||
api
|
||||
.get(`${host()}${endpoints.detailedImageInfo(name, tag)}`)
|
||||
.get(`${host()}${endpoints.detailedImageInfo(name, tag)}`, abortController.signal)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
let imageInfo = response.data.data.Image;
|
||||
@ -158,6 +159,9 @@ function TagDetails() {
|
||||
console.error(e);
|
||||
setImageDetailData({});
|
||||
});
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [name, tag]);
|
||||
//function that returns a random element from an array
|
||||
// function getRandom(list) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
// utility
|
||||
import { api, endpoints } from '../api';
|
||||
@ -260,12 +260,13 @@ function VulnerabilitiesDetails(props) {
|
||||
const classes = useStyles();
|
||||
const [cveData, setCveData] = useState({});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
const { name } = props;
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
api
|
||||
.get(`${host()}${endpoints.vulnerabilitiesForRepo(name)}`)
|
||||
.get(`${host()}${endpoints.vulnerabilitiesForRepo(name)}`, abortController.signal)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
let cveInfo = response.data.data.CVEListForImage;
|
||||
@ -280,6 +281,9 @@ function VulnerabilitiesDetails(props) {
|
||||
console.error(e);
|
||||
setCveData({});
|
||||
});
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const renderCVEs = (cves) => {
|
||||
|
@ -25,7 +25,7 @@ const useStyles = makeStyles(() => ({
|
||||
}
|
||||
}));
|
||||
|
||||
function HomePage({ data, updateData }) {
|
||||
function HomePage() {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
@ -34,7 +34,7 @@ function HomePage({ data, updateData }) {
|
||||
<Container className={classes.container}>
|
||||
<Grid container className={classes.gridWrapper}>
|
||||
<Grid item className={classes.tile}>
|
||||
<Home data={data} updateData={updateData} />
|
||||
<Home />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
|
Loading…
Reference in New Issue
Block a user