1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-14 00:19:16 +01:00

refactor: improve feature not found page (#774)

* refactor: improve feature not found page

* refactor: fix feature cache mutation mismatch
This commit is contained in:
olav 2022-03-10 10:52:50 +01:00 committed by GitHub
parent 4066382b8f
commit 419f655ef5
6 changed files with 94 additions and 48 deletions

View File

@ -0,0 +1,7 @@
import { makeStyles } from '@material-ui/core/styles';
export const useStyles = makeStyles(theme => ({
featureId: {
wordBreak: 'break-all',
},
}));

View File

@ -0,0 +1,44 @@
import React from 'react';
import { Link, useParams } from 'react-router-dom';
import { getCreateTogglePath } from 'utils/route-path-helpers';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useStyles } from 'component/feature/FeatureView/FeatureNotFound/FeatureNotFound.styles';
import { IFeatureViewParams } from 'interfaces/params';
import { useFeaturesArchive } from 'hooks/api/getters/useFeaturesArchive/useFeaturesArchive';
export const FeatureNotFound = () => {
const { projectId, featureId } = useParams<IFeatureViewParams>();
const { archivedFeatures } = useFeaturesArchive();
const styles = useStyles();
const { uiConfig } = useUiConfig();
const createFeatureTogglePath = getCreateTogglePath(
projectId,
uiConfig.flags.E,
{ name: featureId }
);
const isArchived = archivedFeatures.some(archivedFeature => {
return archivedFeature.name === featureId;
});
if (isArchived) {
return (
<p>
The feature{' '}
<strong className={styles.featureId}>{featureId}</strong> has
been archived. You can find it on the{' '}
<Link to={'/archive'}>archive page</Link>.
</p>
);
}
return (
<p>
The feature{' '}
<strong className={styles.featureId}>{featureId}</strong> does not
exist. Would you like to{' '}
<Link to={createFeatureTogglePath}>create it</Link>?
</p>
);
};

View File

