mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-17 01:17:29 +02:00
feat: get suggested changeset draft (#2274)
* feat: get suggested changeset draft * fix: update routes snapshot
This commit is contained in:
parent
c6c873d67d
commit
b7183fdf98
@ -30,12 +30,16 @@ import StatusChip from 'component/common/StatusChip/StatusChip';
|
|||||||
import { FeatureNotFound } from 'component/feature/FeatureView/FeatureNotFound/FeatureNotFound';
|
import { FeatureNotFound } from 'component/feature/FeatureView/FeatureNotFound/FeatureNotFound';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { FeatureArchiveDialog } from '../../common/FeatureArchiveDialog/FeatureArchiveDialog';
|
import { FeatureArchiveDialog } from '../../common/FeatureArchiveDialog/FeatureArchiveDialog';
|
||||||
|
import { DraftBanner } from 'component/suggestChanges/DraftBanner/DraftBanner';
|
||||||
|
import { MainLayout } from 'component/layout/MainLayout/MainLayout';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
|
||||||
export const FeatureView = () => {
|
export const FeatureView = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
const { refetch: projectRefetch } = useProject(projectId);
|
const { refetch: projectRefetch } = useProject(projectId);
|
||||||
const { refetchFeature } = useFeature(projectId, featureId);
|
const { refetchFeature } = useFeature(projectId, featureId);
|
||||||
|
const { uiConfig } = useUiConfig();
|
||||||
|
|
||||||
const [openTagDialog, setOpenTagDialog] = useState(false);
|
const [openTagDialog, setOpenTagDialog] = useState(false);
|
||||||
const [showDelDialog, setShowDelDialog] = useState(false);
|
const [showDelDialog, setShowDelDialog] = useState(false);
|
||||||
@ -81,6 +85,14 @@ export const FeatureView = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<MainLayout
|
||||||
|
ref={ref}
|
||||||
|
subheader={
|
||||||
|
uiConfig?.flags?.suggestChanges ? (
|
||||||
|
<DraftBanner project={projectId} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={error === undefined}
|
condition={error === undefined}
|
||||||
show={
|
show={
|
||||||
@ -96,7 +108,11 @@ export const FeatureView = () => {
|
|||||||
</h1>
|
</h1>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={!smallScreen}
|
condition={!smallScreen}
|
||||||
show={<StatusChip stale={feature?.stale} />}
|
show={
|
||||||
|
<StatusChip
|
||||||
|
stale={feature?.stale}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -166,10 +182,19 @@ export const FeatureView = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="metrics" element={<FeatureMetrics />} />
|
<Route
|
||||||
|
path="metrics"
|
||||||
|
element={<FeatureMetrics />}
|
||||||
|
/>
|
||||||
<Route path="logs" element={<FeatureLog />} />
|
<Route path="logs" element={<FeatureLog />} />
|
||||||
<Route path="variants" element={<FeatureVariants />} />
|
<Route
|
||||||
<Route path="settings" element={<FeatureSettings />} />
|
path="variants"
|
||||||
|
element={<FeatureVariants />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="settings"
|
||||||
|
element={<FeatureSettings />}
|
||||||
|
/>
|
||||||
<Route path="*" element={<FeatureOverview />} />
|
<Route path="*" element={<FeatureOverview />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
<FeatureArchiveDialog
|
<FeatureArchiveDialog
|
||||||
@ -199,5 +224,6 @@ export const FeatureView = () => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
</MainLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -54,6 +54,7 @@ exports[`returns all baseRoutes 1`] = `
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": [Function],
|
"component": [Function],
|
||||||
|
"isStandalone": true,
|
||||||
"menu": {},
|
"menu": {},
|
||||||
"parent": "/projects",
|
"parent": "/projects",
|
||||||
"path": "/projects/:projectId/features/:featureId/*",
|
"path": "/projects/:projectId/features/:featureId/*",
|
||||||
|
@ -121,6 +121,7 @@ export const routes: IRoute[] = [
|
|||||||
title: 'FeatureView',
|
title: 'FeatureView',
|
||||||
component: FeatureView,
|
component: FeatureView,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
|
isStandalone: true,
|
||||||
menu: {},
|
menu: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -117,7 +117,11 @@ const Project = () => {
|
|||||||
return (
|
return (
|
||||||
<MainLayout
|
<MainLayout
|
||||||
ref={ref}
|
ref={ref}
|
||||||
subheader={uiConfig?.flags?.suggestChanges ? <DraftBanner /> : null}
|
subheader={
|
||||||
|
uiConfig?.flags?.suggestChanges ? (
|
||||||
|
<DraftBanner project={projectId} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className={styles.innerContainer}>
|
<div className={styles.innerContainer}>
|
||||||
|
@ -4,14 +4,21 @@ import { useStyles as useAppStyles } from 'component/App.styles';
|
|||||||
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { SuggestedChangesSidebar } from '../SuggestedChangesSidebar/SuggestedChangesSidebar';
|
import { SuggestedChangesSidebar } from '../SuggestedChangesSidebar/SuggestedChangesSidebar';
|
||||||
|
import { useSuggestedChangesDraft } from 'hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft';
|
||||||
|
|
||||||
interface IDraftBannerProps {
|
interface IDraftBannerProps {
|
||||||
environment?: string;
|
project: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DraftBanner: VFC<IDraftBannerProps> = ({ environment }) => {
|
export const DraftBanner: VFC<IDraftBannerProps> = ({ project }) => {
|
||||||
const { classes } = useAppStyles();
|
const { classes } = useAppStyles();
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
|
const { draft, loading } = useSuggestedChangesDraft(project);
|
||||||
|
const environment = '';
|
||||||
|
|
||||||
|
if (!loading && !draft) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -63,6 +70,7 @@ export const DraftBanner: VFC<IDraftBannerProps> = ({ environment }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<SuggestedChangesSidebar
|
<SuggestedChangesSidebar
|
||||||
|
project={project}
|
||||||
open={isSidebarOpen}
|
open={isSidebarOpen}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setIsSidebarOpen(false);
|
setIsSidebarOpen(false);
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import React, { useState, VFC } from 'react';
|
import { VFC } from 'react';
|
||||||
import { Box, Button, Typography, styled, Tooltip } from '@mui/material';
|
import { Box, Button, Typography, styled, Tooltip } from '@mui/material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
import { HelpOutline } from '@mui/icons-material';
|
import { HelpOutline } from '@mui/icons-material';
|
||||||
import { useSuggestedChange } from 'hooks/api/getters/useSuggestChange/useSuggestedChange';
|
|
||||||
import { SuggestedChangeset } from '../SuggestedChangeset/SuggestedChangeset';
|
import { SuggestedChangeset } from '../SuggestedChangeset/SuggestedChangeset';
|
||||||
|
import { useSuggestedChangesDraft } from 'hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft';
|
||||||
|
|
||||||
interface ISuggestedChangesSidebarProps {
|
interface ISuggestedChangesSidebarProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
project: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
const StyledPageContent = styled(PageContent)(({ theme }) => ({
|
const StyledPageContent = styled(PageContent)(({ theme }) => ({
|
||||||
@ -26,11 +28,13 @@ const StyledPageContent = styled(PageContent)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
borderRadius: `${theme.spacing(1.5, 0, 0, 1.5)} !important`,
|
borderRadius: `${theme.spacing(1.5, 0, 0, 1.5)} !important`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledHelpOutline = styled(HelpOutline)(({ theme }) => ({
|
const StyledHelpOutline = styled(HelpOutline)(({ theme }) => ({
|
||||||
fontSize: theme.fontSizes.mainHeader,
|
fontSize: theme.fontSizes.mainHeader,
|
||||||
marginLeft: '0.3rem',
|
marginLeft: '0.3rem',
|
||||||
color: theme.palette.grey[700],
|
color: theme.palette.grey[700],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledHeaderHint = styled('div')(({ theme }) => ({
|
const StyledHeaderHint = styled('div')(({ theme }) => ({
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
fontSize: theme.fontSizes.smallBody,
|
fontSize: theme.fontSizes.smallBody,
|
||||||
@ -38,23 +42,43 @@ const StyledHeaderHint = styled('div')(({ theme }) => ({
|
|||||||
|
|
||||||
export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
|
export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
|
||||||
open,
|
open,
|
||||||
|
project,
|
||||||
onClose,
|
onClose,
|
||||||
}) => {
|
}) => {
|
||||||
const { data: suggestedChange } = useSuggestedChange();
|
const { draft, loading } = useSuggestedChangesDraft(project);
|
||||||
|
|
||||||
const onReview = async () => {
|
const onReview = async () => {
|
||||||
console.log('approve');
|
alert('approve');
|
||||||
};
|
};
|
||||||
const onDiscard = async () => {
|
const onDiscard = async () => {
|
||||||
console.log('discard');
|
alert('discard');
|
||||||
};
|
};
|
||||||
const onApply = async () => {
|
const onApply = async () => {
|
||||||
try {
|
try {
|
||||||
console.log('apply');
|
alert('apply');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!loading && !draft) {
|
||||||
|
return (
|
||||||
|
<SidebarModal open={open} onClose={onClose} label="Review changes">
|
||||||
|
<StyledPageContent
|
||||||
|
header={
|
||||||
|
<PageHeader
|
||||||
|
secondary
|
||||||
|
titleElement="Review your changes"
|
||||||
|
></PageHeader>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
There are no changes to review.
|
||||||
|
{/* FIXME: empty state */}
|
||||||
|
</StyledPageContent>
|
||||||
|
</SidebarModal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarModal open={open} onClose={onClose} label="Review changes">
|
<SidebarModal open={open} onClose={onClose} label="Review changes">
|
||||||
<StyledPageContent
|
<StyledPageContent
|
||||||
@ -80,21 +104,44 @@ export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
|
|||||||
></PageHeader>
|
></PageHeader>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{/* TODO: multiple environments (changesets) */}
|
{draft?.map(environmentChangeset => (
|
||||||
<Typography>{suggestedChange?.state}</Typography>
|
<Box
|
||||||
<br />
|
key={environmentChangeset.id}
|
||||||
<SuggestedChangeset suggestedChange={suggestedChange} />
|
sx={{
|
||||||
|
padding: 2,
|
||||||
|
border: '2px solid',
|
||||||
|
borderColor: theme => theme.palette.neutral.light,
|
||||||
|
borderRadius: theme =>
|
||||||
|
`${theme.shape.borderRadiusLarge}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography>
|
||||||
|
env: {environmentChangeset?.environment}
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
state: {environmentChangeset?.state}
|
||||||
|
</Typography>
|
||||||
|
<hr />
|
||||||
|
<SuggestedChangeset
|
||||||
|
suggestedChange={environmentChangeset}
|
||||||
|
/>
|
||||||
<Box sx={{ display: 'flex' }}>
|
<Box sx={{ display: 'flex' }}>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={suggestedChange?.state === 'APPROVED'}
|
condition={
|
||||||
|
environmentChangeset?.state === 'APPROVED'
|
||||||
|
}
|
||||||
show={<Typography>Applied</Typography>}
|
show={<Typography>Applied</Typography>}
|
||||||
/>
|
/>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={suggestedChange?.state === 'CLOSED'}
|
condition={
|
||||||
|
environmentChangeset?.state === 'CLOSED'
|
||||||
|
}
|
||||||
show={<Typography>Applied</Typography>}
|
show={<Typography>Applied</Typography>}
|
||||||
/>
|
/>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={suggestedChange?.state === 'APPROVED'}
|
condition={
|
||||||
|
environmentChangeset?.state === 'APPROVED'
|
||||||
|
}
|
||||||
show={
|
show={
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
@ -108,7 +155,9 @@ export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={suggestedChange?.state === 'CREATED'}
|
condition={
|
||||||
|
environmentChangeset?.state === 'Draft'
|
||||||
|
}
|
||||||
show={
|
show={
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
@ -129,6 +178,8 @@ export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
</StyledPageContent>
|
</StyledPageContent>
|
||||||
</SidebarModal>
|
</SidebarModal>
|
||||||
);
|
);
|
||||||
|
@ -4,38 +4,39 @@ import { SuggestedFeatureToggleChange } from '../SuggestedChangeOverview/Suggest
|
|||||||
import { objectId } from 'utils/objectId';
|
import { objectId } from 'utils/objectId';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { ToggleStatusChange } from '../SuggestedChangeOverview/SuggestedFeatureToggleChange/ToggleStatusChange';
|
import { ToggleStatusChange } from '../SuggestedChangeOverview/SuggestedFeatureToggleChange/ToggleStatusChange';
|
||||||
import {
|
// import {
|
||||||
StrategyAddedChange,
|
// StrategyAddedChange,
|
||||||
StrategyDeletedChange,
|
// StrategyDeletedChange,
|
||||||
StrategyEditedChange,
|
// StrategyEditedChange,
|
||||||
} from '../SuggestedChangeOverview/SuggestedFeatureToggleChange/StrategyChange';
|
// } from '../SuggestedChangeOverview/SuggestedFeatureToggleChange/StrategyChange';
|
||||||
import {
|
// import {
|
||||||
formatStrategyName,
|
// formatStrategyName,
|
||||||
GetFeatureStrategyIcon,
|
// GetFeatureStrategyIcon,
|
||||||
} from 'utils/strategyNames';
|
// } from 'utils/strategyNames';
|
||||||
|
import type { ISuggestChangesResponse } from 'hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft';
|
||||||
|
|
||||||
export const SuggestedChangeset: FC<{ suggestedChange: any }> = ({
|
export const SuggestedChangeset: FC<{
|
||||||
suggestedChange,
|
suggestedChange: ISuggestChangesResponse;
|
||||||
}) => {
|
}> = ({ suggestedChange }) => {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
Changes
|
Changes
|
||||||
{suggestedChange.changes?.map((featureToggleChange: any) => (
|
{suggestedChange.features?.map(featureToggleChange => (
|
||||||
<SuggestedFeatureToggleChange
|
<SuggestedFeatureToggleChange
|
||||||
key={featureToggleChange.feature}
|
key={featureToggleChange.name}
|
||||||
featureToggleName={featureToggleChange.feature}
|
featureToggleName={featureToggleChange.name}
|
||||||
>
|
>
|
||||||
{featureToggleChange.changeSet.map((change: any) => (
|
{featureToggleChange.changes.map(change => (
|
||||||
<Box key={objectId(change)}>
|
<Box key={objectId(change)}>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={change.action === 'updateEnabled'}
|
condition={change.action === 'updateEnabled'}
|
||||||
show={
|
show={
|
||||||
<ToggleStatusChange
|
<ToggleStatusChange
|
||||||
enabled={change?.payload?.data?.data}
|
enabled={change?.payload?.enabled}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ConditionallyRender
|
{/* <ConditionallyRender
|
||||||
condition={change.action === 'addStrategy'}
|
condition={change.action === 'addStrategy'}
|
||||||
show={
|
show={
|
||||||
<StrategyAddedChange>
|
<StrategyAddedChange>
|
||||||
@ -55,7 +56,7 @@ export const SuggestedChangeset: FC<{ suggestedChange: any }> = ({
|
|||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={change.action === 'updateStrategy'}
|
condition={change.action === 'updateStrategy'}
|
||||||
show={<StrategyEditedChange />}
|
show={<StrategyEditedChange />}
|
||||||
/>
|
/> */}
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</SuggestedFeatureToggleChange>
|
</SuggestedFeatureToggleChange>
|
||||||
|
@ -80,6 +80,9 @@ const data: any = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated for draft: useSuggestedChangesDraft
|
||||||
|
*/
|
||||||
export const useSuggestedChange = () => {
|
export const useSuggestedChange = () => {
|
||||||
// const { data, error, mutate } = useSWR(
|
// const { data, error, mutate } = useSWR(
|
||||||
// formatApiPath(`api/admin/suggest-changes/${id}`),
|
// formatApiPath(`api/admin/suggest-changes/${id}`),
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
import useSWR from 'swr';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { formatApiPath } from 'utils/formatPath';
|
||||||
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
|
interface IChange {
|
||||||
|
id: number;
|
||||||
|
action: string;
|
||||||
|
payload: {
|
||||||
|
enabled: boolean; // FIXME: add other action types
|
||||||
|
};
|
||||||
|
createdAt: Date;
|
||||||
|
createdBy: {
|
||||||
|
id: number;
|
||||||
|
username?: any;
|
||||||
|
imageUrl?: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISuggestChangesResponse {
|
||||||
|
id: number;
|
||||||
|
environment: string;
|
||||||
|
state: string;
|
||||||
|
project: string;
|
||||||
|
createdBy: {
|
||||||
|
id: number;
|
||||||
|
username?: any;
|
||||||
|
imageUrl?: any;
|
||||||
|
};
|
||||||
|
createdAt: Date;
|
||||||
|
features: Array<{
|
||||||
|
name: string;
|
||||||
|
changes: IChange[];
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetcher = (path: string) => {
|
||||||
|
return fetch(path)
|
||||||
|
.then(handleErrorResponses('SuggestedChanges'))
|
||||||
|
.then(res => res.json());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSuggestedChangesDraft = (project: string) => {
|
||||||
|
const { data, error, mutate } = useSWR<ISuggestChangesResponse[]>(
|
||||||
|
formatApiPath(`api/admin/projects/${project}/suggest-changes/draft`),
|
||||||
|
fetcher
|
||||||
|
);
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
draft: data,
|
||||||
|
loading: !error && !data,
|
||||||
|
refetch: () => mutate(),
|
||||||
|
error,
|
||||||
|
}),
|
||||||
|
[data, error, mutate]
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user