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:
parent
4066382b8f
commit
419f655ef5
@ -0,0 +1,7 @@
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export const useStyles = makeStyles(theme => ({
|
||||
featureId: {
|
||||
wordBreak: 'break-all',
|
||||
},
|
||||
}));
|
@ -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>
|
||||
);
|
||||
};
|
@ -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',
|
||||
|
@ -44,7 +44,4 @@ export const useStyles = makeStyles(theme => ({
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
featureId: {
|
||||
wordBreak: 'break-all',
|
||||
},
|
||||
}));
|
||||
|
@ -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
|
||||
<Link
|
||||
to={getCreateTogglePath(projectId, uiConfig.flags.E, {
|
||||
name: featureId,
|
||||
})}
|
||||
>
|
||||
create it
|
||||
</Link>
|
||||
?
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
if (status === 404) {
|
||||
return <FeatureNotFound />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ConditionallyRender
|
||||
@ -242,7 +228,6 @@ export const FeatureView = () => {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
elseShow={renderFeatureNotExist()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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(),
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user