@ -29,13 +29,12 @@ import { updateWeight } from '../../../../common/util';
import cloneDeep from 'lodash.clonedeep';
import useDeleteVariantMarkup from './FeatureVariantsListItem/useDeleteVariantMarkup';
import PermissionButton from '../../../../common/PermissionButton/PermissionButton';
import { mutate } from 'swr';
import { formatUnknownError } from '../../../../../utils/format-unknown-error';
const FeatureOverviewVariants = () => {
const { hasAccess } = useContext(AccessContext);
const { projectId, featureId } = useParams<IFeatureViewParams>();
const { feature, featureCacheKey } = useFeature(projectId, featureId);
const { feature, refetchFeature } = useFeature(projectId, featureId);
const [variants, setVariants] = useState<IFeatureVariant[]>([]);
const [editing, setEditing] = useState(false);
const { context } = useUnleashContext();
@ -153,9 +152,8 @@ const FeatureOverviewVariants = () => {
if (patch.length === 0) return;
try {
const res = await patchFeatureVariants(projectId, featureId, patch);
const { variants } = await res.json();
mutate(featureCacheKey, { ...feature, variants }, false);
await patchFeatureVariants(projectId, featureId, patch);
refetchFeature();
setToastData({
title: 'Updated variant',
confetti: true,
@ -209,9 +207,8 @@ const FeatureOverviewVariants = () => {
if (patch.length === 0) return;
try {
const res = await patchFeatureVariants(projectId, featureId, patch);
const { variants } = await res.json();
mutate(featureCacheKey, { ...feature, variants }, false);
await patchFeatureVariants(projectId, featureId, patch);
refetchFeature();
setToastData({
title: 'Updated variant',
type: 'success',

View File

@ -44,7 +44,4 @@ export const useStyles = makeStyles(theme => ({
flexDirection: 'column',
},
},
featureId: {
wordBreak: 'break-all',
},
}));

View File

@ -3,11 +3,11 @@ import React, { useState } from 'react';
import { Archive, FileCopy, Label, WatchLater } from '@material-ui/icons';
import { Link, Route, useHistory, useParams, Switch } from 'react-router-dom';
import useFeatureApi from '../../../hooks/api/actions/useFeatureApi/useFeatureApi';
import { useFeature } from '../../../hooks/api/getters/useFeature/useFeature';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import useProject from '../../../hooks/api/getters/useProject/useProject';
import useTabs from '../../../hooks/useTabs';
import useToast from '../../../hooks/useToast';
import { IFeatureViewParams } from '../../../interfaces/params';
import { IFeatureViewParams } from 'interfaces/params';
import {
CREATE_FEATURE,
DELETE_FEATURE,
@ -23,16 +23,14 @@ import { useStyles } from './FeatureView.styles';
import { FeatureSettings } from './FeatureSettings/FeatureSettings';
import useLoading from '../../../hooks/useLoading';
import ConditionallyRender from '../../common/ConditionallyRender';
import { getCreateTogglePath } from '../../../utils/route-path-helpers';
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
import StaleDialog from './FeatureOverview/StaleDialog/StaleDialog';
import AddTagDialog from './FeatureOverview/AddTagDialog/AddTagDialog';
import StatusChip from '../../common/StatusChip/StatusChip';
import { formatUnknownError } from '../../../utils/format-unknown-error';
import { formatUnknownError } from 'utils/format-unknown-error';
import { FeatureNotFound } from 'component/feature/FeatureView/FeatureNotFound/FeatureNotFound';
export const FeatureView = () => {
const { projectId, featureId } = useParams<IFeatureViewParams>();
const { feature, loading, error } = useFeature(projectId, featureId);
const { refetch: projectRefetch } = useProject(projectId);
const [openTagDialog, setOpenTagDialog] = useState(false);
const { a11yProps } = useTabs(0);
@ -42,10 +40,14 @@ export const FeatureView = () => {
const [openStaleDialog, setOpenStaleDialog] = useState(false);
const smallScreen = useMediaQuery(`(max-width:${500}px)`);
const { feature, loading, error, status } = useFeature(
projectId,
featureId
);
const styles = useStyles();
const history = useHistory();
const ref = useLoading(loading);
const { uiConfig } = useUiConfig();
const basePath = `/projects/${projectId}/features/${featureId}`;
@ -110,25 +112,9 @@ export const FeatureView = () => {
});
};
const renderFeatureNotExist = () => {
return (
<div>
<p>
The feature{' '}
<strong className={styles.featureId}>{featureId}</strong>{' '}
does not exist. Do you want to &nbsp;
<Link
to={getCreateTogglePath(projectId, uiConfig.flags.E, {
name: featureId,
})}
>
create it
</Link>
&nbsp;?
</p>
</div>
);
};
if (status === 404) {
return <FeatureNotFound />;
}
return (
<ConditionallyRender
@ -242,7 +228,6 @@ export const FeatureView = () => {
/>
</div>
}
elseShow={renderFeatureNotExist()}
/>
);
};

View File

@ -1,18 +1,23 @@
import useSWR, { mutate, SWRConfiguration } from 'swr';
import { useCallback } from 'react';
import { IFeatureToggle } from '../../../../interfaces/featureToggle';
import { emptyFeature } from './emptyFeature';
import handleErrorResponses from '../httpErrorResponseHandler';
import { formatApiPath } from '../../../../utils/format-path';
import { formatApiPath } from 'utils/format-path';
import { IFeatureToggle } from 'interfaces/featureToggle';
interface IUseFeatureOutput {
feature: IFeatureToggle;
featureCacheKey: string;
refetchFeature: () => void;
loading: boolean;
status?: number;
error?: Error;
}
interface IFeatureResponse {
status: number;
body?: IFeatureToggle;
}
export const useFeature = (
projectId: string,
featureId: string,
@ -22,7 +27,7 @@ export const useFeature = (
`api/admin/projects/${projectId}/features/${featureId}`
);
const { data, error } = useSWR<IFeatureToggle>(
const { data, error } = useSWR<IFeatureResponse>(
path,
() => fetcher(path),
options
@ -33,16 +38,27 @@ export const useFeature = (
}, [path]);
return {
feature: data || emptyFeature,
featureCacheKey: path,
feature: data?.body || emptyFeature,
refetchFeature,
loading: !error && !data,
status: data?.status,
error,
};
};
const fetcher = async (path: string) => {
return fetch(path)
.then(handleErrorResponses('Feature toggle data'))
.then(res => res.json());
const fetcher = async (path: string): Promise<IFeatureResponse> => {
const res = await fetch(path);
if (res.status === 404) {
return { status: 404 };
}
if (!res.ok) {
await handleErrorResponses('Feature toggle data')(res);
}
return {
status: res.status,
body: await res.json(),
};
};