1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

Rename suggest changes to change request (#2311)

* Rename change request

* Merge with review status

* Move events and permissions
This commit is contained in:
sjaanus 2022-11-02 07:34:14 +01:00 committed by GitHub
parent da102a3e98
commit 5dd8616c74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 328 additions and 414 deletions

View File

@ -1,45 +1,36 @@
import { useCallback, VFC } from 'react';
import { VFC } from 'react';
import { Box } from '@mui/material';
import { SuggestedFeatureToggleChange } from '../SuggestedChangeOverview/SuggestedFeatureToggleChange/SuggestedFeatureToggleChange';
import { ChangeRequestFeatureToggleChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ChangeRequestFeatureToggleChange';
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 type { ISuggestChangesResponse } from 'hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft';
import { useSuggestChangeApi } from 'hooks/api/actions/useSuggestChangeApi/useSuggestChangeApi';
import { ToggleStatusChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ToggleStatusChange';
import type { IChangeRequestResponse } from 'hooks/api/getters/useChangeRequestDraft/useChangeRequestDraft';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { formatUnknownError } from 'utils/formatUnknownError';
import useToast from 'hooks/useToast';
interface ISuggestedChangeset {
suggestedChange: ISuggestChangesResponse;
interface IChangeRequest {
changeRequest: IChangeRequestResponse;
onRefetch?: () => void;
onNavigate?: () => void;
}
export const SuggestedChangeset: VFC<ISuggestedChangeset> = ({
suggestedChange,
export const ChangeRequest: VFC<IChangeRequest> = ({
changeRequest,
onRefetch,
onNavigate,
}) => {
const { discardSuggestions } = useSuggestChangeApi();
const { discardChangeRequestEvent } = useChangeRequestApi();
const { setToastData, setToastApiError } = useToast();
const onDiscard = (id: number) => async () => {
try {
await discardSuggestions(
suggestedChange.project,
suggestedChange.id,
await discardChangeRequestEvent(
changeRequest.project,
changeRequest.id,
id
);
setToastData({
title: 'Change discarded from suggestion draft.',
title: 'Change discarded from change request draft.',
type: 'success',
});
onRefetch?.();
@ -51,11 +42,11 @@ export const SuggestedChangeset: VFC<ISuggestedChangeset> = ({
return (
<Box>
Changes
{suggestedChange.features?.map(featureToggleChange => (
<SuggestedFeatureToggleChange
{changeRequest.features?.map(featureToggleChange => (
<ChangeRequestFeatureToggleChange
key={featureToggleChange.name}
featureName={featureToggleChange.name}
projectId={suggestedChange.project}
projectId={changeRequest.project}
onNavigate={onNavigate}
>
{featureToggleChange.changes.map(change => (
@ -92,7 +83,7 @@ export const SuggestedChangeset: VFC<ISuggestedChangeset> = ({
/> */}
</Box>
))}
</SuggestedFeatureToggleChange>
</ChangeRequestFeatureToggleChange>
))}
</Box>
);

View File

@ -2,7 +2,7 @@ import { FC } from 'react';
import { Alert, Typography } from '@mui/material';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
interface ISuggestChangesDialogueProps {
interface IChangeRequestDialogueProps {
isOpen: boolean;
onConfirm: () => void;
onClose: () => void;
@ -11,7 +11,7 @@ interface ISuggestChangesDialogueProps {
enabled?: boolean;
}
export const SuggestChangesDialogue: FC<ISuggestChangesDialogueProps> = ({
export const ChangeRequestDialogue: FC<IChangeRequestDialogueProps> = ({
isOpen,
onConfirm,
onClose,
@ -25,15 +25,15 @@ export const SuggestChangesDialogue: FC<ISuggestChangesDialogueProps> = ({
secondaryButtonText="Cancel"
onClick={onConfirm}
onClose={onClose}
title="Suggest changes"
title="Request changes"
>
<Alert severity="info" sx={{ mb: 2 }}>
Suggest changes is enabled for {environment}. Your changes needs to
Change requests is enabled for {environment}. Your changes needs to
be approved before they will be live. All the changes you do now
will be added into a draft that you can submit for review.
</Alert>
<Typography variant="body2" color="text.secondary">
Suggested changes:
Change requests:
</Typography>
<Typography>
<strong>{enabled ? 'Disable' : 'Enable'}</strong> feature toggle{' '}

View File

@ -3,14 +3,14 @@ import { Link } from 'react-router-dom';
import { Box, Card, Typography } from '@mui/material';
import ToggleOnIcon from '@mui/icons-material/ToggleOn';
interface ISuggestedFeatureToggleChange {
interface IChangeRequestToggleChange {
featureName: string;
projectId: string;
onNavigate?: () => void;
}
export const SuggestedFeatureToggleChange: FC<
ISuggestedFeatureToggleChange
export const ChangeRequestFeatureToggleChange: FC<
IChangeRequestToggleChange
> = ({ featureName, projectId, onNavigate, children }) => {
return (
<Card

View File

@ -4,8 +4,8 @@ import { PlaygroundResultChip } from 'component/playground/Playground/Playground
import { ReactComponent as ChangesAppliedIcon } from 'assets/icons/merge.svg';
import TimeAgo from 'react-timeago';
export const SuggestedChangeHeader: FC<{ suggestedChange: any }> = ({
suggestedChange,
export const ChangeRequestHeader: FC<{ changeRequest: any }> = ({
changeRequest,
}) => {
return (
<Paper
@ -30,9 +30,9 @@ export const SuggestedChangeHeader: FC<{ suggestedChange: any }> = ({
}}
variant="h1"
>
Suggestion
Change request
<Typography variant="h1" component="p">
#{suggestedChange.id}
#{changeRequest.id}
</Typography>
</Typography>
<PlaygroundResultChip
@ -43,10 +43,10 @@ export const SuggestedChangeHeader: FC<{ suggestedChange: any }> = ({
</Box>
<Box sx={{ display: 'flex', verticalAlign: 'center', gap: 2 }}>
<Typography sx={{ margin: 'auto 0' }}>
Created{' '}
<TimeAgo date={new Date(suggestedChange.createdAt)} /> by
Created <TimeAgo date={new Date(changeRequest.createdAt)} />{' '}
by
</Typography>
<Avatar src={suggestedChange?.createdBy?.avatar} />
<Avatar src={changeRequest?.createdBy?.avatar} />
<Card
variant="outlined"
sx={theme => ({
@ -56,11 +56,11 @@ export const SuggestedChangeHeader: FC<{ suggestedChange: any }> = ({
>
Environment:{' '}
<Typography display="inline" fontWeight="bold">
{suggestedChange?.environment}
{changeRequest?.environment}
</Typography>{' '}
| Updates:{' '}
<Typography display="inline" fontWeight="bold">
{suggestedChange?.features.length} feature toggles
{changeRequest?.features.length} feature toggles
</Typography>
</Card>
</Box>

View File

@ -1,24 +1,24 @@
import { FC } from 'react';
import { Box, Button, Paper } from '@mui/material';
import { useSuggestedChange } from 'hooks/api/getters/useSuggestChange/useSuggestedChange';
import { SuggestedChangeHeader } from './SuggestedChangeHeader/SuggestedChangeHeader';
import { SuggestedChangeTimeline } from './SuggestedChangeTimeline/SuggestedChangeTimeline';
import { SuggestedChangeReviewers } from './SuggestedChangeReviewers/SuggestedChangeReviewers';
import { SuggestedChangeset } from '../SuggestedChangeset/SuggestedChangeset';
import { useChangeRequest } from 'hooks/api/getters/useChangeRequest/useChangeRequest';
import { ChangeRequestHeader } from './ChangeRequestHeader/ChangeRequestHeader';
import { ChangeRequestTimeline } from './ChangeRequestTimeline/ChangeRequestTimeline';
import { ChangeRequestReviewers } from './ChangeRequestReviewers/ChangeRequestReviewers';
import { ChangeRequest } from '../ChangeRequest/ChangeRequest';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useSuggestChangeApi } from 'hooks/api/actions/useSuggestChangeApi/useSuggestChangeApi';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { SuggestedChangeReviewStatus } from './SuggestedChangeReviewStatus/SuggestedChangeReviewStatus';
import { ChangeRequestReviewStatus } from './ChangeRequestReviewStatus/ChangeRequestReviewStatus';
export const SuggestedChangeOverview: FC = () => {
export const ChangeRequestOverview: FC = () => {
const projectId = useRequiredPathParam('projectId');
const id = useRequiredPathParam('id');
const { data: suggestedChange } = useSuggestedChange(projectId, id);
const { applyChanges } = useSuggestChangeApi();
const { data: changeRequest } = useChangeRequest(projectId, id);
const { applyChanges } = useChangeRequestApi();
const { setToastData, setToastApiError } = useToast();
if (!suggestedChange) {
if (!changeRequest) {
return null;
}
@ -37,7 +37,7 @@ export const SuggestedChangeOverview: FC = () => {
return (
<>
<SuggestedChangeHeader suggestedChange={suggestedChange} />
<ChangeRequestHeader changeRequest={changeRequest} />
<Box sx={{ display: 'flex' }}>
<Box
sx={{
@ -46,8 +46,8 @@ export const SuggestedChangeOverview: FC = () => {
flexDirection: 'column',
}}
>
<SuggestedChangeTimeline />
<SuggestedChangeReviewers />
<ChangeRequestTimeline />
<ChangeRequestReviewers />
</Box>
<Paper
elevation={0}
@ -65,8 +65,8 @@ export const SuggestedChangeOverview: FC = () => {
padding: theme.spacing(2),
})}
>
<SuggestedChangeset suggestedChange={suggestedChange} />
<SuggestedChangeReviewStatus approved={true} />
<ChangeRequest changeRequest={changeRequest} />
<ChangeRequestReviewStatus approved={true} />
<Button
variant="contained"
sx={{ marginTop: 2 }}

View File

@ -11,13 +11,13 @@ import {
StyledErrorIcon,
StyledReviewTitle,
StyledDivider,
} from './SuggestChangeReviewStatus.styles';
} from './ChangeRequestReviewStatus.styles';
interface ISuggestChangeReviewsStatusProps {
approved: boolean;
}
export const SuggestedChangeReviewStatus: FC<
export const ChangeRequestReviewStatus: FC<
ISuggestChangeReviewsStatusProps
> = ({ approved }) => {
return (

View File

@ -1,6 +1,6 @@
import { Box, Paper } from '@mui/material';
export const SuggestedChangeReviewers = () => {
export const ChangeRequestReviewers = () => {
return (
<Paper
elevation={0}

View File

@ -7,7 +7,7 @@ import TimelineDot from '@mui/lab/TimelineDot';
import TimelineConnector from '@mui/lab/TimelineConnector';
import TimelineContent from '@mui/lab/TimelineContent';
export const SuggestedChangeTimeline: FC = () => {
export const ChangeRequestTimeline: FC = () => {
return (
<Paper
elevation={0}

View File

@ -5,11 +5,11 @@ 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 { SuggestedChangeset } from '../SuggestedChangeset/SuggestedChangeset';
import { useSuggestedChangesDraft } from 'hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft';
import { useSuggestChangeApi } from 'hooks/api/actions/useSuggestChangeApi/useSuggestChangeApi';
import { ChangeRequest } from '../ChangeRequest/ChangeRequest';
import { useChangeRequestDraft } from 'hooks/api/getters/useChangeRequestDraft/useChangeRequestDraft';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
interface ISuggestedChangesSidebarProps {
interface IChangeRequestSidebarProps {
open: boolean;
project: string;
onClose: () => void;
@ -41,7 +41,7 @@ const StyledHeaderHint = styled('div')(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
}));
export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
open,
project,
onClose,
@ -49,14 +49,14 @@ export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
const {
draft,
loading,
refetch: refetchSuggestedChanges,
} = useSuggestedChangesDraft(project);
const { changeState } = useSuggestChangeApi();
refetch: refetchChangeRequest,
} = useChangeRequestDraft(project);
const { changeState } = useChangeRequestApi();
const onReview = async (draftId: number) => {
try {
await changeState(project, draftId, { state: 'In review' });
refetchSuggestedChanges();
refetchChangeRequest();
} catch (e) {
console.log('something went wrong');
}
@ -108,16 +108,16 @@ export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
</Tooltip>
<StyledHeaderHint>
Make sure you are sending the right changes
suggestions to be reviewed
to be reviewed
</StyledHeaderHint>
</>
}
></PageHeader>
}
>
{draft?.map(environmentChangeset => (
{draft?.map(environmentChangeRequest => (
<Box
key={environmentChangeset.id}
key={environmentChangeRequest.id}
sx={{
padding: 2,
border: '2px solid',
@ -127,35 +127,37 @@ export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
}}
>
<Typography>
env: {environmentChangeset?.environment}
env: {environmentChangeRequest?.environment}
</Typography>
<Typography>
state: {environmentChangeset?.state}
state: {environmentChangeRequest?.state}
</Typography>
<hr />
<SuggestedChangeset
suggestedChange={environmentChangeset}
<ChangeRequest
changeRequest={environmentChangeRequest}
onNavigate={() => {
onClose();
}}
onRefetch={refetchSuggestedChanges}
onRefetch={refetchChangeRequest}
/>
<Box sx={{ display: 'flex' }}>
<ConditionallyRender
condition={
environmentChangeset?.state === 'APPROVED'
environmentChangeRequest?.state ===
'APPROVED'
}
show={<Typography>Applied</Typography>}
/>
<ConditionallyRender
condition={
environmentChangeset?.state === 'CLOSED'
environmentChangeRequest?.state === 'CLOSED'
}
show={<Typography>Applied</Typography>}
/>
<ConditionallyRender
condition={
environmentChangeset?.state === 'APPROVED'
environmentChangeRequest?.state ===
'APPROVED'
}
show={
<>
@ -171,7 +173,7 @@ export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
/>
<ConditionallyRender
condition={
environmentChangeset?.state === 'Draft'
environmentChangeRequest?.state === 'Draft'
}
show={
<>
@ -180,7 +182,7 @@ export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
variant="contained"
onClick={() =>
onReview(
environmentChangeset.id
environmentChangeRequest.id
)
}
>

View File

@ -3,8 +3,8 @@ import { Box, Button, Typography } from '@mui/material';
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';
import { ChangeRequestSidebar } from '../ChangeRequestSidebar/ChangeRequestSidebar';
import { useChangeRequestDraft } from 'hooks/api/getters/useChangeRequestDraft/useChangeRequestDraft';
interface IDraftBannerProps {
project: string;
@ -13,7 +13,7 @@ interface IDraftBannerProps {
export const DraftBanner: VFC<IDraftBannerProps> = ({ project }) => {
const { classes } = useAppStyles();
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const { draft, loading } = useSuggestedChangesDraft(project);
const { draft, loading } = useChangeRequestDraft(project);
const environment = '';
if ((!loading && !draft) || draft?.length === 0) {
@ -69,7 +69,7 @@ export const DraftBanner: VFC<IDraftBannerProps> = ({ project }) => {
</Button>
</Box>
</Box>
<SuggestedChangesSidebar
<ChangeRequestSidebar
project={project}
open={isSidebarOpen}
onClose={() => {

View File

@ -2,7 +2,7 @@ import { ArrowRight } from '@mui/icons-material';
import { useTheme } from '@mui/system';
import { TextCell } from '../../../../common/Table/cells/TextCell/TextCell';
export const ChangesetActionCell = () => {
export const ChangeRequestActionCell = () => {
const theme = useTheme();
return (
<TextCell sx={{ textAlign: 'right' }}>

View File

@ -4,11 +4,11 @@ import { colors } from 'themes/colors';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { Check, CircleOutlined, Close } from '@mui/icons-material';
interface IChangesetStatusCellProps {
interface IChangeRequestStatusCellProps {
value?: string | null;
}
export enum SuggestChangesetState {
export enum ChangeRequestState {
DRAFT = 'Draft',
APPROVED = 'Approved',
IN_REVIEW = 'In review',
@ -62,40 +62,40 @@ export const StyledReviewChip = styled(StyledChip)(({ theme }) => ({
},
}));
export const ChangesetStatusCell: VFC<IChangesetStatusCellProps> = ({
export const ChangeRequestStatusCell: VFC<IChangeRequestStatusCellProps> = ({
value,
}) => {
const renderState = (state: string) => {
switch (state) {
case SuggestChangesetState.IN_REVIEW:
case ChangeRequestState.IN_REVIEW:
return (
<StyledReviewChip
label={'Review required'}
icon={<CircleOutlined fontSize={'small'} />}
/>
);
case SuggestChangesetState.APPROVED:
case ChangeRequestState.APPROVED:
return (
<StyledApprovedChip
label={'Approved'}
icon={<Check fontSize={'small'} />}
/>
);
case SuggestChangesetState.APPLIED:
case ChangeRequestState.APPLIED:
return (
<StyledApprovedChip
label={'Applied'}
icon={<Check fontSize={'small'} />}
/>
);
case SuggestChangesetState.CANCELLED:
case ChangeRequestState.CANCELLED:
return (
<StyledRejectedChip
label={'Cancelled'}
icon={<Close fontSize={'small'} sx={{ mr: 8 }} />}
/>
);
case SuggestChangesetState.REJECTED:
case ChangeRequestState.REJECTED:
return (
<StyledRejectedChip
label={'Rejected'}

View File

@ -4,7 +4,7 @@ import { Link as RouterLink } from 'react-router-dom';
import { useTheme } from '@mui/system';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
interface IChangesetTitleCellProps {
interface IChangeRequestTitleCellProps {
value?: any;
row: { original: any };
}
@ -15,14 +15,14 @@ export const StyledLink = styled('div')(({ theme }) => ({
margin: 0,
}));
export const ChangesetTitleCell = ({
export const ChangeRequestTitleCell = ({
value,
row: { original },
}: IChangesetTitleCellProps) => {
}: IChangeRequestTitleCellProps) => {
const projectId = useRequiredPathParam('projectId');
const { id, features: changes } = original;
const theme = useTheme();
const path = `/projects/${projectId}/suggest-changes/${id}`;
const path = `/projects/${projectId}/change-requests/${id}`;
if (!value) {
return <TextCell />;
@ -37,7 +37,7 @@ export const ChangesetTitleCell = ({
to={path}
sx={{ pt: 0.2 }}
>
Suggestion
Change request
</Link>
<Typography
component={'span'}

View File

@ -19,15 +19,15 @@ import { useSearch } from 'hooks/useSearch';
import { useSearchParams } from 'react-router-dom';
import { TimeAgoCell } from '../../../common/Table/cells/TimeAgoCell/TimeAgoCell';
import { TextCell } from '../../../common/Table/cells/TextCell/TextCell';
import { ChangesetStatusCell } from './ChangesetStatusCell/ChangesetStatusCell';
import { ChangesetActionCell } from './ChangesetActionCell/ChangesetActionCell';
import { ChangeRequestStatusCell } from './ChangeRequestStatusCell/ChangeRequestStatusCell';
import { ChangeRequestActionCell } from './ChangeRequestActionCell/ChangeRequestActionCell';
import { AvatarCell } from './AvatarCell/AvatarCell';
import { ChangesetTitleCell } from './ChangesetTitleCell/ChangesetTitleCell';
import { ChangeRequestTitleCell } from './ChangeRequestTitleCell/ChangeRequestTitleCell';
import { TableBody, TableRow } from '../../../common/Table';
import { useStyles } from './SuggestionsTabs.styles';
import { useStyles } from './ChangeRequestsTabs.styles';
export interface IChangeSetTableProps {
changesets: any[];
export interface IChangeRequestTableProps {
changeRequests: any[];
loading: boolean;
storedParams: SortingRule<string>;
setStoredParams: (
@ -38,13 +38,13 @@ export interface IChangeSetTableProps {
projectId: string;
}
export const SuggestionsTabs = ({
changesets = [],
export const ChangeRequestsTabs = ({
changeRequests = [],
loading,
storedParams,
setStoredParams,
projectId,
}: IChangeSetTableProps) => {
}: IChangeRequestTableProps) => {
const { classes } = useStyles();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const [searchParams, setSearchParams] = useSearchParams();
@ -53,27 +53,29 @@ export const SuggestionsTabs = ({
searchParams.get('search') || ''
);
const [openChangesets, closedChangesets] = useMemo(() => {
const open = changesets.filter(
changeset =>
changeset.state !== 'Cancelled' && changeset.state !== 'Applied'
const [openChangeRequests, closedChangeRequests] = useMemo(() => {
const open = changeRequests.filter(
changeRequest =>
changeRequest.state !== 'Cancelled' &&
changeRequest.state !== 'Applied'
);
const closed = changesets.filter(
changeset =>
changeset.state === 'Cancelled' || changeset.state === 'Applied'
const closed = changeRequests.filter(
changeRequest =>
changeRequest.state === 'Cancelled' ||
changeRequest.state === 'Applied'
);
return [open, closed];
}, [changesets]);
}, [changeRequests]);
const tabs = [
{
title: 'Suggestions',
data: openChangesets,
title: 'Change requests',
data: openChangeRequests,
},
{
title: 'Closed',
data: closedChangesets,
data: closedChangeRequests,
},
];
@ -87,7 +89,7 @@ export const SuggestionsTabs = ({
width: 100,
canSort: true,
accessor: 'id',
Cell: ChangesetTitleCell,
Cell: ChangeRequestTitleCell,
},
{
Header: 'By',
@ -117,7 +119,7 @@ export const SuggestionsTabs = ({
accessor: 'state',
minWidth: 150,
width: 150,
Cell: ChangesetStatusCell,
Cell: ChangeRequestStatusCell,
sortType: 'text',
},
{
@ -126,7 +128,7 @@ export const SuggestionsTabs = ({
minWidth: 50,
width: 50,
canSort: false,
Cell: ChangesetActionCell,
Cell: ChangeRequestActionCell,
},
],
//eslint-disable-next-line

View File

@ -2,28 +2,28 @@ import { usePageTitle } from 'hooks/usePageTitle';
import { createLocalStorage } from 'utils/createLocalStorage';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useProjectNameOrId } from 'hooks/api/getters/useProject/useProject';
import { SuggestionsTabs } from './SuggestionsTabs/SuggestionsTabs';
import { ChangeRequestsTabs } from './ChangeRequestsTabs/ChangeRequestsTabs';
import { SortingRule } from 'react-table';
import { useProjectSuggestedChanges } from 'hooks/api/getters/useProjectSuggestedChanges/useProjectSuggestedChanges';
import { useProjectChangeRequests } from 'hooks/api/getters/useProjectChangeRequests/useProjectChangeRequests';
const defaultSort: SortingRule<string> = { id: 'updatedAt', desc: true };
export const ProjectSuggestedChanges = () => {
export const ProjectChangeRequests = () => {
const projectId = useRequiredPathParam('projectId');
const projectName = useProjectNameOrId(projectId);
usePageTitle(`Change requests ${projectName}`);
const { changesets, loading } = useProjectSuggestedChanges(projectId);
const { changeRequests, loading } = useProjectChangeRequests(projectId);
const { value, setValue } = createLocalStorage(
`${projectId}:ProjectSuggestedChanges`,
`${projectId}:ProjectChangeRequest`,
defaultSort
);
return (
<SuggestionsTabs
changesets={changesets}
<ChangeRequestsTabs
changeRequests={changeRequests}
storedParams={value}
setStoredParams={setValue}
projectId={projectId}

View File

@ -11,8 +11,8 @@ import { formatUnknownError } from 'utils/formatUnknownError';
import { useStyles } from './FeatureOverviewEnvSwitch.styles';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useSuggestToggle } from 'hooks/useSuggestToggle';
import { SuggestChangesDialogue } from 'component/suggestChanges/SuggestChangeConfirmDialog/SuggestChangeConfirmDialog';
import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle';
import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
interface IFeatureOverviewEnvSwitchProps {
env: IFeatureEnvironment;
@ -36,11 +36,11 @@ const FeatureOverviewEnvSwitch = ({
const { classes: styles } = useStyles();
const { uiConfig } = useUiConfig();
const {
onSuggestToggle,
onSuggestToggleClose,
onSuggestToggleConfirm,
suggestChangesDialogDetails,
} = useSuggestToggle(projectId);
onChangeRequestToggle,
onChangeRequestToggleClose,
onChangeRequestToggleConfirm,
changeRequestDialogDetails,
} = useChangeRequestToggle(projectId);
const handleToggleEnvironmentOn = async () => {
try {
@ -84,9 +84,9 @@ const FeatureOverviewEnvSwitch = ({
};
const toggleEnvironment = async (e: React.ChangeEvent) => {
if (uiConfig?.flags?.suggestChanges && env.name === 'production') {
if (uiConfig?.flags?.changeRequests && env.name === 'production') {
e.preventDefault();
onSuggestToggle(featureId, env.name, env.enabled);
onChangeRequestToggle(featureId, env.name, env.enabled);
return;
}
if (env.enabled) {
@ -119,12 +119,12 @@ const FeatureOverviewEnvSwitch = ({
/>
{content}
</label>
<SuggestChangesDialogue
isOpen={suggestChangesDialogDetails.isOpen}
onClose={onSuggestToggleClose}
<ChangeRequestDialogue
isOpen={changeRequestDialogDetails.isOpen}
onClose={onChangeRequestToggleClose}
featureName={featureId}
environment={suggestChangesDialogDetails?.environment}
onConfirm={onSuggestToggleConfirm}
environment={changeRequestDialogDetails?.environment}
onConfirm={onChangeRequestToggleConfirm}
/>
</div>
);

View File

@ -30,7 +30,7 @@ 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 { DraftBanner } from 'component/changeRequest/DraftBanner/DraftBanner';
import { MainLayout } from 'component/layout/MainLayout/MainLayout';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
@ -88,7 +88,7 @@ export const FeatureView = () => {
<MainLayout
ref={ref}
subheader={
uiConfig?.flags?.suggestChanges ? (
uiConfig?.flags?.changeRequests ? (
<DraftBanner project={projectId} />
) : null
}

View File

@ -24,10 +24,10 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { Routes, Route, useLocation } from 'react-router-dom';
import { DeleteProjectDialogue } from './DeleteProject/DeleteProjectDialogue';
import { ProjectLog } from './ProjectLog/ProjectLog';
import { SuggestedChangeOverview } from 'component/suggestChanges/SuggestedChangeOverview/SuggestedChangeOverview';
import { DraftBanner } from 'component/suggestChanges/DraftBanner/DraftBanner';
import { ChangeRequestOverview } from 'component/changeRequest/ChangeRequestOverview/ChangeRequestOverview';
import { DraftBanner } from 'component/changeRequest/DraftBanner/DraftBanner';
import { MainLayout } from 'component/layout/MainLayout/MainLayout';
import { ProjectSuggestedChanges } from '../../suggest-changes/ProjectSuggestions/ProjectSuggestedChanges';
import { ProjectChangeRequests } from '../../changeRequest/ProjectChangeRequests/ProjectChangeRequests';
const StyledDiv = styled('div')(() => ({
display: 'flex',
@ -91,8 +91,8 @@ const Project = () => {
},
{
title: 'Change requests',
path: `${basePath}/suggest-changes`,
name: 'suggest-changes' + '',
path: `${basePath}/change-requests`,
name: 'change-request' + '',
},
{
title: 'Event log',
@ -124,7 +124,7 @@ const Project = () => {
<MainLayout
ref={ref}
subheader={
uiConfig?.flags?.suggestChanges ? (
!uiConfig?.flags?.changeRequests ? (
<DraftBanner project={projectId} />
) : null
}
@ -235,20 +235,20 @@ const Project = () => {
<Route path="archive" element={<ProjectFeaturesArchive />} />
<Route path="logs" element={<ProjectLog />} />
<Route
path="suggest-changes"
path="change-requests"
element={
<ConditionallyRender
condition={Boolean(uiConfig?.flags?.suggestChanges)}
show={<ProjectSuggestedChanges />}
condition={Boolean(uiConfig?.flags?.changeRequests)}
show={<ProjectChangeRequests />}
/>
}
/>
<Route
path="suggest-changes/:id"
path="change-requests/:id"
element={
<ConditionallyRender
condition={Boolean(uiConfig?.flags?.suggestChanges)}
show={<SuggestedChangeOverview />}
condition={Boolean(uiConfig?.flags?.changeRequests)}
show={<ChangeRequestOverview />}
/>
}
/>

View File

@ -36,8 +36,8 @@ import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/Feat
import { useSearch } from 'hooks/useSearch';
import { useMediaQuery } from '@mui/material';
import { Search } from 'component/common/Search/Search';
import { useSuggestToggle } from 'hooks/useSuggestToggle';
import { SuggestChangesDialogue } from 'component/suggestChanges/SuggestChangeConfirmDialog/SuggestChangeConfirmDialog';
import { useChangeRequestToggle } from 'hooks/useChangeRequestToggle';
import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
interface IProjectFeatureTogglesProps {
features: IProject['features'];
@ -102,11 +102,11 @@ export const ProjectFeatureToggles = ({
const { toggleFeatureEnvironmentOn, toggleFeatureEnvironmentOff } =
useFeatureApi();
const {
onSuggestToggle,
onSuggestToggleClose,
onSuggestToggleConfirm,
suggestChangesDialogDetails,
} = useSuggestToggle(projectId);
onChangeRequestToggle,
onChangeRequestToggleClose,
onChangeRequestToggleConfirm,
changeRequestDialogDetails,
} = useChangeRequestToggle(projectId);
const onToggle = useCallback(
async (
@ -116,10 +116,10 @@ export const ProjectFeatureToggles = ({
enabled: boolean
) => {
if (
uiConfig?.flags?.suggestChanges &&
uiConfig?.flags?.changeRequests &&
environment === 'production'
) {
onSuggestToggle(featureName, environment, enabled);
onChangeRequestToggle(featureName, environment, enabled);
throw new Error('Additional approval required');
}
try {
@ -517,12 +517,12 @@ export const ProjectFeatureToggles = ({
featureId={featureArchiveState || ''}
projectId={projectId}
/>{' '}
<SuggestChangesDialogue
isOpen={suggestChangesDialogDetails.isOpen}
onClose={onSuggestToggleClose}
featureName={suggestChangesDialogDetails?.featureName}
environment={suggestChangesDialogDetails?.environment}
onConfirm={onSuggestToggleConfirm}
<ChangeRequestDialogue
isOpen={changeRequestDialogDetails.isOpen}
onClose={onChangeRequestToggleClose}
featureName={changeRequestDialogDetails?.featureName}
environment={changeRequestDialogDetails?.environment}
onConfirm={onChangeRequestToggleConfirm}
/>
</PageContent>
);

View File

@ -1,6 +1,6 @@
import useAPI from '../useApi/useApi';
interface ISuggestChangeSchema {
interface IChangeRequestsSchema {
feature: string;
action:
| 'updateEnabled'
@ -10,17 +10,17 @@ interface ISuggestChangeSchema {
payload: string | boolean | object | number;
}
export const useSuggestChangeApi = () => {
export const useChangeRequestApi = () => {
const { makeRequest, createRequest, errors, loading } = useAPI({
propagateErrors: true,
});
const addSuggestion = async (
const addChangeRequest = async (
project: string,
environment: string,
payload: ISuggestChangeSchema
payload: IChangeRequestsSchema
) => {
const path = `api/admin/projects/${project}/environments/${environment}/suggest-changes`;
const path = `api/admin/projects/${project}/environments/${environment}/change-requests`;
const req = createRequest(path, {
method: 'POST',
body: JSON.stringify(payload),
@ -35,10 +35,10 @@ export const useSuggestChangeApi = () => {
const changeState = async (
project: string,
suggestChangeId: number,
changeRequestId: number,
payload: any
) => {
const path = `api/admin/projects/${project}/suggest-changes/${suggestChangeId}/state`;
const path = `api/admin/projects/${project}/change-requests/${changeRequestId}/state`;
const req = createRequest(path, {
method: 'PUT',
body: JSON.stringify(payload),
@ -51,8 +51,8 @@ export const useSuggestChangeApi = () => {
}
};
const applyChanges = async (project: string, suggestChangeId: string) => {
const path = `api/admin/projects/${project}/suggest-changes/${suggestChangeId}/apply`;
const applyChanges = async (project: string, changeRequestId: string) => {
const path = `api/admin/projects/${project}/change-requests/${changeRequestId}/apply`;
const req = createRequest(path, {
method: 'PUT',
});
@ -64,12 +64,12 @@ export const useSuggestChangeApi = () => {
}
};
const discardSuggestions = async (
const discardChangeRequestEvent = async (
project: string,
changesetId: number,
changeId: number
changeRequestId: number,
changeRequestEventId: number
) => {
const path = `api/admin/projects/${project}/suggest-changes/${changesetId}/changes/${changeId}`;
const path = `api/admin/projects/${project}/change-requests/${changeRequestId}/changes/${changeRequestEventId}`;
const req = createRequest(path, {
method: 'DELETE',
});
@ -81,10 +81,10 @@ export const useSuggestChangeApi = () => {
};
return {
addSuggestion,
addChangeRequest,
applyChanges,
changeState,
discardSuggestions,
discardChangeRequestEvent,
errors,
loading,
};

View File

@ -2,16 +2,16 @@ import useSWR from 'swr';
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
export const useSuggestedChange = (projectId: string, id: string) => {
export const useChangeRequest = (projectId: string, id: string) => {
const { data, error, mutate } = useSWR(
formatApiPath(`api/admin/projects/${projectId}/suggest-changes/${id}`),
formatApiPath(`api/admin/projects/${projectId}/change-requests/${id}`),
fetcher
);
return {
data,
loading: !error && !data,
refetchSuggestedChange: () => mutate(),
refetchChangeRequest: () => mutate(),
error,
};
};

View File

@ -17,7 +17,7 @@ interface IChange {
};
}
export interface ISuggestChangesResponse {
export interface IChangeRequestResponse {
id: number;
environment: string;
state: string;
@ -36,13 +36,13 @@ export interface ISuggestChangesResponse {
const fetcher = (path: string) => {
return fetch(path)
.then(handleErrorResponses('SuggestedChanges'))
.then(handleErrorResponses('ChangeRequest'))
.then(res => res.json());
};
export const useSuggestedChangesDraft = (project: string) => {
const { data, error, mutate } = useSWR<ISuggestChangesResponse[]>(
formatApiPath(`api/admin/projects/${project}/suggest-changes/draft`),
export const useChangeRequestDraft = (project: string) => {
const { data, error, mutate } = useSWR<IChangeRequestResponse[]>(
formatApiPath(`api/admin/projects/${project}/change-requests/draft`),
fetcher
);

View File

@ -5,19 +5,19 @@ import handleErrorResponses from '../httpErrorResponseHandler';
const fetcher = (path: string) => {
return fetch(path)
.then(handleErrorResponses('SuggestedChanges'))
.then(handleErrorResponses('ChangeRequest'))
.then(res => res.json());
};
export const useProjectSuggestedChanges = (project: string) => {
export const useProjectChangeRequests = (project: string) => {
const { data, error, mutate } = useSWR(
formatApiPath(`api/admin/projects/${project}/suggest-changes`),
formatApiPath(`api/admin/projects/${project}/change-requests`),
fetcher
);
return useMemo(
() => ({
changesets: data,
changeRequests: data,
loading: !error && !data,
refetch: () => mutate(),
error,

View File

@ -0,0 +1,67 @@
import { useCallback, useState } from 'react';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useChangeRequestApi } from './api/actions/useChangeRequestApi/useChangeRequestApi';
import { useChangeRequestDraft } from './api/getters/useChangeRequestDraft/useChangeRequestDraft';
export const useChangeRequestToggle = (project: string) => {
const { setToastData, setToastApiError } = useToast();
const { addChangeRequest } = useChangeRequestApi();
const { refetch: refetchChangeRequests } = useChangeRequestDraft(project);
const [changeRequestDialogDetails, setChangeRequestDialogDetails] =
useState<{
enabled?: boolean;
featureName?: string;
environment?: string;
isOpen: boolean;
}>({ isOpen: false });
const onChangeRequestToggle = useCallback(
(featureName: string, environment: string, enabled: boolean) => {
setChangeRequestDialogDetails({
featureName,
environment,
enabled,
isOpen: true,
});
},
[]
);
const onChangeRequestToggleClose = useCallback(() => {
setChangeRequestDialogDetails({ isOpen: false });
}, []);
const onChangeRequestToggleConfirm = useCallback(async () => {
try {
await addChangeRequest(
project,
changeRequestDialogDetails.environment!,
{
feature: changeRequestDialogDetails.featureName!,
action: 'updateEnabled',
payload: {
enabled: Boolean(changeRequestDialogDetails.enabled),
},
}
);
refetchChangeRequests();
setChangeRequestDialogDetails({ isOpen: false });
setToastData({
type: 'success',
title: 'Changes added to the draft!',
});
} catch (error) {
setToastApiError(formatUnknownError(error));
setChangeRequestDialogDetails({ isOpen: false });
}
}, [addChangeRequest]);
return {
onChangeRequestToggle,
onChangeRequestToggleClose,
onChangeRequestToggleConfirm,
changeRequestDialogDetails,
};
};

View File

@ -1,68 +0,0 @@
import { useCallback, useState } from 'react';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useSuggestChangeApi } from './api/actions/useSuggestChangeApi/useSuggestChangeApi';
import { useSuggestedChangesDraft } from './api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft';
export const useSuggestToggle = (project: string) => {
const { setToastData, setToastApiError } = useToast();
const { addSuggestion } = useSuggestChangeApi();
const { refetch: refetchSuggestedChange } =
useSuggestedChangesDraft(project);
const [suggestChangesDialogDetails, setSuggestChangesDialogDetails] =
useState<{
enabled?: boolean;
featureName?: string;
environment?: string;
isOpen: boolean;
}>({ isOpen: false });
const onSuggestToggle = useCallback(
(featureName: string, environment: string, enabled: boolean) => {
setSuggestChangesDialogDetails({
featureName,
environment,
enabled,
isOpen: true,
});
},
[]
);
const onSuggestToggleClose = useCallback(() => {
setSuggestChangesDialogDetails({ isOpen: false });
}, []);
const onSuggestToggleConfirm = useCallback(async () => {
try {
await addSuggestion(
project,
suggestChangesDialogDetails.environment!,
{
feature: suggestChangesDialogDetails.featureName!,
action: 'updateEnabled',
payload: {
enabled: Boolean(suggestChangesDialogDetails.enabled),
},
}
);
refetchSuggestedChange();
setSuggestChangesDialogDetails({ isOpen: false });
setToastData({
type: 'success',
title: 'Changes added to the draft!',
});
} catch (error) {
setToastApiError(formatUnknownError(error));
setSuggestChangesDialogDetails({ isOpen: false });
}
}, [addSuggestion]);
return {
onSuggestToggle,
onSuggestToggleClose,
onSuggestToggleConfirm,
suggestChangesDialogDetails,
};
};

View File

@ -1,4 +1,4 @@
export interface ISuggestChangeset {
export interface IChangeRequest {
id: number;
state:
| 'CREATED'
@ -11,11 +11,10 @@ export interface ISuggestChangeset {
environment: string;
createdBy?: string;
createdAt?: Date;
changes?: ISuggestChange[];
events?: ISuggestChangeEvent[];
changes?: IChangeRequestEvent[];
}
export interface ISuggestChange {
export interface IChangeRequestEvent {
id: number;
action:
| 'updateEnabled'
@ -28,7 +27,7 @@ export interface ISuggestChange {
createdAt?: Date;
}
export enum SuggestChangesetEvent {
export enum ChangeRequestEvent {
CREATED = 'CREATED',
UPDATED = 'UPDATED',
SUBMITTED = 'SUBMITTED',
@ -36,23 +35,3 @@ export enum SuggestChangesetEvent {
REJECTED = 'REJECTED',
CLOSED = 'CLOSED',
}
export enum SuggestChangeEvent {
UPDATE_ENABLED = 'updateFeatureEnabledEvent',
ADD_STRATEGY = 'addStrategyEvent',
UPDATE_STRATEGY = 'updateStrategyEvent',
DELETE_STRATEGY = 'deleteStrategyEvent',
}
export interface ISuggestChangeEvent {
id: number;
event: SuggestChangesetEvent;
data: ISuggestChangeEventData;
createdBy?: string;
createdAt?: Date;
}
export interface ISuggestChangeEventData {
feature: string;
data: unknown;
}

View File

@ -42,7 +42,7 @@ export interface IFlags {
embedProxyFrontend?: boolean;
publicSignup?: boolean;
syncSSOGroups?: boolean;
suggestChanges?: boolean;
changeRequests?: boolean;
cloneEnvironment?: boolean;
}

View File

@ -69,12 +69,12 @@ exports[`should create default config 1`] = `
"ENABLE_DARK_MODE_SUPPORT": false,
"anonymiseEventLog": false,
"batchMetrics": false,
"changeRequests": false,
"cloneEnvironment": false,
"embedProxy": false,
"embedProxyFrontend": false,
"publicSignup": false,
"responseTimeWithAppName": false,
"suggestChanges": false,
"syncSSOGroups": false,
},
},
@ -83,12 +83,12 @@ exports[`should create default config 1`] = `
"ENABLE_DARK_MODE_SUPPORT": false,
"anonymiseEventLog": false,
"batchMetrics": false,
"changeRequests": false,
"cloneEnvironment": false,
"embedProxy": false,
"embedProxyFrontend": false,
"publicSignup": false,
"responseTimeWithAppName": false,
"suggestChanges": false,
"syncSSOGroups": false,
},
"externalResolver": {

View File

@ -82,8 +82,6 @@ export const PUBLIC_SIGNUP_TOKEN_CREATED = 'public-signup-token-created';
export const PUBLIC_SIGNUP_TOKEN_USER_ADDED = 'public-signup-token-user-added';
export const PUBLIC_SIGNUP_TOKEN_TOKEN_UPDATED = 'public-signup-token-updated';
export const SUGGEST_CHANGE_CREATED = 'suggest-change-created';
export interface IBaseEvent {
type: string;
createdBy: string;

View File

@ -10,8 +10,8 @@ export const defaultExperimentalOptions = {
process.env.UNLEASH_EXPERIMENTAL_EMBED_PROXY,
false,
),
suggestChanges: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_SUGGEST_CHANGES,
changeRequests: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_CHANGE_REQUESTS,
false,
),
syncSSOGroups: parseEnvVarBoolean(
@ -51,7 +51,7 @@ export interface IExperimentalOptions {
batchMetrics?: boolean;
anonymiseEventLog?: boolean;
syncSSOGroups?: boolean;
suggestChanges?: boolean;
changeRequests?: boolean;
cloneEnvironment?: boolean;
};
externalResolver: IExternalFlagResolver;

View File

@ -1,7 +1,7 @@
import { ITagType } from './stores/tag-type-store';
import { LogProvider } from '../logger';
import { IRole } from './stores/access-store';
import User, { IUser } from './user';
import { IUser } from './user';
import { ALL_OPERATORS } from '../util/constants';
export type Operator = typeof ALL_OPERATORS[number];
@ -372,99 +372,3 @@ export interface IFeatureStrategySegment {
featureStrategyId: string;
segmentId: number;
}
export interface ISuggestChangeset {
id: number;
state: string;
project: string;
environment: string;
createdBy: Pick<User, 'id' | 'username' | 'imageUrl'>;
createdAt: Date;
features: ISuggestChangeFeature[];
}
export interface ISuggestChangeFeature {
name: string;
changes: ISuggestChange[];
}
export interface ISuggestChangeBase {
id?: number;
action: SuggestChangeAction;
payload: SuggestChangePayload;
createdBy?: Pick<User, 'id' | 'username' | 'imageUrl'>;
createdAt?: Date;
}
export enum SuggestChangesetState {
DRAFT = 'Draft',
APPROVED = 'Approved',
IN_REVIEW = 'In review',
APPLIED = 'Applied',
CANCELLED = 'Cancelled',
}
type SuggestChangePayload =
| SuggestChangeEnabled
| SuggestChangeAddStrategy
| SuggestChangeEditStrategy
| SuggestChangeDeleteStrategy;
export interface ISuggestChangeAddStrategy extends ISuggestChangeBase {
action: 'addStrategy';
payload: SuggestChangeAddStrategy;
}
export interface ISuggestChangeDeleteStrategy extends ISuggestChangeBase {
action: 'deleteStrategy';
payload: SuggestChangeDeleteStrategy;
}
export interface ISuggestChangeUpdateStrategy extends ISuggestChangeBase {
action: 'updateStrategy';
payload: SuggestChangeEditStrategy;
}
export interface ISuggestChangeEnabled extends ISuggestChangeBase {
action: 'updateEnabled';
payload: SuggestChangeEnabled;
}
export type ISuggestChange =
| ISuggestChangeAddStrategy
| ISuggestChangeDeleteStrategy
| ISuggestChangeUpdateStrategy
| ISuggestChangeEnabled;
type SuggestChangeEnabled = { enabled: boolean };
type SuggestChangeAddStrategy = Pick<
IFeatureStrategy,
'parameters' | 'constraints'
> & { name: string };
type SuggestChangeEditStrategy = SuggestChangeAddStrategy & { id: string };
type SuggestChangeDeleteStrategy = {
deleteId: string;
};
export enum SuggestChangesetEvent {
CREATED = 'CREATED',
UPDATED = 'UPDATED',
SUBMITTED = 'SUBMITTED',
APPROVED = 'APPROVED',
REJECTED = 'REJECTED',
CLOSED = 'CLOSED',
}
export type SuggestChangeAction =
| 'updateEnabled'
| 'addStrategy'
| 'updateStrategy'
| 'deleteStrategy';
export interface ISuggestChangeEventData {
feature: string;
data: unknown;
}

View File

@ -37,4 +37,3 @@ export const MOVE_FEATURE_TOGGLE = 'MOVE_FEATURE_TOGGLE';
export const CREATE_SEGMENT = 'CREATE_SEGMENT';
export const UPDATE_SEGMENT = 'UPDATE_SEGMENT';
export const DELETE_SEGMENT = 'DELETE_SEGMENT';
export const SUGGEST_CHANGE = 'SUGGEST_CHANGE';

View File

@ -0,0 +1,40 @@
'use strict';
exports.up = function (db, callback) {
db.runSql(
`
DROP TABLE IF EXISTS suggest_change;
DROP TABLE IF EXISTS suggest_change_set;
CREATE TABLE IF NOT EXISTS change_requests (
id serial primary key,
environment varchar(100) REFERENCES environments(name) ON DELETE CASCADE,
state varchar(255) NOT NULL,
project varchar(255) REFERENCES projects(id) ON DELETE CASCADE,
created_by integer not null references users (id) ON DELETE CASCADE,
created_at timestamp default now()
);
CREATE TABLE IF NOT EXISTS change_request_events (
id serial primary key,
feature varchar(255) NOT NULL references features(name) on delete cascade,
action varchar(255) NOT NULL,
payload jsonb not null default '[]'::jsonb,
created_by integer not null references users (id) ON DELETE CASCADE,
created_at timestamp default now(),
change_request_id integer NOT NULL REFERENCES change_requests(id) ON DELETE CASCADE,
UNIQUE (feature, action, change_request_id)
);
`,
callback,
);
};
exports.down = function (db, callback) {
db.runSql(
`
DROP TABLE IF EXISTS change_request_events;
DROP TABLE IF EXISTS change_requests;
`,
callback,
);
};

View File

@ -39,7 +39,7 @@ process.nextTick(async () => {
anonymiseEventLog: false,
responseTimeWithAppName: true,
syncSSOGroups: true,
suggestChanges: true,
changeRequests: true,
cloneEnvironment: true,
},
},

View File

@ -28,7 +28,7 @@ export function createTestConfig(config?: IUnleashOptions): IUnleashConfig {
embedProxyFrontend: true,
batchMetrics: true,
syncSSOGroups: true,
suggestChanges: true,
changeRequests: true,
cloneEnvironment: true,
},
},

View File

@ -11,7 +11,7 @@ test('disabled middleware should not block paths that use the same path', async
conditionalMiddleware(
() => false,
(req, res) => {
res.send({ suggestChanges: 'hello' });
res.send({ changeRequest: 'hello' });
},
),
);
@ -30,11 +30,11 @@ test('should return 404 when path is not enabled', async () => {
const path = '/api/admin/projects';
app.use(
`${path}/suggest-changes`,
`${path}/change-requests`,
conditionalMiddleware(
() => false,
(req, res) => {
res.send({ suggestChanges: 'hello' });
res.send({ changeRequest: 'hello' });
},
),
);
@ -43,7 +43,7 @@ test('should return 404 when path is not enabled', async () => {
res.json({ projects: [] });
});
await supertest(app).get('/api/admin/projects/suggest-changes').expect(404);
await supertest(app).get('/api/admin/projects/change-requests').expect(404);
});
test('should respect ordering of endpoints', async () => {
@ -55,7 +55,7 @@ test('should respect ordering of endpoints', async () => {
conditionalMiddleware(
() => true,
(req, res) => {
res.json({ name: 'Suggest changes' });
res.json({ name: 'Request changes' });
},
),
);
@ -66,7 +66,7 @@ test('should respect ordering of endpoints', async () => {
await supertest(app)
.get('/api/admin/projects')
.expect(200, { name: 'Suggest changes' });
.expect(200, { name: 'Request changes' });
});
test('disabled middleware should not block paths that use the same basepath', async () => {
@ -74,11 +74,11 @@ test('disabled middleware should not block paths that use the same basepath', as
const path = '/api/admin/projects';
app.use(
`${path}/suggest-changes`,
`${path}/change-requests`,
conditionalMiddleware(
() => false,
(req, res) => {
res.json({ name: 'Suggest changes' });
res.json({ name: 'Request changes' });
},
),
);

View File

@ -1562,7 +1562,7 @@ This event fires when you delete a segment.
### 'suggest-change-created'
This event fires when you create a a suggest-changes draft.
This event fires when you create a a change-request draft.
```json title="example event: suggest-change-created"
{