selectVersion(id)}
{...customSelectProps}
className={` ${darkMode && 'dark-theme'}`}
diff --git a/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx b/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx
index 2801f278b0..07c67506d5 100644
--- a/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx
+++ b/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx
@@ -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 (
diff --git a/frontend/src/Editor/AppVersionsManager/EditVersionModal.jsx b/frontend/src/Editor/AppVersionsManager/EditVersionModal.jsx
index e7e91a5dac..1ba1a30994 100644
--- a/frontend/src/Editor/AppVersionsManager/EditVersionModal.jsx
+++ b/frontend/src/Editor/AppVersionsManager/EditVersionModal.jsx
@@ -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 (
diff --git a/frontend/src/Editor/CommentNotifications/index.jsx b/frontend/src/Editor/CommentNotifications/index.jsx
index f2eda8323e..63f7969b1e 100644
--- a/frontend/src/Editor/CommentNotifications/index.jsx
+++ b/frontend/src/Editor/CommentNotifications/index.jsx
@@ -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);
diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx
index 2dbbd42d3f..b4666b2eab 100644
--- a/frontend/src/Editor/Editor.jsx
+++ b/frontend/src/Editor/Editor.jsx
@@ -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 (
-
-
-
-
-
-
-
-
- {Array.from(Array(4)).map((_item, index) => (
-
- ))}
-
-
-
-
-
-
-
-
-
-
- );
+ return
;
}
return (
@@ -1970,7 +1966,6 @@ const EditorComponent = (props) => {
setAppDefinitionFromVersion={setAppDefinitionFromVersion}
onVersionRelease={onVersionRelease}
saveEditingVersion={saveEditingVersion}
- onVersionDelete={onVersionDelete}
isMaintenanceOn={isMaintenanceOn}
appName={appName}
appId={appId}
diff --git a/frontend/src/Editor/Header/RightTopHeaderButtons/EnvironmentManager/EnvironmentsManager.jsx b/frontend/src/Editor/Header/EnvironmentManager/EnvironmentsManager.jsx
similarity index 53%
rename from frontend/src/Editor/Header/RightTopHeaderButtons/EnvironmentManager/EnvironmentsManager.jsx
rename to frontend/src/Editor/Header/EnvironmentManager/EnvironmentsManager.jsx
index e8a116e077..73571d6320 100644
--- a/frontend/src/Editor/Header/RightTopHeaderButtons/EnvironmentManager/EnvironmentsManager.jsx
+++ b/frontend/src/Editor/Header/EnvironmentManager/EnvironmentsManager.jsx
@@ -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 ;
+ return <>>;
};
export default EnvironmentManager;
diff --git a/frontend/src/Editor/Header/RightTopHeaderButtons/EnvironmentManager/index.js b/frontend/src/Editor/Header/EnvironmentManager/index.js
similarity index 100%
rename from frontend/src/Editor/Header/RightTopHeaderButtons/EnvironmentManager/index.js
rename to frontend/src/Editor/Header/EnvironmentManager/index.js
diff --git a/frontend/src/Editor/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx b/frontend/src/Editor/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx
index 3c72c32a89..198bf3926f 100644
--- a/frontend/src/Editor/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx
+++ b/frontend/src/Editor/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx
@@ -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: '🚀',
diff --git a/frontend/src/Editor/Header/index.js b/frontend/src/Editor/Header/index.js
index 51696634b4..9b089b682a 100644
--- a/frontend/src/Editor/Header/index.js
+++ b/frontend/src/Editor/Header/index.js
@@ -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({
)}
diff --git a/frontend/src/Editor/LeftSidebar/SidebarComment.jsx b/frontend/src/Editor/LeftSidebar/SidebarComment.jsx
index 3c64014eb2..7be1378c8d 100644
--- a/frontend/src/Editor/LeftSidebar/SidebarComment.jsx
+++ b/frontend/src/Editor/LeftSidebar/SidebarComment.jsx
@@ -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 (
0}
diff --git a/frontend/src/Editor/Viewer/PreviewSettings.jsx b/frontend/src/Editor/Viewer/PreviewSettings.jsx
index 3ec6acbf29..6218813833 100644
--- a/frontend/src/Editor/Viewer/PreviewSettings.jsx
+++ b/frontend/src/Editor/Viewer/PreviewSettings.jsx
@@ -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
Preview settings
+
{editingVersion && (
+
{editingVersion && (
({
+ 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 (
switchOrganization(id)}
diff --git a/frontend/src/_helpers/authorizeWorkspace.js b/frontend/src/_helpers/authorizeWorkspace.js
index 1f8e0a0298..5dc9ad7711 100644
--- a/frontend/src/_helpers/authorizeWorkspace.js
+++ b/frontend/src/_helpers/authorizeWorkspace.js
@@ -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 */
diff --git a/frontend/src/_helpers/websocketConnection.js b/frontend/src/_helpers/websocketConnection.js
index a8ca670323..837ecf0d28 100644
--- a/frontend/src/_helpers/websocketConnection.js
+++ b/frontend/src/_helpers/websocketConnection.js
@@ -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() {
diff --git a/frontend/src/_services/app_environment.service.js b/frontend/src/_services/app_environment.service.js
index 411ef368ca..9f6b5286c8 100644
--- a/frontend/src/_services/app_environment.service.js
+++ b/frontend/src/_services/app_environment.service.js
@@ -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);
+}
diff --git a/frontend/src/_stores/currentSessionStore.js b/frontend/src/_stores/currentSessionStore.js
new file mode 100644
index 0000000000..f62d974346
--- /dev/null
+++ b/frontend/src/_stores/currentSessionStore.js
@@ -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);
diff --git a/frontend/src/_stores/environmentsAndVersionsStore.js b/frontend/src/_stores/environmentsAndVersionsStore.js
index 80c9cba59c..56af559edc 100644
--- a/frontend/src/_stores/environmentsAndVersionsStore.js
+++ b/frontend/src/_stores/environmentsAndVersionsStore.js
@@ -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' }
diff --git a/server/src/controllers/app.controller.ts b/server/src/controllers/app.controller.ts
index 721a4fa277..2c1993f6c5 100644
--- a/server/src/controllers/app.controller.ts
+++ b/server/src/controllers/app.controller.ts
@@ -78,7 +78,7 @@ export class AppController {
currentOrganization = organization;
}
- return this.authService.generateSessionPayload(user, currentOrganization);
+ return await this.authService.generateSessionPayload(user, currentOrganization);
}
@UseGuards(JwtAuthGuard)
diff --git a/server/src/controllers/app_environments.controller.ts b/server/src/controllers/app_environments.controller.ts
index 50f8dbc674..b15a9fc199 100644
--- a/server/src/controllers/app_environments.controller.ts
+++ b/server/src/controllers/app_environments.controller.ts
@@ -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)
diff --git a/server/src/dto/environment_action_parameters.dto.ts b/server/src/dto/environment_action_parameters.dto.ts
new file mode 100644
index 0000000000..a3994c309e
--- /dev/null
+++ b/server/src/dto/environment_action_parameters.dto.ts
@@ -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;
+}
diff --git a/server/src/services/app_environments.service.ts b/server/src/services/app_environments.service.ts
index 635dddc7d2..1a01afa28e 100644
--- a/server/src/services/app_environments.service.ts
+++ b/server/src/services/app_environments.service.ts
@@ -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;
@@ -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 {
+ async init(
+ editingVersionId: string,
+ organizationId: string,
+ manager?: EntityManager
+ ): Promise {
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 {
+ 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 = {};
+ 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.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 {
return await dbTransactionWrap(async (manager: EntityManager) => {
const condition: FindOneOptions = {
- 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 {
+ async getAll(organizationId: string, manager?: EntityManager, appId?: string): Promise {
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 },
diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts
index 91e5c99a6e..df039c2ad1 100644
--- a/server/src/services/auth.service.ts
+++ b/server/src/services/auth.service.ts
@@ -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,
+ });
});
}