Skip to content

Commit e201a96

Browse files
authored
Implement state change in the ui (#7056)
1 parent ee3a033 commit e201a96

File tree

5 files changed

+208
-3
lines changed

5 files changed

+208
-3
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { FunctionComponent, useEffect, useState } from "react";
2+
import { Button, Form, FormGroup, FormHelperText, HelperText, HelperTextItem, Modal, Text } from "@patternfly/react-core";
3+
import { VersionState, VersionStateObject } from "@sdk/lib/generated-client/models";
4+
import { ObjectSelect } from "@apicurio/common-ui-components";
5+
6+
7+
/**
8+
* Properties
9+
*/
10+
export type ChangeVersionStateModalProps = {
11+
isOpen: boolean;
12+
currentState: VersionState;
13+
onClose: () => void;
14+
onChangeState: (newState: VersionState) => void;
15+
};
16+
17+
type StateOption = {
18+
value: VersionState;
19+
label: string;
20+
description: string;
21+
};
22+
23+
const getStateOptions = (currentState: VersionState): StateOption[] => {
24+
const options: StateOption[] = [];
25+
26+
// DRAFT can only transition to ENABLED
27+
if (currentState === VersionStateObject.DRAFT) {
28+
options.push({
29+
value: VersionStateObject.ENABLED,
30+
label: "Enabled",
31+
description: "The version is active and can be used."
32+
});
33+
} else {
34+
// Non-DRAFT states can transition to ENABLED, DISABLED, or DEPRECATED (but not DRAFT)
35+
if (currentState !== VersionStateObject.ENABLED) {
36+
options.push({
37+
value: VersionStateObject.ENABLED,
38+
label: "Enabled",
39+
description: "The version is active and can be used."
40+
});
41+
}
42+
if (currentState !== VersionStateObject.DISABLED) {
43+
options.push({
44+
value: VersionStateObject.DISABLED,
45+
label: "Disabled",
46+
description: "The version is disabled and cannot be used."
47+
});
48+
}
49+
if (currentState !== VersionStateObject.DEPRECATED) {
50+
options.push({
51+
value: VersionStateObject.DEPRECATED,
52+
label: "Deprecated",
53+
description: "The version is deprecated and should not be used for new projects."
54+
});
55+
}
56+
}
57+
58+
return options;
59+
};
60+
61+
const stateToLabel = (state: VersionState | undefined): string => {
62+
switch (state) {
63+
case VersionStateObject.ENABLED:
64+
return "Enabled";
65+
case VersionStateObject.DISABLED:
66+
return "Disabled";
67+
case VersionStateObject.DEPRECATED:
68+
return "Deprecated";
69+
case VersionStateObject.DRAFT:
70+
return "Draft";
71+
default:
72+
return "Unknown";
73+
}
74+
};
75+
76+
export const ChangeVersionStateModal: FunctionComponent<ChangeVersionStateModalProps> = (
77+
{ isOpen, onClose, currentState, onChangeState }: ChangeVersionStateModalProps) => {
78+
79+
const [isValid, setValid] = useState(false);
80+
const [newState, setNewState] = useState<VersionState | undefined>();
81+
const [stateOptions, setStateOptions] = useState<StateOption[]>([]);
82+
83+
useEffect(() => {
84+
if (isOpen) {
85+
setNewState(undefined);
86+
setStateOptions(getStateOptions(currentState));
87+
}
88+
}, [isOpen, currentState]);
89+
90+
useEffect(() => {
91+
setValid(newState !== undefined && newState !== currentState);
92+
}, [newState, currentState]);
93+
94+
const onStateSelect = (value: StateOption | undefined): void => {
95+
setNewState(value?.value);
96+
};
97+
98+
return (
99+
<Modal
100+
title="Change version state"
101+
variant="medium"
102+
isOpen={isOpen}
103+
onClose={onClose}
104+
className="change-version-state pf-m-redhat-font"
105+
actions={[
106+
<Button key="change" variant="primary" data-testid="modal-btn-change" onClick={() => { onChangeState(newState!); }} isDisabled={!isValid}>Change state</Button>,
107+
<Button key="cancel" variant="link" data-testid="modal-btn-cancel" onClick={onClose}>Cancel</Button>
108+
]}
109+
>
110+
<Form>
111+
<FormGroup label="Current state" fieldId="form-current-state">
112+
<Text>{ stateToLabel(currentState) }</Text>
113+
</FormGroup>
114+
<FormGroup label="New state" fieldId="form-new-state" isRequired={true}>
115+
<ObjectSelect
116+
value={stateOptions.find(o => o.value === newState)}
117+
items={stateOptions}
118+
toggleId="form-new-state"
119+
testId="form-new-state"
120+
onSelect={onStateSelect}
121+
itemToString={(item) => item.label}
122+
itemToTestId={(item) => `state-option-${item.value}`}
123+
noSelectionLabel="Select a state"
124+
/>
125+
<FormHelperText>
126+
<HelperText>
127+
<HelperTextItem>
128+
{newState ? stateOptions.find(o => o.value === newState)?.description : "Select a new state for this version."}
129+
</HelperTextItem>
130+
</HelperText>
131+
</FormHelperText>
132+
</FormGroup>
133+
</Form>
134+
</Modal>
135+
);
136+
137+
};

ui/ui-app/src/app/components/modals/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from "./ChangeOwnerModal";
2+
export * from "./ChangeVersionStateModal";
23
export * from "./ConfirmDeleteModal";
34
export * from "./CreateArtifactModal";
45
export * from "./CreateBranchModal";

ui/ui-app/src/app/pages/version/VersionPage.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from "@app/pages";
1717
import { ReferencesTabContent } from "@app/pages/version/components/tabs/ReferencesTabContent.tsx";
1818
import {
19+
ChangeVersionStateModal,
1920
ConfirmDeleteModal,
2021
EditMetaDataModal,
2122
GenerateClientModal,
@@ -36,7 +37,8 @@ import {
3637
Labels,
3738
RuleViolationProblemDetails,
3839
SearchedVersion,
39-
VersionMetaData
40+
VersionMetaData,
41+
VersionState
4042
} from "@sdk/lib/generated-client/models";
4143
import { DraftsService, useDraftsService } from "@services/useDraftsService.ts";
4244
import { CreateDraft, Draft } from "@models/drafts";
@@ -67,6 +69,7 @@ export const VersionPage: FunctionComponent<PageProperties> = () => {
6769
const [isInvalidContentModalOpen, setIsInvalidContentModalOpen] = useState<boolean>(false);
6870
const [invalidContentError, setInvalidContentError] = useState<RuleViolationProblemDetails>();
6971
const [isFinalizeDryRunSuccessModalOpen, setIsFinalizeDryRunSuccessModalOpen] = useState(false);
72+
const [isChangeStateModalOpen, setIsChangeStateModalOpen] = useState(false);
7073

7174
const appNavigation: AppNavigation = useAppNavigation();
7275
const logger: LoggerService = useLoggerService();
@@ -247,6 +250,33 @@ export const VersionPage: FunctionComponent<PageProperties> = () => {
247250
onEditModalClose();
248251
};
249252

253+
const openChangeStateModal = (): void => {
254+
setIsChangeStateModalOpen(true);
255+
};
256+
257+
const onChangeStateModalClose = (): void => {
258+
setIsChangeStateModalOpen(false);
259+
};
260+
261+
const doChangeState = (newState: VersionState): void => {
262+
onChangeStateModalClose();
263+
pleaseWait(true, "Changing version state, please wait...");
264+
groups.updateArtifactVersionState(groupId as string, artifactId as string, version as string, newState).then(() => {
265+
pleaseWait(false);
266+
setArtifactVersion({
267+
...artifactVersion,
268+
state: newState
269+
} as VersionMetaData);
270+
}).catch(error => {
271+
pleaseWait(false);
272+
if (error && (error.status === 400 || error.status === 409)) {
273+
handleInvalidContentError(error);
274+
} else {
275+
setPageError(toPageError(error, "Error changing version state."));
276+
}
277+
});
278+
};
279+
250280
const handleInvalidContentError = (error: any): void => {
251281
console.info("[DraftsPage] Invalid content error:", error);
252282
setInvalidContentError(error);
@@ -353,6 +383,7 @@ export const VersionPage: FunctionComponent<PageProperties> = () => {
353383
artifact={artifact as ArtifactMetaData}
354384
version={artifactVersion as VersionMetaData}
355385
onEditMetaData={openEditMetaDataModal}
386+
onChangeState={openChangeStateModal}
356387
/>
357388
</Tab>,
358389
<Tab data-testid="version-documentation-tab" eventKey="documentation" title="Documentation" key="documentation" className="documentation-tab" tabContentId="tab-documentation">
@@ -458,6 +489,12 @@ export const VersionPage: FunctionComponent<PageProperties> = () => {
458489
<FinalizeDryRunSuccessModal
459490
isOpen={isFinalizeDryRunSuccessModalOpen}
460491
onClose={() => setIsFinalizeDryRunSuccessModalOpen(false)} />
492+
<ChangeVersionStateModal
493+
isOpen={isChangeStateModalOpen}
494+
currentState={artifactVersion?.state as VersionState}
495+
onClose={onChangeStateModalClose}
496+
onChangeState={doChangeState}
497+
/>
461498
<PleaseWaitModal
462499
message={pleaseWaitMessage}
463500
isOpen={isPleaseWaitModalOpen} />

ui/ui-app/src/app/pages/version/components/tabs/VersionOverviewTabContent.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type VersionOverviewTabContentProps = {
3333
artifact: ArtifactMetaData;
3434
version: VersionMetaData;
3535
onEditMetaData: () => void;
36+
onChangeState: () => void;
3637
};
3738

3839
/**
@@ -103,7 +104,24 @@ export const VersionOverviewTabContent: FunctionComponent<VersionOverviewTabCont
103104
<DescriptionListGroup>
104105
<DescriptionListTerm>Status</DescriptionListTerm>
105106
<DescriptionListDescription data-testid="version-details-state">
106-
<VersionStateBadge version={props.version} showEnabled={true} />
107+
<Flex>
108+
<FlexItem>
109+
<VersionStateBadge version={props.version} showEnabled={true} />
110+
</FlexItem>
111+
<IfAuth isDeveloper={true} owner={props.artifact.owner}>
112+
<IfFeature feature="readOnly" isNot={true}>
113+
<FlexItem>
114+
<Button
115+
id="change-state-action"
116+
data-testid="version-btn-change-state"
117+
onClick={props.onChangeState}
118+
style={{ padding: "0", marginLeft: "10px" }}
119+
variant="link"
120+
>Change</Button>
121+
</FlexItem>
122+
</IfFeature>
123+
</IfAuth>
124+
</Flex>
107125
</DescriptionListDescription>
108126
</DescriptionListGroup>
109127
<DescriptionListGroup>

ui/ui-app/src/services/useGroupsService.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import {
2525
SortOrder,
2626
VersionMetaData,
2727
VersionSearchResults,
28-
VersionSortBy
28+
VersionSortBy,
29+
VersionState
2930
} from "@sdk/lib/generated-client/models";
3031

3132

@@ -402,6 +403,13 @@ const deleteArtifactVersion = async (config: ConfigService, auth: AuthService, g
402403
.versions.byVersionExpression(version).delete();
403404
};
404405

406+
const updateArtifactVersionState = async (config: ConfigService, auth: AuthService, groupId: string|null, artifactId: string, version: string, state: VersionState): Promise<void> => {
407+
groupId = normalizeGroupId(groupId);
408+
console.info("[GroupsService] Updating version state: ", groupId, artifactId, version, state);
409+
return getRegistryClient(config, auth).groups.byGroupId(groupId).artifacts.byArtifactId(artifactId)
410+
.versions.byVersionExpression(version).state.put({ state });
411+
};
412+
405413
const normalizeGroupId = (groupId: string|null): string => {
406414
return groupId || "default";
407415
};
@@ -440,6 +448,7 @@ export interface GroupsService {
440448
getArtifactVersionMetaData(groupId: string|null, artifactId: string, version: string): Promise<VersionMetaData>;
441449
getArtifactVersionContent(groupId: string|null, artifactId: string, version: string): Promise<string>;
442450
updateArtifactVersionMetaData(groupId: string|null, artifactId: string, version: string, metaData: EditableVersionMetaData): Promise<void>;
451+
updateArtifactVersionState(groupId: string|null, artifactId: string, version: string, state: VersionState): Promise<void>;
443452
deleteArtifactVersion(groupId: string|null, artifactId: string, version: string): Promise<void>;
444453

445454
getArtifactVersionComments(groupId: string|null, artifactId: string, version: string): Promise<Comment[]>;
@@ -554,6 +563,9 @@ export const useGroupsService: () => GroupsService = (): GroupsService => {
554563
updateArtifactVersionMetaData(groupId: string|null, artifactId: string, version: string, metaData: EditableVersionMetaData): Promise<void> {
555564
return updateArtifactVersionMetaData(config, auth, groupId, artifactId, version, metaData);
556565
},
566+
updateArtifactVersionState(groupId: string|null, artifactId: string, version: string, state: VersionState): Promise<void> {
567+
return updateArtifactVersionState(config, auth, groupId, artifactId, version, state);
568+
},
557569
deleteArtifactVersion(groupId: string|null, artifactId: string, version: string): Promise<void> {
558570
return deleteArtifactVersion(config, auth, groupId, artifactId, version);
559571
},

0 commit comments

Comments
 (0)