mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01: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 { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
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 = () => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { refetch: projectRefetch } = useProject(projectId);
|
||||
const { refetchFeature } = useFeature(projectId, featureId);
|
||||
const { uiConfig } = useUiConfig();
|
||||
|
||||
const [openTagDialog, setOpenTagDialog] = useState(false);
|
||||
const [showDelDialog, setShowDelDialog] = useState(false);
|
||||
@ -81,123 +85,145 @@ export const FeatureView = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<ConditionallyRender
|
||||
condition={error === undefined}
|
||||
show={
|
||||
<div ref={ref}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.innerContainer}>
|
||||
<div className={styles.toggleInfoContainer}>
|
||||
<h1
|
||||
className={styles.featureViewHeader}
|
||||
data-loading
|
||||
>
|
||||
{feature.name}{' '}
|
||||
</h1>
|
||||
<ConditionallyRender
|
||||
condition={!smallScreen}
|
||||
show={<StatusChip stale={feature?.stale} />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.toolbarContainer}>
|
||||
<PermissionIconButton
|
||||
permission={CREATE_FEATURE}
|
||||
projectId={projectId}
|
||||
data-loading
|
||||
component={Link}
|
||||
to={`/projects/${projectId}/features/${featureId}/strategies/copy`}
|
||||
tooltipProps={{
|
||||
title: 'Copy feature toggle',
|
||||
}}
|
||||
>
|
||||
<FileCopy />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
permission={DELETE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{
|
||||
title: 'Archive feature toggle',
|
||||
}}
|
||||
data-loading
|
||||
onClick={() => setShowDelDialog(true)}
|
||||
>
|
||||
<Archive />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
onClick={() => setOpenStaleDialog(true)}
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{
|
||||
title: 'Toggle stale state',
|
||||
}}
|
||||
data-loading
|
||||
>
|
||||
<WatchLater />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
onClick={() => setOpenTagDialog(true)}
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{ title: 'Add tag' }}
|
||||
data-loading
|
||||
>
|
||||
<Label />
|
||||
</PermissionIconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.separator} />
|
||||
<div className={styles.tabContainer}>
|
||||
<Tabs
|
||||
value={activeTab.path}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
>
|
||||
{tabData.map(tab => (
|
||||
<Tab
|
||||
key={tab.title}
|
||||
label={tab.title}
|
||||
value={tab.path}
|
||||
onClick={() => navigate(tab.path)}
|
||||
className={styles.tabButton}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
<Routes>
|
||||
<Route path="metrics" element={<FeatureMetrics />} />
|
||||
<Route path="logs" element={<FeatureLog />} />
|
||||
<Route path="variants" element={<FeatureVariants />} />
|
||||
<Route path="settings" element={<FeatureSettings />} />
|
||||
<Route path="*" element={<FeatureOverview />} />
|
||||
</Routes>
|
||||
<FeatureArchiveDialog
|
||||
isOpen={showDelDialog}
|
||||
onConfirm={() => {
|
||||
projectRefetch();
|
||||
navigate(`/projects/${projectId}`);
|
||||
}}
|
||||
onClose={() => setShowDelDialog(false)}
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
/>
|
||||
<FeatureStaleDialog
|
||||
isStale={feature.stale}
|
||||
isOpen={openStaleDialog}
|
||||
onClose={() => {
|
||||
setOpenStaleDialog(false);
|
||||
refetchFeature();
|
||||
}}
|
||||
featureId={featureId}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<AddTagDialog
|
||||
open={openTagDialog}
|
||||
setOpen={setOpenTagDialog}
|
||||
/>
|
||||
</div>
|
||||
<MainLayout
|
||||
ref={ref}
|
||||
subheader={
|
||||
uiConfig?.flags?.suggestChanges ? (
|
||||
<DraftBanner project={projectId} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={error === undefined}
|
||||
show={
|
||||
<div ref={ref}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.innerContainer}>
|
||||
<div className={styles.toggleInfoContainer}>
|
||||
<h1
|
||||
className={styles.featureViewHeader}
|
||||
data-loading
|
||||
>
|
||||
{feature.name}{' '}
|
||||
</h1>
|
||||
<ConditionallyRender
|
||||
condition={!smallScreen}
|
||||
show={
|
||||
<StatusChip
|
||||
stale={feature?.stale}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.toolbarContainer}>
|
||||
<PermissionIconButton
|
||||
permission={CREATE_FEATURE}
|
||||
projectId={projectId}
|
||||
data-loading
|
||||
component={Link}
|
||||
to={`/projects/${projectId}/features/${featureId}/strategies/copy`}
|
||||
tooltipProps={{
|
||||
title: 'Copy feature toggle',
|
||||
}}
|
||||
>
|
||||
<FileCopy />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
permission={DELETE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{
|
||||
title: 'Archive feature toggle',
|
||||
}}
|
||||
data-loading
|
||||
onClick={() => setShowDelDialog(true)}
|
||||
>
|
||||
<Archive />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
onClick={() => setOpenStaleDialog(true)}
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{
|
||||
title: 'Toggle stale state',
|
||||
}}
|
||||
data-loading
|
||||
>
|
||||
<WatchLater />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
onClick={() => setOpenTagDialog(true)}
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
tooltipProps={{ title: 'Add tag' }}
|
||||
data-loading
|
||||
>
|
||||
<Label />
|
||||
</PermissionIconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.separator} />
|
||||
<div className={styles.tabContainer}>
|
||||
<Tabs
|
||||
value={activeTab.path}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
>
|
||||
{tabData.map(tab => (
|
||||
<Tab
|
||||
key={tab.title}
|
||||
label={tab.title}
|
||||
value={tab.path}
|
||||
onClick={() => navigate(tab.path)}
|
||||
className={styles.tabButton}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
<Routes>
|
||||
<Route
|
||||
path="metrics"
|
||||
element={<FeatureMetrics />}
|
||||
/>
|
||||
<Route path="logs" element={<FeatureLog />} />
|
||||
<Route
|
||||
path="variants"
|
||||
element={<FeatureVariants />}
|
||||
/>
|
||||
<Route
|
||||
path="settings"
|
||||
element={<FeatureSettings />}
|
||||
/>
|
||||
<Route path="*" element={<FeatureOverview />} />
|
||||
</Routes>
|
||||
<FeatureArchiveDialog
|
||||
isOpen={showDelDialog}
|
||||
onConfirm={() => {
|
||||
projectRefetch();
|
||||
navigate(`/projects/${projectId}`);
|
||||
}}
|
||||
onClose={() => setShowDelDialog(false)}
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
/>
|
||||
<FeatureStaleDialog
|
||||
isStale={feature.stale}
|
||||
isOpen={openStaleDialog}
|
||||
onClose={() => {
|
||||
setOpenStaleDialog(false);
|
||||
refetchFeature();
|
||||
}}
|
||||
featureId={featureId}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<AddTagDialog
|
||||
open={openTagDialog}
|
||||
setOpen={setOpenTagDialog}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</MainLayout>
|
||||
);
|
||||
};
|
||||
|
@ -54,6 +54,7 @@ exports[`returns all baseRoutes 1`] = `
|
||||
},
|
||||
{
|
||||
"component": [Function],
|
||||
"isStandalone": true,
|
||||
"menu": {},
|
||||
"parent": "/projects",
|
||||
"path": "/projects/:projectId/features/:featureId/*",
|
||||
|
@ -121,6 +121,7 @@ export const routes: IRoute[] = [
|
||||
title: 'FeatureView',
|
||||
component: FeatureView,
|
||||
type: 'protected',
|
||||
isStandalone: true,
|
||||
menu: {},
|
||||
},
|
||||
{
|
||||
|
@ -117,7 +117,11 @@ const Project = () => {
|
||||
return (
|
||||
<MainLayout
|
||||
ref={ref}
|
||||
subheader={uiConfig?.flags?.suggestChanges ? <DraftBanner /> : null}
|
||||
subheader={
|
||||
uiConfig?.flags?.suggestChanges ? (
|
||||
<DraftBanner project={projectId} />
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.innerContainer}>
|
||||
|
@ -4,14 +4,21 @@ import { useStyles as useAppStyles } from 'component/App.styles';
|
||||
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { SuggestedChangesSidebar } from '../SuggestedChangesSidebar/SuggestedChangesSidebar';
|
||||
import { useSuggestedChangesDraft } from 'hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft';
|
||||
|
||||
interface IDraftBannerProps {
|
||||
environment?: string;
|
||||
project: string;
|
||||
}
|
||||
|
||||
export const DraftBanner: VFC<IDraftBannerProps> = ({ environment }) => {
|
||||
export const DraftBanner: VFC<IDraftBannerProps> = ({ project }) => {
|
||||
const { classes } = useAppStyles();
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
const { draft, loading } = useSuggestedChangesDraft(project);
|
||||
const environment = '';
|
||||
|
||||
if (!loading && !draft) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -63,6 +70,7 @@ export const DraftBanner: VFC<IDraftBannerProps> = ({ environment }) => {
|
||||
</Box>
|
||||
</Box>
|
||||
<SuggestedChangesSidebar
|
||||
project={project}
|
||||
open={isSidebarOpen}
|
||||
onClose={() => {
|
||||
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 { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { HelpOutline } from '@mui/icons-material';
|
||||
import { useSuggestedChange } from 'hooks/api/getters/useSuggestChange/useSuggestedChange';
|
||||
import { SuggestedChangeset } from '../SuggestedChangeset/SuggestedChangeset';
|
||||
import { useSuggestedChangesDraft } from 'hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft';
|
||||
|
||||
interface ISuggestedChangesSidebarProps {
|
||||
open: boolean;
|
||||
project: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
const StyledPageContent = styled(PageContent)(({ theme }) => ({
|
||||
@ -26,11 +28,13 @@ const StyledPageContent = styled(PageContent)(({ theme }) => ({
|
||||
},
|
||||
borderRadius: `${theme.spacing(1.5, 0, 0, 1.5)} !important`,
|
||||
}));
|
||||
|
||||
const StyledHelpOutline = styled(HelpOutline)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.mainHeader,
|
||||
marginLeft: '0.3rem',
|
||||
color: theme.palette.grey[700],
|
||||
}));
|
||||
|
||||
const StyledHeaderHint = styled('div')(({ theme }) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
@ -38,23 +42,43 @@ const StyledHeaderHint = styled('div')(({ theme }) => ({
|
||||
|
||||
export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
|
||||
open,
|
||||
project,
|
||||
onClose,
|
||||
}) => {
|
||||
const { data: suggestedChange } = useSuggestedChange();
|
||||
const { draft, loading } = useSuggestedChangesDraft(project);
|
||||
|
||||
const onReview = async () => {
|
||||
console.log('approve');
|
||||
alert('approve');
|
||||
};
|
||||
const onDiscard = async () => {
|
||||
console.log('discard');
|
||||
alert('discard');
|
||||
};
|
||||
const onApply = async () => {
|
||||
try {
|
||||
console.log('apply');
|
||||
alert('apply');
|
||||
} catch (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 (
|
||||
<SidebarModal open={open} onClose={onClose} label="Review changes">
|
||||
<StyledPageContent
|
||||
@ -80,55 +104,82 @@ export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
|
||||
></PageHeader>
|
||||
}
|
||||
>
|
||||
{/* TODO: multiple environments (changesets) */}
|
||||
<Typography>{suggestedChange?.state}</Typography>
|
||||
<br />
|
||||
<SuggestedChangeset suggestedChange={suggestedChange} />
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<ConditionallyRender
|
||||
condition={suggestedChange?.state === 'APPROVED'}
|
||||
show={<Typography>Applied</Typography>}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={suggestedChange?.state === 'CLOSED'}
|
||||
show={<Typography>Applied</Typography>}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={suggestedChange?.state === 'APPROVED'}
|
||||
show={
|
||||
<>
|
||||
<Button
|
||||
sx={{ mt: 2 }}
|
||||
variant="contained"
|
||||
onClick={onApply}
|
||||
>
|
||||
Apply changes
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={suggestedChange?.state === 'CREATED'}
|
||||
show={
|
||||
<>
|
||||
<Button
|
||||
sx={{ mt: 2, ml: 'auto' }}
|
||||
variant="contained"
|
||||
onClick={onReview}
|
||||
>
|
||||
Request changes
|
||||
</Button>
|
||||
<Button
|
||||
sx={{ mt: 2, ml: 2 }}
|
||||
variant="outlined"
|
||||
onClick={onDiscard}
|
||||
>
|
||||
Discard changes
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
{draft?.map(environmentChangeset => (
|
||||
<Box
|
||||
key={environmentChangeset.id}
|
||||
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' }}>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
environmentChangeset?.state === 'APPROVED'
|
||||
}
|
||||
show={<Typography>Applied</Typography>}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
environmentChangeset?.state === 'CLOSED'
|
||||
}
|
||||
show={<Typography>Applied</Typography>}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
environmentChangeset?.state === 'APPROVED'
|
||||
}
|
||||
show={
|
||||
<>
|
||||
<Button
|
||||
sx={{ mt: 2 }}
|
||||
variant="contained"
|
||||
onClick={onApply}
|
||||
>
|
||||
Apply changes
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
environmentChangeset?.state === 'Draft'
|
||||
}
|
||||
show={
|
||||
<>
|
||||
<Button
|
||||
sx={{ mt: 2, ml: 'auto' }}
|
||||
variant="contained"
|
||||
onClick={onReview}
|
||||
>
|
||||
Request changes
|
||||
</Button>
|
||||
<Button
|
||||
sx={{ mt: 2, ml: 2 }}
|
||||
variant="outlined"
|
||||
onClick={onDiscard}
|
||||
>
|
||||
Discard changes
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</StyledPageContent>
|
||||
</SidebarModal>
|
||||
);
|
||||
|
@ -4,38 +4,39 @@ import { SuggestedFeatureToggleChange } from '../SuggestedChangeOverview/Suggest
|
||||
import { objectId } from 'utils/objectId';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { ToggleStatusChange } from '../SuggestedChangeOverview/SuggestedFeatureToggleChange/ToggleStatusChange';
|
||||
import {
|
||||
StrategyAddedChange,
|
||||
StrategyDeletedChange,
|
||||
StrategyEditedChange,
|
||||
} from '../SuggestedChangeOverview/SuggestedFeatureToggleChange/StrategyChange';
|
||||
import {
|
||||
formatStrategyName,
|
||||
GetFeatureStrategyIcon,
|
||||
} from 'utils/strategyNames';
|
||||
// import {
|
||||
// StrategyAddedChange,
|
||||
// StrategyDeletedChange,
|
||||
// StrategyEditedChange,
|
||||
// } from '../SuggestedChangeOverview/SuggestedFeatureToggleChange/StrategyChange';
|
||||
// import {
|
||||
// formatStrategyName,
|
||||
// GetFeatureStrategyIcon,
|
||||
// } from 'utils/strategyNames';
|
||||
import type { ISuggestChangesResponse } from 'hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft';
|
||||
|
||||
export const SuggestedChangeset: FC<{ suggestedChange: any }> = ({
|
||||
suggestedChange,
|
||||
}) => {
|
||||
export const SuggestedChangeset: FC<{
|
||||
suggestedChange: ISuggestChangesResponse;
|
||||
}> = ({ suggestedChange }) => {
|
||||
return (
|
||||
<Box>
|
||||
Changes
|
||||
{suggestedChange.changes?.map((featureToggleChange: any) => (
|
||||
{suggestedChange.features?.map(featureToggleChange => (
|
||||
<SuggestedFeatureToggleChange
|
||||
key={featureToggleChange.feature}
|
||||
featureToggleName={featureToggleChange.feature}
|
||||
key={featureToggleChange.name}
|
||||
featureToggleName={featureToggleChange.name}
|
||||
>
|
||||
{featureToggleChange.changeSet.map((change: any) => (
|
||||
{featureToggleChange.changes.map(change => (
|
||||
<Box key={objectId(change)}>
|
||||
<ConditionallyRender
|
||||
condition={change.action === 'updateEnabled'}
|
||||
show={
|
||||
<ToggleStatusChange
|
||||
enabled={change?.payload?.data?.data}
|
||||
enabled={change?.payload?.enabled}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
{/* <ConditionallyRender
|
||||
condition={change.action === 'addStrategy'}
|
||||
show={
|
||||
<StrategyAddedChange>
|
||||
@ -55,7 +56,7 @@ export const SuggestedChangeset: FC<{ suggestedChange: any }> = ({
|
||||
<ConditionallyRender
|
||||
condition={change.action === 'updateStrategy'}
|
||||
show={<StrategyEditedChange />}
|
||||
/>
|
||||
/> */}
|
||||
</Box>
|
||||
))}
|
||||
</SuggestedFeatureToggleChange>
|
||||
|
@ -80,6 +80,9 @@ const data: any = {
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated for draft: useSuggestedChangesDraft
|
||||
*/
|
||||
export const useSuggestedChange = () => {
|
||||
// const { data, error, mutate } = useSWR(
|
||||
// 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