mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
Refactor/project overview api calls (#5279)
This PR reduces the overhead of making API calls on pages with heavy renders. We forego loading states and default error handling in favor of more speed by avoiding triggering multiple re-renders from the API call.
This commit is contained in:
parent
312999066b
commit
92e2b1890c
@ -38,6 +38,7 @@ import { ExportDialog } from './ExportDialog';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { focusable } from 'themes/themeStyles';
|
||||
import { FeatureEnvironmentSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell';
|
||||
import useToast from 'hooks/useToast';
|
||||
|
||||
export const featuresPlaceholder: FeatureSchema[] = Array(15).fill({
|
||||
name: 'Name of the feature',
|
||||
@ -69,6 +70,7 @@ export const FeatureToggleListTable: VFC = () => {
|
||||
const [showExportDialog, setShowExportDialog] = useState(false);
|
||||
const { features = [], loading, refetchFeatures } = useFeatures();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const { setToastApiError } = useToast();
|
||||
const { uiConfig } = useUiConfig();
|
||||
const showEnvironmentLastSeen = Boolean(
|
||||
uiConfig.flags.lastSeenByEnvironment,
|
||||
@ -97,14 +99,20 @@ export const FeatureToggleListTable: VFC = () => {
|
||||
const { favorite, unfavorite } = useFavoriteFeaturesApi();
|
||||
const onFavorite = useCallback(
|
||||
async (feature: any) => {
|
||||
if (feature?.favorite) {
|
||||
await unfavorite(feature.project, feature.name);
|
||||
} else {
|
||||
await favorite(feature.project, feature.name);
|
||||
try {
|
||||
if (feature?.favorite) {
|
||||
await unfavorite(feature.project, feature.name);
|
||||
} else {
|
||||
await favorite(feature.project, feature.name);
|
||||
}
|
||||
refetchFeatures();
|
||||
} catch (error) {
|
||||
setToastApiError(
|
||||
'Something went wrong, could not update favorite',
|
||||
);
|
||||
}
|
||||
refetchFeatures();
|
||||
},
|
||||
[favorite, refetchFeatures, unfavorite],
|
||||
[favorite, refetchFeatures, unfavorite, setToastApiError],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
|
@ -144,7 +144,7 @@ export const FeatureView = () => {
|
||||
const { favorite, unfavorite } = useFavoriteFeaturesApi();
|
||||
const { refetchFeature } = useFeature(projectId, featureId);
|
||||
const dependentFeatures = useUiFlag('dependentFeatures');
|
||||
const { setToastData } = useToast();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
|
||||
const [openTagDialog, setOpenTagDialog] = useState(false);
|
||||
const [showDelDialog, setShowDelDialog] = useState(false);
|
||||
@ -187,12 +187,16 @@ export const FeatureView = () => {
|
||||
tabData.find((tab) => tab.path === pathname) ?? tabData[0];
|
||||
|
||||
const onFavorite = async () => {
|
||||
if (feature?.favorite) {
|
||||
await unfavorite(projectId, feature.name);
|
||||
} else {
|
||||
await favorite(projectId, feature.name);
|
||||
try {
|
||||
if (feature?.favorite) {
|
||||
await unfavorite(projectId, feature.name);
|
||||
} else {
|
||||
await favorite(projectId, feature.name);
|
||||
}
|
||||
refetchFeature();
|
||||
} catch (error) {
|
||||
setToastApiError('Something went wrong, could not update favorite');
|
||||
}
|
||||
refetchFeature();
|
||||
};
|
||||
|
||||
if (status === 404) {
|
||||
|
@ -64,7 +64,7 @@ export const Project = () => {
|
||||
const params = useQueryParams();
|
||||
const { project, loading, error, refetch } = useProject(projectId);
|
||||
const ref = useLoading(loading);
|
||||
const { setToastData } = useToast();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
@ -155,12 +155,16 @@ export const Project = () => {
|
||||
}
|
||||
|
||||
const onFavorite = async () => {
|
||||
if (project?.favorite) {
|
||||
await unfavorite(projectId);
|
||||
} else {
|
||||
await favorite(projectId);
|
||||
try {
|
||||
if (project?.favorite) {
|
||||
await unfavorite(projectId);
|
||||
} else {
|
||||
await favorite(projectId);
|
||||
}
|
||||
refetch();
|
||||
} catch (error) {
|
||||
setToastApiError('Something went wrong, could not update favorite');
|
||||
}
|
||||
refetch();
|
||||
};
|
||||
|
||||
const enterpriseIcon = (
|
||||
|
@ -65,6 +65,7 @@ import { RowSelectCell } from './RowSelectCell/RowSelectCell';
|
||||
import { BatchSelectionActionsBar } from '../../../common/BatchSelectionActionsBar/BatchSelectionActionsBar';
|
||||
import { ProjectFeaturesBatchActions } from './ProjectFeaturesBatchActions/ProjectFeaturesBatchActions';
|
||||
import { FeatureEnvironmentSeenCell } from '../../../common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell';
|
||||
import useToast from 'hooks/useToast';
|
||||
|
||||
const StyledResponsiveButton = styled(ResponsiveButton)(() => ({
|
||||
whiteSpace: 'nowrap',
|
||||
@ -135,7 +136,7 @@ export const ProjectFeatureToggles = ({
|
||||
string | undefined
|
||||
>();
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
|
||||
const { setToastApiError } = useToast();
|
||||
const { value: storedParams, setValue: setStoredParams } =
|
||||
createLocalStorage(
|
||||
`${projectId}:FeatureToggleListTable:v1`,
|
||||
@ -171,14 +172,20 @@ export const ProjectFeatureToggles = ({
|
||||
|
||||
const onFavorite = useCallback(
|
||||
async (feature: IFeatureToggleListItem) => {
|
||||
if (feature?.favorite) {
|
||||
await unfavorite(projectId, feature.name);
|
||||
} else {
|
||||
await favorite(projectId, feature.name);
|
||||
try {
|
||||
if (feature?.favorite) {
|
||||
await unfavorite(projectId, feature.name);
|
||||
} else {
|
||||
await favorite(projectId, feature.name);
|
||||
}
|
||||
refetch();
|
||||
} catch (error) {
|
||||
setToastApiError(
|
||||
'Something went wrong, could not update favorite',
|
||||
);
|
||||
}
|
||||
refetch();
|
||||
},
|
||||
[projectId, refetch],
|
||||
[projectId, refetch, setToastApiError],
|
||||
);
|
||||
|
||||
const showTagsColumn = useMemo(
|
||||
|
@ -63,6 +63,7 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
import { ListItemType } from './ProjectFeatureToggles.types';
|
||||
import { createFeatureToggleCell } from './FeatureToggleSwitch/createFeatureToggleCell';
|
||||
import { useFeatureToggleSwitch } from './FeatureToggleSwitch/useFeatureToggleSwitch';
|
||||
import useToast from 'hooks/useToast';
|
||||
|
||||
const StyledResponsiveButton = styled(ResponsiveButton)(() => ({
|
||||
whiteSpace: 'nowrap',
|
||||
@ -91,6 +92,7 @@ export const ProjectFeatureToggles = ({
|
||||
}: IProjectFeatureTogglesProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const theme = useTheme();
|
||||
const { setToastApiError } = useToast();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const [strategiesDialogState, setStrategiesDialogState] = useState({
|
||||
open: false,
|
||||
@ -138,12 +140,18 @@ export const ProjectFeatureToggles = ({
|
||||
|
||||
const onFavorite = useCallback(
|
||||
async (feature: IFeatureToggleListItem) => {
|
||||
if (feature?.favorite) {
|
||||
await unfavorite(projectId, feature.name);
|
||||
} else {
|
||||
await favorite(projectId, feature.name);
|
||||
try {
|
||||
if (feature?.favorite) {
|
||||
await unfavorite(projectId, feature.name);
|
||||
} else {
|
||||
await favorite(projectId, feature.name);
|
||||
}
|
||||
onChange();
|
||||
} catch (error) {
|
||||
setToastApiError(
|
||||
'Something went wrong, could not update favorite',
|
||||
);
|
||||
}
|
||||
onChange();
|
||||
},
|
||||
[projectId, onChange],
|
||||
);
|
||||
|
@ -27,6 +27,7 @@ import {
|
||||
StyledDivInfoContainer,
|
||||
StyledParagraphInfo,
|
||||
} from './ProjectCard.styles';
|
||||
import useToast from 'hooks/useToast';
|
||||
|
||||
interface IProjectCardProps {
|
||||
name: string;
|
||||
@ -48,6 +49,7 @@ export const ProjectCard = ({
|
||||
isFavorite = false,
|
||||
}: IProjectCardProps) => {
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
const { setToastApiError } = useToast();
|
||||
const { isOss } = useUiConfig();
|
||||
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
|
||||
const [showDelDialog, setShowDelDialog] = useState(false);
|
||||
@ -62,12 +64,16 @@ export const ProjectCard = ({
|
||||
|
||||
const onFavorite = async (e: React.SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
if (isFavorite) {
|
||||
await unfavorite(id);
|
||||
} else {
|
||||
await favorite(id);
|
||||
try {
|
||||
if (isFavorite) {
|
||||
await unfavorite(id);
|
||||
} else {
|
||||
await favorite(id);
|
||||
}
|
||||
refetch();
|
||||
} catch (error) {
|
||||
setToastApiError('Something went wrong, could not update favorite');
|
||||
}
|
||||
refetch();
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -24,6 +24,13 @@ type ApiErrorHandler = (
|
||||
requestId: string,
|
||||
) => void;
|
||||
|
||||
type ApiCaller = () => Promise<Response>;
|
||||
type RequestFunction = (
|
||||
apiCaller: ApiCaller,
|
||||
requestId: string,
|
||||
loadingOn?: boolean,
|
||||
) => Promise<Response>;
|
||||
|
||||
interface IUseAPI {
|
||||
handleBadRequest?: ApiErrorHandler;
|
||||
handleNotFound?: ApiErrorHandler;
|
||||
@ -33,6 +40,29 @@ interface IUseAPI {
|
||||
propagateErrors?: boolean;
|
||||
}
|
||||
|
||||
const timeApiCallStart = (requestId: string) => {
|
||||
// Store the start time in milliseconds
|
||||
console.log(`Starting timing for request: ${requestId}`);
|
||||
return Date.now();
|
||||
};
|
||||
|
||||
const timeApiCallEnd = (startTime: number, requestId: string) => {
|
||||
// Calculate the end time and subtract the start time
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
console.log(`Timing for request ${requestId}: ${duration} ms`);
|
||||
|
||||
if (duration > 500) {
|
||||
console.error(
|
||||
'API call took over 500ms. This may indicate a rendering performance problem in your React component.',
|
||||
requestId,
|
||||
duration,
|
||||
);
|
||||
}
|
||||
|
||||
return duration;
|
||||
};
|
||||
|
||||
const useAPI = ({
|
||||
handleBadRequest,
|
||||
handleNotFound,
|
||||
@ -157,6 +187,27 @@ const useAPI = ({
|
||||
],
|
||||
);
|
||||
|
||||
const requestWithTimer = (requestFunction: RequestFunction) => {
|
||||
return async (
|
||||
apiCaller: () => Promise<Response>,
|
||||
requestId: string,
|
||||
loadingOn: boolean = true,
|
||||
) => {
|
||||
const start = timeApiCallStart(
|
||||
requestId || `Uknown request happening on ${apiCaller}`,
|
||||
);
|
||||
|
||||
const res = await requestFunction(apiCaller, requestId, loadingOn);
|
||||
|
||||
timeApiCallEnd(
|
||||
start,
|
||||
requestId || `Uknown request happening on ${apiCaller}`,
|
||||
);
|
||||
|
||||
return res;
|
||||
};
|
||||
};
|
||||
|
||||
const makeRequest = useCallback(
|
||||
async (
|
||||
apiCaller: () => Promise<Response>,
|
||||
@ -187,6 +238,27 @@ const useAPI = ({
|
||||
[handleResponses],
|
||||
);
|
||||
|
||||
const makeLightRequest = useCallback(
|
||||
async (
|
||||
apiCaller: () => Promise<Response>,
|
||||
requestId: string,
|
||||
loadingOn: boolean = true,
|
||||
): Promise<Response> => {
|
||||
try {
|
||||
const res = await apiCaller();
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw new Error('Could not make request | makeLightRequest');
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const createRequest = useCallback(
|
||||
(path: string, options: any, requestId: string = '') => {
|
||||
const defaultOptions: RequestInit = {
|
||||
@ -207,9 +279,17 @@ const useAPI = ({
|
||||
[],
|
||||
);
|
||||
|
||||
const makeRequestWithTimer = requestWithTimer(makeRequest);
|
||||
const makeLightRequestWithTimer = requestWithTimer(makeLightRequest);
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
|
||||
return {
|
||||
loading,
|
||||
makeRequest,
|
||||
makeRequest: isDevelopment ? makeRequestWithTimer : makeRequest,
|
||||
makeLightRequest: isDevelopment
|
||||
? makeLightRequestWithTimer
|
||||
: makeLightRequest,
|
||||
createRequest,
|
||||
errors,
|
||||
};
|
||||
|
@ -5,7 +5,7 @@ import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import useAPI from '../useApi/useApi';
|
||||
|
||||
export const useFavoriteFeaturesApi = () => {
|
||||
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||
const { makeLightRequest, createRequest, errors, loading } = useAPI({
|
||||
propagateErrors: true,
|
||||
});
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
@ -21,7 +21,7 @@ export const useFavoriteFeaturesApi = () => {
|
||||
);
|
||||
|
||||
try {
|
||||
await makeRequest(req.caller, req.id);
|
||||
await makeLightRequest(req.caller, req.id);
|
||||
|
||||
setToastData({
|
||||
title: 'Toggle added to favorites',
|
||||
@ -36,7 +36,7 @@ export const useFavoriteFeaturesApi = () => {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
},
|
||||
[createRequest, makeRequest],
|
||||
[createRequest, makeLightRequest],
|
||||
);
|
||||
|
||||
const unfavorite = useCallback(
|
||||
@ -49,7 +49,7 @@ export const useFavoriteFeaturesApi = () => {
|
||||
);
|
||||
|
||||
try {
|
||||
await makeRequest(req.caller, req.id);
|
||||
await makeLightRequest(req.caller, req.id);
|
||||
|
||||
setToastData({
|
||||
title: 'Toggle removed from favorites',
|
||||
@ -64,7 +64,7 @@ export const useFavoriteFeaturesApi = () => {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
},
|
||||
[createRequest, makeRequest],
|
||||
[createRequest, makeLightRequest],
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -5,7 +5,7 @@ import useAPI from '../useApi/useApi';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
|
||||
export const useFavoriteProjectsApi = () => {
|
||||
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||
const { makeLightRequest, createRequest, errors, loading } = useAPI({
|
||||
propagateErrors: true,
|
||||
});
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
@ -21,7 +21,7 @@ export const useFavoriteProjectsApi = () => {
|
||||
);
|
||||
|
||||
try {
|
||||
await makeRequest(req.caller, req.id);
|
||||
await makeLightRequest(req.caller, req.id);
|
||||
|
||||
setToastData({
|
||||
title: 'Project added to favorites',
|
||||
@ -36,7 +36,7 @@ export const useFavoriteProjectsApi = () => {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
},
|
||||
[createRequest, makeRequest],
|
||||
[createRequest, makeLightRequest],
|
||||
);
|
||||
|
||||
const unfavorite = useCallback(
|
||||
@ -49,7 +49,7 @@ export const useFavoriteProjectsApi = () => {
|
||||
);
|
||||
|
||||
try {
|
||||
await makeRequest(req.caller, req.id);
|
||||
await makeLightRequest(req.caller, req.id);
|
||||
|
||||
setToastData({
|
||||
title: 'Project removed from favorites',
|
||||
@ -64,7 +64,7 @@ export const useFavoriteProjectsApi = () => {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
},
|
||||
[createRequest, makeRequest],
|
||||
[createRequest, makeLightRequest],
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -7,9 +7,10 @@ import useAPI from '../useApi/useApi';
|
||||
import { IFeatureVariant } from 'interfaces/featureToggle';
|
||||
|
||||
const useFeatureApi = () => {
|
||||
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||
propagateErrors: true,
|
||||
});
|
||||
const { makeRequest, makeLightRequest, createRequest, errors, loading } =
|
||||
useAPI({
|
||||
propagateErrors: true,
|
||||
});
|
||||
|
||||
const validateFeatureToggleName = async (
|
||||
name: string | undefined,
|
||||
@ -61,9 +62,9 @@ const useFeatureApi = () => {
|
||||
'toggleFeatureEnvironmentOn',
|
||||
);
|
||||
|
||||
return makeRequest(req.caller, req.id);
|
||||
return makeLightRequest(req.caller, req.id);
|
||||
},
|
||||
[createRequest, makeRequest],
|
||||
[createRequest, makeLightRequest],
|
||||
);
|
||||
|
||||
const bulkToggleFeaturesEnvironmentOn = useCallback(
|
||||
@ -119,9 +120,9 @@ const useFeatureApi = () => {
|
||||
'toggleFeatureEnvironmentOff',
|
||||
);
|
||||
|
||||
return makeRequest(req.caller, req.id);
|
||||
return makeLightRequest(req.caller, req.id);
|
||||
},
|
||||
[createRequest, makeRequest],
|
||||
[createRequest, makeLightRequest],
|
||||
);
|
||||
|
||||
const changeFeatureProject = async (
|
||||
|
Loading…
Reference in New Issue
Block a user