mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 08:58:26 +00:00
Platform-Editor API improvements (#9460)
* Editor header and sequence of API calls Improvements. (#9366) * Added createVersion and updatedVersionName changes * Added deff-call for version delete action * removed callBack func(re-arranged as sub-functions) * removed unwanted functions * fixed merge issues, appVersioManager and environment store issues * Resolved query-onload issue and release button bugs * working on preview setting issues * refactoring the code * moved environment manager directory * fixed import issues * Refactoring the service functions * Added environments details to the init response * Added appVersionsCount to the response * Added new post action to the controller * Refactored recent commit * Fixed query response array issue * Fixed await issue * Added extra release/promote button cases * Removed organizations call from the authorizeWorkspace function * added a length check to prevent calling the api again * removed the loader and notification API call * Fixed on blur issue of react-select using event handlers
This commit is contained in:
parent
eaa4b9c68c
commit
b8ce0bbcda
23 changed files with 635 additions and 302 deletions
|
|
@ -1,41 +1,11 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { Editor } from '../Editor/Editor';
|
||||
import { RealtimeEditor } from '@/Editor/RealtimeEditor';
|
||||
import config from 'config';
|
||||
import { appService } from '@/_services';
|
||||
import { useAppDataActions } from '@/_stores/appDataStore';
|
||||
|
||||
const AppLoaderComponent = React.memo((props) => {
|
||||
const [shouldLoadApp, setShouldLoadApp] = React.useState(false);
|
||||
const { updateState } = useAppDataActions();
|
||||
|
||||
useEffect(() => {
|
||||
props?.id && props?.slug && loadAppDetails(props?.id);
|
||||
|
||||
return () => {
|
||||
setShouldLoadApp(false);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const loadAppDetails = (appId) => {
|
||||
appService.fetchApp(appId, 'edit').then((data) => {
|
||||
setShouldLoadApp(true);
|
||||
updateState({
|
||||
app: data,
|
||||
appId: data.id,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (!shouldLoadApp) return <></>;
|
||||
|
||||
return config.ENABLE_MULTIPLAYER_EDITING ? (
|
||||
<RealtimeEditor {...props} shouldLoadApp={shouldLoadApp} />
|
||||
) : (
|
||||
<Editor {...props} />
|
||||
);
|
||||
return config.ENABLE_MULTIPLAYER_EDITING ? <RealtimeEditor {...props} /> : <Editor {...props} />;
|
||||
});
|
||||
|
||||
export const AppLoader = withTranslation()(AppLoaderComponent);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { appVersionService } from '@/_services';
|
||||
import { CustomSelect } from './CustomSelect';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
|
@ -15,53 +14,8 @@ const appVersionLoadingStatus = Object.freeze({
|
|||
error: 'error',
|
||||
});
|
||||
|
||||
export const AppVersionsManager = function ({
|
||||
appId,
|
||||
setAppDefinitionFromVersion,
|
||||
onVersionDelete,
|
||||
isEditable = true,
|
||||
isViewer,
|
||||
}) {
|
||||
const { initializedEnvironmentDropdown, versionsPromotedToEnvironment, lazyLoadAppVersions, appVersionsLazyLoaded } =
|
||||
useEnvironmentsAndVersionsStore(
|
||||
(state) => ({
|
||||
appVersionsLazyLoaded: state.appVersionsLazyLoaded,
|
||||
initializedEnvironmentDropdown: state.initializedEnvironmentDropdown,
|
||||
versionsPromotedToEnvironment: state.versionsPromotedToEnvironment,
|
||||
lazyLoadAppVersions: state.actions.lazyLoadAppVersions,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
||||
if (initializedEnvironmentDropdown) {
|
||||
return (
|
||||
<RenderComponent
|
||||
appId={appId}
|
||||
setAppDefinitionFromVersion={setAppDefinitionFromVersion}
|
||||
onVersionDelete={onVersionDelete}
|
||||
isEditable={isEditable}
|
||||
isViewer={isViewer}
|
||||
versionsPromotedToEnvironment={versionsPromotedToEnvironment}
|
||||
lazyLoadAppVersions={lazyLoadAppVersions}
|
||||
appVersionsLazyLoaded={appVersionsLazyLoaded}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
const RenderComponent = ({
|
||||
appId,
|
||||
isEditable,
|
||||
isViewer,
|
||||
setAppDefinitionFromVersion,
|
||||
onVersionDelete,
|
||||
versionsPromotedToEnvironment,
|
||||
lazyLoadAppVersions,
|
||||
appVersionsLazyLoaded,
|
||||
}) => {
|
||||
const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loaded);
|
||||
export const AppVersionsManager = function ({ appId, setAppDefinitionFromVersion, isEditable = true, isViewer }) {
|
||||
const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loading);
|
||||
const [deleteVersion, setDeleteVersion] = useState({
|
||||
versionId: '',
|
||||
versionName: '',
|
||||
|
|
@ -85,6 +39,40 @@ const RenderComponent = ({
|
|||
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
|
||||
const {
|
||||
initializedEnvironmentDropdown,
|
||||
versionsPromotedToEnvironment,
|
||||
lazyLoadAppVersions,
|
||||
appVersionsLazyLoaded,
|
||||
setEnvironmentAndVersionsInitStatus,
|
||||
changeEditorVersionAction,
|
||||
selectedVersion,
|
||||
deleteVersionAction,
|
||||
} = useEnvironmentsAndVersionsStore(
|
||||
(state) => ({
|
||||
appVersionsLazyLoaded: state.appVersionsLazyLoaded,
|
||||
initializedEnvironmentDropdown: state.initializedEnvironmentDropdown,
|
||||
versionsPromotedToEnvironment: state.versionsPromotedToEnvironment,
|
||||
selectedVersion: state.selectedVersion,
|
||||
lazyLoadAppVersions: state.actions.lazyLoadAppVersions,
|
||||
setEnvironmentAndVersionsInitStatus: state.actions.setEnvironmentAndVersionsInitStatus,
|
||||
deleteVersionAction: state.actions.deleteVersionAction,
|
||||
changeEditorVersionAction: state.actions.changeEditorVersionAction,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setEnvironmentAndVersionsInitStatus(true);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (initializedEnvironmentDropdown) {
|
||||
setGetAppVersionStatus(appVersionLoadingStatus.loaded);
|
||||
}
|
||||
}, [initializedEnvironmentDropdown]);
|
||||
|
||||
const selectVersion = (id) => {
|
||||
const currentVersionId = useAppDataStore.getState().currentVersionId;
|
||||
|
||||
|
|
@ -96,15 +84,18 @@ const RenderComponent = ({
|
|||
});
|
||||
}
|
||||
|
||||
return appVersionService
|
||||
.getAppVersionData(appId, id)
|
||||
.then((data) => {
|
||||
const isCurrentVersionReleased = data.currentVersionId ? true : false;
|
||||
setAppDefinitionFromVersion(data, isCurrentVersionReleased);
|
||||
})
|
||||
.catch((error) => {
|
||||
changeEditorVersionAction(
|
||||
appId,
|
||||
id,
|
||||
(newDeff) => {
|
||||
setAppDefinitionFromVersion(newDeff);
|
||||
},
|
||||
(error) => {
|
||||
toast.error(error);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
const resetDeleteModal = () => {
|
||||
|
|
@ -117,25 +108,26 @@ const RenderComponent = ({
|
|||
|
||||
const deleteAppVersion = (versionId, versionName) => {
|
||||
const deleteingToastId = toast.loading('Deleting version...');
|
||||
appVersionService
|
||||
.del(appId, versionId)
|
||||
.then(() => {
|
||||
deleteVersionAction(
|
||||
appId,
|
||||
versionId,
|
||||
(newVersionDef) => {
|
||||
if (newVersionDef) {
|
||||
/* User deleted new version */
|
||||
setAppDefinitionFromVersion(newVersionDef);
|
||||
}
|
||||
toast.dismiss(deleteingToastId);
|
||||
toast.success(`Version - ${versionName} Deleted`);
|
||||
resetDeleteModal();
|
||||
setGetAppVersionStatus(appVersionLoadingStatus.loading);
|
||||
})
|
||||
.catch((error) => {
|
||||
setGetAppVersionStatus(appVersionLoadingStatus.loaded);
|
||||
},
|
||||
(error) => {
|
||||
toast.dismiss(deleteingToastId);
|
||||
toast.error(error?.error ?? 'Oops, something went wrong');
|
||||
setGetAppVersionStatus(appVersionLoadingStatus.error);
|
||||
resetDeleteModal();
|
||||
})
|
||||
.finally(() => {
|
||||
appVersionService.getAll(appId, true).then((data) => {
|
||||
onVersionDelete();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const options = versionsPromotedToEnvironment.map((appVersion) => ({
|
||||
|
|
@ -197,10 +189,28 @@ const RenderComponent = ({
|
|||
resetDeleteModal,
|
||||
};
|
||||
|
||||
/* Force close is not working with usual blur function of react-select */
|
||||
const clickedOutsideRef = useRef(null);
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event) {
|
||||
if (clickedOutsideRef.current && !clickedOutsideRef.current.contains(event.target)) {
|
||||
if (!forceMenuOpen) {
|
||||
setForceMenuOpen(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [clickedOutsideRef]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="d-flex align-items-center p-0"
|
||||
style={{ margin: isViewer && currentLayout === 'mobile' ? '0px' : '0 24px' }}
|
||||
ref={clickedOutsideRef}
|
||||
>
|
||||
<div
|
||||
className={cx('d-flex version-manager-container p-0', {
|
||||
|
|
@ -216,7 +226,7 @@ const RenderComponent = ({
|
|||
<CustomSelect
|
||||
isLoading={appVersionStatus === 'loading'}
|
||||
options={options}
|
||||
value={editingVersion?.id}
|
||||
value={selectedVersion?.id}
|
||||
onChange={(id) => selectVersion(id)}
|
||||
{...customSelectProps}
|
||||
className={` ${darkMode && 'dark-theme'}`}
|
||||
|
|
|
|||
|
|
@ -6,17 +6,25 @@ import { useTranslation } from 'react-i18next';
|
|||
import Select from '@/_ui/Select';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useEnvironmentsAndVersionsStore } from '@/_stores/environmentsAndVersionsStore';
|
||||
|
||||
export const CreateVersion = ({
|
||||
appId,
|
||||
appVersions,
|
||||
setAppVersions,
|
||||
setAppDefinitionFromVersion,
|
||||
showCreateAppVersion,
|
||||
setShowCreateAppVersion,
|
||||
}) => {
|
||||
const [isCreatingVersion, setIsCreatingVersion] = useState(false);
|
||||
const [versionName, setVersionName] = useState('');
|
||||
const { versionsPromotedToEnvironment: appVersions, createNewVersionAction } = useEnvironmentsAndVersionsStore(
|
||||
(state) => ({
|
||||
appVersionsLazyLoaded: state.appVersionsLazyLoaded,
|
||||
versionsPromotedToEnvironment: state.versionsPromotedToEnvironment,
|
||||
lazyLoadAppVersions: state.actions.lazyLoadAppVersions,
|
||||
createNewVersionAction: state.actions.createNewVersionAction,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { editingVersion } = useAppVersionStore(
|
||||
|
|
@ -46,30 +54,29 @@ export const CreateVersion = ({
|
|||
|
||||
setIsCreatingVersion(true);
|
||||
|
||||
appVersionService
|
||||
.create(appId, versionName, selectedVersion.id)
|
||||
.then((data) => {
|
||||
createNewVersionAction(
|
||||
appId,
|
||||
versionName,
|
||||
selectedVersion.id,
|
||||
(newVersion) => {
|
||||
toast.success('Version Created');
|
||||
appVersionService.getAll(appId).then((data) => {
|
||||
setVersionName('');
|
||||
setIsCreatingVersion(false);
|
||||
setAppVersions(data.versions);
|
||||
setShowCreateAppVersion(false);
|
||||
});
|
||||
|
||||
setVersionName('');
|
||||
setIsCreatingVersion(false);
|
||||
setShowCreateAppVersion(false);
|
||||
appVersionService
|
||||
.getAppVersionData(appId, data.id)
|
||||
.getAppVersionData(appId, newVersion.id)
|
||||
.then((data) => {
|
||||
setAppDefinitionFromVersion(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(error);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
},
|
||||
(error) => {
|
||||
toast.error(error?.error);
|
||||
setIsCreatingVersion(false);
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import React, { useState } from 'react';
|
||||
import { appVersionService } from '@/_services';
|
||||
import AlertDialog from '@/_ui/AlertDialog';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useEnvironmentsAndVersionsStore } from '@/_stores/environmentsAndVersionsStore';
|
||||
|
||||
export const EditVersion = ({
|
||||
appId,
|
||||
value: editingVersionId,
|
||||
setAppVersions,
|
||||
setShowEditAppVersion,
|
||||
showEditAppVersion,
|
||||
appVersions,
|
||||
}) => {
|
||||
export const EditVersion = ({ appId, setShowEditAppVersion, showEditAppVersion }) => {
|
||||
const [isEditingVersion, setIsEditingVersion] = useState(false);
|
||||
const editingVersion = appVersions?.find((version) => version.id === editingVersionId);
|
||||
const { updateVersionNameAction, selectedVersion: editingVersion } = useEnvironmentsAndVersionsStore(
|
||||
(state) => ({
|
||||
updateVersionNameAction: state.actions.updateVersionNameAction,
|
||||
selectedVersion: state.selectedVersion,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
const [versionName, setVersionName] = useState(editingVersion?.name || '');
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
@ -28,21 +28,20 @@ export const EditVersion = ({
|
|||
}
|
||||
|
||||
setIsEditingVersion(true);
|
||||
appVersionService
|
||||
.save(appId, editingVersionId, { name: versionName })
|
||||
.then(() => {
|
||||
updateVersionNameAction(
|
||||
appId,
|
||||
editingVersion?.id,
|
||||
versionName,
|
||||
() => {
|
||||
toast.success('Version name updated');
|
||||
appVersionService.getAll(appId).then((data) => {
|
||||
const versions = data.versions;
|
||||
setAppVersions(versions);
|
||||
});
|
||||
setIsEditingVersion(false);
|
||||
setShowEditAppVersion(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
},
|
||||
(error) => {
|
||||
setIsEditingVersion(false);
|
||||
toast.error(error?.error);
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ const CommentNotifications = ({ socket, pageId }) => {
|
|||
|
||||
async function fetchData(selectedKey) {
|
||||
if (appId) {
|
||||
console.log('inside-CommentNotifications', appId);
|
||||
const isResolved = selectedKey === 'resolved';
|
||||
setLoading(true);
|
||||
const { data } = await commentsService.getNotifications(appId, isResolved, appVersionsId, pageId);
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ import { useResolveStore } from '@/_stores/resolverStore';
|
|||
import { dfs } from '@/_stores/handleReferenceTransactions';
|
||||
import { decimalToHex } from './editorConstants';
|
||||
import { findComponentsWithReferences, handleLowPriorityWork } from '@/_helpers/editorHelpers';
|
||||
import { TJLoader } from '@/_ui/TJLoader/TJLoader';
|
||||
|
||||
setAutoFreeze(false);
|
||||
enablePatches();
|
||||
|
|
@ -491,7 +492,9 @@ const EditorComponent = (props) => {
|
|||
const initEventListeners = () => {
|
||||
socket?.addEventListener('message', (event) => {
|
||||
const data = event.data.replace(/^"(.+(?="$))"$/, '$1');
|
||||
if (data === 'versionReleased') fetchApp();
|
||||
if (data === 'versionReleased') {
|
||||
//TODO update the released version id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -500,7 +503,7 @@ const EditorComponent = (props) => {
|
|||
|
||||
useResolveStore.getState().actions.updateJSHints();
|
||||
|
||||
await fetchApp(props.params.pageHandle, true);
|
||||
await runForInitialLoad();
|
||||
await fetchOrgEnvironmentVariables();
|
||||
await fetchOrgEnvironmentConstants();
|
||||
initComponentVersioning();
|
||||
|
|
@ -534,10 +537,6 @@ const EditorComponent = (props) => {
|
|||
useDataSourcesStore.getState().actions.fetchGlobalDataSources(organizationId);
|
||||
};
|
||||
|
||||
const onVersionDelete = () => {
|
||||
fetchApp(props.params.pageHandle);
|
||||
};
|
||||
|
||||
const toggleAppMaintenance = () => {
|
||||
const newState = !isMaintenanceOn;
|
||||
|
||||
|
|
@ -698,28 +697,56 @@ const EditorComponent = (props) => {
|
|||
});
|
||||
};
|
||||
|
||||
const callBack = async (data, startingPageHandle, versionSwitched = false) => {
|
||||
setWindowTitle({ page: pageTitles.EDITOR, appName: data.name });
|
||||
useAppVersionStore.getState().actions.updateEditingVersion(data.editing_version);
|
||||
if (!releasedVersionId || !versionSwitched) {
|
||||
useAppVersionStore.getState().actions.updateReleasedVersionId(data.current_version_id);
|
||||
}
|
||||
|
||||
const currentOrgId = data?.organization_id || data?.organizationId;
|
||||
/* Only for the first load of an app. Should not use for any other cases */
|
||||
const runForInitialLoad = async () => {
|
||||
const appId = props.id;
|
||||
const appData = await appService.fetchApp(appId);
|
||||
const {
|
||||
name: appName,
|
||||
current_version_id,
|
||||
editing_version,
|
||||
organization_id: organizationId,
|
||||
slug,
|
||||
is_maintenance_on: isMaintenanceOn,
|
||||
is_public: isPublic,
|
||||
user_id: userId,
|
||||
events,
|
||||
} = appData;
|
||||
const startingPageHandle = props.params.pageHandle;
|
||||
setWindowTitle({ page: pageTitles.EDITOR, appName });
|
||||
useAppVersionStore.getState().actions.updateEditingVersion(editing_version);
|
||||
current_version_id && useAppVersionStore.getState().actions.updateReleasedVersionId(current_version_id);
|
||||
|
||||
updateState({
|
||||
slug: data.slug,
|
||||
isMaintenanceOn: data?.is_maintenance_on,
|
||||
organizationId: currentOrgId,
|
||||
isPublic: data?.is_public || data?.isPublic,
|
||||
appName: data?.name,
|
||||
userId: data?.user_id,
|
||||
appId: data?.id,
|
||||
events: data.events,
|
||||
currentVersionId: data?.editing_version?.id,
|
||||
app: data,
|
||||
slug,
|
||||
isMaintenanceOn,
|
||||
organizationId,
|
||||
isPublic,
|
||||
appName,
|
||||
userId,
|
||||
appId,
|
||||
events,
|
||||
currentVersionId: editing_version?.id,
|
||||
app: appData,
|
||||
});
|
||||
|
||||
await processNewAppDefinition(appData, startingPageHandle, false, ({ homePageId }) => {
|
||||
handleLowPriorityWork(async () => {
|
||||
await useDataSourcesStore.getState().actions.fetchGlobalDataSources(organizationId);
|
||||
await fetchDataSources(editing_version?.id);
|
||||
commonLowPriorityActions(events, { homePageId });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const commonLowPriorityActions = async (events, { homePageId }) => {
|
||||
const currentPageEvents = events.filter((event) => event.target === 'page' && event.sourceId === homePageId);
|
||||
const editorRef = getEditorRef();
|
||||
await runQueries(useDataQueriesStore.getState().dataQueries, editorRef, true);
|
||||
await handleEvent('onPageLoad', currentPageEvents, {}, true);
|
||||
};
|
||||
|
||||
const processNewAppDefinition = async (data, startingPageHandle, versionSwitched = false, onComplete) => {
|
||||
const appDefData = buildAppDefinition(data);
|
||||
|
||||
const appJson = appDefData;
|
||||
|
|
@ -756,39 +783,17 @@ const EditorComponent = (props) => {
|
|||
});
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
await useDataSourcesStore.getState().actions.fetchGlobalDataSources(data?.organization_id),
|
||||
await fetchDataSources(data.editing_version?.id),
|
||||
await fetchDataQueries(data.editing_version?.id, true, true),
|
||||
])
|
||||
Promise.all([await fetchDataQueries(data.editing_version?.id, true, true)])
|
||||
.then(async () => {
|
||||
await onEditorLoad(appJson, homePageId, versionSwitched);
|
||||
updateEntityReferences(appJson, homePageId);
|
||||
})
|
||||
.finally(async () => {
|
||||
const currentPageEvents = data.events.filter(
|
||||
(event) => event.target === 'page' && event.sourceId === homePageId
|
||||
);
|
||||
|
||||
const editorRef = getEditorRef();
|
||||
|
||||
handleLowPriorityWork(async () => {
|
||||
await runQueries(useDataQueriesStore.getState().dataQueries, editorRef, true);
|
||||
await handleEvent('onPageLoad', currentPageEvents, {}, true);
|
||||
});
|
||||
const funcParams = { homePageId };
|
||||
typeof onComplete === 'function' && (await onComplete(funcParams));
|
||||
});
|
||||
};
|
||||
|
||||
const fetchApp = async (startingPageHandle, onMount = false) => {
|
||||
const _appId = props?.params?.id || props?.params?.slug;
|
||||
|
||||
if (!onMount) {
|
||||
await appService.fetchApp(_appId).then((data) => callBack(data, startingPageHandle));
|
||||
} else {
|
||||
callBack(app, startingPageHandle);
|
||||
}
|
||||
};
|
||||
|
||||
const setAppDefinitionFromVersion = (appData) => {
|
||||
const version = appData?.editing_version?.id;
|
||||
if (version?.id !== editingVersionId) {
|
||||
|
|
@ -802,11 +807,24 @@ const EditorComponent = (props) => {
|
|||
updateEditorState({
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
useCurrentStateStore.getState().actions.setCurrentState({});
|
||||
useCurrentStateStore.getState().actions.setEditorReady(false);
|
||||
useResolveStore.getState().actions.resetStore();
|
||||
|
||||
callBack(appData, null, true);
|
||||
const { editing_version } = appData;
|
||||
useAppVersionStore.getState().actions.updateEditingVersion(editing_version);
|
||||
updateState({
|
||||
events,
|
||||
currentVersionId: editing_version?.id,
|
||||
app: appData,
|
||||
});
|
||||
processNewAppDefinition(appData, null, true, ({ homePageId }) => {
|
||||
handleLowPriorityWork(async () => {
|
||||
await fetchDataSources(editing_version?.id);
|
||||
commonLowPriorityActions(events, homePageId);
|
||||
});
|
||||
});
|
||||
initComponentVersioning();
|
||||
}
|
||||
};
|
||||
|
|
@ -1912,29 +1930,7 @@ const EditorComponent = (props) => {
|
|||
const isEditorReady = useCurrentStateStore((state) => state.isEditorReady);
|
||||
|
||||
if (isLoading && !isEditorReady) {
|
||||
return (
|
||||
<div className="apploader">
|
||||
<div className="col col-* editor-center-wrapper">
|
||||
<div className="editor-center">
|
||||
<div className="canvas">
|
||||
<div className="mt-5 d-flex flex-column">
|
||||
<div className="mb-1">
|
||||
<Skeleton width={'150px'} height={15} className="skeleton" />
|
||||
</div>
|
||||
{Array.from(Array(4)).map((_item, index) => (
|
||||
<Skeleton key={index} width={'300px'} height={10} className="skeleton" />
|
||||
))}
|
||||
<div className="align-self-end">
|
||||
<Skeleton width={'100px'} className="skeleton" />
|
||||
</div>
|
||||
<Skeleton className="skeleton mt-4" />
|
||||
<Skeleton height={'150px'} className="skeleton mt-2" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <TJLoader />;
|
||||
}
|
||||
return (
|
||||
<HotkeysProvider initiallyActiveScopes={['editor']}>
|
||||
|
|
@ -1970,7 +1966,6 @@ const EditorComponent = (props) => {
|
|||
setAppDefinitionFromVersion={setAppDefinitionFromVersion}
|
||||
onVersionRelease={onVersionRelease}
|
||||
saveEditingVersion={saveEditingVersion}
|
||||
onVersionDelete={onVersionDelete}
|
||||
isMaintenanceOn={isMaintenanceOn}
|
||||
appName={appName}
|
||||
appId={appId}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEnvironmentsAndVersionsActions } from '@/_stores/environmentsAndVersionsStore';
|
||||
import { useEnvironmentsAndVersionsStore } from '@/_stores/environmentsAndVersionsStore';
|
||||
import React, { useEffect } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
|
|
@ -10,9 +10,17 @@ const EnvironmentManager = () => {
|
|||
}),
|
||||
shallow
|
||||
);
|
||||
const { init, setEnvironmentDropdownStatus } = useEnvironmentsAndVersionsActions();
|
||||
const { init, setEnvironmentDropdownStatus, initializedEnvironmentDropdown } = useEnvironmentsAndVersionsStore(
|
||||
(state) => ({
|
||||
initializedEnvironmentDropdown: state.initializedEnvironmentDropdown,
|
||||
init: state.actions.init,
|
||||
setEnvironmentDropdownStatus: state.actions.setEnvironmentDropdownStatus,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
initComponent();
|
||||
!initializedEnvironmentDropdown && initComponent();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
|
|
@ -21,7 +29,7 @@ const EnvironmentManager = () => {
|
|||
setEnvironmentDropdownStatus(true);
|
||||
};
|
||||
|
||||
return <div></div>;
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default EnvironmentManager;
|
||||
|
|
@ -24,10 +24,10 @@ export const ReleaseVersionButton = function DeployVersionButton({ onVersionRele
|
|||
setShowPageDeletionConfirmation(false);
|
||||
setIsReleasing(true);
|
||||
|
||||
const { id: versionToBeReleased, name, app_id } = editingVersion;
|
||||
const { id: versionToBeReleased, name, app_id, appId } = editingVersion;
|
||||
|
||||
appsService
|
||||
.releaseVersion(app_id, versionToBeReleased)
|
||||
.releaseVersion(app_id || appId, versionToBeReleased)
|
||||
.then(() => {
|
||||
toast(`Version ${name} released`, {
|
||||
icon: '🚀',
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import queryString from 'query-string';
|
|||
import { isEmpty } from 'lodash';
|
||||
import LogoNavDropdown from '@/_components/LogoNavDropdown';
|
||||
import RightTopHeaderButtons from './RightTopHeaderButtons';
|
||||
import EnvironmentManager from './RightTopHeaderButtons/EnvironmentManager';
|
||||
import EnvironmentManager from './EnvironmentManager';
|
||||
|
||||
export default function EditorHeader({
|
||||
M,
|
||||
|
|
@ -28,7 +28,6 @@ export default function EditorHeader({
|
|||
onNameChanged,
|
||||
setAppDefinitionFromVersion,
|
||||
onVersionRelease,
|
||||
onVersionDelete,
|
||||
slug,
|
||||
darkMode,
|
||||
}) {
|
||||
|
|
@ -149,7 +148,6 @@ export default function EditorHeader({
|
|||
<AppVersionsManager
|
||||
appId={appId}
|
||||
setAppDefinitionFromVersion={setAppDefinitionFromVersion}
|
||||
onVersionDelete={onVersionDelete}
|
||||
isPublic={isPublic ?? false}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -30,13 +30,14 @@ export const LeftSidebarComment = forwardRef(({ selectedSidebarItem, currentPage
|
|||
const [notifications, setNotifications] = React.useState([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (appVersionsId && appId) {
|
||||
if (isActive) {
|
||||
commentsService.getNotifications(appId, false, appVersionsId, currentPageId).then(({ data }) => {
|
||||
setNotifications(data);
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appVersionsId, currentPageId, appId]);
|
||||
}, [isActive]);
|
||||
|
||||
return (
|
||||
<LeftSidebarItem
|
||||
commentBadge={notifications?.length > 0}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import Navbar from 'react-bootstrap/Navbar';
|
|||
import Offcanvas from 'react-bootstrap/Offcanvas';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import classNames from 'classnames';
|
||||
import EnvironmentManager from '@/Editor/Header/EnvironmentManager';
|
||||
|
||||
const PreviewSettings = ({ isMobileLayout, setAppDefinitionFromVersion, showHeader, darkMode }) => {
|
||||
const { editingVersion } = useAppVersionStore((state) => ({
|
||||
|
|
@ -23,6 +24,7 @@ const PreviewSettings = ({ isMobileLayout, setAppDefinitionFromVersion, showHead
|
|||
<div className="preview-settings-overlay" style={{ borderColor: darkMode ? '#2B3036' : '#E4E7EB' }}>
|
||||
<span className="preview-settings-text">Preview settings</span>
|
||||
<span>
|
||||
<EnvironmentManager />
|
||||
{editingVersion && (
|
||||
<AppVersionsManager
|
||||
appId={appId}
|
||||
|
|
@ -88,6 +90,7 @@ const PreviewSettings = ({ isMobileLayout, setAppDefinitionFromVersion, showHead
|
|||
{previewNavbar && (
|
||||
<Offcanvas.Body>
|
||||
<span>
|
||||
<EnvironmentManager />
|
||||
{editingVersion && (
|
||||
<AppVersionsManager
|
||||
appId={appId}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,31 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { CustomSelect } from './CustomSelect';
|
||||
import { getAvatar } from '@/_helpers/utils';
|
||||
import { appendWorkspaceId, getWorkspaceIdOrSlugFromURL } from '@/_helpers/routes';
|
||||
import { ToolTip } from '@/_components';
|
||||
import { useCurrentSessionStore } from '@/_stores/currentSessionStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
/* TODO:
|
||||
each workspace related component has organizations list component which can be moved to a single wrapper.
|
||||
otherwise this component will intiate everytime we switch between pages
|
||||
*/
|
||||
export const OrganizationList = function () {
|
||||
const { current_organization_id } = authenticationService.currentSessionValue;
|
||||
const [organizationList, setOrganizationList] = useState([]);
|
||||
const [getOrgStatus, setGetOrgStatus] = useState('');
|
||||
const { fetchOrganizations, organizationList, isGettingOrganizations } = useCurrentSessionStore(
|
||||
(state) => ({
|
||||
organizationList: state.organizations,
|
||||
isGettingOrganizations: state.isGettingOrganizations,
|
||||
fetchOrganizations: state.actions.fetchOrganizations,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
|
||||
useEffect(() => {
|
||||
setGetOrgStatus('loading');
|
||||
const sessionObservable = authenticationService.currentSession.subscribe((newSession) => {
|
||||
setOrganizationList(newSession.organizations ?? []);
|
||||
if (newSession.organizations?.length > 0) setGetOrgStatus('success');
|
||||
});
|
||||
|
||||
() => sessionObservable.unsubscribe();
|
||||
fetchOrganizations();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const switchOrganization = (id) => {
|
||||
|
|
@ -54,7 +61,7 @@ export const OrganizationList = function () {
|
|||
return (
|
||||
<div className="org-select-container">
|
||||
<CustomSelect
|
||||
isLoading={getOrgStatus === 'loading'}
|
||||
isLoading={isGettingOrganizations}
|
||||
options={options}
|
||||
value={current_organization_id}
|
||||
onChange={(id) => switchOrganization(id)}
|
||||
|
|
|
|||
|
|
@ -85,16 +85,6 @@ const isThisExistedRoute = () => {
|
|||
return pathnames?.length > 0 ? (checkPath() ? true : false) : false;
|
||||
};
|
||||
|
||||
const fetchOrganizations = (current_organization_id, callback) => {
|
||||
organizationService.getOrganizations().then((response) => {
|
||||
const current_organization = response.organizations?.find((org) => org.id === current_organization_id);
|
||||
callback({
|
||||
organizations: response.organizations,
|
||||
current_organization,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const isThisWorkspaceLoginPage = (justLoginPage = false) => {
|
||||
const subpath = getSubpath();
|
||||
const pathname = location.pathname.replace(subpath, '');
|
||||
|
|
@ -102,7 +92,7 @@ const isThisWorkspaceLoginPage = (justLoginPage = false) => {
|
|||
return (justLoginPage && pathnames[0] === 'login') || (pathnames.length === 2 && pathnames[0] === 'login');
|
||||
};
|
||||
|
||||
const updateCurrentSession = (newSession) => {
|
||||
export const updateCurrentSession = (newSession) => {
|
||||
const currentSession = authenticationService.currentSessionValue;
|
||||
authenticationService.updateCurrentSession({ ...currentSession, ...newSession });
|
||||
};
|
||||
|
|
@ -125,16 +115,12 @@ export const authorizeUserAndHandleErrors = (workspace_id, workspace_slug) => {
|
|||
.authorize()
|
||||
.then((data) => {
|
||||
/* CASE-1 */
|
||||
const { current_organization_id } = data;
|
||||
fetchOrganizations(current_organization_id, ({ organizations, current_organization }) => {
|
||||
const { name: current_organization_name } = current_organization;
|
||||
/* add the user details like permission and user previlliage details to the subject */
|
||||
updateCurrentSession({
|
||||
...data,
|
||||
current_organization_name,
|
||||
organizations,
|
||||
load_app: true,
|
||||
});
|
||||
const { current_organization_name } = data;
|
||||
/* add the user details like permission and user previlliage details to the subject */
|
||||
updateCurrentSession({
|
||||
...data,
|
||||
current_organization_name,
|
||||
load_app: true,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
@ -148,7 +134,7 @@ export const authorizeUserAndHandleErrors = (workspace_id, workspace_slug) => {
|
|||
/* get current session's workspace id */
|
||||
authenticationService
|
||||
.validateSession()
|
||||
.then(({ current_organization_id }) => {
|
||||
.then(({ current_organization_id, ...restSessionData }) => {
|
||||
/* change current organization id to valid one [current logged in organization] */
|
||||
updateCurrentSession({
|
||||
current_organization_id,
|
||||
|
|
@ -160,20 +146,17 @@ export const authorizeUserAndHandleErrors = (workspace_id, workspace_slug) => {
|
|||
authorizeUserAndHandleErrors(unauthorized_organization_id);
|
||||
})
|
||||
.catch(() => {
|
||||
/* CASE-3 */
|
||||
fetchOrganizations(current_organization_id, ({ current_organization }) => {
|
||||
const { name: current_organization_name, slug: current_organization_slug } = current_organization;
|
||||
updateCurrentSession({
|
||||
current_organization_name,
|
||||
current_organization_slug,
|
||||
load_app: true,
|
||||
});
|
||||
|
||||
if (!isThisWorkspaceLoginPage())
|
||||
return (window.location = `${
|
||||
subpath ?? ''
|
||||
}/login/${unauthorized_organization_slug}?redirectTo=${getRedirectToWithParams()}`);
|
||||
const { current_organization_name, current_organization_slug } = restSessionData;
|
||||
updateCurrentSession({
|
||||
current_organization_name,
|
||||
current_organization_slug,
|
||||
load_app: true,
|
||||
});
|
||||
|
||||
if (!isThisWorkspaceLoginPage())
|
||||
return (window.location = `${
|
||||
subpath ?? ''
|
||||
}/login/${unauthorized_organization_slug}?redirectTo=${getRedirectToWithParams()}`);
|
||||
});
|
||||
})
|
||||
/* CASE-3 */
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
import config from 'config';
|
||||
|
||||
class WebSocketConnection {
|
||||
constructor(appId) {
|
||||
this.socket = new WebSocket(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${this.getWebsocketUrl()}`);
|
||||
static instance;
|
||||
static appId;
|
||||
|
||||
constructor(appId) {
|
||||
if (WebSocketConnection.instance && WebSocketConnection.appId === appId) {
|
||||
return WebSocketConnection.instance;
|
||||
}
|
||||
|
||||
this.socket = new WebSocket(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${this.getWebsocketUrl()}`);
|
||||
this.addListeners(appId);
|
||||
|
||||
WebSocketConnection.instance = this;
|
||||
}
|
||||
|
||||
getWebsocketUrl() {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export const appEnvironmentService = {
|
|||
getAllEnvironments,
|
||||
getVersionsByEnvironment,
|
||||
init,
|
||||
postVersionDeleteAction,
|
||||
};
|
||||
|
||||
function getAllEnvironments() {
|
||||
|
|
@ -36,3 +37,13 @@ function init(editing_version_id = null) {
|
|||
const query = queryString.stringify({ editing_version_id });
|
||||
return fetch(`${config.apiUrl}/app-environments/init?${query}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function postVersionDeleteAction(actionParams = {}) {
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(actionParams),
|
||||
headers: authHeader(),
|
||||
credentials: 'include',
|
||||
};
|
||||
return fetch(`${config.apiUrl}/app-environments/post-action/version_deleted`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
|
|
|||
32
frontend/src/_stores/currentSessionStore.js
Normal file
32
frontend/src/_stores/currentSessionStore.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import create from 'zustand';
|
||||
import { zustandDevTools } from './utils';
|
||||
import { organizationService } from '@/_services';
|
||||
|
||||
const initialState = {
|
||||
organizations: [],
|
||||
isGettingOrganizations: false,
|
||||
};
|
||||
|
||||
//TODO: migrate all rxjs functions to zustand in future
|
||||
export const useCurrentSessionStore = create(
|
||||
zustandDevTools(
|
||||
(set, get) => ({
|
||||
...initialState,
|
||||
actions: {
|
||||
fetchOrganizations: async () => {
|
||||
if (get().isGettingOrganizations || get().organizations.length) return;
|
||||
try {
|
||||
set({ isGettingOrganizations: true });
|
||||
const response = await organizationService.getOrganizations();
|
||||
set({ organizations: response.organizations, isGettingOrganizations: false });
|
||||
} catch (error) {
|
||||
console.error('Error while fetching organizations', error);
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ name: 'Current Session Store' }
|
||||
)
|
||||
);
|
||||
|
||||
export const useCurrentSessionStoreActions = () => useCurrentSessionStore((state) => state.actions);
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import create from 'zustand';
|
||||
import { zustandDevTools } from './utils';
|
||||
import { appEnvironmentService } from '../_services/app_environment.service';
|
||||
import { appEnvironmentService, appVersionService } from '@/_services';
|
||||
|
||||
const initialState = {
|
||||
selectedVersion: null,
|
||||
|
|
@ -11,7 +11,6 @@ const initialState = {
|
|||
shouldRenderPromoteButton: false,
|
||||
shouldRenderReleaseButton: false,
|
||||
initializedEnvironmentDropdown: false,
|
||||
initializedVersionsDropdown: false,
|
||||
environmentsLazyLoaded: false,
|
||||
appVersionsLazyLoaded: false,
|
||||
};
|
||||
|
|
@ -31,7 +30,7 @@ export const useEnvironmentsAndVersionsStore = create(
|
|||
appVersionEnvironment: response.appVersionEnvironment,
|
||||
shouldRenderPromoteButton: response.shouldRenderPromoteButton,
|
||||
shouldRenderReleaseButton: response.shouldRenderReleaseButton,
|
||||
environments: [response.editorEnvironment],
|
||||
environments: response.environments,
|
||||
versionsPromotedToEnvironment: [response.editorVersion],
|
||||
}));
|
||||
} catch (error) {
|
||||
|
|
@ -51,6 +50,95 @@ export const useEnvironmentsAndVersionsStore = create(
|
|||
console.error('Error while getting the versions', error);
|
||||
}
|
||||
},
|
||||
setSelectedVersion: (selectedVersion) => set({ selectedVersion }),
|
||||
setEnvironmentAndVersionsInitStatus: (state) => set({ completedEnvironmentAndVersionsInit: state }),
|
||||
createNewVersionAction: async (appId, versionName, selectedVersionId, onSuccess, onFailure) => {
|
||||
try {
|
||||
const newVersion = await appVersionService.create(appId, versionName, selectedVersionId);
|
||||
const editorVersion = {
|
||||
id: newVersion.id,
|
||||
name: newVersion.name,
|
||||
current_environment_id: newVersion.current_environment_id,
|
||||
};
|
||||
set((state) => ({
|
||||
...state,
|
||||
selectedVersion: editorVersion,
|
||||
versionsPromotedToEnvironment: [editorVersion],
|
||||
appVersionsLazyLoaded: false,
|
||||
}));
|
||||
onSuccess(newVersion);
|
||||
} catch (error) {
|
||||
onFailure(error);
|
||||
}
|
||||
},
|
||||
updateVersionNameAction: async (appId, versionId, versionName, onSuccess, onFailure) => {
|
||||
try {
|
||||
await appVersionService.save(appId, versionId, { name: versionName });
|
||||
const selectedVersion = get().selectedVersion;
|
||||
selectedVersion.name = versionName;
|
||||
set((state) => ({
|
||||
...state,
|
||||
selectedVersion,
|
||||
versionsPromotedToEnvironment: [selectedVersion],
|
||||
appVersionsLazyLoaded: false,
|
||||
}));
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
onFailure(error);
|
||||
}
|
||||
},
|
||||
deleteVersionAction: async (appId, versionId, onSuccess, onFailure) => {
|
||||
try {
|
||||
await appVersionService.del(appId, versionId);
|
||||
const isCurrentVersion = get().selectedVersion.id === versionId;
|
||||
let optionsToUpdate = {
|
||||
appVersionsLazyLoaded: false,
|
||||
};
|
||||
if (isCurrentVersion) {
|
||||
/* User is deleted the editor version (currently selected). */
|
||||
const response = await appEnvironmentService.postVersionDeleteAction({
|
||||
appId,
|
||||
editorVersionId: versionId,
|
||||
deletedVersionId: versionId,
|
||||
});
|
||||
const selectedVersion = response.editorVersion;
|
||||
optionsToUpdate = {
|
||||
...optionsToUpdate,
|
||||
selectedVersion,
|
||||
selectedEnvironment: response.editorEnvironment,
|
||||
appVersionEnvironment: response.appVersionEnvironment,
|
||||
shouldRenderPromoteButton: response.shouldRenderPromoteButton,
|
||||
shouldRenderReleaseButton: response.shouldRenderReleaseButton,
|
||||
};
|
||||
}
|
||||
set((state) => ({
|
||||
...state,
|
||||
...optionsToUpdate,
|
||||
}));
|
||||
let newVersionDef;
|
||||
const newEditorVersion = optionsToUpdate.selectedVersion;
|
||||
if (newEditorVersion) {
|
||||
newVersionDef = await appVersionService.getAppVersionData(appId, newEditorVersion.id);
|
||||
}
|
||||
onSuccess(newVersionDef);
|
||||
} catch (error) {
|
||||
onFailure(error);
|
||||
}
|
||||
},
|
||||
changeEditorVersionAction: async (appId, versionId, onSuccess, onFailure) => {
|
||||
try {
|
||||
const data = await appVersionService.getAppVersionData(appId, versionId);
|
||||
const selectedVersion = {
|
||||
id: data.editing_version.id,
|
||||
name: data.editing_version.name,
|
||||
current_environment_id: data.editing_version.currentEnvironmentId,
|
||||
};
|
||||
set((state) => ({ ...state, selectedVersion }));
|
||||
onSuccess(data);
|
||||
} catch (error) {
|
||||
onFailure(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ name: 'App Version Manager Store' }
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export class AppController {
|
|||
currentOrganization = organization;
|
||||
}
|
||||
|
||||
return this.authService.generateSessionPayload(user, currentOrganization);
|
||||
return await this.authService.generateSessionPayload(user, currentOrganization);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
|
||||
import { Body, Controller, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
|
||||
import { decamelizeKeys } from 'humps';
|
||||
import { JwtAuthGuard } from '../modules/auth/jwt-auth.guard';
|
||||
import { ForbiddenException } from '@nestjs/common';
|
||||
|
|
@ -8,6 +8,7 @@ import { GlobalDataSourceAbilityFactory } from 'src/modules/casl/abilities/globa
|
|||
import { DataSource } from 'src/entities/data_source.entity';
|
||||
import { OrgEnvironmentVariablesAbilityFactory } from 'src/modules/casl/abilities/org-environment-variables-ability.factory';
|
||||
import { OrgEnvironmentVariable } from 'src/entities/org_envirnoment_variable.entity';
|
||||
import { AppEnvironmentActionParametersDto } from '@dto/environment_action_parameters.dto';
|
||||
|
||||
@Controller('app-environments')
|
||||
export class AppEnvironmentsController {
|
||||
|
|
@ -24,7 +25,20 @@ export class AppEnvironmentsController {
|
|||
init is a method in the AppEnvironmentService class that is used to initialize the app environment mananger.
|
||||
Should not use for any other purpose.
|
||||
*/
|
||||
return await this.appEnvironmentServices.init(editingVersionId);
|
||||
return await this.appEnvironmentServices.init(editingVersionId, user.organizationId);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('/post-action/:action')
|
||||
async environmentActions(
|
||||
@Param('action') action: string,
|
||||
@Body() appEnvironmentActionParametersDto: AppEnvironmentActionParametersDto
|
||||
) {
|
||||
/*
|
||||
init is a method in the AppEnvironmentService class that is used to initialize the app environment mananger.
|
||||
Should not use for any other purpose.
|
||||
*/
|
||||
return await this.appEnvironmentServices.processActions(action, appEnvironmentActionParametersDto);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
|
|
|||
19
server/src/dto/environment_action_parameters.dto.ts
Normal file
19
server/src/dto/environment_action_parameters.dto.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { IsOptional, IsUUID } from 'class-validator';
|
||||
|
||||
export class AppEnvironmentActionParametersDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
editorEnvironmentId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
editorVersionId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
deletedVersionId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
appId: string;
|
||||
}
|
||||
|
|
@ -6,6 +6,12 @@ import { OrgEnvironmentConstantValue } from 'src/entities/org_environment_consta
|
|||
import { OrganizationConstant } from 'src/entities/organization_constants.entity';
|
||||
import { EntityManager, FindOneOptions, In, DeleteResult } from 'typeorm';
|
||||
import { AppVersion } from 'src/entities/app_version.entity';
|
||||
import { AppEnvironmentActionParametersDto } from '@dto/environment_action_parameters.dto';
|
||||
import { App } from 'src/entities/app.entity';
|
||||
|
||||
interface ExtendedEnvironment extends AppEnvironment {
|
||||
appVersionsCount?: number;
|
||||
}
|
||||
|
||||
export interface AppEnvironmentResponse {
|
||||
editorVersion: Partial<AppVersion>;
|
||||
|
|
@ -13,37 +19,184 @@ export interface AppEnvironmentResponse {
|
|||
appVersionEnvironment: AppEnvironment;
|
||||
shouldRenderPromoteButton: boolean;
|
||||
shouldRenderReleaseButton: boolean;
|
||||
environments: ExtendedEnvironment[];
|
||||
}
|
||||
|
||||
export enum AppEnvironmentActions {
|
||||
VERSION_DELETED = 'version_deleted',
|
||||
ENVIROMENT_CHANGED = 'environment_changed',
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AppEnvironmentService {
|
||||
async init(editingVersionId: string, manager?: EntityManager): Promise<AppEnvironmentResponse> {
|
||||
async init(
|
||||
editingVersionId: string,
|
||||
organizationId: string,
|
||||
manager?: EntityManager
|
||||
): Promise<AppEnvironmentResponse> {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const editorVersion = await manager.findOne(AppVersion, {
|
||||
select: ['id', 'name', 'currentEnvironmentId'],
|
||||
select: ['id', 'name', 'currentEnvironmentId', 'appId'],
|
||||
where: { id: editingVersionId },
|
||||
});
|
||||
const editorEnvironment = await manager.findOne(AppEnvironment, { id: editorVersion.currentEnvironmentId });
|
||||
const { shouldRenderPromoteButton, shouldRenderReleaseButton } =
|
||||
this.calculateButtonVisibility(editorEnvironment);
|
||||
const environments: ExtendedEnvironment[] = await this.getAll(organizationId, manager, editorVersion.appId);
|
||||
const editorEnvironment = environments.find((env) => env.id === editorVersion.currentEnvironmentId);
|
||||
const { shouldRenderPromoteButton, shouldRenderReleaseButton } = await this.calculateButtonVisibility(
|
||||
false,
|
||||
editorEnvironment,
|
||||
editorVersion.appId,
|
||||
editorVersion.id,
|
||||
manager
|
||||
);
|
||||
const response: AppEnvironmentResponse = {
|
||||
editorVersion,
|
||||
editorEnvironment,
|
||||
appVersionEnvironment: editorEnvironment,
|
||||
shouldRenderPromoteButton,
|
||||
shouldRenderReleaseButton,
|
||||
environments,
|
||||
};
|
||||
return response;
|
||||
}, manager);
|
||||
}
|
||||
|
||||
calculateButtonVisibility(appVersionEnvironment: AppEnvironment) {
|
||||
async calculateButtonVisibility(
|
||||
isMultiEnvironmentEnabled: boolean,
|
||||
appVersionEnvironment?: AppEnvironment,
|
||||
appId?: string,
|
||||
versionId?: string,
|
||||
manager?: EntityManager
|
||||
) {
|
||||
/* Further conditions can handle from here */
|
||||
const shouldRenderPromoteButton = false;
|
||||
const shouldRenderReleaseButton = true;
|
||||
if (!isMultiEnvironmentEnabled) {
|
||||
return {
|
||||
shouldRenderPromoteButton: false,
|
||||
shouldRenderReleaseButton: true,
|
||||
};
|
||||
}
|
||||
const appDetails = await manager.findOneOrFail(App, {
|
||||
select: ['id', 'currentVersionId'],
|
||||
where: { id: appId },
|
||||
});
|
||||
const isVersionReleased = appDetails.currentVersionId === versionId;
|
||||
const isCurrentVersionInProduction = appVersionEnvironment?.isDefault;
|
||||
const shouldRenderPromoteButton = !isCurrentVersionInProduction && !isVersionReleased;
|
||||
const shouldRenderReleaseButton = isCurrentVersionInProduction || isVersionReleased;
|
||||
return { shouldRenderPromoteButton, shouldRenderReleaseButton };
|
||||
}
|
||||
|
||||
async getSelectedVersion(selectedEnvironmentId: string, appId: string, manager?: EntityManager): Promise<any> {
|
||||
const newVersionQuery = `
|
||||
SELECT name, id, current_environment_id
|
||||
FROM app_versions
|
||||
WHERE current_environment_id IN (
|
||||
SELECT id
|
||||
FROM app_environments
|
||||
WHERE priority >= (
|
||||
SELECT priority
|
||||
FROM app_environments
|
||||
WHERE id = $1
|
||||
)
|
||||
)
|
||||
AND app_id = $2
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 1;
|
||||
`;
|
||||
const result = await manager.query(newVersionQuery, [selectedEnvironmentId, appId]);
|
||||
return result[0];
|
||||
}
|
||||
|
||||
async processActions(action: string, actionParameters: AppEnvironmentActionParametersDto) {
|
||||
const { editorEnvironmentId, deletedVersionId, editorVersionId, appId } = actionParameters;
|
||||
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
switch (action) {
|
||||
case AppEnvironmentActions.VERSION_DELETED: {
|
||||
const appEnvironmentResponse: Partial<AppEnvironmentResponse> = {};
|
||||
const isUserDeletedTheCurrentVersion = editorVersionId === deletedVersionId;
|
||||
/*
|
||||
This is post action which is triggered when a version is deleted from the app version manager.
|
||||
*/
|
||||
|
||||
const multiEnvironmentsNotAvailable = !editorEnvironmentId;
|
||||
if (multiEnvironmentsNotAvailable) {
|
||||
const { shouldRenderPromoteButton, shouldRenderReleaseButton } = await this.calculateButtonVisibility(
|
||||
false
|
||||
);
|
||||
appEnvironmentResponse.shouldRenderPromoteButton = shouldRenderPromoteButton;
|
||||
appEnvironmentResponse.shouldRenderReleaseButton = shouldRenderReleaseButton;
|
||||
if (isUserDeletedTheCurrentVersion) {
|
||||
const newVersionQuery = `
|
||||
SELECT name, id, current_environment_id
|
||||
FROM app_versions
|
||||
WHERE app_id = $1
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 1;
|
||||
`;
|
||||
const selectedVersionQueryResponse = await manager.query(newVersionQuery, [appId]);
|
||||
const selectedVersion = selectedVersionQueryResponse[0];
|
||||
const selectedEnvironment = await manager.findOneOrFail(AppEnvironment, {
|
||||
where: { id: selectedVersion.current_environment_id },
|
||||
});
|
||||
appEnvironmentResponse.editorEnvironment = selectedEnvironment;
|
||||
appEnvironmentResponse.editorVersion = selectedVersion;
|
||||
appEnvironmentResponse.appVersionEnvironment = selectedEnvironment;
|
||||
}
|
||||
return appEnvironmentResponse;
|
||||
}
|
||||
|
||||
/* If the editorEnvironment is null then the method will return all the versions of an App */
|
||||
const versionsCountOfEnvironment = await manager.count(AppVersion, {
|
||||
where: { currentEnvironmentId: editorEnvironmentId, appId },
|
||||
});
|
||||
const environmentDoensNotHaveVersions = versionsCountOfEnvironment === 0;
|
||||
if (environmentDoensNotHaveVersions) {
|
||||
/* Send back new editor environment and version */
|
||||
const newEnvironmentQuery = `
|
||||
SELECT *
|
||||
FROM app_environments
|
||||
WHERE priority < (
|
||||
SELECT priority
|
||||
FROM app_environments
|
||||
WHERE id = $1
|
||||
)
|
||||
ORDER BY priority DESC
|
||||
LIMIT 1;
|
||||
`;
|
||||
const selectedEnvironmentResponse = await manager.query(newEnvironmentQuery, [editorEnvironmentId]);
|
||||
const selectedEnvironment = selectedEnvironmentResponse[0];
|
||||
const selectedVersion = await this.getSelectedVersion(selectedEnvironment.id, appId, manager);
|
||||
appEnvironmentResponse.editorEnvironment = selectedEnvironment;
|
||||
appEnvironmentResponse.editorVersion = selectedVersion;
|
||||
/* Add extra things to respons */
|
||||
} else if (isUserDeletedTheCurrentVersion) {
|
||||
const selectedEnvironment = await manager.findOneOrFail(AppEnvironment, {
|
||||
id: editorEnvironmentId,
|
||||
});
|
||||
/* User deleted current editor version. Client needs new editor version */
|
||||
if (selectedEnvironment) {
|
||||
const selectedVersion = await this.getSelectedVersion(editorEnvironmentId, appId, manager);
|
||||
const appVersionEnvironment = await manager.findOneOrFail(AppEnvironment, {
|
||||
where: { id: selectedVersion.current_environment_id },
|
||||
});
|
||||
appEnvironmentResponse.editorVersion = selectedVersion;
|
||||
appEnvironmentResponse.editorEnvironment = selectedEnvironment;
|
||||
appEnvironmentResponse.appVersionEnvironment = appVersionEnvironment;
|
||||
}
|
||||
}
|
||||
return appEnvironmentResponse;
|
||||
}
|
||||
case AppEnvironmentActions.ENVIROMENT_CHANGED: {
|
||||
const appEnvironmentResponse: Partial<AppEnvironmentResponse> = {};
|
||||
appEnvironmentResponse.editorVersion = await this.getSelectedVersion(editorEnvironmentId, appId, manager);
|
||||
return appEnvironmentResponse;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async get(
|
||||
organizationId: string,
|
||||
id?: string,
|
||||
|
|
@ -52,7 +205,10 @@ export class AppEnvironmentService {
|
|||
): Promise<AppEnvironment> {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const condition: FindOneOptions<AppEnvironment> = {
|
||||
where: { organizationId, ...(id ? { id } : !priorityCheck && { isDefault: true }) },
|
||||
where: {
|
||||
organizationId,
|
||||
...(id ? { id } : !priorityCheck && { isDefault: true }),
|
||||
},
|
||||
...(priorityCheck && { order: { priority: 'ASC' } }),
|
||||
};
|
||||
return await manager.findOneOrFail(AppEnvironment, condition);
|
||||
|
|
@ -65,7 +221,9 @@ export class AppEnvironmentService {
|
|||
if (!environmentId) {
|
||||
envId = (await this.get(organizationId, null, false, manager)).id;
|
||||
}
|
||||
return await manager.findOneOrFail(DataSourceOptions, { where: { environmentId: envId, dataSourceId } });
|
||||
return await manager.findOneOrFail(DataSourceOptions, {
|
||||
where: { environmentId: envId, dataSourceId },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +249,7 @@ export class AppEnvironmentService {
|
|||
}, manager);
|
||||
}
|
||||
|
||||
async getAll(organizationId: string, manager?: EntityManager, appId?: string): Promise<AppEnvironment[]> {
|
||||
async getAll(organizationId: string, manager?: EntityManager, appId?: string): Promise<ExtendedEnvironment[]> {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const appEnvironments = await manager.find(AppEnvironment, {
|
||||
where: {
|
||||
|
|
@ -107,7 +265,9 @@ export class AppEnvironmentService {
|
|||
for (const appEnvironment of appEnvironments) {
|
||||
const count = await manager.count(AppVersion, {
|
||||
where: {
|
||||
...(appEnvironment.priority !== 1 && { currentEnvironmentId: appEnvironment.id }),
|
||||
...(appEnvironment.priority !== 1 && {
|
||||
currentEnvironmentId: appEnvironment.id,
|
||||
}),
|
||||
appId,
|
||||
},
|
||||
});
|
||||
|
|
@ -120,7 +280,12 @@ export class AppEnvironmentService {
|
|||
}, manager);
|
||||
}
|
||||
|
||||
async getVersionsByEnvironment(organizationId: string, appId: string, currentEnvironmentId?: string) {
|
||||
async getVersionsByEnvironment(
|
||||
organizationId: string,
|
||||
appId: string,
|
||||
currentEnvironmentId?: string,
|
||||
manager?: EntityManager
|
||||
) {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const conditions = { appId };
|
||||
if (currentEnvironmentId) {
|
||||
|
|
@ -151,7 +316,7 @@ export class AppEnvironmentService {
|
|||
},
|
||||
select: ['id', 'name', 'appId'],
|
||||
});
|
||||
});
|
||||
}, manager);
|
||||
}
|
||||
|
||||
async updateOptions(options: object, environmentId: string, dataSourceId: string, manager?: EntityManager) {
|
||||
|
|
@ -247,7 +412,11 @@ export class AppEnvironmentService {
|
|||
envId = (await this.get(organizationId, environmentId, false, manager)).id;
|
||||
}
|
||||
|
||||
const constantId = (await manager.findOne(OrganizationConstant, { where: { constantName, organizationId } })).id;
|
||||
const constantId = (
|
||||
await manager.findOne(OrganizationConstant, {
|
||||
where: { constantName, organizationId },
|
||||
})
|
||||
).id;
|
||||
|
||||
return await manager.findOneOrFail(OrgEnvironmentConstantValue, {
|
||||
where: { organizationConstantId: constantId, environmentId: envId },
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ export class AuthService {
|
|||
return decamelizeKeys({
|
||||
currentOrganizationId: user.organizationId,
|
||||
currentOrganizationSlug: organization.slug,
|
||||
currentOrganizationName: organization.name,
|
||||
admin: await this.usersService.hasGroup(user, 'admin', null, manager),
|
||||
groupPermissions: await this.usersService.groupPermissions(user, manager),
|
||||
appGroupPermissions: await this.usersService.appGroupPermissions(user, null, manager),
|
||||
|
|
@ -555,18 +556,29 @@ export class AuthService {
|
|||
};
|
||||
}
|
||||
|
||||
generateSessionPayload(user: User, currentOrganization: Organization) {
|
||||
return decamelizeKeys({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
currentOrganizationSlug: currentOrganization?.slug,
|
||||
currentOrganizationId: currentOrganization?.id
|
||||
async generateSessionPayload(user: User, currentOrganization: Organization) {
|
||||
return dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const currentOrganizationId = currentOrganization?.id
|
||||
? currentOrganization?.id
|
||||
: user?.organizationIds?.includes(user?.defaultOrganizationId)
|
||||
? user.defaultOrganizationId
|
||||
: user?.organizationIds?.[0],
|
||||
: user?.organizationIds?.[0];
|
||||
const organizationDetails = currentOrganization
|
||||
? currentOrganization
|
||||
: await manager.findOneOrFail(Organization, {
|
||||
where: { id: currentOrganizationId },
|
||||
select: ['slug', 'name', 'id'],
|
||||
});
|
||||
|
||||
return decamelizeKeys({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
currentOrganizationSlug: organizationDetails.slug,
|
||||
currentOrganizationName: organizationDetails.name,
|
||||
currentOrganizationId,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue