diff --git a/ui/ui-app/src/app/components/common/IfRegistryFeature.tsx b/ui/ui-app/src/app/components/common/IfRegistryFeature.tsx index 5072decb4..e19572c00 100644 --- a/ui/ui-app/src/app/components/common/IfRegistryFeature.tsx +++ b/ui/ui-app/src/app/components/common/IfRegistryFeature.tsx @@ -20,7 +20,7 @@ export const IfRegistryFeature: FunctionComponent = (pro const config = useConfigService(); const accept = () => { - const features: any = config.getApicurioRegistryConfig().features; + const features: any = config.getApicurioRegistryConfig().features || {}; const featureValue: any = features[props.feature]; if (props.is !== undefined) { return featureValue === props.is; diff --git a/ui/ui-app/src/app/pages/editor/EditorPage.tsx b/ui/ui-app/src/app/pages/editor/EditorPage.tsx index 6e70cec8c..f2a17a17e 100644 --- a/ui/ui-app/src/app/pages/editor/EditorPage.tsx +++ b/ui/ui-app/src/app/pages/editor/EditorPage.tsx @@ -76,6 +76,8 @@ export const EditorPage: FunctionComponent = () => { const [isPleaseWaitModalOpen, setPleaseWaitModalOpen] = useState(false); const [isConfirmOverwriteModalOpen, setConfirmOverwriteModalOpen] = useState(false); const [pleaseWaitMessage, setPleaseWaitMessage] = useState(""); + const [intervalId, setIntervalId] = useState(); + const [isContentConflicting, setIsContentConflicting] = useState(false); const { groupId, draftId, version } = useParams(); @@ -85,7 +87,13 @@ export const EditorPage: FunctionComponent = () => { const createLoaders = (): Promise[] => { return [ drafts.getDraft(groupId as string, draftId as string, version as string) - .then(setDraft) + .then(d => { + setDraft(d); + + // Poll the server for new content every 60s. If the content has been updated on + // the server then we have a conflict that we need to report to the user. + setIntervalId(setInterval(detectContentConflict, 60000)); + }) .catch(error => { setPageError(toPageError(error, "Error loading page data.")); }), @@ -93,7 +101,7 @@ export const EditorPage: FunctionComponent = () => { setOriginalContent(content.content); setCurrentContent(content.content); setDraftContent(content); - }) + }), ]; }; @@ -102,6 +110,7 @@ export const EditorPage: FunctionComponent = () => { // Cleanup any possible event listener we might still have registered return () => { window.removeEventListener("beforeunload", onBeforeUnload); + clearInterval(intervalId); }; }, []); @@ -134,6 +143,15 @@ export const EditorPage: FunctionComponent = () => { setPleaseWaitMessage(message); }; + const detectContentConflict = (): void => { + drafts.getDraft(groupId as string, draftId as string, version as string).then(currentDraft => { + if (currentDraft.contentId !== draft.contentId) { + console.debug(`[EditorPage] Detected Draft content conflict. Expected '${draft.contentId}' but found '${currentDraft.contentId}'.'`); + setIsContentConflicting(true); + } + }); + }; + const updateDraftMetadata = (): void => { drafts.getDraft(groupId as string, draftId as string, version as string).then(setDraft); }; @@ -254,6 +272,7 @@ export const EditorPage: FunctionComponent = () => { void; onFormat: () => void; onDownload: () => void; @@ -74,33 +76,50 @@ export const EditorContext: FunctionComponent = (props: Edit ); + const contentConflictHeader = ( + Content conflict + ); + const contentConflictComponent = ( +
The content of this Draft has been saved by someone else since you opened this editor!
+ ); + return ( - -
-
- -
- Last modified: - -
-
-
- item.onSelect()} - noSelectionLabel="Actions" - itemToTestId={item => item.testId} - itemIsVisible={item => !item.isVisible || item.isVisible()} - itemIsDivider={item => item.isDivider} - itemIsDisabled={item => item.isDisabled === undefined ? false : item.isDisabled()} - itemToString={item => item.label} /> +
+
+ +
+ + + + +
-
- + + +
+ Last modified: +
+
+
+ item.onSelect()} + noSelectionLabel="Actions" + itemToTestId={item => item.testId} + itemIsVisible={item => !item.isVisible || item.isVisible()} + itemIsDivider={item => item.isDivider} + itemIsDisabled={item => item.isDisabled === undefined ? false : item.isDisabled()} + itemToString={item => item.label} /> +
+
+
- +
); }; diff --git a/ui/ui-app/src/services/useConfigService.ts b/ui/ui-app/src/services/useConfigService.ts index 2a272b56f..62d7abf4a 100644 --- a/ui/ui-app/src/services/useConfigService.ts +++ b/ui/ui-app/src/services/useConfigService.ts @@ -166,6 +166,20 @@ export class ConfigServiceImpl implements ConfigService { return fetch(registryConfigEndpoint).then(response => { console.info("[ConfigService] Loaded Registry UI config: ", response); registryConfig = JSON.parse(response as string); + }).catch(() => { + registryConfig = { + auth: { + type: "none", + obacEnabled: false, + rbacEnabled: false, + }, + features: { + deleteVersion: false, + deleteArtifact: false, + deleteGroup: false, + draftMutability: false + }, + }; }); }