diff --git a/frontend/src/Editor/AppVersionsManager/AppVersionsManager.jsx b/frontend/src/Editor/AppVersionsManager/AppVersionsManager.jsx
index 6c71dc6a43..f43445cec5 100644
--- a/frontend/src/Editor/AppVersionsManager/AppVersionsManager.jsx
+++ b/frontend/src/Editor/AppVersionsManager/AppVersionsManager.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useState } from 'react';
import cx from 'classnames';
import { appVersionService } from '@/_services';
import { CustomSelect } from './CustomSelect';
@@ -6,6 +6,7 @@ import { toast } from 'react-hot-toast';
import { shallow } from 'zustand/shallow';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import { useEditorStore } from '@/_stores/editorStore';
+import { useEnvironmentsAndVersionsStore } from '@/_stores/environmentsAndVersionsStore';
const appVersionLoadingStatus = Object.freeze({
loading: 'loading',
@@ -20,18 +21,56 @@ export const AppVersionsManager = function ({
isEditable = true,
isViewer,
}) {
- const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loading);
+ const { initializedEnvironmentDropdown, versionsPromotedToEnvironment, lazyLoadAppVersions, appVersionsLazyLoaded } =
+ useEnvironmentsAndVersionsStore(
+ (state) => ({
+ appVersionsLazyLoaded: state.appVersionsLazyLoaded,
+ initializedEnvironmentDropdown: state.initializedEnvironmentDropdown,
+ versionsPromotedToEnvironment: state.versionsPromotedToEnvironment,
+ lazyLoadAppVersions: state.actions.lazyLoadAppVersions,
+ }),
+ shallow
+ );
+
+ if (initializedEnvironmentDropdown) {
+ return (
+
+ );
+ } else {
+ return <>>;
+ }
+};
+
+const RenderComponent = ({
+ appId,
+ isEditable,
+ isViewer,
+ setAppDefinitionFromVersion,
+ onVersionDelete,
+ versionsPromotedToEnvironment,
+ lazyLoadAppVersions,
+ appVersionsLazyLoaded,
+}) => {
+ const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loaded);
const [deleteVersion, setDeleteVersion] = useState({
versionId: '',
versionName: '',
showModal: false,
});
+ const [forceMenuOpen, setForceMenuOpen] = useState(false);
- const { releasedVersionId, editingVersion, appVersions, setAppVersions } = useAppVersionStore(
+ const { releasedVersionId, editingVersion } = useAppVersionStore(
(state) => ({
editingVersion: state.editingVersion,
- appVersions: state.appVersions,
- setAppVersions: state.actions?.setAppVersions,
releasedVersionId: state.releasedVersionId,
}),
shallow
@@ -43,16 +82,6 @@ export const AppVersionsManager = function ({
shallow
);
- useEffect(() => {
- if (appVersions && appVersions.length > 0) {
- setGetAppVersionStatus(appVersionLoadingStatus.loaded);
- }
-
- return () => {
- setGetAppVersionStatus(appVersionLoadingStatus.loading);
- };
- }, [appVersions]);
-
const darkMode = localStorage.getItem('darkMode') === 'true';
const selectVersion = (id) => {
@@ -93,13 +122,12 @@ export const AppVersionsManager = function ({
})
.finally(() => {
appVersionService.getAll(appId, true).then((data) => {
- setAppVersions(data.versions);
onVersionDelete();
});
});
};
- const options = appVersions.map((appVersion) => ({
+ const options = versionsPromotedToEnvironment.map((appVersion) => ({
value: appVersion.id,
isReleasedVersion: appVersion.id === releasedVersionId,
appVersionName: appVersion.name,
@@ -139,10 +167,17 @@ export const AppVersionsManager = function ({
),
}));
+ const onMenuOpen = async () => {
+ if (!appVersionsLazyLoaded) {
+ setGetAppVersionStatus(appVersionLoadingStatus.loading);
+ await lazyLoadAppVersions(appId);
+ setGetAppVersionStatus(appVersionLoadingStatus.loaded);
+ }
+ setForceMenuOpen(!forceMenuOpen);
+ };
+
const customSelectProps = {
appId,
- appVersions,
- setAppVersions,
setAppDefinitionFromVersion,
editingVersion,
setDeleteVersion,
@@ -175,6 +210,9 @@ export const AppVersionsManager = function ({
{...customSelectProps}
className={` ${darkMode && 'dark-theme'}`}
isEditable={isEditable}
+ onMenuOpen={onMenuOpen}
+ onMenuClose={() => setForceMenuOpen(false)}
+ menuIsOpen={forceMenuOpen}
/>
diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx
index 39a9cadb74..2a21c2af09 100644
--- a/frontend/src/Editor/Editor.jsx
+++ b/frontend/src/Editor/Editor.jsx
@@ -707,8 +707,6 @@ const EditorComponent = (props) => {
useAppVersionStore.getState().actions.updateReleasedVersionId(data.current_version_id);
}
- const appVersions = await appEnvironmentService.getVersionsByEnvironment(data?.id);
- setAppVersions(appVersions.appVersions);
const currentOrgId = data?.organization_id || data?.organizationId;
updateState({
diff --git a/frontend/src/Editor/Header/RightTopHeaderButtons/EnvironmentManager/EnvironmentsManager.jsx b/frontend/src/Editor/Header/RightTopHeaderButtons/EnvironmentManager/EnvironmentsManager.jsx
new file mode 100644
index 0000000000..e8a116e077
--- /dev/null
+++ b/frontend/src/Editor/Header/RightTopHeaderButtons/EnvironmentManager/EnvironmentsManager.jsx
@@ -0,0 +1,27 @@
+import { useEnvironmentsAndVersionsActions } from '@/_stores/environmentsAndVersionsStore';
+import React, { useEffect } from 'react';
+import { shallow } from 'zustand/shallow';
+import { useAppVersionStore } from '@/_stores/appVersionStore';
+
+const EnvironmentManager = () => {
+ const { editingVersionId } = useAppVersionStore(
+ (state) => ({
+ editingVersionId: state?.editingVersion?.id,
+ }),
+ shallow
+ );
+ const { init, setEnvironmentDropdownStatus } = useEnvironmentsAndVersionsActions();
+ useEffect(() => {
+ initComponent();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const initComponent = async () => {
+ await init(editingVersionId);
+ setEnvironmentDropdownStatus(true);
+ };
+
+ return
;
+};
+
+export default EnvironmentManager;
diff --git a/frontend/src/Editor/Header/RightTopHeaderButtons/EnvironmentManager/index.js b/frontend/src/Editor/Header/RightTopHeaderButtons/EnvironmentManager/index.js
new file mode 100644
index 0000000000..34f253383a
--- /dev/null
+++ b/frontend/src/Editor/Header/RightTopHeaderButtons/EnvironmentManager/index.js
@@ -0,0 +1,2 @@
+import EnvironmentManager from './EnvironmentsManager';
+export default EnvironmentManager;
diff --git a/frontend/src/Editor/ManageAppUsers.jsx b/frontend/src/Editor/Header/RightTopHeaderButtons/ManageAppUsers.jsx
similarity index 100%
rename from frontend/src/Editor/ManageAppUsers.jsx
rename to frontend/src/Editor/Header/RightTopHeaderButtons/ManageAppUsers.jsx
diff --git a/frontend/src/Editor/Header/RightTopHeaderButtons/PromoteVersionButton.jsx b/frontend/src/Editor/Header/RightTopHeaderButtons/PromoteVersionButton.jsx
new file mode 100644
index 0000000000..57201589d4
--- /dev/null
+++ b/frontend/src/Editor/Header/RightTopHeaderButtons/PromoteVersionButton.jsx
@@ -0,0 +1,7 @@
+import React from 'react';
+
+const PromoteVersionButton = () => {
+ return <>>;
+};
+
+export default PromoteVersionButton;
diff --git a/frontend/src/Editor/ReleaseVersionButton.jsx b/frontend/src/Editor/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx
similarity index 88%
rename from frontend/src/Editor/ReleaseVersionButton.jsx
rename to frontend/src/Editor/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx
index 04973bbd03..3c72c32a89 100644
--- a/frontend/src/Editor/ReleaseVersionButton.jsx
+++ b/frontend/src/Editor/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx
@@ -8,7 +8,7 @@ import { ConfirmDialog } from '@/_components/ConfirmDialog';
import { shallow } from 'zustand/shallow';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
-export const ReleaseVersionButton = function DeployVersionButton({ appId, appName, fetchApp, onVersionRelease }) {
+export const ReleaseVersionButton = function DeployVersionButton({ onVersionRelease }) {
const [isReleasing, setIsReleasing] = useState(false);
const { isVersionReleased, editingVersion } = useAppVersionStore(
(state) => ({
@@ -24,17 +24,15 @@ export const ReleaseVersionButton = function DeployVersionButton({ appId, appNam
setShowPageDeletionConfirmation(false);
setIsReleasing(true);
+ const { id: versionToBeReleased, name, app_id } = editingVersion;
+
appsService
- .saveApp(appId, {
- name: appName,
- current_version_id: editingVersion.id,
- })
+ .releaseVersion(app_id, versionToBeReleased)
.then(() => {
- toast(`Version ${editingVersion.name} released`, {
+ toast(`Version ${name} released`, {
icon: '🚀',
});
- fetchApp && fetchApp();
- onVersionRelease(editingVersion.id);
+ onVersionRelease(versionToBeReleased);
setIsReleasing(false);
})
.catch((_error) => {
diff --git a/frontend/src/Editor/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx b/frontend/src/Editor/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx
new file mode 100644
index 0000000000..95c399f194
--- /dev/null
+++ b/frontend/src/Editor/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx
@@ -0,0 +1,101 @@
+import React, { useEffect } from 'react';
+import { ReleaseVersionButton } from './ReleaseVersionButton';
+import { Link } from 'react-router-dom';
+import { useAppInfo, useAppDataActions } from '@/_stores/appDataStore';
+import { ManageAppUsers } from './ManageAppUsers';
+import { useAppVersionStore } from '@/_stores/appVersionStore';
+import { shallow } from 'zustand/shallow';
+import queryString from 'query-string';
+import { isEmpty } from 'lodash';
+import { useCurrentStateStore } from '@/_stores/currentStateStore';
+import SolidIcon from '@/_ui/Icon/SolidIcons';
+import { useEnvironmentsAndVersionsStore } from '@/_stores/environmentsAndVersionsStore';
+import PromoteVersionButton from './PromoteVersionButton';
+
+const RightTopHeaderButtons = ({ onVersionRelease }) => {
+ return (
+
+ );
+};
+
+const PreviewAndShareIcons = () => {
+ const { appId, app, slug, isPublic, appVersionPreviewLink, currentVersionId } = useAppInfo();
+ const { setAppPreviewLink } = useAppDataActions();
+ const { isVersionReleased, editingVersion } = useAppVersionStore(
+ (state) => ({
+ isVersionReleased: state.isVersionReleased,
+ editingVersion: state.editingVersion,
+ }),
+ shallow
+ );
+ const { pageHandle } = useCurrentStateStore(
+ (state) => ({
+ pageHandle: state?.page?.handle,
+ }),
+ shallow
+ );
+ const darkMode = localStorage.getItem('darkMode') === 'true';
+
+ useEffect(() => {
+ const previewQuery = queryString.stringify({ version: editingVersion.name });
+ const appVersionPreviewLink = editingVersion.id
+ ? `/applications/${slug || appId}/${pageHandle}${!isEmpty(previewQuery) ? `?${previewQuery}` : ''}`
+ : '';
+ setAppPreviewLink(appVersionPreviewLink);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [slug, currentVersionId, editingVersion]);
+
+ return (
+
+
+ {appId && (
+
+ )}
+
+
+
+
+
+
+
+ );
+};
+
+const PromoteAndReleaseButton = ({ onVersionRelease }) => {
+ const { shouldRenderPromoteButton, shouldRenderReleaseButton } = useEnvironmentsAndVersionsStore(
+ (state) => ({
+ shouldRenderPromoteButton: state.shouldRenderPromoteButton,
+ shouldRenderReleaseButton: state.shouldRenderReleaseButton,
+ }),
+ shallow
+ );
+
+ return (
+
+ {shouldRenderPromoteButton &&
}
+ {shouldRenderReleaseButton &&
}
+
+ );
+};
+
+export default RightTopHeaderButtons;
diff --git a/frontend/src/Editor/Header/RightTopHeaderButtons/index.js b/frontend/src/Editor/Header/RightTopHeaderButtons/index.js
new file mode 100644
index 0000000000..5d3d19228f
--- /dev/null
+++ b/frontend/src/Editor/Header/RightTopHeaderButtons/index.js
@@ -0,0 +1,2 @@
+import RightTopHeaderButtons from './RightTopHeaderButtons';
+export default RightTopHeaderButtons;
diff --git a/frontend/src/Editor/Header/index.js b/frontend/src/Editor/Header/index.js
index 1bc6eda0bb..51696634b4 100644
--- a/frontend/src/Editor/Header/index.js
+++ b/frontend/src/Editor/Header/index.js
@@ -1,12 +1,8 @@
import React, { useEffect } from 'react';
-import { Link } from 'react-router-dom';
-import AppLogo from '@/_components/AppLogo';
import EditAppName from './EditAppName';
import HeaderActions from './HeaderActions';
import RealtimeAvatars from '../RealtimeAvatars';
import { AppVersionsManager } from '@/Editor/AppVersionsManager/AppVersionsManager';
-import { ManageAppUsers } from '../ManageAppUsers';
-import { ReleaseVersionButton } from '../ReleaseVersionButton';
import cx from 'classnames';
import config from 'config';
// eslint-disable-next-line import/no-unresolved
@@ -16,10 +12,11 @@ import { useCurrentStateStore } from '@/_stores/currentStateStore';
import { shallow } from 'zustand/shallow';
import { useAppDataActions, useAppInfo, useCurrentUser } from '@/_stores/appDataStore';
import SolidIcon from '@/_ui/Icon/SolidIcons';
-import { redirectToDashboard } from '@/_helpers/routes';
import queryString from 'query-string';
import { isEmpty } from 'lodash';
import LogoNavDropdown from '@/_components/LogoNavDropdown';
+import RightTopHeaderButtons from './RightTopHeaderButtons';
+import EnvironmentManager from './RightTopHeaderButtons/EnvironmentManager';
export default function EditorHeader({
M,
@@ -31,15 +28,13 @@ export default function EditorHeader({
onNameChanged,
setAppDefinitionFromVersion,
onVersionRelease,
- saveEditingVersion,
onVersionDelete,
slug,
darkMode,
- isSocketOpen,
}) {
const currentUser = useCurrentUser();
- const { isSaving, appId, appName, app, isPublic, appVersionPreviewLink, currentVersionId } = useAppInfo();
+ const { isSaving, appId, appName, isPublic, currentVersionId } = useAppInfo();
const { setAppPreviewLink } = useAppDataActions();
const { isVersionReleased, editingVersion } = useAppVersionStore(
(state) => ({
@@ -148,6 +143,8 @@ export default function EditorHeader({
+
+
{editingVersion && (
)}
-
-
-
-
- {appId && (
-
- )}
-
-
-
-
-
-
-
-
- {isSocketOpen && (
-
-
-
- )}
-
-
+
diff --git a/frontend/src/_services/app_environment.service.js b/frontend/src/_services/app_environment.service.js
index 8e237c2db1..411ef368ca 100644
--- a/frontend/src/_services/app_environment.service.js
+++ b/frontend/src/_services/app_environment.service.js
@@ -5,6 +5,7 @@ import queryString from 'query-string';
export const appEnvironmentService = {
getAllEnvironments,
getVersionsByEnvironment,
+ init,
};
function getAllEnvironments() {
@@ -29,3 +30,9 @@ function getVersionsByEnvironment(appId, environmentId /* not needed for CE */)
requestOptions
).then(handleResponse);
}
+
+function init(editing_version_id = null) {
+ const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
+ const query = queryString.stringify({ editing_version_id });
+ return fetch(`${config.apiUrl}/app-environments/init?${query}`, requestOptions).then(handleResponse);
+}
diff --git a/frontend/src/_services/apps.service.js b/frontend/src/_services/apps.service.js
index 6af8a7c70c..f1f816cfd3 100644
--- a/frontend/src/_services/apps.service.js
+++ b/frontend/src/_services/apps.service.js
@@ -25,6 +25,7 @@ export const appsService = {
getAppUsers,
getVersions,
getTables,
+ releaseVersion,
};
function validateReleasedApp(slug) {
@@ -204,3 +205,14 @@ function getTables(id) {
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
return fetch(`${config.apiUrl}/apps/${id}/tables`, requestOptions).then(handleResponse);
}
+
+function releaseVersion(appId, versionToBeReleased) {
+ const requestOptions = {
+ method: 'PUT',
+ headers: authHeader(),
+ body: JSON.stringify({ versionToBeReleased }),
+ credentials: 'include',
+ };
+
+ return fetch(`${config.apiUrl}/v2/apps/${appId}/release`, requestOptions).then(handleResponse);
+}
diff --git a/frontend/src/_stores/environmentsAndVersionsStore.js b/frontend/src/_stores/environmentsAndVersionsStore.js
new file mode 100644
index 0000000000..80c9cba59c
--- /dev/null
+++ b/frontend/src/_stores/environmentsAndVersionsStore.js
@@ -0,0 +1,60 @@
+import create from 'zustand';
+import { zustandDevTools } from './utils';
+import { appEnvironmentService } from '../_services/app_environment.service';
+
+const initialState = {
+ selectedVersion: null,
+ selectedEnvironment: null,
+ appVersionEnvironment: null,
+ versionsPromotedToEnvironment: [],
+ environments: [],
+ shouldRenderPromoteButton: false,
+ shouldRenderReleaseButton: false,
+ initializedEnvironmentDropdown: false,
+ initializedVersionsDropdown: false,
+ environmentsLazyLoaded: false,
+ appVersionsLazyLoaded: false,
+};
+
+export const useEnvironmentsAndVersionsStore = create(
+ zustandDevTools(
+ (set, get) => ({
+ ...initialState,
+ actions: {
+ init: async (editingVersionId) => {
+ try {
+ const response = await appEnvironmentService.init(editingVersionId);
+ set((state) => ({
+ ...state,
+ selectedEnvironment: response.editorEnvironment,
+ selectedVersion: response.editorVersion,
+ appVersionEnvironment: response.appVersionEnvironment,
+ shouldRenderPromoteButton: response.shouldRenderPromoteButton,
+ shouldRenderReleaseButton: response.shouldRenderReleaseButton,
+ environments: [response.editorEnvironment],
+ versionsPromotedToEnvironment: [response.editorVersion],
+ }));
+ } catch (error) {
+ console.error('Error while initializing the environment dropdown', error);
+ }
+ },
+ setEnvironmentDropdownStatus: (state) => set({ initializedEnvironmentDropdown: state }),
+ lazyLoadAppVersions: async (appId) => {
+ try {
+ const response = await appEnvironmentService.getVersionsByEnvironment(appId, get().selectedEnvironment.id);
+ set((state) => ({
+ ...state,
+ versionsPromotedToEnvironment: response.appVersions,
+ appVersionsLazyLoaded: true,
+ }));
+ } catch (error) {
+ console.error('Error while getting the versions', error);
+ }
+ },
+ },
+ }),
+ { name: 'App Version Manager Store' }
+ )
+);
+
+export const useEnvironmentsAndVersionsActions = () => useEnvironmentsAndVersionsStore((state) => state.actions);
diff --git a/server/src/controllers/app_environments.controller.ts b/server/src/controllers/app_environments.controller.ts
index 1b7e411904..50f8dbc674 100644
--- a/server/src/controllers/app_environments.controller.ts
+++ b/server/src/controllers/app_environments.controller.ts
@@ -1,4 +1,4 @@
-import { Controller, Get, Query, UseGuards } from '@nestjs/common';
+import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
import { decamelizeKeys } from 'humps';
import { JwtAuthGuard } from '../modules/auth/jwt-auth.guard';
import { ForbiddenException } from '@nestjs/common';
@@ -17,6 +17,16 @@ export class AppEnvironmentsController {
private orgEnvironmentVariablesAbilityFactory: OrgEnvironmentVariablesAbilityFactory
) {}
+ @UseGuards(JwtAuthGuard)
+ @Get('init')
+ async init(@User() user, @Query('editing_version_id') editingVersionId: string) {
+ /*
+ 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);
+ }
+
@UseGuards(JwtAuthGuard)
@Get()
async index(@User() user, @Query('app_id') appId: string) {
@@ -39,9 +49,13 @@ export class AppEnvironmentsController {
}
@UseGuards(JwtAuthGuard)
- @Get('versions')
- async getVersions(@User() user, @Query('app_id') appId: string) {
- const appVersions = await this.appEnvironmentServices.getVersionsByEnvironment(user?.organizationId, appId);
+ @Get(':id/versions')
+ async getVersionsByEnvironment(@User() user, @Param('id') environmentId: string, @Query('app_id') appId: string) {
+ const appVersions = await this.appEnvironmentServices.getVersionsByEnvironment(
+ user?.organizationId,
+ appId,
+ environmentId
+ );
return { appVersions };
}
}
diff --git a/server/src/controllers/apps.controller.v2.ts b/server/src/controllers/apps.controller.v2.ts
index 45944e7210..3acf8768c0 100644
--- a/server/src/controllers/apps.controller.v2.ts
+++ b/server/src/controllers/apps.controller.v2.ts
@@ -32,6 +32,7 @@ import { PageService } from '@services/page.service';
import { EventsService } from '@services/events_handler.service';
import { AppVersionUpdateDto } from '@dto/app-version-update.dto';
import { CreateEventHandlerDto, UpdateEventHandlerDto } from '@dto/event-handler.dto';
+import { VersionReleaseDto } from '@dto/version-release.dto';
@Controller({
path: 'apps',
@@ -499,4 +500,20 @@ export class AppsControllerV2 {
return await this.eventService.deleteEvent(eventId, versionId);
}
+
+ @UseGuards(JwtAuthGuard)
+ @UseInterceptors(ValidAppInterceptor)
+ @Put(':id/release')
+ async releaseVersion(
+ @User() user,
+ @Param('id') id,
+ @AppDecorator() app: App,
+ @Body() versionReleaseDto: VersionReleaseDto
+ ) {
+ const ability = await this.appsAbilityFactory.appsActions(user, app.id);
+ if (!ability.can('updateParams', app)) {
+ throw new ForbiddenException('You do not have permissions to perform this action');
+ }
+ return await this.appsService.releaseVersion(app.id, versionReleaseDto);
+ }
}
diff --git a/server/src/dto/version-release.dto.ts b/server/src/dto/version-release.dto.ts
new file mode 100644
index 0000000000..f191290a14
--- /dev/null
+++ b/server/src/dto/version-release.dto.ts
@@ -0,0 +1,10 @@
+import { IsNotEmpty, IsUUID } from 'class-validator';
+import { Transform } from 'class-transformer';
+import { sanitizeInput } from 'src/helpers/utils.helper';
+
+export class VersionReleaseDto {
+ @IsNotEmpty()
+ @IsUUID()
+ @Transform(({ value }) => sanitizeInput(value))
+ versionToBeReleased: string;
+}
diff --git a/server/src/services/app_environments.service.ts b/server/src/services/app_environments.service.ts
index 461f486e1c..635dddc7d2 100644
--- a/server/src/services/app_environments.service.ts
+++ b/server/src/services/app_environments.service.ts
@@ -7,8 +7,43 @@ import { OrganizationConstant } from 'src/entities/organization_constants.entity
import { EntityManager, FindOneOptions, In, DeleteResult } from 'typeorm';
import { AppVersion } from 'src/entities/app_version.entity';
+export interface AppEnvironmentResponse {
+ editorVersion: Partial;
+ editorEnvironment: AppEnvironment;
+ appVersionEnvironment: AppEnvironment;
+ shouldRenderPromoteButton: boolean;
+ shouldRenderReleaseButton: boolean;
+}
+
@Injectable()
export class AppEnvironmentService {
+ async init(editingVersionId: string, manager?: EntityManager): Promise {
+ return await dbTransactionWrap(async (manager: EntityManager) => {
+ const editorVersion = await manager.findOne(AppVersion, {
+ select: ['id', 'name', 'currentEnvironmentId'],
+ where: { id: editingVersionId },
+ });
+ const editorEnvironment = await manager.findOne(AppEnvironment, { id: editorVersion.currentEnvironmentId });
+ const { shouldRenderPromoteButton, shouldRenderReleaseButton } =
+ this.calculateButtonVisibility(editorEnvironment);
+ const response: AppEnvironmentResponse = {
+ editorVersion,
+ editorEnvironment,
+ appVersionEnvironment: editorEnvironment,
+ shouldRenderPromoteButton,
+ shouldRenderReleaseButton,
+ };
+ return response;
+ }, manager);
+ }
+
+ calculateButtonVisibility(appVersionEnvironment: AppEnvironment) {
+ /* Further conditions can handle from here */
+ const shouldRenderPromoteButton = false;
+ const shouldRenderReleaseButton = true;
+ return { shouldRenderPromoteButton, shouldRenderReleaseButton };
+ }
+
async get(
organizationId: string,
id?: string,
diff --git a/server/src/services/apps.service.ts b/server/src/services/apps.service.ts
index e98474dd4f..fa28f609bd 100644
--- a/server/src/services/apps.service.ts
+++ b/server/src/services/apps.service.ts
@@ -28,6 +28,7 @@ import { Layout } from 'src/entities/layout.entity';
import { Component } from 'src/entities/component.entity';
import { EventHandler } from 'src/entities/event_handler.entity';
+import { VersionReleaseDto } from '@dto/version-release.dto';
const uuid = require('uuid');
@Injectable()
@@ -1044,4 +1045,25 @@ export class AppsService {
});
});
}
+
+ async releaseVersion(appId: string, versionReleaseDto: VersionReleaseDto, manager?: EntityManager) {
+ return await dbTransactionWrap(async (manager: EntityManager) => {
+ const { versionToBeReleased } = versionReleaseDto;
+ //check if the app version is eligible for release
+ const currentEnvironment: AppEnvironment = await manager
+ .createQueryBuilder(AppEnvironment, 'app_environments')
+ .select(['app_environments.id', 'app_environments.isDefault'])
+ .innerJoinAndSelect('app_versions', 'app_versions', 'app_versions.current_environment_id = app_environments.id')
+ .where('app_versions.id = :versionToBeReleased', {
+ versionToBeReleased,
+ })
+ .getOne();
+
+ if (!currentEnvironment?.isDefault) {
+ throw new BadRequestException('You can only release when the version is promoted to production');
+ }
+
+ return await manager.update(App, appId, { currentVersionId: versionToBeReleased });
+ }, manager);
+ }
}