Merge pull request #34124 from appsmithorg/release

10/06 Daily Promotion
This commit is contained in:
yatinappsmith 2024-06-10 13:23:24 +05:30 committed by GitHub
commit f0b3147d9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
79 changed files with 2298 additions and 1088 deletions

View File

@ -30,10 +30,10 @@ COPY ./app/client/build editor/
# Add RTS - Application Layer # Add RTS - Application Layer
COPY ./app/client/packages/rts/dist rts/ COPY ./app/client/packages/rts/dist rts/
ENV PATH /opt/appsmith/utils/node_modules/.bin:/opt/java/bin:/opt/node/bin:$PATH ENV PATH /opt/bin:/opt/appsmith/utils/node_modules/.bin:/opt/java/bin:/opt/node/bin:$PATH
RUN cd ./utils && npm install --only=prod && npm install --only=prod -g . && cd - \ RUN cd ./utils && npm install --only=prod && npm install --only=prod -g . && cd - \
&& chmod +x *.sh /watchtower-hooks/*.sh \ && chmod +x /opt/bin/* *.sh /watchtower-hooks/*.sh \
# Disable setuid/setgid bits for the files inside container. # Disable setuid/setgid bits for the files inside container.
&& find / \( -path /proc -prune \) -o \( \( -perm -2000 -o -perm -4000 \) -print -exec chmod -s '{}' + \) || true \ && find / \( -path /proc -prune \) -o \( \( -perm -2000 -o -perm -4000 \) -print -exec chmod -s '{}' + \) || true \
&& mkdir -p /.mongodb/mongosh /appsmith-stacks \ && mkdir -p /.mongodb/mongosh /appsmith-stacks \

View File

@ -1,31 +0,0 @@
import { WIDGET } from "../../../../locators/WidgetLocators";
import { ObjectsRegistry } from "../../../../support/Objects/Registry";
import EditorNavigation, {
EntityType,
} from "../../../../support/Pages/EditorNavigation";
import PageList from "../../../../support/Pages/PageList";
const { CommonLocators: locators, EntityExplorer: ee } = ObjectsRegistry;
describe("Empty canvas ctas", () => {
it("1. Ctas validations", () => {
cy.wait(3000); // for page to load, failing in CI
//Ctas should not be shown in the second page
cy.get(locators._emptyCanvasCta).should("be.visible");
PageList.AddNewPage();
cy.get(locators._emptyCanvasCta).should("not.exist");
EditorNavigation.SelectEntityByName("Page1", EntityType.Page);
//Ctas should continue to show on refresh
cy.get(locators._emptyCanvasCta).should("be.visible");
cy.reload();
cy.get(locators._emptyCanvasCta).should("be.visible");
//Hide cta on adding a widget
cy.get(locators._emptyCanvasCta).should("be.visible");
ee.DragDropWidgetNVerify(WIDGET.BUTTON, 200, 200);
cy.get(locators._emptyCanvasCta).should("not.exist");
PageList.AddNewPage();
cy.get(locators._emptyCanvasCta).should("not.exist");
});
});

View File

@ -1,5 +1,6 @@
import homePage from "../../../../locators/HomePage"; import homePage from "../../../../locators/HomePage";
import * as _ from "../../../../support/Objects/ObjectsCore"; import * as _ from "../../../../support/Objects/ObjectsCore";
import PageList from "../../../../support/Pages/PageList";
describe("Visual regression tests", { tags: ["@tag.Visual"] }, () => { describe("Visual regression tests", { tags: ["@tag.Visual"] }, () => {
// for any changes in UI, update the screenshot in snapshot folder, to do so: // for any changes in UI, update the screenshot in snapshot folder, to do so:
@ -18,7 +19,7 @@ describe("Visual regression tests", { tags: ["@tag.Visual"] }, () => {
cy.get("#root").matchImageSnapshot("apppage"); cy.get("#root").matchImageSnapshot("apppage");
//Layout validation for Quick page wizard //Layout validation for Quick page wizard
cy.get("[data-testid='generate-app']").click(); PageList.AddNewPage(Cypress.env("MESSAGES").GENERATE_PAGE_ACTION_TITLE());
cy.wait(2000); cy.wait(2000);
// taking screenshot of generate crud page // taking screenshot of generate crud page
cy.get("#root").matchImageSnapshot("quickPageWizard"); cy.get("#root").matchImageSnapshot("quickPageWizard");

View File

@ -1,5 +1,4 @@
{ {
"generateCRUDPageActionCard": "[data-testid='generate-app']",
"selectDatasourceDropdown": "[data-testid=t--datasource-dropdown]", "selectDatasourceDropdown": "[data-testid=t--datasource-dropdown]",
"datasourceDropdownOption": "[data-testid=t--datasource-dropdown-option]", "datasourceDropdownOption": "[data-testid=t--datasource-dropdown-option]",
"selectTableDropdown": "[data-testid=t--table-dropdown]", "selectTableDropdown": "[data-testid=t--table-dropdown]",

View File

@ -68,7 +68,6 @@ export class HomePage {
_applicationName = ".t--application-name"; _applicationName = ".t--application-name";
private _editAppName = "bp3-editable-text-editing"; private _editAppName = "bp3-editable-text-editing";
private _appMenu = ".ads-v2-menu__menu-item-children"; private _appMenu = ".ads-v2-menu__menu-item-children";
_buildFromDataTableActionCard = "[data-testid='generate-app']";
private _selectRole = "//span[text()='Select a role']/ancestor::div"; private _selectRole = "//span[text()='Select a role']/ancestor::div";
private _searchInput = "input[type='text']"; private _searchInput = "input[type='text']";
_appHoverIcon = (action: string) => ".t--application-" + action + "-link"; _appHoverIcon = (action: string) => ".t--application-" + action + "-link";

View File

@ -37,9 +37,7 @@ export class Table {
private assertHelper = ObjectsRegistry.AssertHelper; private assertHelper = ObjectsRegistry.AssertHelper;
private _tableWrap = "//div[contains(@class,'tableWrap')]"; private _tableWrap = "//div[contains(@class,'tableWrap')]";
private _tableHeader = private _tableHeader = ".thead div[role=columnheader]";
this._tableWrap +
"//div[contains(@class,'thead')]//div[contains(@class,'tr')][1]";
private _columnHeader = (columnName: string) => private _columnHeader = (columnName: string) =>
this._tableWrap + this._tableWrap +
"//div[contains(@class,'thead')]//div[contains(@class,'tr')][1]//div[@role='columnheader']//div[contains(text(),'" + "//div[contains(@class,'thead')]//div[contains(@class,'tr')][1]//div[@role='columnheader']//div[contains(text(),'" +
@ -258,7 +256,7 @@ export class Table {
} }
public AssertTableHeaderOrder(expectedOrder: string) { public AssertTableHeaderOrder(expectedOrder: string) {
cy.xpath(this._tableHeader) cy.get(this._tableHeader)
.invoke("text") .invoke("text")
.then((x) => { .then((x) => {
expect(x).to.eq(expectedOrder); expect(x).to.eq(expectedOrder);

View File

@ -18,7 +18,6 @@ export function Link(props: LinkProps) {
} = props; } = props;
return ( return (
<Text color="accent" {...rest}>
<HeadlessLink <HeadlessLink
className={styles.link} className={styles.link}
download={download} download={download}
@ -28,8 +27,9 @@ export function Link(props: LinkProps) {
rel={rel} rel={rel}
target={target} target={target}
> >
<Text color="accent" {...rest}>
{children} {children}
</HeadlessLink>
</Text> </Text>
</HeadlessLink>
); );
} }

View File

@ -1,11 +1,19 @@
.link { .link {
position: relative;
text-decoration: underline; text-decoration: underline;
text-decoration-color: currentColor;
text-underline-offset: 2px; text-underline-offset: 2px;
color: currentColor; color: currentColor;
text-decoration-color: var(--color-bd-accent);
text-decoration-thickness: 0.5px;
&:hover { &[data-hovered] {
color: currentColor; text-decoration: none;
text-decoration-color: currentColor; }
&[data-focus-visible] {
border-radius: var(--border-radius-elevation-3);
outline: 2px solid var(--color-bd-focus);
outline-offset: 4px;
text-decoration: none;
} }
} }

View File

@ -14,6 +14,9 @@ import { matchBuilderPath, matchViewerPath } from "constants/routes";
const GENERATOR_TRACE = "generator-tracer"; const GENERATOR_TRACE = "generator-tracer";
export type OtlpSpan = Span;
export type SpanAttributes = Attributes;
const getCommonTelemetryAttributes = () => { const getCommonTelemetryAttributes = () => {
const pathname = window.location.pathname; const pathname = window.location.pathname;
const isEditorUrl = matchBuilderPath(pathname); const isEditorUrl = matchBuilderPath(pathname);
@ -33,16 +36,13 @@ const getCommonTelemetryAttributes = () => {
export function startRootSpan( export function startRootSpan(
spanName: string, spanName: string,
spanAttributes: Attributes = {}, spanAttributes: SpanAttributes = {},
startTime?: TimeInput, startTime?: TimeInput,
) { ) {
const tracer = trace.getTracer(GENERATOR_TRACE); const tracer = trace.getTracer(GENERATOR_TRACE);
if (!spanName) {
return;
}
const commonAttributes = getCommonTelemetryAttributes(); const commonAttributes = getCommonTelemetryAttributes();
return tracer?.startSpan(spanName, { return tracer.startSpan(spanName, {
kind: SpanKind.CLIENT, kind: SpanKind.CLIENT,
attributes: { attributes: {
...commonAttributes, ...commonAttributes,
@ -56,15 +56,10 @@ export const generateContext = (span: Span) => {
}; };
export function startNestedSpan( export function startNestedSpan(
spanName: string, spanName: string,
parentSpan?: Span, parentSpan: Span,
spanAttributes: Attributes = {}, spanAttributes: SpanAttributes = {},
startTime?: TimeInput, startTime?: TimeInput,
) { ) {
if (!spanName || !parentSpan) {
// do not generate nested span without parentSpan..we cannot generate context out of it
return;
}
const parentContext = generateContext(parentSpan); const parentContext = generateContext(parentSpan);
const generatorTrace = trace.getTracer(GENERATOR_TRACE); const generatorTrace = trace.getTracer(GENERATOR_TRACE);
@ -81,14 +76,14 @@ export function startNestedSpan(
return generatorTrace.startSpan(spanName, spanOptions, parentContext); return generatorTrace.startSpan(spanName, spanOptions, parentContext);
} }
export function endSpan(span?: Span) { export function endSpan(span: Span) {
span?.end(); span.end();
} }
export function setAttributesToSpan(span: Span, spanAttributes: Attributes) { export function setAttributesToSpan(
if (!span) { span: Span,
return; spanAttributes: SpanAttributes,
} ) {
span.setAttributes(spanAttributes); span.setAttributes(spanAttributes);
} }
@ -96,5 +91,3 @@ export function wrapFnWithParentTraceContext(parentSpan: Span, fn: () => any) {
const parentContext = trace.setSpan(context.active(), parentSpan); const parentContext = trace.setSpan(context.active(), parentSpan);
return context.with(parentContext, fn); return context.with(parentContext, fn);
} }
export type OtlpSpan = Span;

View File

@ -1,8 +1,9 @@
import type { OtlpSpan, SpanAttributes } from "./generateTraces";
import { startNestedSpan } from "./generateTraces"; import { startNestedSpan } from "./generateTraces";
import type { TimeInput, Attributes, Span } from "@opentelemetry/api"; import type { TimeInput } from "@opentelemetry/api";
export interface WebworkerSpanData { export interface WebworkerSpanData {
attributes: Attributes; attributes: SpanAttributes;
spanName: string; spanName: string;
startTime: TimeInput; startTime: TimeInput;
endTime: TimeInput; endTime: TimeInput;
@ -13,7 +14,7 @@ export interface WebworkerSpanData {
//to regular otlp telemetry data and subsequently exported to our telemetry collector //to regular otlp telemetry data and subsequently exported to our telemetry collector
export const newWebWorkerSpanData = ( export const newWebWorkerSpanData = (
spanName: string, spanName: string,
attributes: Attributes, attributes: SpanAttributes,
): WebworkerSpanData => { ): WebworkerSpanData => {
return { return {
attributes, attributes,
@ -29,8 +30,8 @@ const addEndTimeForWebWorkerSpanData = (span: WebworkerSpanData) => {
export const profileFn = ( export const profileFn = (
spanName: string, spanName: string,
attributes: Attributes = {}, attributes: SpanAttributes = {},
allSpans: Record<string, WebworkerSpanData>, allSpans: Record<string, WebworkerSpanData | SpanAttributes>,
fn: (...args: any[]) => any, fn: (...args: any[]) => any,
) => { ) => {
const span = newWebWorkerSpanData(spanName, attributes); const span = newWebWorkerSpanData(spanName, attributes);
@ -42,7 +43,7 @@ export const profileFn = (
//convert webworker spans to OTLP spans //convert webworker spans to OTLP spans
export const convertWebworkerSpansToRegularSpans = ( export const convertWebworkerSpansToRegularSpans = (
parentSpan: Span, parentSpan: OtlpSpan,
allSpans: Record<string, WebworkerSpanData> = {}, allSpans: Record<string, WebworkerSpanData> = {},
) => { ) => {
Object.values(allSpans) Object.values(allSpans)
@ -53,3 +54,14 @@ export const convertWebworkerSpansToRegularSpans = (
span?.end(endTime); span?.end(endTime);
}); });
}; };
export const filterSpanData = (
spanData: Record<string, WebworkerSpanData | SpanAttributes>,
): Record<string, WebworkerSpanData> => {
return Object.keys(spanData)
.filter((key) => !key.startsWith("__"))
.reduce<Record<string, WebworkerSpanData>>((obj, key) => {
obj[key] = spanData[key] as WebworkerSpanData;
return obj;
}, {});
};

View File

@ -6,7 +6,10 @@ import {
ReduxActionErrorTypes, ReduxActionErrorTypes,
ReduxActionTypes, ReduxActionTypes,
} from "@appsmith/constants/ReduxActionConstants"; } from "@appsmith/constants/ReduxActionConstants";
import type { ConnectToGitPayload } from "api/GitSyncAPI"; import type {
ConnectToGitPayload,
GitAutocommitProgressResponse,
} from "api/GitSyncAPI";
import type { GitConfig, GitSyncModalTab, MergeStatus } from "entities/GitSync"; import type { GitConfig, GitSyncModalTab, MergeStatus } from "entities/GitSync";
import type { GitApplicationMetadata } from "@appsmith/api/ApplicationApi"; import type { GitApplicationMetadata } from "@appsmith/api/ApplicationApi";
import { import {
@ -480,6 +483,7 @@ export const setShowBranchPopupAction = (show: boolean) => {
}; };
}; };
// START autocommit
export const toggleAutocommitEnabledInit = () => ({ export const toggleAutocommitEnabledInit = () => ({
type: ReduxActionTypes.GIT_TOGGLE_AUTOCOMMIT_ENABLED_INIT, type: ReduxActionTypes.GIT_TOGGLE_AUTOCOMMIT_ENABLED_INIT,
}); });
@ -489,14 +493,60 @@ export const setIsAutocommitModalOpen = (isAutocommitModalOpen: boolean) => ({
payload: { isAutocommitModalOpen }, payload: { isAutocommitModalOpen },
}); });
export const startAutocommitProgressPolling = () => ({ export const triggerAutocommitInitAction = () => ({
type: ReduxActionTypes.GIT_AUTOCOMMIT_INITIATE_PROGRESS_POLLING, type: ReduxActionTypes.GIT_AUTOCOMMIT_TRIGGER_INIT,
}); });
export const stopAutocommitProgressPolling = () => ({ export const triggerAutocommitSuccessAction = () => ({
type: ReduxActionTypes.GIT_AUTOCOMMIT_TRIGGER_SUCCESS,
});
export interface TriggerAutocommitErrorActionPayload {
error: any;
show: boolean;
}
export const triggerAutocommitErrorAction = (
payload: TriggerAutocommitErrorActionPayload,
) => ({
type: ReduxActionErrorTypes.GIT_AUTOCOMMIT_TRIGGER_ERROR,
payload,
});
export const startAutocommitProgressPollingAction = () => ({
type: ReduxActionTypes.GIT_AUTOCOMMIT_START_PROGRESS_POLLING,
});
export const stopAutocommitProgressPollingAction = () => ({
type: ReduxActionTypes.GIT_AUTOCOMMIT_STOP_PROGRESS_POLLING, type: ReduxActionTypes.GIT_AUTOCOMMIT_STOP_PROGRESS_POLLING,
}); });
export type SetAutocommitActionPayload = GitAutocommitProgressResponse;
export const setAutocommitProgressAction = (
payload: SetAutocommitActionPayload,
) => ({
type: ReduxActionTypes.GIT_SET_AUTOCOMMIT_PROGRESS,
payload,
});
export const resetAutocommitProgressAction = () => ({
type: ReduxActionTypes.GIT_RESET_AUTOCOMMIT_PROGRESS,
});
export interface AutocommitProgressErrorActionPayload {
error: any;
show: boolean;
}
export const autoCommitProgressErrorAction = (
payload: AutocommitProgressErrorActionPayload,
) => ({
type: ReduxActionErrorTypes.GIT_AUTOCOMMIT_PROGRESS_POLLING_ERROR,
payload,
});
// END autocommit
export const getGitMetadataInitAction = () => ({ export const getGitMetadataInitAction = () => ({
type: ReduxActionTypes.GIT_GET_METADATA_INIT, type: ReduxActionTypes.GIT_GET_METADATA_INIT,
}); });

View File

@ -43,6 +43,27 @@ interface GitRemoteStatusParam {
branch: string; branch: string;
} }
export enum AutocommitResponseEnum {
IN_PROGRESS = "IN_PROGRESS",
LOCKED = "LOCKED",
PUBLISHED = "PUBLISHED",
IDLE = "IDLE",
NOT_REQUIRED = "NOT_REQUIRED",
NON_GIT_APP = "NON_GIT_APP",
}
export interface GitAutocommitProgressResponse {
autoCommitResponse: AutocommitResponseEnum;
progress: number;
branchName: string;
}
export interface GitTriggerAutocommitResponse {
autoCommitResponse: AutocommitResponseEnum;
progress: number;
branchName: string;
}
class GitSyncAPI extends Api { class GitSyncAPI extends Api {
static baseURL = `/v1/git`; static baseURL = `/v1/git`;
@ -227,6 +248,12 @@ class GitSyncAPI extends Api {
); );
} }
static async triggerAutocommit(applicationId: string, branchName: string) {
return Api.post(
`${GitSyncAPI.baseURL}/auto-commit/app/${applicationId}?branchName=${branchName}`,
);
}
static async getAutocommitProgress(applicationId: string) { static async getAutocommitProgress(applicationId: string) {
return Api.get( return Api.get(
`${GitSyncAPI.baseURL}/auto-commit/progress/app/${applicationId}`, `${GitSyncAPI.baseURL}/auto-commit/progress/app/${applicationId}`,

View File

@ -131,8 +131,8 @@ const ActionTypes = {
GIT_TOGGLE_AUTOCOMMIT_ENABLED_INIT: "GIT_TOGGLE_AUTOCOMMIT_ENABLED_INIT", GIT_TOGGLE_AUTOCOMMIT_ENABLED_INIT: "GIT_TOGGLE_AUTOCOMMIT_ENABLED_INIT",
GIT_TOGGLE_AUTOCOMMIT_ENABLED_SUCCESS: GIT_TOGGLE_AUTOCOMMIT_ENABLED_SUCCESS:
"GIT_TOGGLE_AUTOCOMMIT_ENABLED_SUCCESS", "GIT_TOGGLE_AUTOCOMMIT_ENABLED_SUCCESS",
GIT_AUTOCOMMIT_INITIATE_PROGRESS_POLLING: GIT_AUTOCOMMIT_TRIGGER_INIT: "GIT_AUTOCOMMIT_TRIGGER_INIT",
"GIT_AUTOCOMMIT_INITIATE_PROGRESS_POLLING", GIT_AUTOCOMMIT_TRIGGER_SUCCESS: "GIT_AUTOCOMMIT_TRIGGER_SUCCESS",
GIT_AUTOCOMMIT_START_PROGRESS_POLLING: GIT_AUTOCOMMIT_START_PROGRESS_POLLING:
"GIT_AUTOCOMMIT_START_PROGRESS_POLLING", "GIT_AUTOCOMMIT_START_PROGRESS_POLLING",
GIT_AUTOCOMMIT_STOP_PROGRESS_POLLING: "GIT_AUTOCOMMIT_STOP_PROGRESS_POLLING", GIT_AUTOCOMMIT_STOP_PROGRESS_POLLING: "GIT_AUTOCOMMIT_STOP_PROGRESS_POLLING",
@ -962,6 +962,7 @@ export const ReduxActionErrorTypes = {
GIT_TOGGLE_AUTOCOMMIT_ENABLED_ERROR: "GIT_TOGGLE_AUTOCOMMIT_ENABLED_ERROR", GIT_TOGGLE_AUTOCOMMIT_ENABLED_ERROR: "GIT_TOGGLE_AUTOCOMMIT_ENABLED_ERROR",
GIT_AUTOCOMMIT_PROGRESS_POLLING_ERROR: GIT_AUTOCOMMIT_PROGRESS_POLLING_ERROR:
"GIT_AUTOCOMMIT_PROGRESS_POLLING_ERROR", "GIT_AUTOCOMMIT_PROGRESS_POLLING_ERROR",
GIT_AUTOCOMMIT_TRIGGER_ERROR: "GIT_AUTOCOMMIT_TRIGGER_ERROR",
GIT_GET_METADATA_ERROR: "GIT_GET_METADATA_ERROR", GIT_GET_METADATA_ERROR: "GIT_GET_METADATA_ERROR",
FETCH_FEATURE_FLAGS_ERROR: "FETCH_FEATURE_FLAGS_ERROR", FETCH_FEATURE_FLAGS_ERROR: "FETCH_FEATURE_FLAGS_ERROR",
FETCH_NOTIFICATIONS_ERROR: "FETCH_NOTIFICATIONS_ERROR", FETCH_NOTIFICATIONS_ERROR: "FETCH_NOTIFICATIONS_ERROR",

View File

@ -0,0 +1,119 @@
import "@testing-library/jest-dom/extend-expect";
import React from "react";
import { render, screen } from "@testing-library/react";
import { Provider } from "react-redux";
import configureStore from "redux-mock-store";
import { lightTheme } from "selectors/themeSelectors";
import { ThemeProvider } from "styled-components";
import CreateNewAppsOption from "./CreateNewAppsOption";
import { BrowserRouter as Router } from "react-router-dom";
import { unitTestBaseMockStore } from "layoutSystems/common/dropTarget/unitTestUtils";
const defaultStoreState = {
...unitTestBaseMockStore,
tenant: {
tenantConfiguration: {},
},
entities: {
...unitTestBaseMockStore.entities,
plugins: {
list: [],
},
datasources: {
list: [],
mockDatasourceList: [],
},
},
ui: {
...unitTestBaseMockStore.ui,
selectedWorkspace: {
...unitTestBaseMockStore.ui.selectedWorkspace,
applications: [unitTestBaseMockStore.ui.applications.currentApplication],
},
debugger: {
isOpen: false,
},
editor: {
loadingStates: {},
isProtectedMode: true,
zoomLevel: 1,
},
gitSync: {
globalGitConfig: {},
branches: [],
localGitConfig: {},
disconnectingGitApp: {},
},
users: {
loadingStates: {},
list: [],
users: [],
error: "",
currentUser: {},
featureFlag: {
data: {},
isFetched: true,
overriddenFlags: {},
},
productAlert: {
config: {},
},
},
apiPane: {
isCreating: false,
isRunning: {},
isSaving: {},
isDeleting: {},
isDirty: {},
extraformData: {},
selectedConfigTabIndex: 0,
debugger: {
open: false,
},
},
},
};
const mockStore = configureStore([]);
describe("CreateNewAppsOption", () => {
let store: any;
it("Should not render skip button if no application is present", () => {
store = mockStore(defaultStoreState);
render(
<Provider store={store}>
<ThemeProvider theme={lightTheme}>
<Router>
<CreateNewAppsOption currentApplicationIdForCreateNewApp="test" />
</Router>
</ThemeProvider>
</Provider>,
);
const button = screen.queryAllByTestId("t--create-new-app-option-skip");
// Check that the skip button to be absent in the document
expect(button).toHaveLength(0);
});
it("Should render skip button if application is present", () => {
render(
<Provider store={store}>
<ThemeProvider theme={lightTheme}>
<Router>
<CreateNewAppsOption currentApplicationIdForCreateNewApp="659d2e15d0cbfb0c5e0a7428" />
</Router>
</ThemeProvider>
</Provider>,
);
const button = screen.queryAllByTestId("t--create-new-app-option-skip");
// Check that the skip button to be present in the document
expect(button).toHaveLength(1);
});
afterAll(() => {
jest.clearAllMocks();
store.clearActions();
});
});

View File

@ -140,14 +140,14 @@ const CreateNewAppsOption = ({
}; };
const onClickSkipButton = () => { const onClickSkipButton = () => {
if (application) { const applicationObject = application!;
urlBuilder.updateURLParams( urlBuilder.updateURLParams(
{ {
applicationSlug: application.slug, applicationSlug: applicationObject.slug,
applicationVersion: application.applicationVersion, applicationVersion: applicationObject.applicationVersion,
applicationId: application.id, applicationId: applicationObject.id,
}, },
application.pages.map((page) => ({ applicationObject.pages.map((page) => ({
pageSlug: page.slug, pageSlug: page.slug,
customSlug: page.customSlug, customSlug: page.customSlug,
pageId: page.id, pageId: page.id,
@ -155,10 +155,9 @@ const CreateNewAppsOption = ({
); );
history.push( history.push(
builderURL({ builderURL({
pageId: application.pages[0].id, pageId: applicationObject.pages[0].id,
}), }),
); );
}
addAnalyticEventsForSkip(); addAnalyticEventsForSkip();
}; };
@ -214,7 +213,7 @@ const CreateNewAppsOption = ({
> >
{createMessage(GO_BACK)} {createMessage(GO_BACK)}
</LinkWrapper> </LinkWrapper>
{application && (
<LinkWrapper <LinkWrapper
className="t--create-new-app-option-skip" className="t--create-new-app-option-skip"
data-testid="t--create-new-app-option-skip" data-testid="t--create-new-app-option-skip"
@ -223,6 +222,7 @@ const CreateNewAppsOption = ({
> >
{createMessage(SKIP_START_WITH_USE_CASE_TEMPLATES)} {createMessage(SKIP_START_WITH_USE_CASE_TEMPLATES)}
</LinkWrapper> </LinkWrapper>
)}
</BackWrapper> </BackWrapper>
<Flex flexDirection="column" pl="spaces-3" pr="spaces-3"> <Flex flexDirection="column" pl="spaces-3" pr="spaces-3">
<Header <Header

View File

@ -1,4 +1,5 @@
import type { WebworkerSpanData } from "UITelemetry/generateWebWorkerTraces"; import type { WebworkerSpanData } from "UITelemetry/generateWebWorkerTraces";
import type { SpanAttributes } from "UITelemetry/generateTraces";
export enum AppsmithWorkers { export enum AppsmithWorkers {
LINT_WORKER = "LINT_WORKER", LINT_WORKER = "LINT_WORKER",
@ -12,5 +13,5 @@ export enum WorkerErrorTypes {
export interface WorkerRequest<TData, TActions> { export interface WorkerRequest<TData, TActions> {
method: TActions; method: TActions;
data: TData; data: TData;
webworkerTelemetry: Record<string, WebworkerSpanData>; webworkerTelemetry: Record<string, WebworkerSpanData | SpanAttributes>;
} }

View File

@ -5,7 +5,7 @@ import {
remoteUrlInputValue, remoteUrlInputValue,
resetPullMergeStatus, resetPullMergeStatus,
fetchBranchesInit, fetchBranchesInit,
startAutocommitProgressPolling, triggerAutocommitInitAction,
getGitMetadataInitAction, getGitMetadataInitAction,
} from "actions/gitSyncActions"; } from "actions/gitSyncActions";
import { restoreRecentEntitiesRequest } from "actions/globalSearchActions"; import { restoreRecentEntitiesRequest } from "actions/globalSearchActions";
@ -296,10 +296,8 @@ export default class AppEditorEngine extends AppEngine {
yield put(fetchGitProtectedBranchesInit()); yield put(fetchGitProtectedBranchesInit());
yield put(fetchGitProtectedBranchesInit()); yield put(fetchGitProtectedBranchesInit());
yield put(getGitMetadataInitAction()); yield put(getGitMetadataInitAction());
yield put(triggerAutocommitInitAction());
yield put(fetchGitStatusInit({ compareRemote: true })); yield put(fetchGitStatusInit({ compareRemote: true }));
yield put(startAutocommitProgressPolling());
yield put(resetPullMergeStatus()); yield put(resetPullMergeStatus());
} }
} }

View File

@ -9,6 +9,8 @@ import { getCurrentAppGitMetaData } from "@appsmith/selectors/applicationSelecto
import BranchList from "../components/BranchList"; import BranchList from "../components/BranchList";
import { import {
getGitStatus, getGitStatus,
getIsPollingAutocommit,
getIsTriggeringAutocommit,
protectedModeSelector, protectedModeSelector,
showBranchPopupSelector, showBranchPopupSelector,
} from "selectors/gitSyncSelectors"; } from "selectors/gitSyncSelectors";
@ -28,6 +30,10 @@ const ButtonContainer = styled(Button)`
margin: 0 ${(props) => props.theme.spaces[4]}px; margin: 0 ${(props) => props.theme.spaces[4]}px;
max-width: 122px; max-width: 122px;
min-width: unset !important; min-width: unset !important;
:active {
border: 1px solid var(--ads-v2-color-border-muted);
}
`; `;
function BranchButton() { function BranchButton() {
@ -38,6 +44,9 @@ function BranchButton() {
const labelTarget = useRef<HTMLSpanElement>(null); const labelTarget = useRef<HTMLSpanElement>(null);
const status = useSelector(getGitStatus); const status = useSelector(getGitStatus);
const isOpen = useSelector(showBranchPopupSelector); const isOpen = useSelector(showBranchPopupSelector);
const triggeringAutocommit = useSelector(getIsTriggeringAutocommit);
const pollingAutocommit = useSelector(getIsPollingAutocommit);
const isBranchChangeDisabled = triggeringAutocommit || pollingAutocommit;
const setIsOpen = (isOpen: boolean) => { const setIsOpen = (isOpen: boolean) => {
dispatch(setShowBranchPopupAction(isOpen)); dispatch(setShowBranchPopupAction(isOpen));
@ -47,6 +56,7 @@ function BranchButton() {
<Popover2 <Popover2
content={<BranchList setIsPopupOpen={setIsOpen} />} content={<BranchList setIsPopupOpen={setIsOpen} />}
data-testid={"t--git-branch-button-popover"} data-testid={"t--git-branch-button-popover"}
disabled={isBranchChangeDisabled}
hasBackdrop hasBackdrop
isOpen={isOpen} isOpen={isOpen}
minimal minimal
@ -69,6 +79,7 @@ function BranchButton() {
<ButtonContainer <ButtonContainer
className="t--branch-button" className="t--branch-button"
data-testid={"t--branch-button-currentBranch"} data-testid={"t--branch-button-currentBranch"}
isDisabled={isBranchChangeDisabled}
kind="secondary" kind="secondary"
> >
{isProtectedMode ? ( {isProtectedMode ? (

View File

@ -55,6 +55,7 @@ const initialState: GitSyncReducerState = {
isAutocommitModalOpen: false, isAutocommitModalOpen: false,
togglingAutocommit: false, togglingAutocommit: false,
triggeringAutocommit: false,
pollingAutocommitStatus: false, pollingAutocommitStatus: false,
gitMetadata: null, gitMetadata: null,
@ -619,6 +620,18 @@ const gitSyncReducer = createReducer(initialState, {
...state, ...state,
togglingAutocommit: false, togglingAutocommit: false,
}), }),
[ReduxActionTypes.GIT_AUTOCOMMIT_TRIGGER_INIT]: (state) => ({
...state,
triggeringAutocommit: true,
}),
[ReduxActionTypes.GIT_AUTOCOMMIT_TRIGGER_SUCCESS]: (state) => ({
...state,
triggeringAutocommit: false,
}),
[ReduxActionErrorTypes.GIT_AUTOCOMMIT_TRIGGER_ERROR]: (state) => ({
...state,
triggeringAutocommit: false,
}),
[ReduxActionTypes.GIT_AUTOCOMMIT_START_PROGRESS_POLLING]: (state) => ({ [ReduxActionTypes.GIT_AUTOCOMMIT_START_PROGRESS_POLLING]: (state) => ({
...state, ...state,
pollingAutocommitStatus: true, pollingAutocommitStatus: true,
@ -833,6 +846,7 @@ export type GitSyncReducerState = GitBranchDeleteState & {
isAutocommitModalOpen: boolean; isAutocommitModalOpen: boolean;
togglingAutocommit: boolean; togglingAutocommit: boolean;
triggeringAutocommit: boolean;
pollingAutocommitStatus: boolean; pollingAutocommitStatus: boolean;
gitMetadata: GitMetadata | null; gitMetadata: GitMetadata | null;

View File

@ -1662,8 +1662,10 @@ function* handleUpdateActionData(
EVAL_WORKER_ACTIONS.UPDATE_ACTION_DATA, EVAL_WORKER_ACTIONS.UPDATE_ACTION_DATA,
actionDataPayload, actionDataPayload,
); );
if (parentSpan) {
endSpan(parentSpan); endSpan(parentSpan);
} }
}
export function* watchPluginActionExecutionSagas() { export function* watchPluginActionExecutionSagas() {
yield all([ yield all([

View File

@ -20,8 +20,13 @@ import {
takeLatest, takeLatest,
} from "redux-saga/effects"; } from "redux-saga/effects";
import type { TakeableChannel } from "@redux-saga/core"; import type { TakeableChannel } from "@redux-saga/core";
import type { MergeBranchPayload, MergeStatusPayload } from "api/GitSyncAPI"; import type {
import GitSyncAPI from "api/GitSyncAPI"; GitAutocommitProgressResponse,
GitTriggerAutocommitResponse,
MergeBranchPayload,
MergeStatusPayload,
} from "api/GitSyncAPI";
import GitSyncAPI, { AutocommitResponseEnum } from "api/GitSyncAPI";
import { import {
getCurrentApplicationId, getCurrentApplicationId,
getCurrentPageId, getCurrentPageId,
@ -40,6 +45,13 @@ import {
updateGitProtectedBranchesInit, updateGitProtectedBranchesInit,
clearCommitSuccessfulState, clearCommitSuccessfulState,
setShowBranchPopupAction, setShowBranchPopupAction,
stopAutocommitProgressPollingAction,
startAutocommitProgressPollingAction,
setAutocommitProgressAction,
autoCommitProgressErrorAction,
resetAutocommitProgressAction,
triggerAutocommitErrorAction,
triggerAutocommitSuccessAction,
} from "actions/gitSyncActions"; } from "actions/gitSyncActions";
import { import {
commitToRepoSuccess, commitToRepoSuccess,
@ -103,6 +115,7 @@ import { addBranchParam, GIT_BRANCH_QUERY_KEY } from "constants/routes";
import { import {
getCurrentGitBranch, getCurrentGitBranch,
getDisconnectingGitApplication, getDisconnectingGitApplication,
getGitMetadataSelector,
} from "selectors/gitSyncSelectors"; } from "selectors/gitSyncSelectors";
import { initEditor } from "actions/initActions"; import { initEditor } from "actions/initActions";
import { fetchPage } from "actions/pageActions"; import { fetchPage } from "actions/pageActions";
@ -113,7 +126,10 @@ import { log } from "loglevel";
import GIT_ERROR_CODES from "constants/GitErrorCodes"; import GIT_ERROR_CODES from "constants/GitErrorCodes";
import { builderURL } from "@appsmith/RouteBuilder"; import { builderURL } from "@appsmith/RouteBuilder";
import { APP_MODE } from "entities/App"; import { APP_MODE } from "entities/App";
import type { GitDiscardResponse } from "reducers/uiReducers/gitSyncReducer"; import type {
GitDiscardResponse,
GitMetadata,
} from "reducers/uiReducers/gitSyncReducer";
import { FocusEntity, identifyEntityFromPath } from "navigation/FocusEntity"; import { FocusEntity, identifyEntityFromPath } from "navigation/FocusEntity";
import { import {
getActions, getActions,
@ -123,6 +139,8 @@ import type { Action } from "entities/Action";
import type { JSCollectionDataState } from "@appsmith/reducers/entityReducers/jsActionsReducer"; import type { JSCollectionDataState } from "@appsmith/reducers/entityReducers/jsActionsReducer";
import { toast } from "design-system"; import { toast } from "design-system";
import { gitExtendedSagas } from "@appsmith/sagas/GitExtendedSagas"; import { gitExtendedSagas } from "@appsmith/sagas/GitExtendedSagas";
import { selectFeatureFlagCheck } from "@appsmith/selectors/featureFlagsSelectors";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
export function* handleRepoLimitReachedError(response?: ApiResponse) { export function* handleRepoLimitReachedError(response?: ApiResponse) {
const { responseMeta } = response || {}; const { responseMeta } = response || {};
@ -1159,78 +1177,110 @@ function* getGitMetadataSaga() {
} }
} }
function isAutocommitHappening(
response:
| GitTriggerAutocommitResponse
| GitAutocommitProgressResponse
| undefined,
): boolean {
return (
!!response &&
!!(
response.autoCommitResponse === AutocommitResponseEnum.PUBLISHED ||
response.autoCommitResponse === AutocommitResponseEnum.IN_PROGRESS ||
response.autoCommitResponse === AutocommitResponseEnum.LOCKED
)
);
}
function* pollAutocommitProgressSaga(): any { function* pollAutocommitProgressSaga(): any {
const applicationId: string = yield select(getCurrentApplicationId); const applicationId: string = yield select(getCurrentApplicationId);
const branchName: string = yield select(getCurrentGitBranch);
let triggerResponse: ApiResponse<GitTriggerAutocommitResponse> | undefined;
try { try {
const response: ApiResponse<any> = yield call( const res = yield call(
GitSyncAPI.getAutocommitProgress, GitSyncAPI.triggerAutocommit,
applicationId, applicationId,
branchName,
); );
const isValidResponse: boolean = yield validateResponse( const isValidResponse: boolean = yield validateResponse(
response, res,
false, false,
getLogToSentryFromResponse(response), getLogToSentryFromResponse(res),
);
if (isValidResponse && response?.data?.isRunning) {
yield put({
type: ReduxActionTypes.GIT_AUTOCOMMIT_START_PROGRESS_POLLING,
});
while (true) {
const response: ApiResponse<any> = yield call(
GitSyncAPI.getAutocommitProgress,
applicationId,
);
const isValidResponse: boolean = yield validateResponse(
response,
false,
getLogToSentryFromResponse(response),
); );
if (isValidResponse) { if (isValidResponse) {
yield put({ triggerResponse = res;
type: ReduxActionTypes.GIT_SET_AUTOCOMMIT_PROGRESS, yield put(triggerAutocommitSuccessAction());
payload: response.data, } else {
}); yield put(
if (!response?.data?.isRunning) { triggerAutocommitErrorAction({
yield put({ error: res?.responseMeta?.error?.message,
type: ReduxActionTypes.GIT_AUTOCOMMIT_STOP_PROGRESS_POLLING, show: true,
}); }),
);
}
} catch (err) {
yield put(triggerAutocommitErrorAction({ error: err, show: false }));
}
try {
if (isAutocommitHappening(triggerResponse?.data)) {
yield put(startAutocommitProgressPollingAction());
while (true) {
const progressResponse: ApiResponse<GitAutocommitProgressResponse> =
yield call(GitSyncAPI.getAutocommitProgress, applicationId);
const isValidResponse: boolean = yield validateResponse(
progressResponse,
false,
getLogToSentryFromResponse(progressResponse),
);
if (isValidResponse) {
yield put(setAutocommitProgressAction(progressResponse.data));
if (!isAutocommitHappening(progressResponse?.data)) {
yield put(stopAutocommitProgressPollingAction());
} }
} else { } else {
yield put({ yield put(stopAutocommitProgressPollingAction());
type: ReduxActionTypes.GIT_AUTOCOMMIT_STOP_PROGRESS_POLLING, yield put(
}); autoCommitProgressErrorAction({
yield put({ error: progressResponse?.responseMeta?.error?.message,
type: ReduxActionErrorTypes.GIT_AUTOCOMMIT_PROGRESS_POLLING_ERROR,
payload: {
error: response?.responseMeta?.error?.message,
show: true, show: true,
}, }),
}); );
} }
yield delay(1000); yield delay(1000);
} }
} else { } else {
yield put({ yield put(stopAutocommitProgressPollingAction());
type: ReduxActionTypes.GIT_AUTOCOMMIT_STOP_PROGRESS_POLLING,
});
} }
} catch (error) { } catch (error) {
yield put({ yield put(stopAutocommitProgressPollingAction());
type: ReduxActionTypes.GIT_AUTOCOMMIT_STOP_PROGRESS_POLLING, yield put(autoCommitProgressErrorAction({ error, show: false }));
});
yield put({
type: ReduxActionErrorTypes.GIT_AUTOCOMMIT_PROGRESS_POLLING_ERROR,
payload: { error },
});
} finally { } finally {
if (yield cancelled()) { if (yield cancelled()) {
yield put({ yield put(resetAutocommitProgressAction());
type: ReduxActionTypes.GIT_RESET_AUTOCOMMIT_PROGRESS,
});
} }
} }
} }
function* triggerAutocommitSaga() {
const isAutocommitFeatureEnabled: boolean = yield select(
selectFeatureFlagCheck,
FEATURE_FLAG.release_git_autocommit_feature_enabled,
);
const gitMetadata: GitMetadata = yield select(getGitMetadataSelector);
const isAutocommitEnabled: boolean = !!gitMetadata?.autoCommitConfig?.enabled;
if (isAutocommitFeatureEnabled && isAutocommitEnabled) {
/* @ts-expect-error: not sure how to do typings of this */
const pollTask = yield fork(pollAutocommitProgressSaga);
yield take(ReduxActionTypes.GIT_AUTOCOMMIT_STOP_PROGRESS_POLLING);
yield cancel(pollTask);
} else {
yield put(triggerAutocommitSuccessAction());
}
}
const gitRequestBlockingActions: Record< const gitRequestBlockingActions: Record<
(typeof ReduxActionTypes)[keyof typeof ReduxActionTypes], (typeof ReduxActionTypes)[keyof typeof ReduxActionTypes],
(...args: any[]) => any (...args: any[]) => any
@ -1252,6 +1302,9 @@ const gitRequestBlockingActions: Record<
[ReduxActionTypes.GIT_DISCARD_CHANGES]: discardChanges, [ReduxActionTypes.GIT_DISCARD_CHANGES]: discardChanges,
[ReduxActionTypes.GIT_UPDATE_PROTECTED_BRANCHES_INIT]: [ReduxActionTypes.GIT_UPDATE_PROTECTED_BRANCHES_INIT]:
updateGitProtectedBranchesSaga, updateGitProtectedBranchesSaga,
[ReduxActionTypes.GIT_AUTOCOMMIT_TRIGGER_INIT]: triggerAutocommitSaga,
[ReduxActionTypes.FETCH_GIT_STATUS_INIT]: fetchGitStatusSaga,
[ReduxActionTypes.GIT_GET_METADATA_INIT]: getGitMetadataSaga,
}; };
const gitRequestNonBlockingActions: Record< const gitRequestNonBlockingActions: Record<
@ -1261,13 +1314,11 @@ const gitRequestNonBlockingActions: Record<
...gitExtendedSagas, ...gitExtendedSagas,
[ReduxActionTypes.FETCH_GLOBAL_GIT_CONFIG_INIT]: fetchGlobalGitConfig, [ReduxActionTypes.FETCH_GLOBAL_GIT_CONFIG_INIT]: fetchGlobalGitConfig,
[ReduxActionTypes.FETCH_LOCAL_GIT_CONFIG_INIT]: fetchLocalGitConfig, [ReduxActionTypes.FETCH_LOCAL_GIT_CONFIG_INIT]: fetchLocalGitConfig,
[ReduxActionTypes.FETCH_GIT_STATUS_INIT]: fetchGitStatusSaga,
[ReduxActionTypes.SHOW_CONNECT_GIT_MODAL]: showConnectGitModal, [ReduxActionTypes.SHOW_CONNECT_GIT_MODAL]: showConnectGitModal,
[ReduxActionTypes.FETCH_SSH_KEY_PAIR_INIT]: getSSHKeyPairSaga, [ReduxActionTypes.FETCH_SSH_KEY_PAIR_INIT]: getSSHKeyPairSaga,
[ReduxActionTypes.GIT_FETCH_PROTECTED_BRANCHES_INIT]: [ReduxActionTypes.GIT_FETCH_PROTECTED_BRANCHES_INIT]:
fetchGitProtectedBranchesSaga, fetchGitProtectedBranchesSaga,
[ReduxActionTypes.GIT_TOGGLE_AUTOCOMMIT_ENABLED_INIT]: toggleAutocommitSaga, [ReduxActionTypes.GIT_TOGGLE_AUTOCOMMIT_ENABLED_INIT]: toggleAutocommitSaga,
[ReduxActionTypes.GIT_GET_METADATA_INIT]: getGitMetadataSaga,
}; };
/** /**
@ -1298,18 +1349,7 @@ function* watchGitNonBlockingRequests() {
} }
} }
function* watchGitAutocommitPolling() {
while (true) {
yield take(ReduxActionTypes.GIT_AUTOCOMMIT_INITIATE_PROGRESS_POLLING);
/* @ts-expect-error: not sure how to do typings of this */
const pollTask = yield fork(pollAutocommitProgressSaga);
yield take(ReduxActionTypes.GIT_AUTOCOMMIT_STOP_PROGRESS_POLLING);
yield cancel(pollTask);
}
}
export default function* gitSyncSagas() { export default function* gitSyncSagas() {
yield fork(watchGitNonBlockingRequests); yield fork(watchGitNonBlockingRequests);
yield fork(watchGitBlockingRequests); yield fork(watchGitBlockingRequests);
yield fork(watchGitAutocommitPolling);
} }

View File

@ -257,6 +257,9 @@ export const getIsAutocommitToggling = (state: AppState) =>
export const getIsAutocommitModalOpen = (state: AppState) => export const getIsAutocommitModalOpen = (state: AppState) =>
state.ui.gitSync.isAutocommitModalOpen; state.ui.gitSync.isAutocommitModalOpen;
export const getIsTriggeringAutocommit = (state: AppState) =>
state.ui.gitSync.triggeringAutocommit;
export const getIsPollingAutocommit = (state: AppState) => export const getIsPollingAutocommit = (state: AppState) =>
state.ui.gitSync.pollingAutocommitStatus; state.ui.gitSync.pollingAutocommitStatus;

View File

@ -5,11 +5,16 @@ import { uniqueId } from "lodash";
import log from "loglevel"; import log from "loglevel";
import type { TMessage } from "./MessageUtil"; import type { TMessage } from "./MessageUtil";
import { MessageType, sendMessage } from "./MessageUtil"; import { MessageType, sendMessage } from "./MessageUtil";
import type { OtlpSpan } from "UITelemetry/generateTraces"; import type { OtlpSpan, SpanAttributes } from "UITelemetry/generateTraces";
import { endSpan, startRootSpan } from "UITelemetry/generateTraces"; import {
endSpan,
setAttributesToSpan,
startRootSpan,
} from "UITelemetry/generateTraces";
import type { WebworkerSpanData } from "UITelemetry/generateWebWorkerTraces"; import type { WebworkerSpanData } from "UITelemetry/generateWebWorkerTraces";
import { import {
convertWebworkerSpansToRegularSpans, convertWebworkerSpansToRegularSpans,
filterSpanData,
newWebWorkerSpanData, newWebWorkerSpanData,
} from "UITelemetry/generateWebWorkerTraces"; } from "UITelemetry/generateWebWorkerTraces";
@ -156,33 +161,35 @@ export class GracefulWorkerService {
startTime, startTime,
webworkerTelemetry, webworkerTelemetry,
}: { }: {
webworkerTelemetry: Record<string, WebworkerSpanData>; webworkerTelemetry:
| Record<string, WebworkerSpanData | SpanAttributes>
| undefined;
rootSpan: OtlpSpan | undefined; rootSpan: OtlpSpan | undefined;
method: string; method: string;
startTime: number; startTime: number;
endTime: number; endTime: number;
}) { }) {
const webworkerTelemetryResponse = webworkerTelemetry as Record< if (!webworkerTelemetry) {
string, return;
WebworkerSpanData }
>;
if (webworkerTelemetryResponse) { const { transferDataToMainThread } = webworkerTelemetry;
const { transferDataToMainThread } = webworkerTelemetryResponse;
if (transferDataToMainThread) { if (transferDataToMainThread) {
transferDataToMainThread.endTime = Date.now(); transferDataToMainThread.endTime = Date.now();
} }
/// Add the completeWebworkerComputation span to the root span /// Add the completeWebworkerComputation span to the root span
webworkerTelemetryResponse["completeWebworkerComputation"] = { webworkerTelemetry["completeWebworkerComputation"] = {
startTime, startTime,
endTime, endTime,
attributes: {}, attributes: {},
spanName: "completeWebworkerComputation", spanName: "completeWebworkerComputation",
}; };
}
//we are attaching the child spans to the root span over here //we are attaching the child spans to the root span over here
rootSpan && rootSpan &&
convertWebworkerSpansToRegularSpans(rootSpan, webworkerTelemetryResponse); convertWebworkerSpansToRegularSpans(
rootSpan,
filterSpanData(webworkerTelemetry),
);
//genereate separate completeWebworkerComputationRoot root span //genereate separate completeWebworkerComputationRoot root span
// this span does not contain any child spans, it just captures the webworker computation alone // this span does not contain any child spans, it just captures the webworker computation alone
@ -218,11 +225,15 @@ export class GracefulWorkerService {
let timeTaken; let timeTaken;
const rootSpan = startRootSpan(method); const rootSpan = startRootSpan(method);
const webworkerTelemetryData: Record<string, WebworkerSpanData> = { const webworkerTelemetryData: Record<
string,
WebworkerSpanData | SpanAttributes
> = {
transferDataToWorkerThread: newWebWorkerSpanData( transferDataToWorkerThread: newWebWorkerSpanData(
"transferDataToWorkerThread", "transferDataToWorkerThread",
{}, {},
), ),
__spanAttributes: {},
}; };
const body = { const body = {
@ -231,6 +242,11 @@ export class GracefulWorkerService {
webworkerTelemetry: webworkerTelemetryData, webworkerTelemetry: webworkerTelemetryData,
}; };
let webworkerTelemetryResponse: Record<
string,
WebworkerSpanData | SpanAttributes
> = {};
try { try {
sendMessage.call(this._Worker, { sendMessage.call(this._Worker, {
messageType: MessageType.REQUEST, messageType: MessageType.REQUEST,
@ -241,9 +257,10 @@ export class GracefulWorkerService {
// The `this._broker` method is listening to events and will pass response to us over this channel. // The `this._broker` method is listening to events and will pass response to us over this channel.
const response = yield take(ch); const response = yield take(ch);
const { data, endTime, startTime } = response; const { data, endTime, startTime } = response;
const { webworkerTelemetry } = data; webworkerTelemetryResponse = data.webworkerTelemetry;
this.addChildSpansToRootSpan({ this.addChildSpansToRootSpan({
webworkerTelemetry, webworkerTelemetry: webworkerTelemetryResponse,
rootSpan, rootSpan,
method, method,
startTime, startTime,
@ -268,6 +285,14 @@ export class GracefulWorkerService {
log.debug(` Worker ${method} took ${timeTaken}ms`); log.debug(` Worker ${method} took ${timeTaken}ms`);
log.debug(` Transfer ${method} took ${transferTime}ms`); log.debug(` Transfer ${method} took ${transferTime}ms`);
} }
if (webworkerTelemetryResponse) {
setAttributesToSpan(
rootSpan,
webworkerTelemetryResponse.__spanAttributes as SpanAttributes,
);
}
endSpan(rootSpan); endSpan(rootSpan);
// Cleanup // Cleanup
ch.close(); ch.close();

View File

@ -34,6 +34,7 @@ import {
profileFn, profileFn,
newWebWorkerSpanData, newWebWorkerSpanData,
} from "UITelemetry/generateWebWorkerTraces"; } from "UITelemetry/generateWebWorkerTraces";
import type { SpanAttributes } from "UITelemetry/generateTraces";
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer"; import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsReducer"; import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsReducer";
@ -85,6 +86,9 @@ export function evalTree(request: EvalWorkerSyncRequest) {
let isNewTree = false; let isNewTree = false;
try { try {
(webworkerTelemetry.__spanAttributes as SpanAttributes)["firstEvaluation"] =
!dataTreeEvaluator;
if (!dataTreeEvaluator) { if (!dataTreeEvaluator) {
isCreateFirstTree = true; isCreateFirstTree = true;
replayMap = replayMap || {}; replayMap = replayMap || {};

View File

@ -16,6 +16,7 @@ import type { WorkerRequest } from "@appsmith/workers/common/types";
import type { DataTreeDiff } from "@appsmith/workers/Evaluation/evaluationUtils"; import type { DataTreeDiff } from "@appsmith/workers/Evaluation/evaluationUtils";
import type { APP_MODE } from "entities/App"; import type { APP_MODE } from "entities/App";
import type { WebworkerSpanData } from "UITelemetry/generateWebWorkerTraces"; import type { WebworkerSpanData } from "UITelemetry/generateWebWorkerTraces";
import type { SpanAttributes } from "UITelemetry/generateTraces";
import type { AffectedJSObjects } from "sagas/EvaluationsSagaUtils"; import type { AffectedJSObjects } from "sagas/EvaluationsSagaUtils";
export type EvalWorkerSyncRequest<T = any> = WorkerRequest< export type EvalWorkerSyncRequest<T = any> = WorkerRequest<
@ -59,6 +60,6 @@ export interface EvalTreeResponseData {
isNewWidgetAdded: boolean; isNewWidgetAdded: boolean;
undefinedEvalValuesMap: Record<string, boolean>; undefinedEvalValuesMap: Record<string, boolean>;
jsVarsCreatedEvent?: { path: string; type: string }[]; jsVarsCreatedEvent?: { path: string; type: string }[];
webworkerTelemetry?: Record<string, WebworkerSpanData>; webworkerTelemetry?: Record<string, WebworkerSpanData | SpanAttributes>;
updates: string; updates: string;
} }

View File

@ -137,6 +137,7 @@ import {
profileFn, profileFn,
type WebworkerSpanData, type WebworkerSpanData,
} from "UITelemetry/generateWebWorkerTraces"; } from "UITelemetry/generateWebWorkerTraces";
import type { SpanAttributes } from "UITelemetry/generateTraces";
import type { AffectedJSObjects } from "sagas/EvaluationsSagaUtils"; import type { AffectedJSObjects } from "sagas/EvaluationsSagaUtils";
import generateOverrideContext from "@appsmith/workers/Evaluation/generateOverrideContext"; import generateOverrideContext from "@appsmith/workers/Evaluation/generateOverrideContext";
@ -233,7 +234,7 @@ export default class DataTreeEvaluator {
setupFirstTree( setupFirstTree(
unEvalTree: any, unEvalTree: any,
configTree: ConfigTree, configTree: ConfigTree,
webworkerTelemetry: Record<string, WebworkerSpanData> = {}, webworkerTelemetry: Record<string, WebworkerSpanData | SpanAttributes> = {},
): { ): {
jsUpdates: Record<string, JSUpdate>; jsUpdates: Record<string, JSUpdate>;
evalOrder: string[]; evalOrder: string[];
@ -489,7 +490,7 @@ export default class DataTreeEvaluator {
setupUpdateTree( setupUpdateTree(
unEvalTree: any, unEvalTree: any,
configTree: ConfigTree, configTree: ConfigTree,
webworkerTelemetry: Record<string, WebworkerSpanData> = {}, webworkerTelemetry: Record<string, WebworkerSpanData | SpanAttributes> = {},
affectedJSObjects: AffectedJSObjects = { isAllAffected: false, ids: [] }, affectedJSObjects: AffectedJSObjects = { isAllAffected: false, ids: [] },
): { ): {
unEvalUpdates: DataTreeDiff[]; unEvalUpdates: DataTreeDiff[];

View File

@ -288,8 +288,10 @@ public class FileUtilsCEImpl implements FileInterface {
// Save JS Libs if there's at least one change // Save JS Libs if there's at least one change
if (modifiedResources != null if (modifiedResources != null
&& !CollectionUtils.isEmpty( && (modifiedResources.isAllModified()
modifiedResources.getModifiedResourceMap().get(CUSTOM_JS_LIB_LIST))) { || !CollectionUtils.isEmpty(
modifiedResources.getModifiedResourceMap().get(CUSTOM_JS_LIB_LIST)))) {
Path jsLibDirectory = baseRepo.resolve(JS_LIB_DIRECTORY); Path jsLibDirectory = baseRepo.resolve(JS_LIB_DIRECTORY);
Set<Map.Entry<String, Object>> jsLibEntries = Set<Map.Entry<String, Object>> jsLibEntries =
applicationGitReference.getJsLibraries().entrySet(); applicationGitReference.getJsLibraries().entrySet();
@ -953,16 +955,23 @@ public class FileUtilsCEImpl implements FileInterface {
@Override @Override
public Mono<Object> reconstructMetadataFromGitRepo( public Mono<Object> reconstructMetadataFromGitRepo(
String workspaceId, String defaultArtifactId, String repoName, String branchName, Path baseRepoSuffix) { String workspaceId,
String defaultArtifactId,
String repoName,
String branchName,
Path baseRepoSuffix,
Boolean isResetToLastCommitRequired) {
Mono<Object> metadataMono; Mono<Object> metadataMono;
try { try {
Mono<Boolean> gitResetMono = Mono.just(Boolean.TRUE);
if (Boolean.TRUE.equals(isResetToLastCommitRequired)) {
// instead of checking out to last branch we are first cleaning the git repo, // instead of checking out to last branch we are first cleaning the git repo,
// then checking out to the desired branch // then checking out to the desired branch
metadataMono = gitExecutor gitResetMono = gitExecutor.resetToLastCommit(baseRepoSuffix, branchName);
.resetToLastCommit(baseRepoSuffix, branchName) }
.map(isSwitched -> {
Path baseRepoPath = metadataMono = gitResetMono.map(isSwitched -> {
Paths.get(gitServiceConfig.getGitRootPath()).resolve(baseRepoSuffix); Path baseRepoPath = Paths.get(gitServiceConfig.getGitRootPath()).resolve(baseRepoSuffix);
Object metadata = fileOperations.readFile( Object metadata = fileOperations.readFile(
baseRepoPath.resolve(CommonConstants.METADATA + CommonConstants.JSON_EXTENSION)); baseRepoPath.resolve(CommonConstants.METADATA + CommonConstants.JSON_EXTENSION));
return metadata; return metadata;
@ -973,4 +982,35 @@ public class FileUtilsCEImpl implements FileInterface {
return metadataMono.subscribeOn(scheduler); return metadataMono.subscribeOn(scheduler);
} }
@Override
public Mono<Object> reconstructPageFromGitRepo(
String pageName, String branchName, Path baseRepoSuffixPath, Boolean resetToLastCommitRequired) {
Mono<Object> pageObjectMono;
try {
Mono<Boolean> resetToLastCommit = Mono.just(Boolean.TRUE);
if (Boolean.TRUE.equals(resetToLastCommitRequired)) {
// instead of checking out to last branch we are first cleaning the git repo,
// then checking out to the desired branch
resetToLastCommit = gitExecutor.resetToLastCommit(baseRepoSuffixPath, branchName);
}
pageObjectMono = resetToLastCommit.map(isSwitched -> {
Path pageSuffix = Paths.get(PAGE_DIRECTORY, pageName);
Path repoPath = Paths.get(gitServiceConfig.getGitRootPath())
.resolve(baseRepoSuffixPath)
.resolve(pageSuffix);
Object pageObject =
fileOperations.readFile(repoPath.resolve(pageName + CommonConstants.JSON_EXTENSION));
return pageObject;
});
} catch (GitAPIException | IOException exception) {
pageObjectMono = Mono.error(exception);
}
return pageObjectMono.subscribeOn(scheduler);
}
} }

View File

@ -70,7 +70,7 @@ public class FileOperationsCEv2Impl extends FileOperationsCEImpl implements File
this.observationHelper = observationHelper; this.observationHelper = observationHelper;
} }
@FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_cleanup_feature_enabled) @FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_feature_enabled)
@Override @Override
public void saveMetadataResource(ApplicationGitReference applicationGitReference, Path baseRepo) { public void saveMetadataResource(ApplicationGitReference applicationGitReference, Path baseRepo) {
ObjectNode metadata = objectMapper.valueToTree(applicationGitReference.getMetadata()); ObjectNode metadata = objectMapper.valueToTree(applicationGitReference.getMetadata());
@ -78,7 +78,7 @@ public class FileOperationsCEv2Impl extends FileOperationsCEImpl implements File
saveResource(metadata, baseRepo.resolve(CommonConstants.METADATA + CommonConstants.JSON_EXTENSION)); saveResource(metadata, baseRepo.resolve(CommonConstants.METADATA + CommonConstants.JSON_EXTENSION));
} }
@FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_cleanup_feature_enabled) @FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_feature_enabled)
@Override @Override
public void saveWidgets(JSONObject sourceEntity, String resourceName, Path path) { public void saveWidgets(JSONObject sourceEntity, String resourceName, Path path) {
Span span = observationHelper.createSpan(GitSpan.FILE_WRITE); Span span = observationHelper.createSpan(GitSpan.FILE_WRITE);
@ -98,7 +98,7 @@ public class FileOperationsCEv2Impl extends FileOperationsCEImpl implements File
} }
} }
@FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_cleanup_feature_enabled) @FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_feature_enabled)
@Override @Override
public boolean writeToFile(Object sourceEntity, Path path) throws IOException { public boolean writeToFile(Object sourceEntity, Path path) throws IOException {
Span span = observationHelper.createSpan(GitSpan.FILE_WRITE); Span span = observationHelper.createSpan(GitSpan.FILE_WRITE);
@ -123,7 +123,7 @@ public class FileOperationsCEv2Impl extends FileOperationsCEImpl implements File
* @param filePath file on which the read operation will be performed * @param filePath file on which the read operation will be performed
* @return resource stored in the JSON file * @return resource stored in the JSON file
*/ */
@FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_cleanup_feature_enabled) @FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_feature_enabled)
@Override @Override
public Object readFile(Path filePath) { public Object readFile(Path filePath) {
Span span = observationHelper.createSpan(GitSpan.FILE_READ); Span span = observationHelper.createSpan(GitSpan.FILE_READ);
@ -147,7 +147,7 @@ public class FileOperationsCEv2Impl extends FileOperationsCEImpl implements File
* @param directoryPath directory path for files on which read operation will be performed * @param directoryPath directory path for files on which read operation will be performed
* @return resources stored in the directory * @return resources stored in the directory
*/ */
@FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_cleanup_feature_enabled) @FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_feature_enabled)
@Override @Override
public Map<String, Object> readFiles(Path directoryPath, String keySuffix) { public Map<String, Object> readFiles(Path directoryPath, String keySuffix) {
Map<String, Object> resource = new HashMap<>(); Map<String, Object> resource = new HashMap<>();
@ -168,7 +168,7 @@ public class FileOperationsCEv2Impl extends FileOperationsCEImpl implements File
return resource; return resource;
} }
@FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_cleanup_feature_enabled) @FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_feature_enabled)
@Override @Override
public Integer getFileFormatVersion(Object metadata) { public Integer getFileFormatVersion(Object metadata) {
if (metadata == null) { if (metadata == null) {
@ -179,7 +179,7 @@ public class FileOperationsCEv2Impl extends FileOperationsCEImpl implements File
return fileFormatVersion; return fileFormatVersion;
} }
@FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_cleanup_feature_enabled) @FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_feature_enabled)
@Override @Override
public JSONObject getMainContainer(Object pageJson) { public JSONObject getMainContainer(Object pageJson) {
JsonNode pageJSON = objectMapper.valueToTree(pageJson); JsonNode pageJSON = objectMapper.valueToTree(pageJson);

View File

@ -15,7 +15,15 @@ public enum FeatureFlagEnum {
release_embed_hide_share_settings_enabled, release_embed_hide_share_settings_enabled,
rollout_datasource_test_rate_limit_enabled, rollout_datasource_test_rate_limit_enabled,
release_git_autocommit_feature_enabled, release_git_autocommit_feature_enabled,
release_git_cleanup_feature_enabled, /**
release_git_server_autocommit_feature_enabled, * Since checking eligibility for autocommit is an expensive operation,
* We want to roll out this feature on cloud in a controlled manner.
* We could have used the autocommit flag itself, however it is on tenant level,
* and it can't be moved to user level due to its usage on non-reactive code paths.
* We would keep the main autocommit flag false on production for the version <= testing versions,
* and turn it to true for later versions
* We would remove this feature flag once the testing is complete.
*/
release_git_autocommit_eligibility_enabled,
// Add EE flags below this line, to avoid conflicts. // Add EE flags below this line, to avoid conflicts.
} }

View File

@ -54,10 +54,19 @@ public interface FileInterface {
* @param repoName * @param repoName
* @param branchName * @param branchName
* @param repoSuffixPath * @param repoSuffixPath
* @param isResetToLastCommitRequired
* @return * @return
*/ */
Mono<Object> reconstructMetadataFromGitRepo( Mono<Object> reconstructMetadataFromGitRepo(
String workspaceId, String defaultApplicationId, String repoName, String branchName, Path repoSuffixPath); String workspaceId,
String defaultApplicationId,
String repoName,
String branchName,
Path repoSuffixPath,
Boolean isResetToLastCommitRequired);
Mono<Object> reconstructPageFromGitRepo(
String pageName, String branchName, Path repoSuffixPath, Boolean checkoutRequired);
/** /**
* Once the user connects the existing application to a remote repo, we will initialize the repo with Readme.md - * Once the user connects the existing application to a remote repo, we will initialize the repo with Readme.md -

View File

@ -22,6 +22,8 @@ public class GitConstantsCE {
public static final String GIT_PROFILE_ERROR = "Unable to find git author configuration for logged-in user. You can" public static final String GIT_PROFILE_ERROR = "Unable to find git author configuration for logged-in user. You can"
+ " set up a git profile from the user profile section."; + " set up a git profile from the user profile section.";
public static final String RECONSTRUCT_PAGE = "reconstruct page";
public class GitMetricConstantsCE { public class GitMetricConstantsCE {
public static final String CHECKOUT_REMOTE = "checkout-remote"; public static final String CHECKOUT_REMOTE = "checkout-remote";
public static final String HARD_RESET = "hard-reset"; public static final String HARD_RESET = "hard-reset";
@ -47,5 +49,7 @@ public class GitConstantsCE {
public static final String MERGE_BRANCH = "mergeBranch"; public static final String MERGE_BRANCH = "mergeBranch";
public static final String DELETE = "delete"; public static final String DELETE = "delete";
public static final String DISCARD = "discard"; public static final String DISCARD = "discard";
public static final String PAGE_DSL_VERSION = "pageDslVersion";
public static final String AUTO_COMMIT_ELIGIBILITY = "autoCommitEligibility";
} }
} }

View File

@ -2,8 +2,10 @@ package com.appsmith.server.controllers;
import com.appsmith.server.constants.Url; import com.appsmith.server.constants.Url;
import com.appsmith.server.controllers.ce.GitControllerCE; import com.appsmith.server.controllers.ce.GitControllerCE;
import com.appsmith.server.git.autocommit.AutoCommitService;
import com.appsmith.server.git.common.CommonGitService; import com.appsmith.server.git.common.CommonGitService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -12,7 +14,8 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping(Url.GIT_URL) @RequestMapping(Url.GIT_URL)
public class GitController extends GitControllerCE { public class GitController extends GitControllerCE {
public GitController(CommonGitService service) { @Autowired
super(service); public GitController(CommonGitService service, AutoCommitService autoCommitService) {
super(service, autoCommitService);
} }
} }

View File

@ -13,7 +13,7 @@ import com.appsmith.server.domains.GitArtifactMetadata;
import com.appsmith.server.domains.GitAuth; import com.appsmith.server.domains.GitAuth;
import com.appsmith.server.domains.GitProfile; import com.appsmith.server.domains.GitProfile;
import com.appsmith.server.dtos.ApplicationImportDTO; import com.appsmith.server.dtos.ApplicationImportDTO;
import com.appsmith.server.dtos.AutoCommitProgressDTO; import com.appsmith.server.dtos.AutoCommitResponseDTO;
import com.appsmith.server.dtos.BranchProtectionRequestDTO; import com.appsmith.server.dtos.BranchProtectionRequestDTO;
import com.appsmith.server.dtos.GitCommitDTO; import com.appsmith.server.dtos.GitCommitDTO;
import com.appsmith.server.dtos.GitConnectDTO; import com.appsmith.server.dtos.GitConnectDTO;
@ -22,6 +22,7 @@ import com.appsmith.server.dtos.GitDocsDTO;
import com.appsmith.server.dtos.GitMergeDTO; import com.appsmith.server.dtos.GitMergeDTO;
import com.appsmith.server.dtos.GitPullDTO; import com.appsmith.server.dtos.GitPullDTO;
import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.dtos.ResponseDTO;
import com.appsmith.server.git.autocommit.AutoCommitService;
import com.appsmith.server.git.common.CommonGitService; import com.appsmith.server.git.common.CommonGitService;
import com.appsmith.server.helpers.GitDeployKeyGenerator; import com.appsmith.server.helpers.GitDeployKeyGenerator;
import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.annotation.JsonView;
@ -53,6 +54,7 @@ import java.util.Map;
public class GitControllerCE { public class GitControllerCE {
private final CommonGitService service; private final CommonGitService service;
private final AutoCommitService autoCommitService;
/** /**
* applicationId is the defaultApplicationId * applicationId is the defaultApplicationId
@ -333,16 +335,18 @@ public class GitControllerCE {
@JsonView(Views.Public.class) @JsonView(Views.Public.class)
@PostMapping("/auto-commit/app/{defaultApplicationId}") @PostMapping("/auto-commit/app/{defaultApplicationId}")
public Mono<ResponseDTO<Boolean>> autoCommit( public Mono<ResponseDTO<AutoCommitResponseDTO>> autoCommitApplication(
@PathVariable String defaultApplicationId, @RequestParam String branchName) { @PathVariable String defaultApplicationId, @RequestHeader(name = FieldName.BRANCH_NAME) String branchName) {
return service.autoCommitApplication(defaultApplicationId, branchName, ArtifactType.APPLICATION) return autoCommitService
.autoCommitApplication(defaultApplicationId, branchName)
.map(data -> new ResponseDTO<>(HttpStatus.OK.value(), data, null)); .map(data -> new ResponseDTO<>(HttpStatus.OK.value(), data, null));
} }
@JsonView(Views.Public.class) @JsonView(Views.Public.class)
@GetMapping("/auto-commit/progress/app/{defaultApplicationId}") @GetMapping("/auto-commit/progress/app/{defaultApplicationId}")
public Mono<ResponseDTO<AutoCommitProgressDTO>> getAutoCommitProgress(@PathVariable String defaultApplicationId) { public Mono<ResponseDTO<AutoCommitResponseDTO>> getAutoCommitProgress(
return service.getAutoCommitProgress(defaultApplicationId, ArtifactType.APPLICATION) @PathVariable String defaultApplicationId, @RequestHeader(name = FieldName.BRANCH_NAME) String branchName) {
return service.getAutoCommitProgress(defaultApplicationId, branchName, ArtifactType.APPLICATION)
.map(data -> new ResponseDTO<>(HttpStatus.OK.value(), data, null)); .map(data -> new ResponseDTO<>(HttpStatus.OK.value(), data, null));
} }

View File

@ -7,7 +7,6 @@ import com.appsmith.external.views.Git;
import com.appsmith.external.views.Views; import com.appsmith.external.views.Views;
import com.appsmith.server.helpers.CollectionUtils; import com.appsmith.server.helpers.CollectionUtils;
import com.appsmith.server.helpers.CompareDslActionDTO; import com.appsmith.server.helpers.CompareDslActionDTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.annotation.JsonView;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
@ -50,7 +49,6 @@ public class Layout {
// this attribute will be used to display errors caused white calculating allOnLoadAction // this attribute will be used to display errors caused white calculating allOnLoadAction
// PageLoadActionsUtilCEImpl.java // PageLoadActionsUtilCEImpl.java
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@JsonView({Views.Public.class, Views.Export.class}) @JsonView({Views.Public.class, Views.Export.class})
List<ErrorDTO> layoutOnLoadActionErrors; List<ErrorDTO> layoutOnLoadActionErrors;

View File

@ -50,8 +50,11 @@ public class NewPage extends BranchAwareDomain implements Context {
@JsonView(Views.Internal.class) @JsonView(Views.Internal.class)
@Override @Override
public Layout getLayout() { public Layout getLayout() {
if (this.getUnpublishedPage() == null || this.getUnpublishedPage().getLayouts() == null) {
return null;
}
List<Layout> layouts = this.getUnpublishedPage().getLayouts(); List<Layout> layouts = this.getUnpublishedPage().getLayouts();
return (layouts != null && !layouts.isEmpty()) ? layouts.get(0) : null; return !layouts.isEmpty() ? layouts.get(0) : null;
} }
public static class Fields extends BranchAwareDomain.Fields { public static class Fields extends BranchAwareDomain.Fields {

View File

@ -38,13 +38,13 @@ public class Theme extends BaseDomain {
@JsonView(Views.Public.class) @JsonView(Views.Public.class)
String workspaceId; String workspaceId;
@JsonView(Views.Public.class) @JsonView({Views.Public.class, Git.class})
private Object config; private Object config;
@JsonView(Views.Public.class) @JsonView({Views.Public.class, Git.class})
private Object properties; private Object properties;
@JsonView(Views.Public.class) @JsonView({Views.Public.class, Git.class})
private Map<String, Object> stylesheet; private Map<String, Object> stylesheet;
@JsonProperty("isSystemTheme") // manually setting property name to make sure it's compatible with Gson @JsonProperty("isSystemTheme") // manually setting property name to make sure it's compatible with Gson

View File

@ -1,17 +0,0 @@
package com.appsmith.server.dtos;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
@NoArgsConstructor
public class AutoCommitProgressDTO {
@NonNull private Boolean isRunning;
// using primitive type int instead of Integer because we want to 0 as default value. Integer have default null
private int progress;
private String branchName;
}

View File

@ -0,0 +1,60 @@
package com.appsmith.server.dtos;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@Data
@AllArgsConstructor
@RequiredArgsConstructor
public class AutoCommitResponseDTO {
public enum AutoCommitResponse {
/**
* This enum is used when an autocommit is in progress for a different branch from the branch on which
* the autocommit is requested.
*/
LOCKED,
/**
* This enum is used when an autocommit has been published.
*/
PUBLISHED,
/**
* This enum is used when an autocommit is in progress for the branch from which
* the autocommit is requested.
*/
IN_PROGRESS,
/**
* This enum is used when an autocommit has been requested however it did not fulfil the pre-requisite.
*/
REQUIRED,
/**
* This enum is used when an autocommit is requested however it's not required.
*/
IDLE,
/**
* This enum is used when the app on which the autocommit is requested is a non git app.
*/
NON_GIT_APP
}
/**
* Enum to denote the current state of autocommit
*/
private AutoCommitResponse autoCommitResponse;
/**
* progress of the already-running auto-commit.
*/
private int progress;
private String branchName;
public AutoCommitResponseDTO(AutoCommitResponse autoCommitResponse) {
this.autoCommitResponse = autoCommitResponse;
}
}

View File

@ -1,22 +1,34 @@
package com.appsmith.server.dtos; package com.appsmith.server.dtos;
import com.appsmith.external.dtos.DslExecutableDTO;
import com.appsmith.server.domains.Layout; import com.appsmith.server.domains.Layout;
import com.appsmith.server.meta.validations.FileName; import com.appsmith.server.meta.validations.FileName;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import net.minidev.json.JSONObject;
import java.util.List; import java.util.List;
import java.util.Set;
public record PageCreationDTO( public record PageCreationDTO(
@FileName(message = "Page names must be valid file names", isNullValid = false) @Size(max = 30) String name, @FileName(message = "Page names must be valid file names", isNullValid = false) @Size(max = 30) String name,
@NotEmpty @Size(min = 24, max = 50) String applicationId, @NotEmpty @Size(min = 24, max = 50) String applicationId,
@NotEmpty List<Layout> layouts) { @NotEmpty List<LayoutDTO> layouts) {
public record LayoutDTO(JSONObject dsl, List<Set<DslExecutableDTO>> layoutOnLoadActions) {}
public PageDTO toPageDTO() { public PageDTO toPageDTO() {
final PageDTO page = new PageDTO(); final PageDTO page = new PageDTO();
page.setName(name.trim()); page.setName(name.trim());
page.setApplicationId(applicationId); page.setApplicationId(applicationId);
page.setLayouts(layouts); page.setLayouts(layouts.stream()
.map(layoutDto -> {
final Layout l = new Layout();
l.setDsl(layoutDto.dsl);
l.setLayoutOnLoadActions(layoutDto.layoutOnLoadActions);
return l;
})
.toList());
return page; return page;
} }
} }

View File

@ -1,10 +1,10 @@
package com.appsmith.server.dtos; package com.appsmith.server.dtos;
import com.appsmith.server.domains.Layout;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import net.minidev.json.JSONObject;
import java.util.List; import java.util.List;
@ -22,6 +22,8 @@ public class UpdateMultiplePageLayoutDTO {
@NotNull private String layoutId; @NotNull private String layoutId;
private Layout layout; private LayoutDTO layout;
} }
public record LayoutDTO(JSONObject dsl) {}
} }

View File

@ -1,3 +1,3 @@
package com.appsmith.server.git; package com.appsmith.server.git.autocommit;
public interface AutoCommitEventHandler extends AutoCommitEventHandlerCE {} public interface AutoCommitEventHandler extends AutoCommitEventHandlerCE {}

View File

@ -1,4 +1,4 @@
package com.appsmith.server.git; package com.appsmith.server.git.autocommit;
import com.appsmith.server.events.AutoCommitEvent; import com.appsmith.server.events.AutoCommitEvent;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;

View File

@ -1,4 +1,4 @@
package com.appsmith.server.git; package com.appsmith.server.git.autocommit;
import com.appsmith.external.constants.AnalyticsEvents; import com.appsmith.external.constants.AnalyticsEvents;
import com.appsmith.external.dtos.ModifiedResources; import com.appsmith.external.dtos.ModifiedResources;
@ -13,6 +13,7 @@ import com.appsmith.server.dtos.ApplicationJson;
import com.appsmith.server.events.AutoCommitEvent; import com.appsmith.server.events.AutoCommitEvent;
import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.git.GitRedisUtils;
import com.appsmith.server.helpers.CollectionUtils; import com.appsmith.server.helpers.CollectionUtils;
import com.appsmith.server.helpers.CommonGitFileUtils; import com.appsmith.server.helpers.CommonGitFileUtils;
import com.appsmith.server.helpers.DSLMigrationUtils; import com.appsmith.server.helpers.DSLMigrationUtils;

View File

@ -1,7 +1,8 @@
package com.appsmith.server.git; package com.appsmith.server.git.autocommit;
import com.appsmith.external.git.GitExecutor; import com.appsmith.external.git.GitExecutor;
import com.appsmith.server.configurations.ProjectProperties; import com.appsmith.server.configurations.ProjectProperties;
import com.appsmith.server.git.GitRedisUtils;
import com.appsmith.server.helpers.CommonGitFileUtils; import com.appsmith.server.helpers.CommonGitFileUtils;
import com.appsmith.server.helpers.DSLMigrationUtils; import com.appsmith.server.helpers.DSLMigrationUtils;
import com.appsmith.server.helpers.RedisUtils; import com.appsmith.server.helpers.RedisUtils;

View File

@ -0,0 +1,3 @@
package com.appsmith.server.git.autocommit;
public interface AutoCommitService extends AutoCommitServiceCE {}

View File

@ -0,0 +1,9 @@
package com.appsmith.server.git.autocommit;
import com.appsmith.server.dtos.AutoCommitResponseDTO;
import reactor.core.publisher.Mono;
public interface AutoCommitServiceCE {
Mono<AutoCommitResponseDTO> autoCommitApplication(String defaultApplicationId, String branchName);
}

View File

@ -0,0 +1,147 @@
package com.appsmith.server.git.autocommit;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.GitArtifactMetadata;
import com.appsmith.server.dtos.AutoCommitResponseDTO;
import com.appsmith.server.dtos.AutoCommitTriggerDTO;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.git.autocommit.helpers.AutoCommitEligibilityHelper;
import com.appsmith.server.git.autocommit.helpers.GitAutoCommitHelperImpl;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.solutions.ApplicationPermission;
import com.appsmith.server.solutions.PagePermission;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
import java.util.Set;
import static com.appsmith.server.dtos.AutoCommitResponseDTO.AutoCommitResponse.IDLE;
import static com.appsmith.server.dtos.AutoCommitResponseDTO.AutoCommitResponse.IN_PROGRESS;
import static com.appsmith.server.dtos.AutoCommitResponseDTO.AutoCommitResponse.LOCKED;
import static com.appsmith.server.dtos.AutoCommitResponseDTO.AutoCommitResponse.NON_GIT_APP;
import static com.appsmith.server.dtos.AutoCommitResponseDTO.AutoCommitResponse.PUBLISHED;
import static com.appsmith.server.dtos.AutoCommitResponseDTO.AutoCommitResponse.REQUIRED;
import static java.lang.Boolean.TRUE;
import static org.springframework.util.StringUtils.hasText;
@Slf4j
@RequiredArgsConstructor
public class AutoCommitServiceCEImpl implements AutoCommitServiceCE {
private final ApplicationService applicationService;
private final ApplicationPermission applicationPermission;
private final NewPageService newPageService;
private final PagePermission pagePermission;
private final AutoCommitEligibilityHelper autoCommitEligibilityHelper;
private final GitAutoCommitHelperImpl gitAutoCommitHelper;
@Override
public Mono<AutoCommitResponseDTO> autoCommitApplication(String defaultApplicationId, String branchName) {
if (!hasText(defaultApplicationId)) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.APPLICATION_ID));
}
if (!hasText(branchName)) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.BRANCH_NAME));
}
Mono<Application> applicationMonoCached = applicationService
.findByBranchNameAndDefaultApplicationId(
branchName, defaultApplicationId, applicationPermission.getEditPermission())
.switchIfEmpty(Mono.error(new AppsmithException(
AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION, defaultApplicationId)))
.cache();
// A page-dto which must exist in the git file system is required,
// this existence can be guaranteed by using application mode = published
// in appsmith git system an application could be only published if it's commited, converse is also true,
// hence a published page would definitely be present in git fs.
Mono<PageDTO> pageDTOMono = applicationMonoCached
.flatMap(application -> newPageService
.findByApplicationIdAndApplicationMode(
application.getId(), pagePermission.getEditPermission(), ApplicationMode.PUBLISHED)
.next())
.switchIfEmpty(Mono.error(
new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.PAGE, defaultApplicationId)));
return applicationMonoCached.zipWith(pageDTOMono).flatMap(tuple2 -> {
Application branchedApplication = tuple2.getT1();
PageDTO pageDTO = tuple2.getT2();
AutoCommitResponseDTO autoCommitResponseDTO = new AutoCommitResponseDTO();
if (branchedApplication.getGitApplicationMetadata() == null) {
autoCommitResponseDTO.setAutoCommitResponse(NON_GIT_APP);
return Mono.just(autoCommitResponseDTO);
}
String workspaceId = branchedApplication.getWorkspaceId();
GitArtifactMetadata gitArtifactMetadata = branchedApplication.getGitArtifactMetadata();
Mono<AutoCommitTriggerDTO> isAutoCommitRequiredMono =
autoCommitEligibilityHelper.isAutoCommitRequired(workspaceId, gitArtifactMetadata, pageDTO);
Mono<AutoCommitResponseDTO> autoCommitProgressDTOMono =
gitAutoCommitHelper.getAutoCommitProgress(defaultApplicationId, branchName);
return autoCommitProgressDTOMono.flatMap(autoCommitProgressDTO -> {
if (Set.of(LOCKED, IN_PROGRESS).contains(autoCommitProgressDTO.getAutoCommitResponse())) {
log.info(
"application with id: {}, has requested auto-commit for branch name: {}, however an event for branch name: {} is already in progress",
defaultApplicationId,
branchName,
autoCommitProgressDTO.getBranchName());
autoCommitResponseDTO.setAutoCommitResponse(autoCommitProgressDTO.getAutoCommitResponse());
autoCommitResponseDTO.setProgress(autoCommitProgressDTO.getProgress());
autoCommitResponseDTO.setBranchName(autoCommitProgressDTO.getBranchName());
return Mono.just(autoCommitResponseDTO);
}
return isAutoCommitRequiredMono.flatMap(autoCommitTriggerDTO -> {
if (!Boolean.TRUE.equals(autoCommitTriggerDTO.getIsAutoCommitRequired())) {
log.info(
"application with id: {}, and branch name: {} is not eligible for autocommit",
defaultApplicationId,
branchName);
autoCommitResponseDTO.setAutoCommitResponse(IDLE);
return Mono.just(autoCommitResponseDTO);
}
// Autocommit can be started
log.info(
"application with id: {}, and branch name: {} is eligible for autocommit",
defaultApplicationId,
branchName);
return gitAutoCommitHelper
.publishAutoCommitEvent(autoCommitTriggerDTO, defaultApplicationId, branchName)
.map(isEventPublished -> {
if (TRUE.equals(isEventPublished)) {
log.info(
"autocommit event for application with id: {}, and branch name: {} is published",
defaultApplicationId,
branchName);
autoCommitResponseDTO.setAutoCommitResponse(PUBLISHED);
return autoCommitResponseDTO;
}
log.info(
"application with id: {}, and branch name: {} does not fulfil the prerequisite for autocommit",
defaultApplicationId,
branchName);
autoCommitResponseDTO.setAutoCommitResponse(REQUIRED);
return autoCommitResponseDTO;
});
});
});
});
}
}

View File

@ -0,0 +1,29 @@
package com.appsmith.server.git.autocommit;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.git.autocommit.helpers.AutoCommitEligibilityHelper;
import com.appsmith.server.git.autocommit.helpers.GitAutoCommitHelperImpl;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.solutions.ApplicationPermission;
import com.appsmith.server.solutions.PagePermission;
import org.springframework.stereotype.Component;
@Component
public class AutoCommitServiceImpl extends AutoCommitServiceCEImpl implements AutoCommitService {
public AutoCommitServiceImpl(
ApplicationService applicationService,
ApplicationPermission applicationPermission,
NewPageService newPageService,
PagePermission pagePermission,
AutoCommitEligibilityHelper autoCommitEligibilityHelper,
GitAutoCommitHelperImpl gitAutoCommitHelper) {
super(
applicationService,
applicationPermission,
newPageService,
pagePermission,
autoCommitEligibilityHelper,
gitAutoCommitHelper);
}
}

View File

@ -11,6 +11,8 @@ public interface AutoCommitEligibilityHelper {
Mono<Boolean> isClientMigrationRequired(PageDTO pageDTO); Mono<Boolean> isClientMigrationRequired(PageDTO pageDTO);
Mono<Boolean> isClientMigrationRequiredFSOps(String workspaceId, GitArtifactMetadata gitMetadata, PageDTO pageDTO);
Mono<AutoCommitTriggerDTO> isAutoCommitRequired( Mono<AutoCommitTriggerDTO> isAutoCommitRequired(
String workspaceId, GitArtifactMetadata gitArtifactMetadata, PageDTO pageDTO); String workspaceId, GitArtifactMetadata gitArtifactMetadata, PageDTO pageDTO);
} }

View File

@ -23,6 +23,12 @@ public class AutoCommitEligibilityHelperFallbackImpl implements AutoCommitEligib
return Mono.just(FALSE); return Mono.just(FALSE);
} }
@Override
public Mono<Boolean> isClientMigrationRequiredFSOps(
String workspaceId, GitArtifactMetadata gitMetadata, PageDTO pageDTO) {
return Mono.just(FALSE);
}
@Override @Override
public Mono<AutoCommitTriggerDTO> isAutoCommitRequired( public Mono<AutoCommitTriggerDTO> isAutoCommitRequired(
String workspaceId, GitArtifactMetadata gitArtifactMetadata, PageDTO pageDTO) { String workspaceId, GitArtifactMetadata gitArtifactMetadata, PageDTO pageDTO) {

View File

@ -2,8 +2,6 @@ package com.appsmith.server.git.autocommit.helpers;
import com.appsmith.external.annotations.FeatureFlagged; import com.appsmith.external.annotations.FeatureFlagged;
import com.appsmith.external.enums.FeatureFlagEnum; import com.appsmith.external.enums.FeatureFlagEnum;
import com.appsmith.external.git.constants.GitConstants.GitCommandConstants;
import com.appsmith.server.constants.ArtifactType;
import com.appsmith.server.domains.GitArtifactMetadata; import com.appsmith.server.domains.GitArtifactMetadata;
import com.appsmith.server.domains.Layout; import com.appsmith.server.domains.Layout;
import com.appsmith.server.dtos.AutoCommitTriggerDTO; import com.appsmith.server.dtos.AutoCommitTriggerDTO;
@ -20,6 +18,8 @@ import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import static com.appsmith.external.git.constants.GitConstants.GitCommandConstants.AUTO_COMMIT_ELIGIBILITY;
import static com.appsmith.server.constants.ArtifactType.APPLICATION;
import static java.lang.Boolean.FALSE; import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
@ -35,16 +35,14 @@ public class AutoCommitEligibilityHelperImpl extends AutoCommitEligibilityHelper
private final GitRedisUtils gitRedisUtils; private final GitRedisUtils gitRedisUtils;
@Override @Override
@FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_server_autocommit_feature_enabled) @FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_eligibility_enabled)
public Mono<Boolean> isServerAutoCommitRequired(String workspaceId, GitArtifactMetadata gitMetadata) { public Mono<Boolean> isServerAutoCommitRequired(String workspaceId, GitArtifactMetadata gitMetadata) {
String defaultApplicationId = gitMetadata.getDefaultArtifactId(); String defaultApplicationId = gitMetadata.getDefaultArtifactId();
String branchName = gitMetadata.getBranchName(); String branchName = gitMetadata.getBranchName();
String repoName = gitMetadata.getRepoName();
Mono<Boolean> isServerMigrationRequiredMonoCached = commonGitFileUtils Mono<Boolean> isServerMigrationRequiredMonoCached = commonGitFileUtils
.getMetadataServerSchemaMigrationVersion( .getMetadataServerSchemaMigrationVersion(workspaceId, gitMetadata, FALSE, APPLICATION)
workspaceId, defaultApplicationId, repoName, branchName, ArtifactType.APPLICATION)
.map(serverSchemaVersion -> { .map(serverSchemaVersion -> {
log.info( log.info(
"server schema for application id : {} and branch name : {} is : {}", "server schema for application id : {} and branch name : {} is : {}",
@ -56,22 +54,27 @@ public class AutoCommitEligibilityHelperImpl extends AutoCommitEligibilityHelper
.defaultIfEmpty(FALSE) .defaultIfEmpty(FALSE)
.cache(); .cache();
return Mono.defer(() -> gitRedisUtils.addFileLock(defaultApplicationId, GitCommandConstants.METADATA, false)) return Mono.defer(() -> isServerMigrationRequiredMonoCached).onErrorResume(error -> {
.then(Mono.defer(() -> isServerMigrationRequiredMonoCached))
.then(Mono.defer(() -> gitRedisUtils.releaseFileLock(defaultApplicationId)))
.then(Mono.defer(() -> isServerMigrationRequiredMonoCached))
.onErrorResume(error -> {
log.debug( log.debug(
"error while retrieving the metadata for defaultApplicationId : {}, branchName : {} error : {}", "error while retrieving the metadata for defaultApplicationId : {}, branchName : {} error : {}",
defaultApplicationId, defaultApplicationId,
branchName, branchName,
error.getMessage()); error.getMessage());
return gitRedisUtils.releaseFileLock(defaultApplicationId).then(Mono.just(FALSE)); return Mono.just(FALSE);
}); });
} }
/**
* This method has been deprecated and is not being used anymore.
* It's been deprecated because, we are using the absolute source of truth
* that is the version key in the layout.
* /pages/<Page-Name>.json in file system for the finding out the Dsl in layout.
* @param pageDTO : pageDTO for the page for which migration was required.
* @return : a boolean whether the client requires a migration or not
*/
@Override @Override
@FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_feature_enabled) @Deprecated
@FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_eligibility_enabled)
public Mono<Boolean> isClientMigrationRequired(PageDTO pageDTO) { public Mono<Boolean> isClientMigrationRequired(PageDTO pageDTO) {
return dslMigrationUtils return dslMigrationUtils
.getLatestDslVersion() .getLatestDslVersion()
@ -91,26 +94,71 @@ public class AutoCommitEligibilityHelperImpl extends AutoCommitEligibilityHelper
} }
@Override @Override
@FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_feature_enabled) @FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_eligibility_enabled)
public Mono<Boolean> isClientMigrationRequiredFSOps(
String workspaceId, GitArtifactMetadata gitMetadata, PageDTO pageDTO) {
String defaultApplicationId = gitMetadata.getDefaultArtifactId();
String branchName = gitMetadata.getBranchName();
Mono<Integer> latestDslVersionMono = dslMigrationUtils.getLatestDslVersion();
Mono<Boolean> isClientMigrationRequired = latestDslVersionMono
.zipWith(commonGitFileUtils.getPageDslVersionNumber(
workspaceId, gitMetadata, pageDTO, TRUE, APPLICATION))
.map(tuple2 -> {
Integer latestDslVersion = tuple2.getT1();
org.json.JSONObject pageDSL = tuple2.getT2();
log.info("page dsl retrieved from file system");
return GitUtils.isMigrationRequired(pageDSL, latestDslVersion);
})
.defaultIfEmpty(FALSE)
.cache();
return Mono.defer(() -> isClientMigrationRequired).onErrorResume(error -> {
log.debug(
"error while fetching the dsl version for page : {}, defaultApplicationId : {}, branchName : {} error : {}",
pageDTO.getName(),
defaultApplicationId,
branchName,
error.getMessage());
return Mono.just(FALSE);
});
}
@Override
@FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_eligibility_enabled)
public Mono<AutoCommitTriggerDTO> isAutoCommitRequired( public Mono<AutoCommitTriggerDTO> isAutoCommitRequired(
String workspaceId, GitArtifactMetadata gitArtifactMetadata, PageDTO pageDTO) { String workspaceId, GitArtifactMetadata gitArtifactMetadata, PageDTO pageDTO) {
String defaultApplicationId = gitArtifactMetadata.getDefaultApplicationId();
Mono<Boolean> isClientAutocommitRequiredMono = Mono<Boolean> isClientAutocommitRequiredMono =
isClientMigrationRequired(pageDTO).defaultIfEmpty(FALSE); isClientMigrationRequiredFSOps(workspaceId, gitArtifactMetadata, pageDTO);
Mono<Boolean> isServerAutocommitRequiredMono = isServerAutoCommitRequired(workspaceId, gitArtifactMetadata); Mono<Boolean> isServerAutocommitRequiredMono =
isServerAutoCommitRequired(workspaceId, gitArtifactMetadata).cache();
return isServerAutocommitRequiredMono return Mono.defer(() -> gitRedisUtils.addFileLock(defaultApplicationId, AUTO_COMMIT_ELIGIBILITY))
.zipWith(isClientAutocommitRequiredMono) .then(isClientAutocommitRequiredMono.zipWhen(clientFlag -> {
.map(tuple2 -> { Mono<Boolean> serverFlagMono = isServerAutocommitRequiredMono;
Boolean serverFlag = tuple2.getT1(); // if client is required to migrate then,
Boolean clientFlag = tuple2.getT2(); // there is no requirement to fetch server flag as server is subset of client migration.
if (Boolean.TRUE.equals(clientFlag)) {
serverFlagMono = Mono.just(TRUE);
}
return serverFlagMono;
}))
.flatMap(tuple2 -> {
Boolean clientFlag = tuple2.getT1();
Boolean serverFlag = tuple2.getT2();
AutoCommitTriggerDTO autoCommitTriggerDTO = new AutoCommitTriggerDTO(); AutoCommitTriggerDTO autoCommitTriggerDTO = new AutoCommitTriggerDTO();
autoCommitTriggerDTO.setIsClientAutoCommitRequired(TRUE.equals(clientFlag)); autoCommitTriggerDTO.setIsClientAutoCommitRequired(TRUE.equals(clientFlag));
autoCommitTriggerDTO.setIsServerAutoCommitRequired(TRUE.equals(serverFlag)); autoCommitTriggerDTO.setIsServerAutoCommitRequired(TRUE.equals(serverFlag));
autoCommitTriggerDTO.setIsAutoCommitRequired((TRUE.equals(serverFlag) || TRUE.equals(clientFlag))); autoCommitTriggerDTO.setIsAutoCommitRequired((TRUE.equals(serverFlag) || TRUE.equals(clientFlag)));
return autoCommitTriggerDTO;
return gitRedisUtils.releaseFileLock(defaultApplicationId).then(Mono.just(autoCommitTriggerDTO));
}); });
} }
} }

View File

@ -1,17 +1,17 @@
package com.appsmith.server.git.autocommit.helpers; package com.appsmith.server.git.autocommit.helpers;
import com.appsmith.server.dtos.AutoCommitProgressDTO; import com.appsmith.server.dtos.AutoCommitResponseDTO;
import com.appsmith.server.dtos.AutoCommitTriggerDTO; import com.appsmith.server.dtos.AutoCommitTriggerDTO;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
public interface GitAutoCommitHelper { public interface GitAutoCommitHelper {
Mono<AutoCommitProgressDTO> getAutoCommitProgress(String applicationId); Mono<AutoCommitResponseDTO> getAutoCommitProgress(String defaultApplicationId, String branchName);
Mono<Boolean> autoCommitClientMigration(String defaultApplicationId, String branchName); Mono<Boolean> autoCommitClientMigration(String defaultApplicationId, String branchName);
Mono<Boolean> autoCommitServerMigration(String defaultApplicationId, String branchName); Mono<Boolean> autoCommitServerMigration(String defaultApplicationId, String branchName);
Mono<Boolean> autoCommitApplication( Mono<Boolean> publishAutoCommitEvent(
AutoCommitTriggerDTO autoCommitTriggerDTO, String defaultApplicationId, String branchName); AutoCommitTriggerDTO autoCommitTriggerDTO, String defaultApplicationId, String branchName);
} }

View File

@ -1,6 +1,6 @@
package com.appsmith.server.git.autocommit.helpers; package com.appsmith.server.git.autocommit.helpers;
import com.appsmith.server.dtos.AutoCommitProgressDTO; import com.appsmith.server.dtos.AutoCommitResponseDTO;
import com.appsmith.server.dtos.AutoCommitTriggerDTO; import com.appsmith.server.dtos.AutoCommitTriggerDTO;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -19,12 +19,12 @@ public class GitAutoCommitHelperFallbackImpl implements GitAutoCommitHelper {
} }
@Override @Override
public Mono<AutoCommitProgressDTO> getAutoCommitProgress(String applicationId) { public Mono<AutoCommitResponseDTO> getAutoCommitProgress(String defaultApplicationId, String branchName) {
return Mono.empty(); return Mono.just(new AutoCommitResponseDTO(AutoCommitResponseDTO.AutoCommitResponse.IDLE));
} }
@Override @Override
public Mono<Boolean> autoCommitApplication( public Mono<Boolean> publishAutoCommitEvent(
AutoCommitTriggerDTO autoCommitTriggerDTO, String defaultApplicationId, String branchName) { AutoCommitTriggerDTO autoCommitTriggerDTO, String defaultApplicationId, String branchName) {
return Mono.just(Boolean.FALSE); return Mono.just(Boolean.FALSE);
} }

View File

@ -6,10 +6,10 @@ import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.domains.Application; import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.GitArtifactMetadata; import com.appsmith.server.domains.GitArtifactMetadata;
import com.appsmith.server.domains.GitProfile; import com.appsmith.server.domains.GitProfile;
import com.appsmith.server.dtos.AutoCommitProgressDTO; import com.appsmith.server.dtos.AutoCommitResponseDTO;
import com.appsmith.server.dtos.AutoCommitTriggerDTO; import com.appsmith.server.dtos.AutoCommitTriggerDTO;
import com.appsmith.server.events.AutoCommitEvent; import com.appsmith.server.events.AutoCommitEvent;
import com.appsmith.server.git.AutoCommitEventHandler; import com.appsmith.server.git.autocommit.AutoCommitEventHandler;
import com.appsmith.server.git.common.CommonGitService; import com.appsmith.server.git.common.CommonGitService;
import com.appsmith.server.helpers.GitPrivateRepoHelper; import com.appsmith.server.helpers.GitPrivateRepoHelper;
import com.appsmith.server.helpers.GitUtils; import com.appsmith.server.helpers.GitUtils;
@ -23,6 +23,10 @@ import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import static com.appsmith.server.dtos.AutoCommitResponseDTO.AutoCommitResponse.IDLE;
import static com.appsmith.server.dtos.AutoCommitResponseDTO.AutoCommitResponse.IN_PROGRESS;
import static com.appsmith.server.dtos.AutoCommitResponseDTO.AutoCommitResponse.LOCKED;
@Slf4j @Slf4j
@Primary @Primary
@Service @Service
@ -53,17 +57,26 @@ public class GitAutoCommitHelperImpl extends GitAutoCommitHelperFallbackImpl imp
} }
@Override @Override
public Mono<AutoCommitProgressDTO> getAutoCommitProgress(String applicationId) { public Mono<AutoCommitResponseDTO> getAutoCommitProgress(String defaultApplicationId, String branchName) {
return redisUtils return redisUtils
.getRunningAutoCommitBranchName(applicationId) .getRunningAutoCommitBranchName(defaultApplicationId)
.zipWith(redisUtils.getAutoCommitProgress(applicationId)) .zipWith(redisUtils.getAutoCommitProgress(defaultApplicationId))
.map(tuple2 -> { .map(tuple2 -> {
AutoCommitProgressDTO autoCommitProgressDTO = new AutoCommitProgressDTO(Boolean.TRUE); String branchNameFromRedis = tuple2.getT1();
autoCommitProgressDTO.setBranchName(tuple2.getT1());
autoCommitProgressDTO.setProgress(tuple2.getT2()); AutoCommitResponseDTO autoCommitResponseDTO = new AutoCommitResponseDTO();
return autoCommitProgressDTO; autoCommitResponseDTO.setProgress(tuple2.getT2());
autoCommitResponseDTO.setBranchName(branchNameFromRedis);
if (branchNameFromRedis.equals(branchName)) {
autoCommitResponseDTO.setAutoCommitResponse(IN_PROGRESS);
} else {
autoCommitResponseDTO.setAutoCommitResponse(LOCKED);
}
return autoCommitResponseDTO;
}) })
.defaultIfEmpty(new AutoCommitProgressDTO(Boolean.FALSE)); .defaultIfEmpty(new AutoCommitResponseDTO(IDLE));
} }
/** /**
@ -114,7 +127,7 @@ public class GitAutoCommitHelperImpl extends GitAutoCommitHelperFallbackImpl imp
} }
@Override @Override
@FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_server_autocommit_feature_enabled) @FeatureFlagged(featureFlagName = FeatureFlagEnum.release_git_autocommit_feature_enabled)
public Mono<Boolean> autoCommitServerMigration(String defaultApplicationId, String branchName) { public Mono<Boolean> autoCommitServerMigration(String defaultApplicationId, String branchName) {
return autoCommitApplication(defaultApplicationId, branchName, Boolean.FALSE); return autoCommitApplication(defaultApplicationId, branchName, Boolean.FALSE);
} }
@ -203,7 +216,7 @@ public class GitAutoCommitHelperImpl extends GitAutoCommitHelperFallbackImpl imp
}); });
} }
public Mono<Boolean> autoCommitApplication( public Mono<Boolean> publishAutoCommitEvent(
AutoCommitTriggerDTO autoCommitTriggerDTO, String defaultApplicationId, String branchName) { AutoCommitTriggerDTO autoCommitTriggerDTO, String defaultApplicationId, String branchName) {
if (!Boolean.TRUE.equals(autoCommitTriggerDTO.getIsAutoCommitRequired())) { if (!Boolean.TRUE.equals(autoCommitTriggerDTO.getIsAutoCommitRequired())) {

View File

@ -10,7 +10,7 @@ import com.appsmith.server.domains.GitArtifactMetadata;
import com.appsmith.server.domains.GitAuth; import com.appsmith.server.domains.GitAuth;
import com.appsmith.server.domains.GitProfile; import com.appsmith.server.domains.GitProfile;
import com.appsmith.server.dtos.ArtifactImportDTO; import com.appsmith.server.dtos.ArtifactImportDTO;
import com.appsmith.server.dtos.AutoCommitProgressDTO; import com.appsmith.server.dtos.AutoCommitResponseDTO;
import com.appsmith.server.dtos.GitCommitDTO; import com.appsmith.server.dtos.GitCommitDTO;
import com.appsmith.server.dtos.GitConnectDTO; import com.appsmith.server.dtos.GitConnectDTO;
import com.appsmith.server.dtos.GitDocsDTO; import com.appsmith.server.dtos.GitDocsDTO;
@ -105,7 +105,8 @@ public interface CommonGitServiceCE {
Mono<Boolean> toggleAutoCommitEnabled(String defaultArtifactId, ArtifactType artifactType); Mono<Boolean> toggleAutoCommitEnabled(String defaultArtifactId, ArtifactType artifactType);
Mono<AutoCommitProgressDTO> getAutoCommitProgress(String applicationId, ArtifactType artifactType); Mono<AutoCommitResponseDTO> getAutoCommitProgress(
String applicationId, String branchName, ArtifactType artifactType);
Mono<Boolean> autoCommitApplication(String defaultApplicationId, String branchName, ArtifactType artifactType); Mono<Boolean> autoCommitApplication(String defaultApplicationId, String branchName, ArtifactType artifactType);

View File

@ -34,7 +34,7 @@ import com.appsmith.server.domains.Workspace;
import com.appsmith.server.dtos.ApplicationImportDTO; import com.appsmith.server.dtos.ApplicationImportDTO;
import com.appsmith.server.dtos.ArtifactExchangeJson; import com.appsmith.server.dtos.ArtifactExchangeJson;
import com.appsmith.server.dtos.ArtifactImportDTO; import com.appsmith.server.dtos.ArtifactImportDTO;
import com.appsmith.server.dtos.AutoCommitProgressDTO; import com.appsmith.server.dtos.AutoCommitResponseDTO;
import com.appsmith.server.dtos.GitCommitDTO; import com.appsmith.server.dtos.GitCommitDTO;
import com.appsmith.server.dtos.GitConnectDTO; import com.appsmith.server.dtos.GitConnectDTO;
import com.appsmith.server.dtos.GitDocsDTO; import com.appsmith.server.dtos.GitDocsDTO;
@ -3377,8 +3377,9 @@ public class CommonGitServiceCEImpl implements CommonGitServiceCE {
} }
@Override @Override
public Mono<AutoCommitProgressDTO> getAutoCommitProgress(String artifactId, ArtifactType artifactType) { public Mono<AutoCommitResponseDTO> getAutoCommitProgress(
return gitAutoCommitHelper.getAutoCommitProgress(artifactId); String artifactId, String branchName, ArtifactType artifactType) {
return gitAutoCommitHelper.getAutoCommitProgress(artifactId, branchName);
} }
@Override @Override

View File

@ -1,11 +1,13 @@
package com.appsmith.server.helpers; package com.appsmith.server.helpers;
import com.appsmith.external.git.FileInterface; import com.appsmith.external.git.FileInterface;
import com.appsmith.external.git.operations.FileOperations;
import com.appsmith.external.models.ApplicationGitReference;
import com.appsmith.git.files.FileUtilsImpl; import com.appsmith.git.files.FileUtilsImpl;
import com.appsmith.server.applications.git.ApplicationGitFileUtilsImpl;
import com.appsmith.server.helpers.ce.CommonGitFileUtilsCE; import com.appsmith.server.helpers.ce.CommonGitFileUtilsCE;
import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.SessionUserService;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -16,10 +18,12 @@ import org.springframework.stereotype.Component;
public class CommonGitFileUtils extends CommonGitFileUtilsCE { public class CommonGitFileUtils extends CommonGitFileUtilsCE {
public CommonGitFileUtils( public CommonGitFileUtils(
ApplicationGitFileUtilsImpl applicationGitFileUtils, ArtifactGitFileUtils<ApplicationGitReference> applicationGitFileUtils,
FileInterface fileUtils, FileInterface fileUtils,
FileOperations fileOperations,
AnalyticsService analyticsService, AnalyticsService analyticsService,
SessionUserService sessionUserService) { SessionUserService sessionUserService,
super(applicationGitFileUtils, fileUtils, analyticsService, sessionUserService); Gson gson) {
super(applicationGitFileUtils, fileUtils, fileOperations, analyticsService, sessionUserService);
} }
} }

View File

@ -177,6 +177,18 @@ public class GitUtils {
return isMigrationRequired; return isMigrationRequired;
} }
public static boolean isMigrationRequired(org.json.JSONObject layoutDsl, Integer latestDslVersion) {
boolean isMigrationRequired = true;
String versionKey = "version";
if (layoutDsl.has(versionKey)) {
int currentDslVersion = layoutDsl.getInt(versionKey);
if (currentDslVersion >= latestDslVersion) {
isMigrationRequired = false;
}
}
return isMigrationRequired;
}
public static boolean isAutoCommitEnabled(GitArtifactMetadata gitArtifactMetadata) { public static boolean isAutoCommitEnabled(GitArtifactMetadata gitArtifactMetadata) {
return gitArtifactMetadata.getAutoCommitConfig() == null return gitArtifactMetadata.getAutoCommitConfig() == null
|| gitArtifactMetadata.getAutoCommitConfig().getEnabled(); || gitArtifactMetadata.getAutoCommitConfig().getEnabled();

View File

@ -2,6 +2,7 @@ package com.appsmith.server.helpers.ce;
import com.appsmith.external.constants.AnalyticsEvents; import com.appsmith.external.constants.AnalyticsEvents;
import com.appsmith.external.git.FileInterface; import com.appsmith.external.git.FileInterface;
import com.appsmith.external.git.operations.FileOperations;
import com.appsmith.external.helpers.Stopwatch; import com.appsmith.external.helpers.Stopwatch;
import com.appsmith.external.models.ApplicationGitReference; import com.appsmith.external.models.ApplicationGitReference;
import com.appsmith.external.models.ArtifactGitReference; import com.appsmith.external.models.ArtifactGitReference;
@ -11,8 +12,10 @@ import com.appsmith.git.constants.CommonConstants;
import com.appsmith.git.files.FileUtilsImpl; import com.appsmith.git.files.FileUtilsImpl;
import com.appsmith.server.constants.ArtifactType; import com.appsmith.server.constants.ArtifactType;
import com.appsmith.server.constants.FieldName; import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.GitArtifactMetadata;
import com.appsmith.server.dtos.ApplicationJson; import com.appsmith.server.dtos.ApplicationJson;
import com.appsmith.server.dtos.ArtifactExchangeJson; import com.appsmith.server.dtos.ArtifactExchangeJson;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.ArtifactGitFileUtils; import com.appsmith.server.helpers.ArtifactGitFileUtils;
@ -25,6 +28,7 @@ import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -37,6 +41,8 @@ import java.nio.file.Paths;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static com.appsmith.external.git.constants.ce.GitConstantsCE.GitCommandConstantsCE.CHECKOUT_BRANCH;
import static com.appsmith.external.git.constants.ce.GitConstantsCE.RECONSTRUCT_PAGE;
import static com.appsmith.git.constants.CommonConstants.CLIENT_SCHEMA_VERSION; import static com.appsmith.git.constants.CommonConstants.CLIENT_SCHEMA_VERSION;
import static com.appsmith.git.constants.CommonConstants.FILE_FORMAT_VERSION; import static com.appsmith.git.constants.CommonConstants.FILE_FORMAT_VERSION;
import static com.appsmith.git.constants.CommonConstants.SERVER_SCHEMA_VERSION; import static com.appsmith.git.constants.CommonConstants.SERVER_SCHEMA_VERSION;
@ -50,6 +56,7 @@ public class CommonGitFileUtilsCE {
protected final ArtifactGitFileUtils<ApplicationGitReference> applicationGitFileUtils; protected final ArtifactGitFileUtils<ApplicationGitReference> applicationGitFileUtils;
private final FileInterface fileUtils; private final FileInterface fileUtils;
private final FileOperations fileOperations;
private final AnalyticsService analyticsService; private final AnalyticsService analyticsService;
private final SessionUserService sessionUserService; private final SessionUserService sessionUserService;
@ -282,15 +289,21 @@ public class CommonGitFileUtilsCE {
} }
public Mono<Map<String, Integer>> reconstructMetadataFromRepo( public Mono<Map<String, Integer>> reconstructMetadataFromRepo(
String workspaceId, String applicationId, String repoName, String branchName, ArtifactType artifactType) { String workspaceId,
String applicationId,
String repoName,
String branchName,
Boolean isResetToLastCommitRequired,
ArtifactType artifactType) {
ArtifactGitFileUtils<?> artifactGitFileUtils = getArtifactBasedFileHelper(artifactType); ArtifactGitFileUtils<?> artifactGitFileUtils = getArtifactBasedFileHelper(artifactType);
Path baseRepoSuffix = artifactGitFileUtils.getRepoSuffixPath(workspaceId, applicationId, repoName); Path baseRepoSuffix = artifactGitFileUtils.getRepoSuffixPath(workspaceId, applicationId, repoName);
return fileUtils return fileUtils
.reconstructMetadataFromGitRepo(workspaceId, applicationId, repoName, branchName, baseRepoSuffix) .reconstructMetadataFromGitRepo(
workspaceId, applicationId, repoName, branchName, baseRepoSuffix, isResetToLastCommitRequired)
.onErrorResume(error -> Mono.error( .onErrorResume(error -> Mono.error(
new AppsmithException(AppsmithError.GIT_ACTION_FAILED, "checkout", error.getMessage()))) new AppsmithException(AppsmithError.GIT_ACTION_FAILED, CHECKOUT_BRANCH, error.getMessage())))
.map(metadata -> { .map(metadata -> {
Gson gson = new Gson(); Gson gson = new Gson();
JsonObject metadataJsonObject = JsonObject metadataJsonObject =
@ -311,19 +324,21 @@ public class CommonGitFileUtilsCE {
* Provides the server schema version in the application json for the given branch * Provides the server schema version in the application json for the given branch
* *
* @param workspaceId : workspaceId of the artifact * @param workspaceId : workspaceId of the artifact
* @param defaultArtifactId : default branch id of the artifact * @param gitArtifactMetadata : git artifact metadata of the application
* @param repoName : repository name * @param isResetToLastCommitRequired : would we need to execute reset command
* @param branchName : current branch name of the artifact
* @param artifactType : artifact type of this operation * @param artifactType : artifact type of this operation
* @return the server schema migration version number * @return the server schema migration version number
*/ */
public Mono<Integer> getMetadataServerSchemaMigrationVersion( public Mono<Integer> getMetadataServerSchemaMigrationVersion(
String workspaceId, String workspaceId,
String defaultArtifactId, GitArtifactMetadata gitArtifactMetadata,
String repoName, Boolean isResetToLastCommitRequired,
String branchName,
ArtifactType artifactType) { ArtifactType artifactType) {
String defaultArtifactId = gitArtifactMetadata.getDefaultArtifactId();
String branchName = gitArtifactMetadata.getBranchName();
String repoName = gitArtifactMetadata.getRepoName();
if (!hasText(workspaceId)) { if (!hasText(workspaceId)) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.WORKSPACE_ID)); return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.WORKSPACE_ID));
} }
@ -340,11 +355,69 @@ public class CommonGitFileUtilsCE {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.REPO_NAME)); return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.REPO_NAME));
} }
return reconstructMetadataFromRepo(workspaceId, defaultArtifactId, repoName, branchName, artifactType) Mono<Integer> serverSchemaNumberMono = reconstructMetadataFromRepo(
workspaceId, defaultArtifactId, repoName, branchName, isResetToLastCommitRequired, artifactType)
.map(metadataMap -> { .map(metadataMap -> {
return metadataMap.getOrDefault( return metadataMap.getOrDefault(
CommonConstants.SERVER_SCHEMA_VERSION, JsonSchemaVersions.serverVersion); CommonConstants.SERVER_SCHEMA_VERSION, JsonSchemaVersions.serverVersion);
}); });
return Mono.create(
sink -> serverSchemaNumberMono.subscribe(sink::success, sink::error, null, sink.currentContext()));
}
/**
* Provides the server schema version in the application json for the given branch
*
* @param workspaceId : workspace id of the application
* @param gitArtifactMetadata : git artifact metadata
* @param isResetToLastCommitRequired : whether git reset hard is required
* @param artifactType : artifact type of this operation
* @return the server schema migration version number
*/
public Mono<JSONObject> getPageDslVersionNumber(
String workspaceId,
GitArtifactMetadata gitArtifactMetadata,
PageDTO pageDTO,
Boolean isResetToLastCommitRequired,
ArtifactType artifactType) {
String defaultArtifactId = gitArtifactMetadata.getDefaultArtifactId();
String branchName = gitArtifactMetadata.getBranchName();
String repoName = gitArtifactMetadata.getRepoName();
if (!hasText(workspaceId)) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.WORKSPACE_ID));
}
if (!hasText(defaultArtifactId)) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ARTIFACT_ID));
}
if (!hasText(branchName)) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.BRANCH_NAME));
}
if (!hasText(repoName)) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.REPO_NAME));
}
if (pageDTO == null) {
return Mono.error(new AppsmithException(AppsmithError.PAGE_ID_NOT_GIVEN, FieldName.PAGE));
}
ArtifactGitFileUtils<?> artifactGitFileUtils = getArtifactBasedFileHelper(artifactType);
Path baseRepoSuffix = artifactGitFileUtils.getRepoSuffixPath(workspaceId, defaultArtifactId, repoName);
Mono<JSONObject> jsonObjectMono = fileUtils
.reconstructPageFromGitRepo(pageDTO.getName(), branchName, baseRepoSuffix, isResetToLastCommitRequired)
.onErrorResume(error -> Mono.error(
new AppsmithException(AppsmithError.GIT_ACTION_FAILED, RECONSTRUCT_PAGE, error.getMessage())))
.map(pageJson -> {
return fileOperations.getMainContainer(pageJson);
});
return Mono.create(sink -> jsonObjectMono.subscribe(sink::success, sink::error, null, sink.currentContext()));
} }
private Integer getServerSchemaVersion(JsonObject metadataJsonObject) { private Integer getServerSchemaVersion(JsonObject metadataJsonObject) {

View File

@ -94,13 +94,15 @@ public class PartialImportServiceCEImpl implements PartialImportServiceCE {
@Override @Override
public Mono<Application> importResourceInPage( public Mono<Application> importResourceInPage(
String workspaceId, String applicationId, String pageId, String branchName, Part file) { String workspaceId, String applicationId, String pageId, String branchName, Part file) {
Mono<User> currUserMono = sessionUserService.getCurrentUser();
return importService return importService
.extractArtifactExchangeJson(file) .extractArtifactExchangeJson(file)
.flatMap(artifactExchangeJson -> { .flatMap(artifactExchangeJson -> {
if (artifactExchangeJson instanceof ApplicationJson if (artifactExchangeJson instanceof ApplicationJson
&& isImportableResource((ApplicationJson) artifactExchangeJson)) { && isImportableResource((ApplicationJson) artifactExchangeJson)) {
return importResourceInPage( return importResourceInPage(workspaceId, applicationId, pageId, branchName, (ApplicationJson)
workspaceId, applicationId, pageId, branchName, (ApplicationJson) artifactExchangeJson); artifactExchangeJson)
.zipWith(currUserMono);
} else { } else {
return Mono.error( return Mono.error(
new AppsmithException( new AppsmithException(
@ -108,7 +110,21 @@ public class PartialImportServiceCEImpl implements PartialImportServiceCE {
"The file is not compatible with the current partial import operation. Please check the file and try again.")); "The file is not compatible with the current partial import operation. Please check the file and try again."));
} }
}) })
.map(BuildingBlockImportDTO::getApplication); .flatMap(tuple -> {
final BuildingBlockImportDTO buildingBlockImportDTO = tuple.getT1();
final User user = tuple.getT2();
final Map<String, Object> eventData =
Map.of(FieldName.APPLICATION, buildingBlockImportDTO.getApplication());
final Map<String, Object> data = Map.of(
FieldName.APPLICATION_ID, applicationId,
FieldName.WORKSPACE_ID,
buildingBlockImportDTO.getApplication().getWorkspaceId(),
FieldName.EVENT_DATA, eventData);
return analyticsService
.sendEvent(AnalyticsEvents.PARTIAL_IMPORT.getEventName(), user.getUsername(), data)
.thenReturn(buildingBlockImportDTO.getApplication());
});
} }
private boolean isImportableResource(ApplicationJson artifactExchangeJson) { private boolean isImportableResource(ApplicationJson artifactExchangeJson) {
@ -128,8 +144,6 @@ public class PartialImportServiceCEImpl implements PartialImportServiceCE {
Mono<String> branchedPageIdMono = Mono<String> branchedPageIdMono =
newPageService.findBranchedPageId(branchName, pageId, AclPermission.MANAGE_PAGES); newPageService.findBranchedPageId(branchName, pageId, AclPermission.MANAGE_PAGES);
Mono<User> currUserMono = sessionUserService.getCurrentUser();
// Extract file and get App Json // Extract file and get App Json
Mono<Application> partiallyImportedAppMono = getImportApplicationPermissions() Mono<Application> partiallyImportedAppMono = getImportApplicationPermissions()
.flatMap(permissionProvider -> { .flatMap(permissionProvider -> {
@ -273,25 +287,13 @@ public class PartialImportServiceCEImpl implements PartialImportServiceCE {
}) })
.as(transactionalOperator::transactional); .as(transactionalOperator::transactional);
// Send Analytics event return partiallyImportedAppMono.map(application -> {
return partiallyImportedAppMono.zipWith(currUserMono).flatMap(tuple -> {
Application application = tuple.getT1();
User user = tuple.getT2();
final Map<String, Object> eventData = Map.of(FieldName.APPLICATION, application);
final Map<String, Object> data = Map.of(
FieldName.APPLICATION_ID, application.getId(),
FieldName.WORKSPACE_ID, application.getWorkspaceId(),
FieldName.EVENT_DATA, eventData);
BuildingBlockImportDTO buildingBlockImportDTO = new BuildingBlockImportDTO(); BuildingBlockImportDTO buildingBlockImportDTO = new BuildingBlockImportDTO();
buildingBlockImportDTO.setApplication(application); buildingBlockImportDTO.setApplication(application);
buildingBlockImportDTO.setWidgetDsl(applicationJson.getWidgets()); buildingBlockImportDTO.setWidgetDsl(applicationJson.getWidgets());
buildingBlockImportDTO.setRefactoredEntityNameMap( buildingBlockImportDTO.setRefactoredEntityNameMap(
mappedImportableResourcesDTO.getRefactoringNameReference()); mappedImportableResourcesDTO.getRefactoringNameReference());
return buildingBlockImportDTO;
return analyticsService
.sendEvent(AnalyticsEvents.PARTIAL_IMPORT.getEventName(), user.getUsername(), data)
.thenReturn(buildingBlockImportDTO);
}); });
} }

View File

@ -244,12 +244,10 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
List<Mono<LayoutDTO>> monoList = new ArrayList<>(); List<Mono<LayoutDTO>> monoList = new ArrayList<>();
for (UpdateMultiplePageLayoutDTO.UpdatePageLayoutDTO pageLayout : for (UpdateMultiplePageLayoutDTO.UpdatePageLayoutDTO pageLayout :
updateMultiplePageLayoutDTO.getPageLayouts()) { updateMultiplePageLayoutDTO.getPageLayouts()) {
final Layout layout = new Layout();
layout.setDsl(pageLayout.getLayout().dsl());
Mono<LayoutDTO> updatedLayoutMono = this.updateLayout( Mono<LayoutDTO> updatedLayoutMono = this.updateLayout(
pageLayout.getPageId(), pageLayout.getPageId(), defaultApplicationId, pageLayout.getLayoutId(), layout, branchName);
defaultApplicationId,
pageLayout.getLayoutId(),
pageLayout.getLayout(),
branchName);
monoList.add(updatedLayoutMono); monoList.add(updatedLayoutMono);
} }
return Flux.merge(monoList).then(Mono.just(monoList.size())); return Flux.merge(monoList).then(Mono.just(monoList.size()));

View File

@ -123,8 +123,10 @@ public class JsonSchemaMigration {
applicationJson.setServerSchemaVersion(6); applicationJson.setServerSchemaVersion(6);
case 6: case 6:
MigrationHelperMethods.ensureXmlParserPresenceInCustomJsLibList(applicationJson); MigrationHelperMethods.ensureXmlParserPresenceInCustomJsLibList(applicationJson);
applicationJson.setServerSchemaVersion(7); applicationJson.setServerSchemaVersion(7);
case 7:
applicationJson.setServerSchemaVersion(8);
default: default:
// Unable to detect the serverSchema // Unable to detect the serverSchema
} }

View File

@ -12,6 +12,6 @@ import lombok.Getter;
*/ */
@Getter @Getter
public class JsonSchemaVersions { public class JsonSchemaVersions {
public static final Integer serverVersion = 7; public static final Integer serverVersion = 8;
public static final Integer clientVersion = 1; public static final Integer clientVersion = 1;
} }

View File

@ -101,4 +101,7 @@ public interface NewPageServiceCE extends CrudService<NewPage, String> {
Application branchedApplication, List<NewPage> newPages, boolean viewMode, boolean isRecentlyAccessed); Application branchedApplication, List<NewPage> newPages, boolean viewMode, boolean isRecentlyAccessed);
Mono<String> updateDependencyMap(String pageId, Map<String, List<String>> dependencyMap, String branchName); Mono<String> updateDependencyMap(String pageId, Map<String, List<String>> dependencyMap, String branchName);
Flux<PageDTO> findByApplicationIdAndApplicationMode(
String applicationId, AclPermission permission, ApplicationMode applicationMode);
} }

View File

@ -687,4 +687,23 @@ public class NewPageServiceCEImpl extends BaseService<NewPageRepository, NewPage
return Mono.just(count.toString()); return Mono.just(count.toString());
}); });
} }
@Override
public Flux<PageDTO> findByApplicationIdAndApplicationMode(
String applicationId, AclPermission permission, ApplicationMode applicationMode) {
Boolean viewMode = ApplicationMode.PUBLISHED.equals(applicationMode);
return findNewPagesByApplicationId(applicationId, permission)
.filter(page -> {
PageDTO pageDTO;
if (ApplicationMode.PUBLISHED.equals(applicationMode)) {
pageDTO = page.getPublishedPage();
} else {
pageDTO = page.getUnpublishedPage();
}
boolean isDeletedOrNull = pageDTO == null || pageDTO.getDeletedAt() != null;
return !isDeletedOrNull;
})
.flatMap(page -> getPageByViewMode(page, viewMode));
}
} }

View File

@ -95,8 +95,6 @@ public class ApplicationPageServiceImpl extends ApplicationPageServiceCEImpl imp
datasourceRepository, datasourceRepository,
datasourcePermission, datasourcePermission,
dslMigrationUtils, dslMigrationUtils,
gitAutoCommitHelper,
autoCommitEligibilityHelper,
actionClonePageService, actionClonePageService,
actionCollectionClonePageService); actionCollectionClonePageService);
} }

View File

@ -34,9 +34,6 @@ import com.appsmith.server.dtos.PageNameIdDTO;
import com.appsmith.server.dtos.PluginTypeAndCountDTO; import com.appsmith.server.dtos.PluginTypeAndCountDTO;
import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.git.autocommit.helpers.AutoCommitEligibilityHelper;
import com.appsmith.server.git.autocommit.helpers.GitAutoCommitHelper;
import com.appsmith.server.helpers.CollectionUtils;
import com.appsmith.server.helpers.CommonGitFileUtils; import com.appsmith.server.helpers.CommonGitFileUtils;
import com.appsmith.server.helpers.DSLMigrationUtils; import com.appsmith.server.helpers.DSLMigrationUtils;
import com.appsmith.server.helpers.GitUtils; import com.appsmith.server.helpers.GitUtils;
@ -127,8 +124,6 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
private final DatasourceRepository datasourceRepository; private final DatasourceRepository datasourceRepository;
private final DatasourcePermission datasourcePermission; private final DatasourcePermission datasourcePermission;
private final DSLMigrationUtils dslMigrationUtils; private final DSLMigrationUtils dslMigrationUtils;
private final GitAutoCommitHelper gitAutoCommitHelper;
private final AutoCommitEligibilityHelper autoCommitEligibilityHelper;
private final ClonePageService<NewAction> actionClonePageService; private final ClonePageService<NewAction> actionClonePageService;
private final ClonePageService<ActionCollection> actionCollectionClonePageService; private final ClonePageService<ActionCollection> actionCollectionClonePageService;
@ -299,76 +294,7 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
return newPageService return newPageService
.findNewPagesByApplicationId(branchedApplication.getId(), pagePermission.getReadPermission()) .findNewPagesByApplicationId(branchedApplication.getId(), pagePermission.getReadPermission())
.filter(newPage -> pageIds.contains(newPage.getId())) .filter(newPage -> pageIds.contains(newPage.getId()))
.collectList() .collectList();
.flatMap(newPageList -> {
if (Boolean.TRUE.equals(viewMode)) {
return Mono.just(newPageList);
}
// autocommit if migration is required
return migrateSchemasForGitConnectedApps(branchedApplication, newPageList)
.onErrorResume(error -> {
log.debug(
"Skipping the autocommit for applicationId : {} due to error; {}",
branchedApplication.getId(),
error.getMessage());
return Mono.just(Boolean.FALSE);
})
.thenReturn(newPageList);
});
}
/**
* Publishes the autocommit if it's eligible for one
* @param application : the branched application which requires schemaMigration
* @param newPages : list of pages from db
* @return : a boolean publisher
*/
private Mono<Boolean> migrateSchemasForGitConnectedApps(Application application, List<NewPage> newPages) {
if (CollectionUtils.isNullOrEmpty(newPages)) {
return Mono.just(Boolean.FALSE);
}
GitArtifactMetadata gitMetadata = application.getGitArtifactMetadata();
if (application.getGitArtifactMetadata() == null) {
return Mono.just(Boolean.FALSE);
}
String defaultApplicationId = gitMetadata.getDefaultArtifactId();
String branchName = gitMetadata.getBranchName();
String workspaceId = application.getWorkspaceId();
if (!StringUtils.hasText(branchName)) {
log.debug(
"Skipping the autocommit for applicationId : {}, branch name is not present", application.getId());
return Mono.just(Boolean.FALSE);
}
if (!StringUtils.hasText(defaultApplicationId)) {
log.debug(
"Skipping the autocommit for applicationId : {}, defaultApplicationId is not present",
application.getId());
return Mono.just(Boolean.FALSE);
}
// since this method is only called when the app is in edit mode
Mono<PageDTO> pageDTOMono = getPage(newPages.get(0), false);
return pageDTOMono.flatMap(pageDTO -> {
return autoCommitEligibilityHelper
.isAutoCommitRequired(workspaceId, gitMetadata, pageDTO)
.flatMap(autoCommitTriggerDTO -> {
if (Boolean.TRUE.equals(autoCommitTriggerDTO.getIsAutoCommitRequired())) {
return gitAutoCommitHelper.autoCommitApplication(
autoCommitTriggerDTO, defaultApplicationId, branchName);
}
return Mono.just(Boolean.FALSE);
});
});
} }
@Override @Override

View File

@ -1,25 +1,37 @@
package com.appsmith.server.git; package com.appsmith.server.git;
import com.appsmith.external.converters.ISOStringToInstantConverter;
import com.appsmith.external.dtos.ModifiedResources;
import com.appsmith.external.enums.FeatureFlagEnum;
import com.appsmith.external.git.GitExecutor;
import com.appsmith.external.models.ApplicationGitReference; import com.appsmith.external.models.ApplicationGitReference;
import com.appsmith.server.constants.ArtifactType;
import com.appsmith.server.constants.SerialiseArtifactObjective; import com.appsmith.server.constants.SerialiseArtifactObjective;
import com.appsmith.server.domains.Workspace; import com.appsmith.server.domains.Workspace;
import com.appsmith.server.dtos.ApplicationImportDTO; import com.appsmith.server.dtos.ApplicationImportDTO;
import com.appsmith.server.dtos.ApplicationJson; import com.appsmith.server.dtos.ApplicationJson;
import com.appsmith.server.exports.internal.ExportService; import com.appsmith.server.exports.internal.ExportService;
import com.appsmith.server.featureflags.CachedFeatures;
import com.appsmith.server.helpers.CommonGitFileUtils; import com.appsmith.server.helpers.CommonGitFileUtils;
import com.appsmith.server.helpers.MockPluginExecutor; import com.appsmith.server.helpers.MockPluginExecutor;
import com.appsmith.server.helpers.PluginExecutorHelper; import com.appsmith.server.helpers.PluginExecutorHelper;
import com.appsmith.server.imports.internal.ImportService; import com.appsmith.server.imports.internal.ImportService;
import com.appsmith.server.services.FeatureFlagService;
import com.appsmith.server.services.WorkspaceService; import com.appsmith.server.services.WorkspaceService;
import com.appsmith.server.testhelpers.git.GitFileSystemTestHelper;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.AutoConfigureDataMongo; import org.springframework.boot.test.autoconfigure.data.mongo.AutoConfigureDataMongo;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@ -37,10 +49,19 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.test.StepVerifier; import reactor.test.StepVerifier;
import java.io.IOException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.appsmith.server.constants.ArtifactType.APPLICATION;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@ -78,9 +99,6 @@ import static org.mockito.ArgumentMatchers.any;
@DirtiesContext @DirtiesContext
public class ServerSchemaMigrationEnforcerTest { public class ServerSchemaMigrationEnforcerTest {
@Autowired
Gson gson;
@Autowired @Autowired
WorkspaceService workspaceService; WorkspaceService workspaceService;
@ -93,9 +111,28 @@ public class ServerSchemaMigrationEnforcerTest {
@Autowired @Autowired
CommonGitFileUtils commonGitFileUtils; CommonGitFileUtils commonGitFileUtils;
@Autowired
GitFileSystemTestHelper gitFileSystemTestHelper;
@SpyBean
GitExecutor gitExecutor;
@SpyBean
FeatureFlagService featureFlagService;
@MockBean @MockBean
PluginExecutorHelper pluginExecutorHelper; PluginExecutorHelper pluginExecutorHelper;
private final Gson gson = new GsonBuilder()
.registerTypeAdapter(Instant.class, new ISOStringToInstantConverter())
.setPrettyPrinting()
.create();
private static final String DEFAULT_APPLICATION_ID = "default-app-id",
BRANCH_NAME = "develop",
REPO_NAME = "repoName",
WORKSPACE_ID = "test-workspace-id";
public static final String CUSTOM_JS_LIB_LIST = "jsLibraries"; public static final String CUSTOM_JS_LIB_LIST = "jsLibraries";
public static final String EXPORTED_APPLICATION = "application"; public static final String EXPORTED_APPLICATION = "application";
public static final String UNPUBLISHED_CUSTOM_JS_LIBS = "unpublishedCustomJSLibs"; public static final String UNPUBLISHED_CUSTOM_JS_LIBS = "unpublishedCustomJSLibs";
@ -167,6 +204,7 @@ public class ServerSchemaMigrationEnforcerTest {
} }
@Test @Test
@Disabled
@WithUserDetails(value = "api_user") @WithUserDetails(value = "api_user")
public void importApplication_ThenExportApplication_MatchJson_equals_Success() throws URISyntaxException { public void importApplication_ThenExportApplication_MatchJson_equals_Success() throws URISyntaxException {
String filePath = "ce-automation-test.json"; String filePath = "ce-automation-test.json";
@ -199,7 +237,7 @@ public class ServerSchemaMigrationEnforcerTest {
.exportByArtifactId( .exportByArtifactId(
applicationImportDTO.getApplication().getId(), applicationImportDTO.getApplication().getId(),
SerialiseArtifactObjective.VERSION_CONTROL, SerialiseArtifactObjective.VERSION_CONTROL,
ArtifactType.APPLICATION) APPLICATION)
.map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson);
}); });
@ -254,4 +292,138 @@ public class ServerSchemaMigrationEnforcerTest {
} }
} }
} }
@Test
public void savedFile_reSavedWithDifferentSerialisationLogic_diffOccurs()
throws URISyntaxException, IOException, GitAPIException {
ApplicationJson applicationJson =
gitFileSystemTestHelper.getApplicationJson(this.getClass().getResource("ce-automation-test.json"));
ModifiedResources modifiedResources = new ModifiedResources();
modifiedResources.setAllModified(true);
applicationJson.setModifiedResources(modifiedResources);
CachedFeatures cachedFeatures = new CachedFeatures();
cachedFeatures.setFeatures(Map.of(FeatureFlagEnum.release_git_autocommit_feature_enabled.name(), FALSE));
Mockito.when(featureFlagService.getCachedTenantFeatureFlags())
.thenAnswer((Answer<CachedFeatures>) invocations -> cachedFeatures);
gitFileSystemTestHelper.setupGitRepository(
WORKSPACE_ID, DEFAULT_APPLICATION_ID, BRANCH_NAME, REPO_NAME, applicationJson);
cachedFeatures.setFeatures(Map.of(FeatureFlagEnum.release_git_autocommit_feature_enabled.name(), TRUE));
Path suffixPath = Paths.get(WORKSPACE_ID, DEFAULT_APPLICATION_ID, REPO_NAME);
Path gitCompletePath = gitExecutor.createRepoPath(suffixPath);
commonGitFileUtils
.saveArtifactToLocalRepo(suffixPath, applicationJson, BRANCH_NAME)
.block();
try (Git gitRepo = Git.open(gitCompletePath.toFile())) {
List<DiffEntry> diffEntries = gitRepo.diff().call();
Set<String> fileChanges = Set.of(
"application.json",
"metadata.json",
"theme.json",
"datasources/JSON typicode API (1).json",
"datasources/TED postgres (1).json",
"datasources/mainGoogleSheetDS.json");
for (DiffEntry diff : diffEntries) {
assertThat(fileChanges).contains(diff.getOldPath());
assertThat(fileChanges).contains(diff.getNewPath());
assertThat(diff.getChangeType()).isEqualTo(DiffEntry.ChangeType.MODIFY);
}
assertThat(diffEntries.size()).isNotZero();
}
}
@Test
public void savedFile_reSavedWithSameSerialisationLogic_noDiffOccurs()
throws URISyntaxException, IOException, GitAPIException {
ApplicationJson applicationJson =
gitFileSystemTestHelper.getApplicationJson(this.getClass().getResource("ce-automation-test.json"));
ModifiedResources modifiedResources = new ModifiedResources();
modifiedResources.setAllModified(true);
applicationJson.setModifiedResources(modifiedResources);
gitFileSystemTestHelper.setupGitRepository(
WORKSPACE_ID, DEFAULT_APPLICATION_ID, BRANCH_NAME, REPO_NAME, applicationJson);
Path suffixPath = Paths.get(WORKSPACE_ID, DEFAULT_APPLICATION_ID, REPO_NAME);
Path gitCompletePath = gitExecutor.createRepoPath(suffixPath);
commonGitFileUtils
.saveArtifactToLocalRepo(suffixPath, applicationJson, BRANCH_NAME)
.block();
try (Git gitRepo = Git.open(gitCompletePath.toFile())) {
List<DiffEntry> diffEntries = gitRepo.diff().call();
assertThat(diffEntries.size()).isZero();
}
}
@Test
@WithUserDetails(value = "api_user")
public void saveGitRepo_ImportAndThenExport_diffOccurs() throws URISyntaxException, IOException, GitAPIException {
ApplicationJson applicationJson =
gitFileSystemTestHelper.getApplicationJson(this.getClass().getResource("ce-automation-test.json"));
ModifiedResources modifiedResources = new ModifiedResources();
modifiedResources.setAllModified(true);
applicationJson.setModifiedResources(modifiedResources);
CachedFeatures cachedFeatures = new CachedFeatures();
cachedFeatures.setFeatures(Map.of(FeatureFlagEnum.release_git_autocommit_feature_enabled.name(), TRUE));
Mockito.when(featureFlagService.getCachedTenantFeatureFlags())
.thenAnswer((Answer<CachedFeatures>) invocations -> cachedFeatures);
gitFileSystemTestHelper.setupGitRepository(
WORKSPACE_ID, DEFAULT_APPLICATION_ID, BRANCH_NAME, REPO_NAME, applicationJson);
ApplicationJson jsonToBeImported = commonGitFileUtils
.reconstructArtifactExchangeJsonFromGitRepo(
WORKSPACE_ID, DEFAULT_APPLICATION_ID, REPO_NAME, BRANCH_NAME, APPLICATION)
.map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson)
.block();
Workspace newWorkspace = new Workspace();
newWorkspace.setName("Template Workspace1");
Workspace workspace = workspaceService.create(newWorkspace).block();
ApplicationJson exportedJson = importService
.importNewArtifactInWorkspaceFromJson(workspace.getId(), jsonToBeImported)
.flatMap(artifactExchangeJson -> {
return exportService
.exportByArtifactId(
artifactExchangeJson.getId(),
SerialiseArtifactObjective.VERSION_CONTROL,
APPLICATION)
.map(exportArtifactJson -> {
ApplicationJson applicationJson1 = (ApplicationJson) exportArtifactJson;
applicationJson1.setModifiedResources(modifiedResources);
return applicationJson1;
});
})
.block();
Path suffixPath = Paths.get(WORKSPACE_ID, DEFAULT_APPLICATION_ID, REPO_NAME);
Path gitCompletePath = gitExecutor.createRepoPath(suffixPath);
// save back to the repository in order to compare the diff.
commonGitFileUtils
.saveArtifactToLocalRepo(suffixPath, exportedJson, BRANCH_NAME)
.block();
try (Git gitRepo = Git.open(gitCompletePath.toFile())) {
List<DiffEntry> diffEntries = gitRepo.diff().call();
assertThat(diffEntries.size()).isNotZero();
for (DiffEntry diffEntry : diffEntries) {
// assertion that no new file has been created
assertThat(diffEntry.getOldPath()).isEqualTo(diffEntry.getNewPath());
}
}
}
} }

View File

@ -1,394 +0,0 @@
package com.appsmith.server.git.autocommit;
import com.appsmith.external.dtos.GitLogDTO;
import com.appsmith.external.enums.FeatureFlagEnum;
import com.appsmith.external.git.GitExecutor;
import com.appsmith.external.helpers.AppsmithBeanUtils;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.ApplicationPage;
import com.appsmith.server.domains.GitArtifactMetadata;
import com.appsmith.server.domains.GitAuth;
import com.appsmith.server.domains.GitProfile;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.dtos.ApplicationJson;
import com.appsmith.server.dtos.AutoCommitTriggerDTO;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.git.autocommit.helpers.AutoCommitEligibilityHelper;
import com.appsmith.server.git.common.CommonGitService;
import com.appsmith.server.helpers.DSLMigrationUtils;
import com.appsmith.server.helpers.GitPrivateRepoHelper;
import com.appsmith.server.migrations.JsonSchemaMigration;
import com.appsmith.server.migrations.JsonSchemaVersions;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.FeatureFlagService;
import com.appsmith.server.services.UserDataService;
import com.appsmith.server.testhelpers.git.GitFileSystemTestHelper;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.BranchTrackingStatus;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.appsmith.server.git.AutoCommitEventHandlerCEImpl.AUTO_COMMIT_MSG_FORMAT;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
@ExtendWith(SpringExtension.class)
@SpringBootTest
@Slf4j
public class ApplicationPageServiceAutoCommitTest {
@SpyBean
ApplicationPageService applicationPageService;
@Autowired
GitFileSystemTestHelper gitFileSystemTestHelper;
@SpyBean
GitExecutor gitExecutor;
@MockBean
FeatureFlagService featureFlagService;
@MockBean
DSLMigrationUtils dslMigrationUtils;
@MockBean
ApplicationService applicationService;
@MockBean
NewPageService newPageService;
@MockBean
CommonGitService commonGitService;
@MockBean
GitPrivateRepoHelper gitPrivateRepoHelper;
@SpyBean
AutoCommitEligibilityHelper autoCommitEligibilityHelper;
@MockBean
BranchTrackingStatus branchTrackingStatus;
@MockBean
UserDataService userDataService;
@SpyBean
JsonSchemaMigration jsonSchemaMigration;
Application testApplication;
Path baseRepoSuffix;
private static final Integer DSL_VERSION_NUMBER = 88;
private static final String WORKSPACE_ID = "test-workspace";
private static final String REPO_NAME = "test-repo";
private static final String BRANCH_NAME = "develop";
private static final String APP_JSON_NAME = "autocommit.json";
private static final String APP_NAME = "autocommit";
private static final Integer WAIT_DURATION_FOR_ASYNC_EVENT = 5;
private static final String PUBLIC_KEY = "public-key";
private static final String PRIVATE_KEY = "private-key";
private static final String REPO_URL = "domain.xy";
private static final String DEFAULT_APP_ID = "default-app-id", DEFAULT_BRANCH_NAME = "master";
private Application createApplication() {
Application application = new Application();
application.setName(APP_NAME);
application.setWorkspaceId(WORKSPACE_ID);
application.setId(DEFAULT_APP_ID);
ApplicationPage applicationPage = new ApplicationPage();
applicationPage.setId("testPageId");
applicationPage.setIsDefault(TRUE);
application.setPages(List.of(applicationPage));
GitArtifactMetadata gitArtifactMetadata = new GitArtifactMetadata();
gitArtifactMetadata.setBranchName(BRANCH_NAME);
gitArtifactMetadata.setDefaultBranchName(DEFAULT_BRANCH_NAME);
gitArtifactMetadata.setRepoName(REPO_NAME);
gitArtifactMetadata.setDefaultApplicationId(DEFAULT_APP_ID);
gitArtifactMetadata.setRemoteUrl(REPO_URL);
GitAuth gitAuth = new GitAuth();
gitAuth.setPrivateKey(PRIVATE_KEY);
gitAuth.setPublicKey(PUBLIC_KEY);
gitArtifactMetadata.setGitAuth(gitAuth);
application.setGitApplicationMetadata(gitArtifactMetadata);
return application;
}
private GitProfile createGitProfile() {
GitProfile gitProfile = new GitProfile();
gitProfile.setAuthorName("authorName");
gitProfile.setAuthorEmail("author@domain.xy");
return gitProfile;
}
private NewPage createNewPage() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("key", "value");
jsonObject.put("version", DSL_VERSION_NUMBER);
Layout layout1 = new Layout();
layout1.setId("testLayoutId");
layout1.setDsl(jsonObject);
PageDTO pageDTO = new PageDTO();
pageDTO.setId("testPageId");
pageDTO.setApplicationId(DEFAULT_APP_ID);
pageDTO.setLayouts(List.of(layout1));
NewPage newPage = new NewPage();
newPage.setId("testPageId");
newPage.setApplicationId(DEFAULT_APP_ID);
newPage.setUnpublishedPage(pageDTO);
return newPage;
}
@BeforeEach
public void beforeTest() {
Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_server_autocommit_feature_enabled))
.thenReturn(Mono.just(TRUE));
Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_feature_enabled))
.thenReturn(Mono.just(TRUE));
Mockito.when(commonGitService.fetchRemoteChanges(
any(Application.class), any(Application.class), anyString(), anyBoolean()))
.thenReturn(Mono.just(branchTrackingStatus));
Mockito.when(branchTrackingStatus.getBehindCount()).thenReturn(0);
// create New Pages
NewPage newPage = createNewPage();
// create application
testApplication = createApplication();
baseRepoSuffix = Paths.get(WORKSPACE_ID, DEFAULT_APP_ID, REPO_NAME);
doReturn(Mono.just("success"))
.when(gitExecutor)
.pushApplication(baseRepoSuffix, REPO_URL, PUBLIC_KEY, PRIVATE_KEY, BRANCH_NAME);
doReturn(Mono.just(newPage.getUnpublishedPage()))
.when(applicationPageService)
.getPage(any(NewPage.class), anyBoolean());
Mockito.when(newPageService.findNewPagesByApplicationId(anyString(), any(AclPermission.class)))
.thenReturn(Flux.just(newPage));
Mockito.when(applicationService.findByBranchNameAndDefaultApplicationId(
anyString(), anyString(), any(AclPermission.class)))
.thenReturn(Mono.just(testApplication));
Mockito.when(applicationService.findById(anyString(), any(AclPermission.class)))
.thenReturn(Mono.just(testApplication));
Mockito.when(gitPrivateRepoHelper.isBranchProtected(any(), anyString())).thenReturn(Mono.just(FALSE));
Mockito.when(userDataService.getGitProfileForCurrentUser(any())).thenReturn(Mono.just(createGitProfile()));
}
@AfterEach
public void afterTest() {
gitFileSystemTestHelper.deleteWorkspaceDirectory(WORKSPACE_ID);
}
@Test
@Disabled
public void testAutoCommit_whenOnlyServerIsEligibleForMigration_commitSuccess()
throws URISyntaxException, IOException, GitAPIException {
ApplicationJson applicationJson =
gitFileSystemTestHelper.getApplicationJson(this.getClass().getResource(APP_JSON_NAME));
doReturn(Mono.just(new AutoCommitTriggerDTO(TRUE, FALSE, TRUE)))
.when(autoCommitEligibilityHelper)
.isAutoCommitRequired(anyString(), any(GitArtifactMetadata.class), any(PageDTO.class));
ApplicationJson applicationJson1 = new ApplicationJson();
AppsmithBeanUtils.copyNewFieldValuesIntoOldObject(applicationJson, applicationJson1);
applicationJson1.setServerSchemaVersion(JsonSchemaVersions.serverVersion + 1);
doReturn(Mono.just(applicationJson1))
.when(jsonSchemaMigration)
.migrateApplicationJsonToLatestSchema(any(ApplicationJson.class));
gitFileSystemTestHelper.setupGitRepository(
WORKSPACE_ID, DEFAULT_APP_ID, BRANCH_NAME, REPO_NAME, applicationJson);
// verifying the initial number of commits
StepVerifier.create(gitExecutor.getCommitHistory(baseRepoSuffix))
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(2);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).doesNotContain(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
// this would trigger autocommit
Mono<List<GitLogDTO>> gitlogDTOsMono = applicationPageService
.getPagesBasedOnApplicationMode(testApplication, ApplicationMode.EDIT)
.then(Mono.delay(Duration.ofSeconds(WAIT_DURATION_FOR_ASYNC_EVENT)))
.then(gitExecutor.getCommitHistory(baseRepoSuffix));
// verifying final number of commits
StepVerifier.create(gitlogDTOsMono)
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(3);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).contains(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
}
@Test
@Disabled
public void testAutoCommit_whenOnlyClientIsEligibleForMigration_commitSuccess()
throws GitAPIException, IOException, URISyntaxException {
ApplicationJson applicationJson =
gitFileSystemTestHelper.getApplicationJson(this.getClass().getResource(APP_JSON_NAME));
int pageDSLNumber = applicationJson
.getPageList()
.get(0)
.getUnpublishedPage()
.getLayouts()
.get(0)
.getDsl()
.getAsNumber("version")
.intValue();
doReturn(Mono.just(new AutoCommitTriggerDTO(TRUE, TRUE, FALSE)))
.when(autoCommitEligibilityHelper)
.isAutoCommitRequired(anyString(), any(GitArtifactMetadata.class), any(PageDTO.class));
Mockito.when(dslMigrationUtils.getLatestDslVersion()).thenReturn(Mono.just(pageDSLNumber + 1));
JSONObject dslAfterMigration = new JSONObject();
dslAfterMigration.put("key", "after migration");
// mock the dsl migration utils to return updated dsl when requested with older dsl
Mockito.when(dslMigrationUtils.migratePageDsl(any(JSONObject.class))).thenReturn(Mono.just(dslAfterMigration));
gitFileSystemTestHelper.setupGitRepository(
WORKSPACE_ID, DEFAULT_APP_ID, BRANCH_NAME, REPO_NAME, applicationJson);
// verifying the initial number of commits
StepVerifier.create(gitExecutor.getCommitHistory(baseRepoSuffix))
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(2);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).doesNotContain(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
// this would trigger autocommit
Mono<List<GitLogDTO>> gitlogDTOsMono = applicationPageService
.getPagesBasedOnApplicationMode(testApplication, ApplicationMode.EDIT)
.then(Mono.delay(Duration.ofSeconds(WAIT_DURATION_FOR_ASYNC_EVENT)))
.then(gitExecutor.getCommitHistory(baseRepoSuffix));
// verifying final number of commits
StepVerifier.create(gitlogDTOsMono)
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(3);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).contains(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
}
@Test
@Disabled
public void testAutoCommit_whenAutoCommitNotEligible_returnsFalse()
throws URISyntaxException, IOException, GitAPIException {
ApplicationJson applicationJson =
gitFileSystemTestHelper.getApplicationJson(this.getClass().getResource(APP_JSON_NAME));
doReturn(Mono.just(new AutoCommitTriggerDTO(FALSE, FALSE, FALSE)))
.when(autoCommitEligibilityHelper)
.isAutoCommitRequired(anyString(), any(GitArtifactMetadata.class), any(PageDTO.class));
gitFileSystemTestHelper.setupGitRepository(
WORKSPACE_ID, DEFAULT_APP_ID, BRANCH_NAME, REPO_NAME, applicationJson);
// verifying the initial number of commits
StepVerifier.create(gitExecutor.getCommitHistory(baseRepoSuffix))
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(2);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).doesNotContain(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
// this would not trigger autocommit
Mono<List<GitLogDTO>> gitlogDTOsMono = applicationPageService
.getPagesBasedOnApplicationMode(testApplication, ApplicationMode.EDIT)
.then(Mono.delay(Duration.ofSeconds(WAIT_DURATION_FOR_ASYNC_EVENT)))
.then(gitExecutor.getCommitHistory(baseRepoSuffix));
// verifying final number of commits
StepVerifier.create(gitlogDTOsMono)
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(2);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).doesNotContain(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
}
}

View File

@ -14,8 +14,6 @@ import com.appsmith.server.dtos.ApplicationJson;
import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.events.AutoCommitEvent; import com.appsmith.server.events.AutoCommitEvent;
import com.appsmith.server.featureflags.CachedFeatures; import com.appsmith.server.featureflags.CachedFeatures;
import com.appsmith.server.git.AutoCommitEventHandler;
import com.appsmith.server.git.AutoCommitEventHandlerImpl;
import com.appsmith.server.git.GitRedisUtils; import com.appsmith.server.git.GitRedisUtils;
import com.appsmith.server.helpers.CommonGitFileUtils; import com.appsmith.server.helpers.CommonGitFileUtils;
import com.appsmith.server.helpers.DSLMigrationUtils; import com.appsmith.server.helpers.DSLMigrationUtils;
@ -54,7 +52,7 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.appsmith.server.git.AutoCommitEventHandlerCEImpl.AUTO_COMMIT_MSG_FORMAT; import static com.appsmith.server.git.autocommit.AutoCommitEventHandlerCEImpl.AUTO_COMMIT_MSG_FORMAT;
import static java.lang.Boolean.FALSE; import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -498,12 +496,12 @@ public class AutoCommitEventHandlerImplTest {
autoCommitEvent.getBranchName()); autoCommitEvent.getBranchName());
CachedFeatures cachedFeatures = new CachedFeatures(); CachedFeatures cachedFeatures = new CachedFeatures();
cachedFeatures.setFeatures(Map.of(FeatureFlagEnum.release_git_cleanup_feature_enabled.name(), FALSE)); cachedFeatures.setFeatures(Map.of(FeatureFlagEnum.release_git_autocommit_feature_enabled.name(), FALSE));
Mockito.when(featureFlagService.getCachedTenantFeatureFlags()) Mockito.when(featureFlagService.getCachedTenantFeatureFlags())
.thenAnswer((Answer<CachedFeatures>) invocations -> cachedFeatures); .thenAnswer((Answer<CachedFeatures>) invocations -> cachedFeatures);
gitFileSystemTestHelper.setupGitRepository(autoCommitEvent, applicationJson); gitFileSystemTestHelper.setupGitRepository(autoCommitEvent, applicationJson);
cachedFeatures.setFeatures(Map.of(FeatureFlagEnum.release_git_cleanup_feature_enabled.name(), TRUE)); cachedFeatures.setFeatures(Map.of(FeatureFlagEnum.release_git_autocommit_feature_enabled.name(), TRUE));
StepVerifier.create(autoCommitEventHandler StepVerifier.create(autoCommitEventHandler
.autoCommitServerMigration(autoCommitEvent) .autoCommitServerMigration(autoCommitEvent)
@ -536,7 +534,7 @@ public class AutoCommitEventHandlerImplTest {
gitFileSystemTestHelper.getApplicationJson(this.getClass().getResource("application.json")); gitFileSystemTestHelper.getApplicationJson(this.getClass().getResource("application.json"));
CachedFeatures cachedFeatures = new CachedFeatures(); CachedFeatures cachedFeatures = new CachedFeatures();
cachedFeatures.setFeatures(Map.of(FeatureFlagEnum.release_git_cleanup_feature_enabled.name(), FALSE)); cachedFeatures.setFeatures(Map.of(FeatureFlagEnum.release_git_autocommit_feature_enabled.name(), FALSE));
Mockito.when(featureFlagService.getCachedTenantFeatureFlags()) Mockito.when(featureFlagService.getCachedTenantFeatureFlags())
.thenAnswer((Answer<CachedFeatures>) invocations -> cachedFeatures); .thenAnswer((Answer<CachedFeatures>) invocations -> cachedFeatures);

View File

@ -0,0 +1,686 @@
package com.appsmith.server.git.autocommit;
import com.appsmith.external.dtos.GitLogDTO;
import com.appsmith.external.enums.FeatureFlagEnum;
import com.appsmith.external.git.GitExecutor;
import com.appsmith.external.helpers.AppsmithBeanUtils;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.ApplicationPage;
import com.appsmith.server.domains.GitArtifactMetadata;
import com.appsmith.server.domains.GitAuth;
import com.appsmith.server.domains.GitProfile;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.dtos.ApplicationJson;
import com.appsmith.server.dtos.AutoCommitResponseDTO;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.git.autocommit.helpers.AutoCommitEligibilityHelper;
import com.appsmith.server.git.common.CommonGitService;
import com.appsmith.server.helpers.CommonGitFileUtils;
import com.appsmith.server.helpers.DSLMigrationUtils;
import com.appsmith.server.helpers.GitPrivateRepoHelper;
import com.appsmith.server.helpers.RedisUtils;
import com.appsmith.server.migrations.JsonSchemaMigration;
import com.appsmith.server.migrations.JsonSchemaVersions;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.services.FeatureFlagService;
import com.appsmith.server.services.UserDataService;
import com.appsmith.server.solutions.PagePermission;
import com.appsmith.server.testhelpers.git.GitFileSystemTestHelper;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.BranchTrackingStatus;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.appsmith.server.git.autocommit.AutoCommitEventHandlerCEImpl.AUTO_COMMIT_MSG_FORMAT;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
@ExtendWith(SpringExtension.class)
@SpringBootTest
@Slf4j
public class AutoCommitServiceTest {
@SpyBean
AutoCommitService autoCommitService;
@Autowired
GitFileSystemTestHelper gitFileSystemTestHelper;
@SpyBean
GitExecutor gitExecutor;
@MockBean
FeatureFlagService featureFlagService;
@MockBean
DSLMigrationUtils dslMigrationUtils;
@MockBean
ApplicationService applicationService;
@MockBean
NewPageService newPageService;
@Autowired
PagePermission pagePermission;
@SpyBean
RedisUtils redisUtils;
@MockBean
CommonGitService commonGitService;
@SpyBean
CommonGitFileUtils commonGitFileUtils;
@MockBean
GitPrivateRepoHelper gitPrivateRepoHelper;
@SpyBean
AutoCommitEligibilityHelper autoCommitEligibilityHelper;
@MockBean
BranchTrackingStatus branchTrackingStatus;
@MockBean
UserDataService userDataService;
@SpyBean
JsonSchemaMigration jsonSchemaMigration;
Application testApplication;
Path baseRepoSuffix;
private static final Integer DSL_VERSION_NUMBER = 88;
private static final String WORKSPACE_ID = "test-workspace";
private static final String REPO_NAME = "test-repo";
private static final String BRANCH_NAME = "develop";
private static final String APP_JSON_NAME = "autocommit.json";
private static final String APP_NAME = "autocommit";
private static final Integer WAIT_DURATION_FOR_ASYNC_EVENT = 5;
private static final String PUBLIC_KEY = "public-key";
private static final String PRIVATE_KEY = "private-key";
private static final String REPO_URL = "domain.xy";
private static final String DEFAULT_APP_ID = "default-app-id", DEFAULT_BRANCH_NAME = "master";
private static final Integer SERVER_SCHEMA_VERSION = JsonSchemaVersions.serverVersion;
private Application createApplication() {
Application application = new Application();
application.setName(APP_NAME);
application.setWorkspaceId(WORKSPACE_ID);
application.setId(DEFAULT_APP_ID);
ApplicationPage applicationPage = new ApplicationPage();
applicationPage.setId("testPageId");
applicationPage.setIsDefault(TRUE);
application.setPages(List.of(applicationPage));
GitArtifactMetadata gitArtifactMetadata = new GitArtifactMetadata();
gitArtifactMetadata.setBranchName(BRANCH_NAME);
gitArtifactMetadata.setDefaultBranchName(DEFAULT_BRANCH_NAME);
gitArtifactMetadata.setRepoName(REPO_NAME);
gitArtifactMetadata.setDefaultApplicationId(DEFAULT_APP_ID);
gitArtifactMetadata.setRemoteUrl(REPO_URL);
GitAuth gitAuth = new GitAuth();
gitAuth.setPrivateKey(PRIVATE_KEY);
gitAuth.setPublicKey(PUBLIC_KEY);
gitArtifactMetadata.setGitAuth(gitAuth);
application.setGitApplicationMetadata(gitArtifactMetadata);
return application;
}
private GitProfile createGitProfile() {
GitProfile gitProfile = new GitProfile();
gitProfile.setAuthorName("authorName");
gitProfile.setAuthorEmail("author@domain.xy");
return gitProfile;
}
private PageDTO createPageDTO() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("key", "value");
jsonObject.put("version", DSL_VERSION_NUMBER);
Layout layout1 = new Layout();
layout1.setId("testLayoutId");
layout1.setDsl(jsonObject);
PageDTO pageDTO = new PageDTO();
pageDTO.setId("testPageId");
pageDTO.setApplicationId(DEFAULT_APP_ID);
pageDTO.setLayouts(List.of(layout1));
return pageDTO;
}
private org.json.JSONObject getMockedDsl() {
org.json.JSONObject jsonObject = new org.json.JSONObject();
jsonObject.put("version", DSL_VERSION_NUMBER);
return jsonObject;
}
private void mockAutoCommitTriggerResponse(Boolean serverMigration, Boolean clientMigration) {
doReturn(Mono.just(getMockedDsl()))
.when(commonGitFileUtils)
.getPageDslVersionNumber(anyString(), any(), any(), anyBoolean(), any());
Integer dslVersionNumber = clientMigration ? DSL_VERSION_NUMBER + 1 : DSL_VERSION_NUMBER;
Integer serverSchemaVersionNumber = serverMigration ? SERVER_SCHEMA_VERSION - 1 : SERVER_SCHEMA_VERSION;
doReturn(Mono.just(dslVersionNumber)).when(dslMigrationUtils).getLatestDslVersion();
// server as true
doReturn(Mono.just(serverSchemaVersionNumber))
.when(commonGitFileUtils)
.getMetadataServerSchemaMigrationVersion(anyString(), any(), anyBoolean(), any());
}
@BeforeEach
public void beforeTest() {
// create application
testApplication = createApplication();
baseRepoSuffix = Paths.get(WORKSPACE_ID, DEFAULT_APP_ID, REPO_NAME);
// used for fetching application on autocommit service and gitAutoCommitHelper.autocommit
Mockito.when(applicationService.findByBranchNameAndDefaultApplicationId(
anyString(), anyString(), any(AclPermission.class)))
.thenReturn(Mono.just(testApplication));
// create page-dto
PageDTO pageDTO = createPageDTO();
Mockito.when(newPageService.findByApplicationIdAndApplicationMode(
DEFAULT_APP_ID, pagePermission.getEditPermission(), ApplicationMode.PUBLISHED))
.thenReturn(Flux.just(pageDTO));
Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_eligibility_enabled))
.thenReturn(Mono.just(TRUE));
Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_feature_enabled))
.thenReturn(Mono.just(TRUE));
Mockito.when(commonGitService.fetchRemoteChanges(
any(Application.class), any(Application.class), anyString(), anyBoolean()))
.thenReturn(Mono.just(branchTrackingStatus));
Mockito.when(branchTrackingStatus.getBehindCount()).thenReturn(0);
doReturn(Mono.just("success"))
.when(gitExecutor)
.pushApplication(baseRepoSuffix, REPO_URL, PUBLIC_KEY, PRIVATE_KEY, BRANCH_NAME);
Mockito.when(applicationService.findById(anyString(), any(AclPermission.class)))
.thenReturn(Mono.just(testApplication));
Mockito.when(gitPrivateRepoHelper.isBranchProtected(any(), anyString())).thenReturn(Mono.just(FALSE));
Mockito.when(userDataService.getGitProfileForCurrentUser(any())).thenReturn(Mono.just(createGitProfile()));
}
@AfterEach
public void afterTest() {
gitFileSystemTestHelper.deleteWorkspaceDirectory(WORKSPACE_ID);
}
@Test
public void testAutoCommit_whenOnlyServerIsEligibleForMigration_commitSuccess()
throws URISyntaxException, IOException, GitAPIException {
ApplicationJson applicationJson =
gitFileSystemTestHelper.getApplicationJson(this.getClass().getResource(APP_JSON_NAME));
mockAutoCommitTriggerResponse(TRUE, FALSE);
ApplicationJson applicationJson1 = new ApplicationJson();
AppsmithBeanUtils.copyNewFieldValuesIntoOldObject(applicationJson, applicationJson1);
applicationJson1.setServerSchemaVersion(JsonSchemaVersions.serverVersion + 1);
doReturn(Mono.just(applicationJson1))
.when(jsonSchemaMigration)
.migrateApplicationJsonToLatestSchema(any(ApplicationJson.class));
gitFileSystemTestHelper.setupGitRepository(
WORKSPACE_ID, DEFAULT_APP_ID, BRANCH_NAME, REPO_NAME, applicationJson);
// verifying the initial number of commits
StepVerifier.create(gitExecutor.getCommitHistory(baseRepoSuffix))
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(2);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).doesNotContain(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
// redis-utils fixing
Mockito.when(redisUtils.getRunningAutoCommitBranchName(DEFAULT_APP_ID)).thenReturn(Mono.empty());
Mockito.when(redisUtils.getAutoCommitProgress(DEFAULT_APP_ID)).thenReturn(Mono.empty());
Mono<AutoCommitResponseDTO> autoCommitResponseDTOMono =
autoCommitService.autoCommitApplication(testApplication.getId(), BRANCH_NAME);
StepVerifier.create(autoCommitResponseDTOMono)
.assertNext(autoCommitResponseDTO -> {
assertThat(autoCommitResponseDTO.getAutoCommitResponse())
.isEqualTo(AutoCommitResponseDTO.AutoCommitResponse.PUBLISHED);
})
.verifyComplete();
// this would trigger autocommit
Mono<List<GitLogDTO>> gitlogDTOsMono = Mono.delay(Duration.ofSeconds(WAIT_DURATION_FOR_ASYNC_EVENT))
.then(gitExecutor.getCommitHistory(baseRepoSuffix));
// verifying final number of commits
StepVerifier.create(gitlogDTOsMono)
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(3);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).contains(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
}
@Test
public void testAutoCommit_whenOnlyClientIsEligibleForMigration_commitSuccess()
throws GitAPIException, IOException, URISyntaxException {
ApplicationJson applicationJson =
gitFileSystemTestHelper.getApplicationJson(this.getClass().getResource(APP_JSON_NAME));
int pageDSLNumber = applicationJson
.getPageList()
.get(0)
.getUnpublishedPage()
.getLayouts()
.get(0)
.getDsl()
.getAsNumber("version")
.intValue();
mockAutoCommitTriggerResponse(FALSE, TRUE);
Mockito.when(dslMigrationUtils.getLatestDslVersion()).thenReturn(Mono.just(pageDSLNumber + 1));
JSONObject dslAfterMigration = new JSONObject();
dslAfterMigration.put("key", "after migration");
// mock the dsl migration utils to return updated dsl when requested with older dsl
Mockito.when(dslMigrationUtils.migratePageDsl(any(JSONObject.class))).thenReturn(Mono.just(dslAfterMigration));
gitFileSystemTestHelper.setupGitRepository(
WORKSPACE_ID, DEFAULT_APP_ID, BRANCH_NAME, REPO_NAME, applicationJson);
// verifying the initial number of commits
StepVerifier.create(gitExecutor.getCommitHistory(baseRepoSuffix))
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(2);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).doesNotContain(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
// redis-utils fixing
Mockito.when(redisUtils.getRunningAutoCommitBranchName(DEFAULT_APP_ID)).thenReturn(Mono.empty());
Mockito.when(redisUtils.getAutoCommitProgress(DEFAULT_APP_ID)).thenReturn(Mono.empty());
// this would trigger autocommit
Mono<AutoCommitResponseDTO> autoCommitResponseDTOMono =
autoCommitService.autoCommitApplication(testApplication.getId(), BRANCH_NAME);
StepVerifier.create(autoCommitResponseDTOMono)
.assertNext(autoCommitResponseDTO -> {
assertThat(autoCommitResponseDTO.getAutoCommitResponse())
.isEqualTo(AutoCommitResponseDTO.AutoCommitResponse.PUBLISHED);
})
.verifyComplete();
Mono<List<GitLogDTO>> gitlogDTOsMono = Mono.delay(Duration.ofSeconds(WAIT_DURATION_FOR_ASYNC_EVENT))
.then(gitExecutor.getCommitHistory(baseRepoSuffix));
// verifying final number of commits
StepVerifier.create(gitlogDTOsMono)
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(3);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).contains(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
}
@Test
public void testAutoCommit_whenAutoCommitNotEligible_returnsFalse()
throws URISyntaxException, IOException, GitAPIException {
ApplicationJson applicationJson =
gitFileSystemTestHelper.getApplicationJson(this.getClass().getResource(APP_JSON_NAME));
mockAutoCommitTriggerResponse(FALSE, FALSE);
gitFileSystemTestHelper.setupGitRepository(
WORKSPACE_ID, DEFAULT_APP_ID, BRANCH_NAME, REPO_NAME, applicationJson);
// verifying the initial number of commits
StepVerifier.create(gitExecutor.getCommitHistory(baseRepoSuffix))
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(2);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).doesNotContain(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
// redis-utils fixing
Mockito.when(redisUtils.getRunningAutoCommitBranchName(DEFAULT_APP_ID)).thenReturn(Mono.empty());
Mockito.when(redisUtils.getAutoCommitProgress(DEFAULT_APP_ID)).thenReturn(Mono.empty());
// this would not trigger autocommit
Mono<AutoCommitResponseDTO> autoCommitResponseDTOMono =
autoCommitService.autoCommitApplication(testApplication.getId(), BRANCH_NAME);
StepVerifier.create(autoCommitResponseDTOMono)
.assertNext(autoCommitResponseDTO -> {
assertThat(autoCommitResponseDTO.getAutoCommitResponse())
.isEqualTo(AutoCommitResponseDTO.AutoCommitResponse.IDLE);
})
.verifyComplete();
Mono<List<GitLogDTO>> gitlogDTOsMono = Mono.delay(Duration.ofSeconds(WAIT_DURATION_FOR_ASYNC_EVENT))
.then(gitExecutor.getCommitHistory(baseRepoSuffix));
// verifying final number of commits
StepVerifier.create(gitlogDTOsMono)
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(2);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).doesNotContain(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
}
@Test
public void testAutoCommit_whenAutoCommitAlreadyInProgressOnAnotherBranch_returnsLocked() {
// redis-utils fixing
Mockito.when(redisUtils.getRunningAutoCommitBranchName(DEFAULT_APP_ID))
.thenReturn(Mono.just(DEFAULT_BRANCH_NAME));
Mockito.when(redisUtils.getAutoCommitProgress(DEFAULT_APP_ID)).thenReturn(Mono.just(70));
// this would not trigger autocommit
Mono<AutoCommitResponseDTO> autoCommitResponseDTOMono =
autoCommitService.autoCommitApplication(testApplication.getId(), BRANCH_NAME);
StepVerifier.create(autoCommitResponseDTOMono)
.assertNext(autoCommitResponseDTO -> {
assertThat(autoCommitResponseDTO.getAutoCommitResponse())
.isEqualTo(AutoCommitResponseDTO.AutoCommitResponse.LOCKED);
assertThat(autoCommitResponseDTO.getBranchName()).isEqualTo(DEFAULT_BRANCH_NAME);
assertThat(autoCommitResponseDTO.getProgress()).isEqualTo(70);
})
.verifyComplete();
}
@Test
public void testAutoCommit_whenAutoCommitAlreadyInProgressOnSameBranch_returnsInProgress() {
// redis-utils fixing
Mockito.when(redisUtils.getRunningAutoCommitBranchName(DEFAULT_APP_ID)).thenReturn(Mono.just(BRANCH_NAME));
Mockito.when(redisUtils.getAutoCommitProgress(DEFAULT_APP_ID)).thenReturn(Mono.just(70));
// this would not trigger autocommit
Mono<AutoCommitResponseDTO> autoCommitResponseDTOMono =
autoCommitService.autoCommitApplication(testApplication.getId(), BRANCH_NAME);
StepVerifier.create(autoCommitResponseDTOMono)
.assertNext(autoCommitResponseDTO -> {
assertThat(autoCommitResponseDTO.getAutoCommitResponse())
.isEqualTo(AutoCommitResponseDTO.AutoCommitResponse.IN_PROGRESS);
assertThat(autoCommitResponseDTO.getBranchName()).isEqualTo(BRANCH_NAME);
assertThat(autoCommitResponseDTO.getProgress()).isEqualTo(70);
})
.verifyComplete();
}
@Test
public void testAutoCommit_whenNoGitMetadata_returnsNonGitApp() {
testApplication.setGitApplicationMetadata(null);
// used for fetching application on autocommit service and gitAutoCommitHelper.autocommit
Mockito.when(applicationService.findByBranchNameAndDefaultApplicationId(
anyString(), anyString(), any(AclPermission.class)))
.thenReturn(Mono.just(testApplication));
// this would not trigger autocommit
Mono<AutoCommitResponseDTO> autoCommitResponseDTOMono =
autoCommitService.autoCommitApplication(testApplication.getId(), BRANCH_NAME);
StepVerifier.create(autoCommitResponseDTOMono)
.assertNext(autoCommitResponseDTO -> {
assertThat(autoCommitResponseDTO.getAutoCommitResponse())
.isEqualTo(AutoCommitResponseDTO.AutoCommitResponse.NON_GIT_APP);
})
.verifyComplete();
}
@Test
public void testAutoCommit_whenAutoCommitEligibleButPrerequisiteNotComplete_returnsRequired() {
mockAutoCommitTriggerResponse(TRUE, FALSE);
// redis-utils fixing
Mockito.when(redisUtils.getRunningAutoCommitBranchName(DEFAULT_APP_ID)).thenReturn(Mono.empty());
Mockito.when(redisUtils.getAutoCommitProgress(DEFAULT_APP_ID)).thenReturn(Mono.empty());
// number of commits behind to make the pre-req fail
Mockito.when(branchTrackingStatus.getBehindCount()).thenReturn(1);
// this would not trigger autocommit
Mono<AutoCommitResponseDTO> autoCommitResponseDTOMono =
autoCommitService.autoCommitApplication(testApplication.getId(), BRANCH_NAME);
StepVerifier.create(autoCommitResponseDTOMono)
.assertNext(autoCommitResponseDTO -> {
assertThat(autoCommitResponseDTO.getAutoCommitResponse())
.isEqualTo(AutoCommitResponseDTO.AutoCommitResponse.REQUIRED);
})
.verifyComplete();
}
@Test
public void
testAutoCommit_whenServerIsRunningMigrationCallsAutocommitAgainOnSameBranch_ReturnsAutoCommitInProgress()
throws URISyntaxException, IOException, GitAPIException {
ApplicationJson applicationJson =
gitFileSystemTestHelper.getApplicationJson(this.getClass().getResource(APP_JSON_NAME));
mockAutoCommitTriggerResponse(TRUE, FALSE);
ApplicationJson applicationJson1 = new ApplicationJson();
AppsmithBeanUtils.copyNewFieldValuesIntoOldObject(applicationJson, applicationJson1);
applicationJson1.setServerSchemaVersion(JsonSchemaVersions.serverVersion + 1);
doReturn(Mono.just(applicationJson1))
.when(jsonSchemaMigration)
.migrateApplicationJsonToLatestSchema(any(ApplicationJson.class));
gitFileSystemTestHelper.setupGitRepository(
WORKSPACE_ID, DEFAULT_APP_ID, BRANCH_NAME, REPO_NAME, applicationJson);
// verifying the initial number of commits
StepVerifier.create(gitExecutor.getCommitHistory(baseRepoSuffix))
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(2);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).doesNotContain(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
// redis-utils fixing
Mockito.when(redisUtils.getRunningAutoCommitBranchName(DEFAULT_APP_ID)).thenReturn(Mono.empty());
Mockito.when(redisUtils.getAutoCommitProgress(DEFAULT_APP_ID)).thenReturn(Mono.empty());
Mono<AutoCommitResponseDTO> autoCommitResponseDTOMono =
autoCommitService.autoCommitApplication(testApplication.getId(), BRANCH_NAME);
StepVerifier.create(autoCommitResponseDTOMono)
.assertNext(autoCommitResponseDTO -> assertThat(autoCommitResponseDTO.getAutoCommitResponse())
.isEqualTo(AutoCommitResponseDTO.AutoCommitResponse.PUBLISHED))
.verifyComplete();
// redis-utils fixing
Mockito.when(redisUtils.getRunningAutoCommitBranchName(DEFAULT_APP_ID)).thenReturn(Mono.just(BRANCH_NAME));
Mockito.when(redisUtils.getAutoCommitProgress(DEFAULT_APP_ID)).thenReturn(Mono.just(20));
StepVerifier.create(autoCommitService.autoCommitApplication(testApplication.getId(), BRANCH_NAME))
.assertNext(autoCommitResponseDTO -> {
assertThat(autoCommitResponseDTO.getAutoCommitResponse())
.isEqualTo(AutoCommitResponseDTO.AutoCommitResponse.IN_PROGRESS);
assertThat(autoCommitResponseDTO.getBranchName()).isEqualTo(BRANCH_NAME);
})
.verifyComplete();
// this would trigger autocommit
Mono<List<GitLogDTO>> gitlogDTOsMono = Mono.delay(Duration.ofSeconds(WAIT_DURATION_FOR_ASYNC_EVENT))
.then(gitExecutor.getCommitHistory(baseRepoSuffix));
// verifying final number of commits
StepVerifier.create(gitlogDTOsMono)
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(3);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).contains(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
}
@Test
public void testAutoCommit_whenServerIsRunningMigrationCallsAutocommitAgainOnDiffBranch_ReturnsAutoCommitLocked()
throws URISyntaxException, IOException, GitAPIException {
ApplicationJson applicationJson =
gitFileSystemTestHelper.getApplicationJson(this.getClass().getResource(APP_JSON_NAME));
mockAutoCommitTriggerResponse(TRUE, FALSE);
ApplicationJson applicationJson1 = new ApplicationJson();
AppsmithBeanUtils.copyNewFieldValuesIntoOldObject(applicationJson, applicationJson1);
applicationJson1.setServerSchemaVersion(JsonSchemaVersions.serverVersion + 1);
doReturn(Mono.just(applicationJson1))
.when(jsonSchemaMigration)
.migrateApplicationJsonToLatestSchema(any(ApplicationJson.class));
gitFileSystemTestHelper.setupGitRepository(
WORKSPACE_ID, DEFAULT_APP_ID, BRANCH_NAME, REPO_NAME, applicationJson);
// verifying the initial number of commits
StepVerifier.create(gitExecutor.getCommitHistory(baseRepoSuffix))
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(2);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).doesNotContain(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
// redis-utils fixing
Mockito.when(redisUtils.getRunningAutoCommitBranchName(DEFAULT_APP_ID)).thenReturn(Mono.empty());
Mockito.when(redisUtils.getAutoCommitProgress(DEFAULT_APP_ID)).thenReturn(Mono.empty());
Mono<AutoCommitResponseDTO> autoCommitResponseDTOMono =
autoCommitService.autoCommitApplication(testApplication.getId(), BRANCH_NAME);
StepVerifier.create(autoCommitResponseDTOMono)
.assertNext(autoCommitResponseDTO -> assertThat(autoCommitResponseDTO.getAutoCommitResponse())
.isEqualTo(AutoCommitResponseDTO.AutoCommitResponse.PUBLISHED))
.verifyComplete();
// redis-utils fixing
Mockito.when(redisUtils.getRunningAutoCommitBranchName(DEFAULT_APP_ID)).thenReturn(Mono.just(BRANCH_NAME));
Mockito.when(redisUtils.getAutoCommitProgress(DEFAULT_APP_ID)).thenReturn(Mono.just(20));
StepVerifier.create(autoCommitService.autoCommitApplication(testApplication.getId(), DEFAULT_BRANCH_NAME))
.assertNext(autoCommitResponseDTO -> {
assertThat(autoCommitResponseDTO.getAutoCommitResponse())
.isEqualTo(AutoCommitResponseDTO.AutoCommitResponse.LOCKED);
assertThat(autoCommitResponseDTO.getBranchName()).isEqualTo(BRANCH_NAME);
})
.verifyComplete();
// this would trigger autocommit
Mono<List<GitLogDTO>> gitlogDTOsMono = Mono.delay(Duration.ofSeconds(WAIT_DURATION_FOR_ASYNC_EVENT))
.then(gitExecutor.getCommitHistory(baseRepoSuffix));
// verifying final number of commits
StepVerifier.create(gitlogDTOsMono)
.assertNext(gitLogDTOs -> {
assertThat(gitLogDTOs).isNotEmpty();
assertThat(gitLogDTOs.size()).isEqualTo(3);
Set<String> commitMessages =
gitLogDTOs.stream().map(GitLogDTO::getCommitMessage).collect(Collectors.toSet());
assertThat(commitMessages).contains(String.format(AUTO_COMMIT_MSG_FORMAT, "UNKNOWN"));
})
.verifyComplete();
}
}

View File

@ -2,7 +2,6 @@ package com.appsmith.server.git.autocommit.helpers;
import com.appsmith.external.dtos.ModifiedResources; import com.appsmith.external.dtos.ModifiedResources;
import com.appsmith.external.enums.FeatureFlagEnum; import com.appsmith.external.enums.FeatureFlagEnum;
import com.appsmith.external.git.constants.GitConstants;
import com.appsmith.server.constants.ArtifactType; import com.appsmith.server.constants.ArtifactType;
import com.appsmith.server.domains.Application; import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.GitArtifactMetadata; import com.appsmith.server.domains.GitArtifactMetadata;
@ -36,6 +35,7 @@ import java.io.IOException;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import static com.appsmith.external.git.constants.ce.GitConstantsCE.GitCommandConstantsCE.AUTO_COMMIT_ELIGIBILITY;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@Slf4j @Slf4j
@ -92,18 +92,23 @@ public class AutoCommitEligibilityHelperTest {
return pageDTO; return pageDTO;
} }
private org.json.JSONObject getPageDSl(Integer dslVersionNumber) {
org.json.JSONObject jsonObject = new org.json.JSONObject();
jsonObject.put("version", dslVersionNumber);
return jsonObject;
}
@BeforeEach @BeforeEach
public void beforeEach() { public void beforeEach() {
Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_feature_enabled)) Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_feature_enabled))
.thenReturn(Mono.just(Boolean.TRUE)); .thenReturn(Mono.just(Boolean.TRUE));
Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_server_autocommit_feature_enabled)) Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_eligibility_enabled))
.thenReturn(Mono.just(Boolean.TRUE)); .thenReturn(Mono.just(Boolean.TRUE));
Mockito.when(dslMigrationUtils.getLatestDslVersion()).thenReturn(Mono.just(RANDOM_DSL_VERSION_NUMBER)); Mockito.when(dslMigrationUtils.getLatestDslVersion()).thenReturn(Mono.just(RANDOM_DSL_VERSION_NUMBER));
Mockito.when(gitRedisUtils.addFileLock( Mockito.when(gitRedisUtils.addFileLock(DEFAULT_APPLICATION_ID, AUTO_COMMIT_ELIGIBILITY))
DEFAULT_APPLICATION_ID, GitConstants.GitCommandConstants.METADATA, false))
.thenReturn(Mono.just(Boolean.TRUE)); .thenReturn(Mono.just(Boolean.TRUE));
Mockito.when(gitRedisUtils.releaseFileLock(DEFAULT_APPLICATION_ID)).thenReturn(Mono.just(Boolean.TRUE)); Mockito.when(gitRedisUtils.releaseFileLock(DEFAULT_APPLICATION_ID)).thenReturn(Mono.just(Boolean.TRUE));
} }
@ -114,11 +119,16 @@ public class AutoCommitEligibilityHelperTest {
GitArtifactMetadata gitArtifactMetadata = createGitMetadata(); GitArtifactMetadata gitArtifactMetadata = createGitMetadata();
PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER - 1); PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER - 1);
Mockito.doReturn(Mono.just(getPageDSl(RANDOM_DSL_VERSION_NUMBER - 1)))
.when(commonGitFileUtils)
.getPageDslVersionNumber(
WORKSPACE_ID, gitArtifactMetadata, pageDTO, Boolean.TRUE, ArtifactType.APPLICATION);
// this leads to server migration requirement as true // this leads to server migration requirement as true
Mockito.doReturn(Mono.just(JsonSchemaVersions.serverVersion - 1)) Mockito.doReturn(Mono.just(JsonSchemaVersions.serverVersion - 1))
.when(commonGitFileUtils) .when(commonGitFileUtils)
.getMetadataServerSchemaMigrationVersion( .getMetadataServerSchemaMigrationVersion(
WORKSPACE_ID, DEFAULT_APPLICATION_ID, REPO_NAME, BRANCH_NAME, ArtifactType.APPLICATION); WORKSPACE_ID, gitArtifactMetadata, Boolean.TRUE, ArtifactType.APPLICATION);
Mono<AutoCommitTriggerDTO> autoCommitTriggerDTOMono = Mono<AutoCommitTriggerDTO> autoCommitTriggerDTOMono =
autoCommitEligibilityHelper.isAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata, pageDTO); autoCommitEligibilityHelper.isAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata, pageDTO);
@ -140,11 +150,16 @@ public class AutoCommitEligibilityHelperTest {
GitArtifactMetadata gitArtifactMetadata = createGitMetadata(); GitArtifactMetadata gitArtifactMetadata = createGitMetadata();
PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER); PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER);
Mockito.doReturn(Mono.just(getPageDSl(RANDOM_DSL_VERSION_NUMBER)))
.when(commonGitFileUtils)
.getPageDslVersionNumber(
WORKSPACE_ID, gitArtifactMetadata, pageDTO, Boolean.TRUE, ArtifactType.APPLICATION);
// this leads to server migration requirement as false // this leads to server migration requirement as false
Mockito.doReturn(Mono.just(JsonSchemaVersions.serverVersion)) Mockito.doReturn(Mono.just(JsonSchemaVersions.serverVersion))
.when(commonGitFileUtils) .when(commonGitFileUtils)
.getMetadataServerSchemaMigrationVersion( .getMetadataServerSchemaMigrationVersion(
WORKSPACE_ID, DEFAULT_APPLICATION_ID, REPO_NAME, BRANCH_NAME, ArtifactType.APPLICATION); WORKSPACE_ID, gitArtifactMetadata, Boolean.FALSE, ArtifactType.APPLICATION);
Mono<AutoCommitTriggerDTO> autoCommitTriggerDTOMono = Mono<AutoCommitTriggerDTO> autoCommitTriggerDTOMono =
autoCommitEligibilityHelper.isAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata, pageDTO); autoCommitEligibilityHelper.isAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata, pageDTO);
@ -166,11 +181,16 @@ public class AutoCommitEligibilityHelperTest {
GitArtifactMetadata gitArtifactMetadata = createGitMetadata(); GitArtifactMetadata gitArtifactMetadata = createGitMetadata();
PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER - 1); PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER - 1);
Mockito.doReturn(Mono.just(getPageDSl(RANDOM_DSL_VERSION_NUMBER - 1)))
.when(commonGitFileUtils)
.getPageDslVersionNumber(
WORKSPACE_ID, gitArtifactMetadata, pageDTO, Boolean.TRUE, ArtifactType.APPLICATION);
// this leads to server migration requirement as false // this leads to server migration requirement as false
Mockito.doReturn(Mono.just(JsonSchemaVersions.serverVersion)) Mockito.doReturn(Mono.just(JsonSchemaVersions.serverVersion))
.when(commonGitFileUtils) .when(commonGitFileUtils)
.getMetadataServerSchemaMigrationVersion( .getMetadataServerSchemaMigrationVersion(
WORKSPACE_ID, DEFAULT_APPLICATION_ID, REPO_NAME, BRANCH_NAME, ArtifactType.APPLICATION); WORKSPACE_ID, gitArtifactMetadata, Boolean.FALSE, ArtifactType.APPLICATION);
Mono<AutoCommitTriggerDTO> autoCommitTriggerDTOMono = Mono<AutoCommitTriggerDTO> autoCommitTriggerDTOMono =
autoCommitEligibilityHelper.isAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata, pageDTO); autoCommitEligibilityHelper.isAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata, pageDTO);
@ -178,8 +198,9 @@ public class AutoCommitEligibilityHelperTest {
StepVerifier.create(autoCommitTriggerDTOMono) StepVerifier.create(autoCommitTriggerDTOMono)
.assertNext(autoCommitTriggerDTO -> { .assertNext(autoCommitTriggerDTO -> {
assertThat(autoCommitTriggerDTO.getIsAutoCommitRequired()).isTrue(); assertThat(autoCommitTriggerDTO.getIsAutoCommitRequired()).isTrue();
// Since client is true, server is true as well
assertThat(autoCommitTriggerDTO.getIsServerAutoCommitRequired()) assertThat(autoCommitTriggerDTO.getIsServerAutoCommitRequired())
.isFalse(); .isTrue();
assertThat(autoCommitTriggerDTO.getIsClientAutoCommitRequired()) assertThat(autoCommitTriggerDTO.getIsClientAutoCommitRequired())
.isTrue(); .isTrue();
}) })
@ -192,11 +213,16 @@ public class AutoCommitEligibilityHelperTest {
GitArtifactMetadata gitArtifactMetadata = createGitMetadata(); GitArtifactMetadata gitArtifactMetadata = createGitMetadata();
PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER); PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER);
Mockito.doReturn(Mono.just(getPageDSl(RANDOM_DSL_VERSION_NUMBER)))
.when(commonGitFileUtils)
.getPageDslVersionNumber(
WORKSPACE_ID, gitArtifactMetadata, pageDTO, Boolean.TRUE, ArtifactType.APPLICATION);
// this leads to server migration requirement as true // this leads to server migration requirement as true
Mockito.doReturn(Mono.just(JsonSchemaVersions.serverVersion - 1)) Mockito.doReturn(Mono.just(JsonSchemaVersions.serverVersion - 1))
.when(commonGitFileUtils) .when(commonGitFileUtils)
.getMetadataServerSchemaMigrationVersion( .getMetadataServerSchemaMigrationVersion(
WORKSPACE_ID, DEFAULT_APPLICATION_ID, REPO_NAME, BRANCH_NAME, ArtifactType.APPLICATION); WORKSPACE_ID, gitArtifactMetadata, Boolean.FALSE, ArtifactType.APPLICATION);
Mono<AutoCommitTriggerDTO> autoCommitTriggerDTOMono = Mono<AutoCommitTriggerDTO> autoCommitTriggerDTOMono =
autoCommitEligibilityHelper.isAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata, pageDTO); autoCommitEligibilityHelper.isAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata, pageDTO);
@ -220,7 +246,7 @@ public class AutoCommitEligibilityHelperTest {
Mockito.doReturn(Mono.just(JsonSchemaVersions.serverVersion)) Mockito.doReturn(Mono.just(JsonSchemaVersions.serverVersion))
.when(commonGitFileUtils) .when(commonGitFileUtils)
.getMetadataServerSchemaMigrationVersion( .getMetadataServerSchemaMigrationVersion(
WORKSPACE_ID, DEFAULT_APPLICATION_ID, REPO_NAME, BRANCH_NAME, ArtifactType.APPLICATION); WORKSPACE_ID, gitArtifactMetadata, Boolean.TRUE, ArtifactType.APPLICATION);
Mono<Boolean> isServerMigrationRequiredMono = Mono<Boolean> isServerMigrationRequiredMono =
autoCommitEligibilityHelper.isServerAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata); autoCommitEligibilityHelper.isServerAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata);
@ -239,7 +265,7 @@ public class AutoCommitEligibilityHelperTest {
Mockito.doReturn(Mono.just(JsonSchemaVersions.serverVersion - 1)) Mockito.doReturn(Mono.just(JsonSchemaVersions.serverVersion - 1))
.when(commonGitFileUtils) .when(commonGitFileUtils)
.getMetadataServerSchemaMigrationVersion( .getMetadataServerSchemaMigrationVersion(
WORKSPACE_ID, DEFAULT_APPLICATION_ID, REPO_NAME, BRANCH_NAME, ArtifactType.APPLICATION); WORKSPACE_ID, gitArtifactMetadata, Boolean.FALSE, ArtifactType.APPLICATION);
Mono<Boolean> isServerMigrationRequiredMono = Mono<Boolean> isServerMigrationRequiredMono =
autoCommitEligibilityHelper.isServerAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata); autoCommitEligibilityHelper.isServerAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata);
@ -252,7 +278,7 @@ public class AutoCommitEligibilityHelperTest {
@Test @Test
public void isServerMigrationRequired_whenFeatureIsFlagFalse_returnsFalse() { public void isServerMigrationRequired_whenFeatureIsFlagFalse_returnsFalse() {
Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_server_autocommit_feature_enabled)) Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_eligibility_enabled))
.thenReturn(Mono.just(Boolean.FALSE)); .thenReturn(Mono.just(Boolean.FALSE));
GitArtifactMetadata gitArtifactMetadata = createGitMetadata(); GitArtifactMetadata gitArtifactMetadata = createGitMetadata();
@ -268,10 +294,18 @@ public class AutoCommitEligibilityHelperTest {
@Test @Test
public void isClientMigrationRequired_whenLatestDslIsNotAhead_returnsFalse() { public void isClientMigrationRequired_whenLatestDslIsNotAhead_returnsFalse() {
GitArtifactMetadata gitArtifactMetadata = createGitMetadata();
PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER); PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER);
Mockito.doReturn(Mono.just(getPageDSl(RANDOM_DSL_VERSION_NUMBER)))
.when(commonGitFileUtils)
.getPageDslVersionNumber(
WORKSPACE_ID, gitArtifactMetadata, pageDTO, Boolean.TRUE, ArtifactType.APPLICATION);
Mockito.when(dslMigrationUtils.getLatestDslVersion()).thenReturn(Mono.just(RANDOM_DSL_VERSION_NUMBER)); Mockito.when(dslMigrationUtils.getLatestDslVersion()).thenReturn(Mono.just(RANDOM_DSL_VERSION_NUMBER));
Mono<Boolean> isClientMigrationRequiredMono = autoCommitEligibilityHelper.isClientMigrationRequired(pageDTO); Mono<Boolean> isClientMigrationRequiredMono =
autoCommitEligibilityHelper.isClientMigrationRequiredFSOps(WORKSPACE_ID, gitArtifactMetadata, pageDTO);
StepVerifier.create(isClientMigrationRequiredMono) StepVerifier.create(isClientMigrationRequiredMono)
.assertNext(isClientMigrationRequired -> .assertNext(isClientMigrationRequired ->
@ -281,10 +315,18 @@ public class AutoCommitEligibilityHelperTest {
@Test @Test
public void isClientMigrationRequired_whenLatestDslIsAhead_returnsTrue() { public void isClientMigrationRequired_whenLatestDslIsAhead_returnsTrue() {
GitArtifactMetadata gitArtifactMetadata = createGitMetadata();
PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER - 1); PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER - 1);
Mockito.doReturn(Mono.just(getPageDSl(RANDOM_DSL_VERSION_NUMBER - 1)))
.when(commonGitFileUtils)
.getPageDslVersionNumber(
WORKSPACE_ID, gitArtifactMetadata, pageDTO, Boolean.TRUE, ArtifactType.APPLICATION);
Mockito.when(dslMigrationUtils.getLatestDslVersion()).thenReturn(Mono.just(RANDOM_DSL_VERSION_NUMBER)); Mockito.when(dslMigrationUtils.getLatestDslVersion()).thenReturn(Mono.just(RANDOM_DSL_VERSION_NUMBER));
Mono<Boolean> isClientMigrationRequiredMono = autoCommitEligibilityHelper.isClientMigrationRequired(pageDTO); Mono<Boolean> isClientMigrationRequiredMono =
autoCommitEligibilityHelper.isClientMigrationRequiredFSOps(WORKSPACE_ID, gitArtifactMetadata, pageDTO);
StepVerifier.create(isClientMigrationRequiredMono) StepVerifier.create(isClientMigrationRequiredMono)
.assertNext(isClientMigrationRequired -> .assertNext(isClientMigrationRequired ->
@ -294,7 +336,7 @@ public class AutoCommitEligibilityHelperTest {
@Test @Test
public void isClientMigrationRequired_whenFeatureFlagIsFalse_returnsFalse() { public void isClientMigrationRequired_whenFeatureFlagIsFalse_returnsFalse() {
Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_feature_enabled)) Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_eligibility_enabled))
.thenReturn(Mono.just(Boolean.FALSE)); .thenReturn(Mono.just(Boolean.FALSE));
PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER); PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER);
@ -308,14 +350,11 @@ public class AutoCommitEligibilityHelperTest {
@Test @Test
public void isAutoCommitRequired_whenFeatureIsFlagFalse_returnsAllFalse() { public void isAutoCommitRequired_whenFeatureIsFlagFalse_returnsAllFalse() {
Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_feature_enabled)) Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_eligibility_enabled))
.thenReturn(Mono.just(Boolean.FALSE));
Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_server_autocommit_feature_enabled))
.thenReturn(Mono.just(Boolean.FALSE)); .thenReturn(Mono.just(Boolean.FALSE));
GitArtifactMetadata gitArtifactMetadata = createGitMetadata(); GitArtifactMetadata gitArtifactMetadata = createGitMetadata();
PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER); PageDTO pageDTO = createPageDTO(RANDOM_DSL_VERSION_NUMBER - 1);
Mono<AutoCommitTriggerDTO> autoCommitTriggerDTOMono = Mono<AutoCommitTriggerDTO> autoCommitTriggerDTOMono =
autoCommitEligibilityHelper.isAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata, pageDTO); autoCommitEligibilityHelper.isAutoCommitRequired(WORKSPACE_ID, gitArtifactMetadata, pageDTO);

View File

@ -8,9 +8,9 @@ import com.appsmith.server.domains.AutoCommitConfig;
import com.appsmith.server.domains.GitArtifactMetadata; import com.appsmith.server.domains.GitArtifactMetadata;
import com.appsmith.server.domains.GitAuth; import com.appsmith.server.domains.GitAuth;
import com.appsmith.server.domains.GitProfile; import com.appsmith.server.domains.GitProfile;
import com.appsmith.server.dtos.AutoCommitProgressDTO; import com.appsmith.server.dtos.AutoCommitResponseDTO;
import com.appsmith.server.events.AutoCommitEvent; import com.appsmith.server.events.AutoCommitEvent;
import com.appsmith.server.git.AutoCommitEventHandler; import com.appsmith.server.git.autocommit.AutoCommitEventHandler;
import com.appsmith.server.git.common.CommonGitService; import com.appsmith.server.git.common.CommonGitService;
import com.appsmith.server.helpers.GitPrivateRepoHelper; import com.appsmith.server.helpers.GitPrivateRepoHelper;
import com.appsmith.server.helpers.RedisUtils; import com.appsmith.server.helpers.RedisUtils;
@ -32,6 +32,8 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.test.StepVerifier; import reactor.test.StepVerifier;
import static com.appsmith.server.dtos.AutoCommitResponseDTO.AutoCommitResponse.IDLE;
import static com.appsmith.server.dtos.AutoCommitResponseDTO.AutoCommitResponse.IN_PROGRESS;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
@ -241,14 +243,14 @@ public class GitAutoCommitHelperImplTest {
@Test @Test
public void getAutoCommitProgress_WhenAutoCommitRunning_ReturnsValidResponse() { public void getAutoCommitProgress_WhenAutoCommitRunning_ReturnsValidResponse() {
Mono<AutoCommitProgressDTO> progressDTOMono = redisUtils Mono<AutoCommitResponseDTO> progressDTOMono = redisUtils
.startAutoCommit(defaultApplicationId, branchName) .startAutoCommit(defaultApplicationId, branchName)
.then(redisUtils.setAutoCommitProgress(defaultApplicationId, 20)) .then(redisUtils.setAutoCommitProgress(defaultApplicationId, 20))
.then(gitAutoCommitHelper.getAutoCommitProgress(defaultApplicationId)); .then(gitAutoCommitHelper.getAutoCommitProgress(defaultApplicationId, branchName));
StepVerifier.create(progressDTOMono) StepVerifier.create(progressDTOMono)
.assertNext(dto -> { .assertNext(dto -> {
assertThat(dto.getIsRunning()).isTrue(); assertThat(dto.getAutoCommitResponse()).isEqualTo(IN_PROGRESS);
assertThat(dto.getProgress()).isEqualTo(20); assertThat(dto.getProgress()).isEqualTo(20);
assertThat(dto.getBranchName()).isEqualTo(branchName); assertThat(dto.getBranchName()).isEqualTo(branchName);
}) })
@ -257,15 +259,15 @@ public class GitAutoCommitHelperImplTest {
@Test @Test
public void getAutoCommitProgress_WhenNoAutoCommitFinished_ReturnsValidResponse() { public void getAutoCommitProgress_WhenNoAutoCommitFinished_ReturnsValidResponse() {
Mono<AutoCommitProgressDTO> progressDTOMono = redisUtils Mono<AutoCommitResponseDTO> progressDTOMono = redisUtils
.startAutoCommit(defaultApplicationId, branchName) .startAutoCommit(defaultApplicationId, branchName)
.then(redisUtils.setAutoCommitProgress(defaultApplicationId, 20)) .then(redisUtils.setAutoCommitProgress(defaultApplicationId, 20))
.then(redisUtils.finishAutoCommit(defaultApplicationId)) .then(redisUtils.finishAutoCommit(defaultApplicationId))
.then(gitAutoCommitHelper.getAutoCommitProgress(defaultApplicationId)); .then(gitAutoCommitHelper.getAutoCommitProgress(defaultApplicationId, branchName));
StepVerifier.create(progressDTOMono) StepVerifier.create(progressDTOMono)
.assertNext(dto -> { .assertNext(dto -> {
assertThat(dto.getIsRunning()).isFalse(); assertThat(dto.getAutoCommitResponse()).isEqualTo(IDLE);
assertThat(dto.getProgress()).isZero(); assertThat(dto.getProgress()).isZero();
assertThat(dto.getBranchName()).isNull(); assertThat(dto.getBranchName()).isNull();
}) })
@ -274,10 +276,11 @@ public class GitAutoCommitHelperImplTest {
@Test @Test
public void getAutoCommitProgress_WhenNoAutoCommitRunning_ReturnsValidResponse() { public void getAutoCommitProgress_WhenNoAutoCommitRunning_ReturnsValidResponse() {
Mono<AutoCommitProgressDTO> progressDTOMono = gitAutoCommitHelper.getAutoCommitProgress(defaultApplicationId); Mono<AutoCommitResponseDTO> progressDTOMono =
gitAutoCommitHelper.getAutoCommitProgress(defaultApplicationId, branchName);
StepVerifier.create(progressDTOMono) StepVerifier.create(progressDTOMono)
.assertNext(dto -> { .assertNext(dto -> {
assertThat(dto.getIsRunning()).isFalse(); assertThat(dto.getAutoCommitResponse()).isEqualTo(IDLE);
assertThat(dto.getProgress()).isZero(); assertThat(dto.getProgress()).isZero();
assertThat(dto.getBranchName()).isNull(); assertThat(dto.getBranchName()).isNull();
}) })
@ -333,7 +336,7 @@ public class GitAutoCommitHelperImplTest {
application.setGitApplicationMetadata(metaData); application.setGitApplicationMetadata(metaData);
Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_server_autocommit_feature_enabled)) Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_feature_enabled))
.thenReturn(Mono.just(Boolean.FALSE)); .thenReturn(Mono.just(Boolean.FALSE));
StepVerifier.create(gitAutoCommitHelper.autoCommitServerMigration(defaultApplicationId, branchName)) StepVerifier.create(gitAutoCommitHelper.autoCommitServerMigration(defaultApplicationId, branchName))
@ -358,7 +361,7 @@ public class GitAutoCommitHelperImplTest {
application.setGitApplicationMetadata(metaData); application.setGitApplicationMetadata(metaData);
Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_server_autocommit_feature_enabled)) Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_feature_enabled))
.thenReturn(Mono.just(Boolean.TRUE)); .thenReturn(Mono.just(Boolean.TRUE));
Mockito.when(applicationService.findById(anyString(), any(AclPermission.class))) Mockito.when(applicationService.findById(anyString(), any(AclPermission.class)))
@ -396,7 +399,7 @@ public class GitAutoCommitHelperImplTest {
application.setGitApplicationMetadata(metaData); application.setGitApplicationMetadata(metaData);
Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_server_autocommit_feature_enabled)) Mockito.when(featureFlagService.check(FeatureFlagEnum.release_git_autocommit_feature_enabled))
.thenReturn(Mono.just(Boolean.TRUE)); .thenReturn(Mono.just(Boolean.TRUE));
Mockito.when(applicationService.findById(anyString(), any(AclPermission.class))) Mockito.when(applicationService.findById(anyString(), any(AclPermission.class)))

View File

@ -1,8 +1,34 @@
package com.appsmith.server.helpers; package com.appsmith.server.helpers;
import com.appsmith.server.constants.ArtifactType;
import com.appsmith.server.domains.Application;
import com.appsmith.server.dtos.ApplicationJson;
import com.appsmith.server.dtos.CacheableApplicationJson;
import com.appsmith.server.solutions.ApplicationPermission;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import mockwebserver3.MockResponse;
import mockwebserver3.MockWebServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringExtension;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.io.IOException;
import java.time.Instant;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
/** /**
* This test is written based on the inspiration from the tutorial: * This test is written based on the inspiration from the tutorial:
@ -11,155 +37,139 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class) @ExtendWith(SpringExtension.class)
@SpringBootTest @SpringBootTest
public class CacheableTemplateHelperTemplateJsonDataTest { public class CacheableTemplateHelperTemplateJsonDataTest {
// private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
// private static MockWebServer mockCloudServices; private static MockWebServer mockCloudServices;
//
// @MockBean @MockBean
// ApplicationPermission applicationPermission; ApplicationPermission applicationPermission;
//
// @MockBean @Autowired
// private CloudServicesConfig cloudServicesConfig; CacheableTemplateHelper cacheableTemplateHelper;
//
// @Autowired @SpyBean
// CacheableTemplateHelper cacheableTemplateHelper; CacheableTemplateHelper spyCacheableTemplateHelper;
//
// @SpyBean String baseUrl;
// CacheableTemplateHelper spyCacheableTemplateHelper;
// @BeforeAll
// @BeforeAll public static void setUp() throws IOException {
// public static void setUp() throws IOException { mockCloudServices = new MockWebServer();
// mockCloudServices = new MockWebServer(); mockCloudServices.start();
// mockCloudServices.start(); }
// }
// @AfterAll
// @AfterAll public static void tearDown() throws IOException {
// public static void tearDown() throws IOException { mockCloudServices.shutdown();
// mockCloudServices.shutdown(); }
// }
// @BeforeEach
// @BeforeEach public void initialize() {
// public void initialize() { baseUrl = String.format("http://localhost:%s", mockCloudServices.getPort());
// String baseUrl = String.format("http://localhost:%s", mockCloudServices.getPort()); }
//
// // mock the cloud services config so that it returns mock server url as cloud /* Scenarios covered via this test:
// // service base url * 1. CacheableTemplateHelper doesn't have the POJO or has an empty POJO.
// Mockito.when(cloudServicesConfig.getBaseUrl()).thenReturn(baseUrl); * 2. Fetch the templates via the normal flow by mocking CS.
// } * 3. Check if the CacheableTemplateHelper.getApplicationTemplateList() is the same as the object returned by
// the normal flow function. This will ensure that the cache is being set correctly.
// private ApplicationTemplate create(String id, String title) { * 4. From the above steps we now have the cache set.
// ApplicationTemplate applicationTemplate = new ApplicationTemplate(); * 5. Fetch the templates again, verify the data is the same as the one fetched in step 2.
// applicationTemplate.setId(id); * 6. Verify the cache is used and not the mock. This is done by asserting the lastUpdated time of the cache.
// applicationTemplate.setTitle(title); */
// return applicationTemplate; @Test
// } public void getApplicationJson_cacheIsEmpty_VerifyDataSavedInCache() throws JsonProcessingException {
// ApplicationJson applicationJson = new ApplicationJson();
// /* Scenarios covered via this test: applicationJson.setArtifactJsonType(ArtifactType.APPLICATION);
// * 1. CacheableTemplateHelper doesn't have the POJO or has an empty POJO. applicationJson.setExportedApplication(new Application());
// * 2. Fetch the templates via the normal flow by mocking CS.
// * 3. Check if the CacheableTemplateHelper.getApplicationTemplateList() is the same as the object returned by assertThat(cacheableTemplateHelper.getCacheableApplicationJsonMap().size())
// the normal flow function. This will ensure that the cache is being set correctly. .isEqualTo(0);
// * 4. From the above steps we now have the cache set.
// * 5. Fetch the templates again, verify the data is the same as the one fetched in step 2. // mock the server to return a template when it's called
// * 6. Verify the cache is used and not the mock. This is done by asserting the lastUpdated time of the cache. mockCloudServices.enqueue(new MockResponse()
// */ .setBody(objectMapper.writeValueAsString(applicationJson))
// @Test .addHeader("Content-Type", "application/json"));
// public void getApplicationJson_cacheIsEmpty_VerifyDataSavedInCache() throws JsonProcessingException {
// ApplicationJson applicationJson = new ApplicationJson(); Mono<CacheableApplicationJson> templateListMono =
// applicationJson.setArtifactJsonType(ArtifactType.APPLICATION); cacheableTemplateHelper.getApplicationByTemplateId("templateId", baseUrl);
// applicationJson.setExportedApplication(new Application());
// final Instant[] timeFromCache = {Instant.now()};
// assertThat(cacheableTemplateHelper.getCacheableApplicationJsonMap().size()) // make sure we've received the response returned by the mockCloudServices
// .isEqualTo(0); StepVerifier.create(templateListMono)
// .assertNext(cacheableApplicationJson1 -> {
// // mock the server to return a template when it's called assertThat(cacheableApplicationJson1.getApplicationJson()).isNotNull();
// mockCloudServices.enqueue(new MockResponse() timeFromCache[0] = cacheableApplicationJson1.getCacheExpiryTime();
// .setBody(objectMapper.writeValueAsString(applicationJson)) })
// .addHeader("Content-Type", "application/json")); .verifyComplete();
//
// Mono<CacheableApplicationJson> templateListMono = // Fetch the same application json again and verify the time stamp to confirm value is coming from POJO
// cacheableTemplateHelper.getApplicationByTemplateId("templateId", StepVerifier.create(cacheableTemplateHelper.getApplicationByTemplateId("templateId", baseUrl))
// cloudServicesConfig.getBaseUrl()); .assertNext(cacheableApplicationJson1 -> {
// assertThat(cacheableApplicationJson1.getApplicationJson()).isNotNull();
// final Instant[] timeFromCache = {Instant.now()}; assertThat(cacheableApplicationJson1.getCacheExpiryTime()).isEqualTo(timeFromCache[0]);
// // make sure we've received the response returned by the mockCloudServices })
// StepVerifier.create(templateListMono) .verifyComplete();
// .assertNext(cacheableApplicationJson1 -> { assertThat(cacheableTemplateHelper.getCacheableApplicationJsonMap().size())
// assertThat(cacheableApplicationJson1.getApplicationJson()).isNotNull(); .isEqualTo(1);
// timeFromCache[0] = cacheableApplicationJson1.getCacheExpiryTime(); }
// })
// .verifyComplete(); /* Scenarios covered via this test:
// * 1. Mock the cache isCacheValid to return false, so the cache is invalidated
// // Fetch the same application json again and verify the time stamp to confirm value is coming from POJO * 2. Fetch the templates again, verify the data is from the mock and not from the cache.
// StepVerifier.create(cacheableTemplateHelper.getApplicationByTemplateId( */
// "templateId", cloudServicesConfig.getBaseUrl())) @Test
// .assertNext(cacheableApplicationJson1 -> { public void getApplicationJson_cacheIsDirty_verifyDataIsFetchedFromSource() {
// assertThat(cacheableApplicationJson1.getApplicationJson()).isNotNull(); ApplicationJson applicationJson = new ApplicationJson();
// assertThat(cacheableApplicationJson1.getCacheExpiryTime()).isEqualTo(timeFromCache[0]); Application test = new Application();
// }) test.setName("New Application");
// .verifyComplete(); applicationJson.setArtifactJsonType(ArtifactType.APPLICATION);
// assertThat(cacheableTemplateHelper.getCacheableApplicationJsonMap().size()) applicationJson.setExportedApplication(test);
// .isEqualTo(1);
// } // mock the server to return the above three templates
// mockCloudServices.enqueue(new MockResponse()
// /* Scenarios covered via this test: .setBody(new Gson().toJson(applicationJson))
// * 1. Mock the cache isCacheValid to return false, so the cache is invalidated .addHeader("Content-Type", "application/json"));
// * 2. Fetch the templates again, verify the data is from the mock and not from the cache.
// */ Mockito.doReturn(false).when(spyCacheableTemplateHelper).isCacheValid(any());
// @Test
// public void getApplicationJson_cacheIsDirty_verifyDataIsFetchedFromSource() { // make sure we've received the response returned by the mock
// ApplicationJson applicationJson = new ApplicationJson(); StepVerifier.create(spyCacheableTemplateHelper.getApplicationByTemplateId("templateId", baseUrl))
// Application test = new Application(); .assertNext(cacheableApplicationJson1 -> {
// test.setName("New Application"); assertThat(cacheableApplicationJson1.getApplicationJson()).isNotNull();
// applicationJson.setArtifactJsonType(ArtifactType.APPLICATION); assertThat(cacheableApplicationJson1
// applicationJson.setExportedApplication(test); .getApplicationJson()
// .getExportedApplication()
// // mock the server to return the above three templates .getName())
// mockCloudServices.enqueue(new MockResponse() .isEqualTo("New Application");
// .setBody(new Gson().toJson(applicationJson)) })
// .addHeader("Content-Type", "application/json")); .verifyComplete();
// }
// Mockito.doReturn(false).when(spyCacheableTemplateHelper).isCacheValid(any());
// @Test
// // make sure we've received the response returned by the mock public void getApplicationJson_cacheKeyIsMissing_verifyDataIsFetchedFromSource() {
// StepVerifier.create(spyCacheableTemplateHelper.getApplicationByTemplateId( ApplicationJson applicationJson1 = new ApplicationJson();
// "templateId", cloudServicesConfig.getBaseUrl())) Application application = new Application();
// .assertNext(cacheableApplicationJson1 -> { application.setName("Test Application");
// assertThat(cacheableApplicationJson1.getApplicationJson()).isNotNull(); applicationJson1.setArtifactJsonType(ArtifactType.APPLICATION);
// assertThat(cacheableApplicationJson1 applicationJson1.setExportedApplication(application);
// .getApplicationJson()
// .getExportedApplication() assertThat(cacheableTemplateHelper.getCacheableApplicationJsonMap().size())
// .getName()) .isEqualTo(1);
// .isEqualTo("New Application");
// }) mockCloudServices.enqueue(new MockResponse()
// .verifyComplete(); .setBody(new Gson().toJson(applicationJson1))
// } .addHeader("Content-Type", "application/json"));
//
// @Test // make sure we've received the response returned by the mock
// public void getApplicationJson_cacheKeyIsMissing_verifyDataIsFetchedFromSource() { StepVerifier.create(cacheableTemplateHelper.getApplicationByTemplateId("templateId1", baseUrl))
// ApplicationJson applicationJson1 = new ApplicationJson(); .assertNext(cacheableApplicationJson1 -> {
// Application application = new Application(); assertThat(cacheableApplicationJson1.getApplicationJson()).isNotNull();
// application.setName("Test Application"); assertThat(cacheableApplicationJson1
// applicationJson1.setArtifactJsonType(ArtifactType.APPLICATION); .getApplicationJson()
// applicationJson1.setExportedApplication(application); .getExportedApplication()
// .getName())
// assertThat(cacheableTemplateHelper.getCacheableApplicationJsonMap().size()) .isEqualTo("Test Application");
// .isEqualTo(1); })
// .verifyComplete();
// mockCloudServices.enqueue(new MockResponse() }
// .setBody(new Gson().toJson(applicationJson1))
// .addHeader("Content-Type", "application/json"));
//
// // make sure we've received the response returned by the mock
// StepVerifier.create(cacheableTemplateHelper.getApplicationByTemplateId(
// "templateId1", cloudServicesConfig.getBaseUrl()))
// .assertNext(cacheableApplicationJson1 -> {
// assertThat(cacheableApplicationJson1.getApplicationJson()).isNotNull();
// assertThat(cacheableApplicationJson1
// .getApplicationJson()
// .getExportedApplication()
// .getName())
// .isEqualTo("Test Application");
// })
// .verifyComplete();
// }
} }

View File

@ -1,6 +1,5 @@
package com.appsmith.server.helpers; package com.appsmith.server.helpers;
import com.appsmith.server.configurations.CloudServicesConfig;
import com.appsmith.server.dtos.ApplicationTemplate; import com.appsmith.server.dtos.ApplicationTemplate;
import com.appsmith.server.dtos.CacheableApplicationTemplate; import com.appsmith.server.dtos.CacheableApplicationTemplate;
import com.appsmith.server.solutions.ApplicationPermission; import com.appsmith.server.solutions.ApplicationPermission;
@ -11,7 +10,6 @@ import mockwebserver3.MockWebServer;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito; import org.mockito.Mockito;
@ -32,7 +30,6 @@ import static org.mockito.ArgumentMatchers.any;
@ExtendWith(SpringExtension.class) @ExtendWith(SpringExtension.class)
@SpringBootTest @SpringBootTest
@Disabled
public class CacheableTemplateHelperTemplateMetadataTest { public class CacheableTemplateHelperTemplateMetadataTest {
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
@ -41,15 +38,14 @@ public class CacheableTemplateHelperTemplateMetadataTest {
@MockBean @MockBean
ApplicationPermission applicationPermission; ApplicationPermission applicationPermission;
@MockBean
private CloudServicesConfig cloudServicesConfig;
@Autowired @Autowired
CacheableTemplateHelper cacheableTemplateHelper; CacheableTemplateHelper cacheableTemplateHelper;
@SpyBean @SpyBean
CacheableTemplateHelper spyCacheableTemplateHelper; CacheableTemplateHelper spyCacheableTemplateHelper;
String baseUrl;
@BeforeAll @BeforeAll
public static void setUp() throws IOException { public static void setUp() throws IOException {
mockCloudServices = new MockWebServer(); mockCloudServices = new MockWebServer();
@ -63,11 +59,7 @@ public class CacheableTemplateHelperTemplateMetadataTest {
@BeforeEach @BeforeEach
public void initialize() { public void initialize() {
String baseUrl = String.format("http://localhost:%s", mockCloudServices.getPort()); baseUrl = String.format("http://localhost:%s", mockCloudServices.getPort());
// mock the cloud services config so that it returns mock server url as cloud
// service base url
Mockito.when(cloudServicesConfig.getBaseUrl()).thenReturn(baseUrl);
} }
private ApplicationTemplate create(String id, String title) { private ApplicationTemplate create(String id, String title) {
@ -99,7 +91,7 @@ public class CacheableTemplateHelperTemplateMetadataTest {
.addHeader("Content-Type", "application/json")); .addHeader("Content-Type", "application/json"));
Mono<CacheableApplicationTemplate> templateListMono = Mono<CacheableApplicationTemplate> templateListMono =
cacheableTemplateHelper.getTemplates("recently-used", cloudServicesConfig.getBaseUrl()); cacheableTemplateHelper.getTemplates("recently-used", baseUrl);
final Instant[] timeFromCache = {Instant.now()}; final Instant[] timeFromCache = {Instant.now()};
StepVerifier.create(templateListMono) StepVerifier.create(templateListMono)
@ -114,7 +106,7 @@ public class CacheableTemplateHelperTemplateMetadataTest {
.verifyComplete(); .verifyComplete();
// Fetch again and verify the time stamp to confirm value is coming from POJO // Fetch again and verify the time stamp to confirm value is coming from POJO
StepVerifier.create(cacheableTemplateHelper.getTemplates("recently-used", cloudServicesConfig.getBaseUrl())) StepVerifier.create(cacheableTemplateHelper.getTemplates("recently-used", baseUrl))
.assertNext(cacheableApplicationTemplate1 -> { .assertNext(cacheableApplicationTemplate1 -> {
assertThat(cacheableApplicationTemplate1.getApplicationTemplateList()) assertThat(cacheableApplicationTemplate1.getApplicationTemplateList())
.hasSize(3); .hasSize(3);
@ -141,7 +133,7 @@ public class CacheableTemplateHelperTemplateMetadataTest {
.setBody(objectMapper.writeValueAsString(List.of(templateFour, templateFive, templateSix))) .setBody(objectMapper.writeValueAsString(List.of(templateFour, templateFive, templateSix)))
.addHeader("Content-Type", "application/json")); .addHeader("Content-Type", "application/json"));
StepVerifier.create(spyCacheableTemplateHelper.getTemplates("recently-used", cloudServicesConfig.getBaseUrl())) StepVerifier.create(spyCacheableTemplateHelper.getTemplates("recently-used", baseUrl))
.assertNext(cacheableApplicationTemplate1 -> { .assertNext(cacheableApplicationTemplate1 -> {
assertThat(cacheableApplicationTemplate1.getApplicationTemplateList()) assertThat(cacheableApplicationTemplate1.getApplicationTemplateList())
.hasSize(3); .hasSize(3);

View File

@ -1023,13 +1023,13 @@ public class LayoutActionServiceTest {
for (int i = 0; i < testPages.size(); i++) { for (int i = 0; i < testPages.size(); i++) {
PageDTO page = testPages.get(i); PageDTO page = testPages.get(i);
Layout layout = page.getLayouts().get(0); final UpdateMultiplePageLayoutDTO.LayoutDTO layout =
layout.setDsl(createTestDslWithTestWidget("Layout" + (i + 1))); new UpdateMultiplePageLayoutDTO.LayoutDTO(createTestDslWithTestWidget("Layout" + (i + 1)));
UpdateMultiplePageLayoutDTO.UpdatePageLayoutDTO pageLayoutDTO = UpdateMultiplePageLayoutDTO.UpdatePageLayoutDTO pageLayoutDTO =
new UpdateMultiplePageLayoutDTO.UpdatePageLayoutDTO(); new UpdateMultiplePageLayoutDTO.UpdatePageLayoutDTO();
pageLayoutDTO.setPageId(page.getId()); pageLayoutDTO.setPageId(page.getId());
pageLayoutDTO.setLayoutId(layout.getId()); pageLayoutDTO.setLayoutId(page.getLayouts().get(0).getId());
pageLayoutDTO.setLayout(layout); pageLayoutDTO.setLayout(layout);
multiplePageLayoutDTO.getPageLayouts().add(pageLayoutDTO); multiplePageLayoutDTO.getPageLayouts().add(pageLayoutDTO);
} }

View File

@ -2,7 +2,7 @@
set -e set -e
echo "Running as: $(id)" tlog "Running as: $(id)"
stacks_path=/appsmith-stacks stacks_path=/appsmith-stacks
@ -48,7 +48,7 @@ init_env_file() {
# Build an env file with current env variables. We single-quote the values, as well as escaping any single-quote characters. # Build an env file with current env variables. We single-quote the values, as well as escaping any single-quote characters.
printenv | grep -E '^APPSMITH_|^MONGO_' | sed "s/'/'\\\''/g; s/=/='/; s/$/'/" > "$TMP/pre-define.env" printenv | grep -E '^APPSMITH_|^MONGO_' | sed "s/'/'\\\''/g; s/=/='/; s/$/'/" > "$TMP/pre-define.env"
echo "Initialize .env file" tlog "Initialize .env file"
if ! [[ -e "$ENV_PATH" ]]; then if ! [[ -e "$ENV_PATH" ]]; then
# Generate new docker.env file when initializing container for first time or in Heroku which does not have persistent volume # Generate new docker.env file when initializing container for first time or in Heroku which does not have persistent volume
echo "Generating default configuration file" echo "Generating default configuration file"
@ -74,7 +74,7 @@ init_env_file() {
fi fi
echo "Load environment configuration" tlog "Load environment configuration"
set -o allexport set -o allexport
. "$ENV_PATH" . "$ENV_PATH"
. "$TMP/pre-define.env" . "$TMP/pre-define.env"
@ -111,20 +111,20 @@ if [[ -n "${FILESTORE_IP_ADDRESS-}" ]]; then
FILESTORE_IP_ADDRESS="$(echo "$FILESTORE_IP_ADDRESS" | xargs)" FILESTORE_IP_ADDRESS="$(echo "$FILESTORE_IP_ADDRESS" | xargs)"
FILE_SHARE_NAME="$(echo "$FILE_SHARE_NAME" | xargs)" FILE_SHARE_NAME="$(echo "$FILE_SHARE_NAME" | xargs)"
echo "Running appsmith for cloudRun" tlog "Running appsmith for cloudRun"
echo "creating mount point" tlog "creating mount point"
mkdir -p "$stacks_path" mkdir -p "$stacks_path"
echo "Mounting File Sytem" tlog "Mounting File Sytem"
mount -t nfs -o nolock "$FILESTORE_IP_ADDRESS:/$FILE_SHARE_NAME" /appsmith-stacks mount -t nfs -o nolock "$FILESTORE_IP_ADDRESS:/$FILE_SHARE_NAME" /appsmith-stacks
echo "Mounted File Sytem" tlog "Mounted File Sytem"
echo "Setting HOSTNAME for Cloudrun" tlog "Setting HOSTNAME for Cloudrun"
export HOSTNAME="cloudrun" export HOSTNAME="cloudrun"
fi fi
function get_maximum_heap() { function get_maximum_heap() {
resource=$(ulimit -u) resource=$(ulimit -u)
echo "Resource : $resource" tlog "Resource : $resource"
if [[ "$resource" -le 256 ]]; then if [[ "$resource" -le 256 ]]; then
maximum_heap=128 maximum_heap=128
elif [[ "$resource" -le 512 ]]; then elif [[ "$resource" -le 512 ]]; then
@ -140,7 +140,7 @@ function setup_backend_heap_arg() {
unset_unused_variables() { unset_unused_variables() {
# Check for enviroment vairalbes # Check for enviroment vairalbes
echo "Checking environment configuration" tlog "Checking environment configuration"
if [[ -z "${APPSMITH_MAIL_ENABLED}" ]]; then if [[ -z "${APPSMITH_MAIL_ENABLED}" ]]; then
unset APPSMITH_MAIL_ENABLED # If this field is empty is might cause application crash unset APPSMITH_MAIL_ENABLED # If this field is empty is might cause application crash
fi fi
@ -169,7 +169,7 @@ unset_unused_variables() {
} }
configure_database_connection_url() { configure_database_connection_url() {
echo "Configuring database connection URL" tlog "Configuring database connection URL"
isPostgresUrl=0 isPostgresUrl=0
isMongoUrl=0 isMongoUrl=0
# Check if APPSMITH_DB_URL is not set # Check if APPSMITH_DB_URL is not set
@ -186,17 +186,17 @@ configure_database_connection_url() {
} }
check_db_uri() { check_db_uri() {
echo "Checking APPSMITH_DB_URL" tlog "Checking APPSMITH_DB_URL"
isUriLocal=1 isUriLocal=1
if [[ $APPSMITH_DB_URL == *"localhost"* || $APPSMITH_DB_URL == *"127.0.0.1"* ]]; then if [[ $APPSMITH_DB_URL == *"localhost"* || $APPSMITH_DB_URL == *"127.0.0.1"* ]]; then
echo "Detected local DB" tlog "Detected local DB"
isUriLocal=0 isUriLocal=0
fi fi
} }
init_mongodb() { init_mongodb() {
if [[ $isUriLocal -eq 0 ]]; then if [[ $isUriLocal -eq 0 ]]; then
echo "Initializing local database" tlog "Initializing local database"
MONGO_DB_PATH="$stacks_path/data/mongodb" MONGO_DB_PATH="$stacks_path/data/mongodb"
MONGO_LOG_PATH="$MONGO_DB_PATH/log" MONGO_LOG_PATH="$MONGO_DB_PATH/log"
MONGO_DB_KEY="$MONGO_DB_PATH/key" MONGO_DB_KEY="$MONGO_DB_PATH/key"
@ -213,7 +213,7 @@ init_mongodb() {
} }
init_replica_set() { init_replica_set() {
echo "Checking initialized database" tlog "Checking initialized database"
shouldPerformInitdb=1 shouldPerformInitdb=1
for path in \ for path in \
"$MONGO_DB_PATH/WiredTiger" \ "$MONGO_DB_PATH/WiredTiger" \
@ -227,21 +227,21 @@ init_replica_set() {
done done
if [[ $isUriLocal -gt 0 && -f /proc/cpuinfo ]] && ! grep --quiet avx /proc/cpuinfo; then if [[ $isUriLocal -gt 0 && -f /proc/cpuinfo ]] && ! grep --quiet avx /proc/cpuinfo; then
echo "====================================================================================================" >&2 tlog "====================================================================================================" >&2
echo "==" >&2 tlog "==" >&2
echo "== AVX instruction not found in your CPU. Appsmith's embedded MongoDB may not start. Please use an external MongoDB instance instead." >&2 tlog "== AVX instruction not found in your CPU. Appsmith's embedded MongoDB may not start. Please use an external MongoDB instance instead." >&2
echo "== See https://docs.appsmith.com/getting-started/setup/instance-configuration/custom-mongodb-redis#custom-mongodb for instructions." >&2 tlog "== See https://docs.appsmith.com/getting-started/setup/instance-configuration/custom-mongodb-redis#custom-mongodb for instructions." >&2
echo "==" >&2 tlog "==" >&2
echo "====================================================================================================" >&2 tlog "====================================================================================================" >&2
fi fi
if [[ $shouldPerformInitdb -gt 0 && $isUriLocal -eq 0 ]]; then if [[ $shouldPerformInitdb -gt 0 && $isUriLocal -eq 0 ]]; then
echo "Initializing Replica Set for local database" tlog "Initializing Replica Set for local database"
# Start installed MongoDB service - Dependencies Layer # Start installed MongoDB service - Dependencies Layer
mongod --fork --port 27017 --dbpath "$MONGO_DB_PATH" --logpath "$MONGO_LOG_PATH" mongod --fork --port 27017 --dbpath "$MONGO_DB_PATH" --logpath "$MONGO_LOG_PATH"
echo "Waiting 10s for MongoDB to start" tlog "Waiting 10s for MongoDB to start"
sleep 10 sleep 10
echo "Creating MongoDB user" tlog "Creating MongoDB user"
mongosh "127.0.0.1/appsmith" --eval "db.createUser({ mongosh "127.0.0.1/appsmith" --eval "db.createUser({
user: '$APPSMITH_MONGODB_USER', user: '$APPSMITH_MONGODB_USER',
pwd: '$APPSMITH_MONGODB_PASSWORD', pwd: '$APPSMITH_MONGODB_PASSWORD',
@ -251,20 +251,20 @@ init_replica_set() {
}, 'readWrite'] }, 'readWrite']
} }
)" )"
echo "Enabling Replica Set" tlog "Enabling Replica Set"
mongod --dbpath "$MONGO_DB_PATH" --shutdown || true mongod --dbpath "$MONGO_DB_PATH" --shutdown || true
mongod --fork --port 27017 --dbpath "$MONGO_DB_PATH" --logpath "$MONGO_LOG_PATH" --replSet mr1 --keyFile "$MONGODB_TMP_KEY_PATH" --bind_ip localhost mongod --fork --port 27017 --dbpath "$MONGO_DB_PATH" --logpath "$MONGO_LOG_PATH" --replSet mr1 --keyFile "$MONGODB_TMP_KEY_PATH" --bind_ip localhost
echo "Waiting 10s for MongoDB to start with Replica Set" tlog "Waiting 10s for MongoDB to start with Replica Set"
sleep 10 sleep 10
mongosh "$APPSMITH_DB_URL" --eval 'rs.initiate()' mongosh "$APPSMITH_DB_URL" --eval 'rs.initiate()'
mongod --dbpath "$MONGO_DB_PATH" --shutdown || true mongod --dbpath "$MONGO_DB_PATH" --shutdown || true
fi fi
if [[ $isUriLocal -gt 0 ]]; then if [[ $isUriLocal -gt 0 ]]; then
echo "Checking Replica Set of external MongoDB" tlog "Checking Replica Set of external MongoDB"
if appsmithctl check-replica-set; then if appsmithctl check-replica-set; then
echo "MongoDB ReplicaSet is enabled" tlog "MongoDB ReplicaSet is enabled"
else else
echo -e "\033[0;31m***************************************************************************************\033[0m" echo -e "\033[0;31m***************************************************************************************\033[0m"
echo -e "\033[0;31m* MongoDB Replica Set is not enabled *\033[0m" echo -e "\033[0;31m* MongoDB Replica Set is not enabled *\033[0m"
@ -301,7 +301,7 @@ check_setup_custom_ca_certificates() {
if is_empty_directory "$container_ca_certs_path"; then if is_empty_directory "$container_ca_certs_path"; then
rmdir -v "$container_ca_certs_path" rmdir -v "$container_ca_certs_path"
else else
echo "The 'ca-certificates' directory inside the container is not empty. Please clear it and restart to use certs from 'stacks/ca-certs' directory." >&2 tlog "The 'ca-certificates' directory inside the container is not empty. Please clear it and restart to use certs from 'stacks/ca-certs' directory." >&2
return return
fi fi
fi fi
@ -325,11 +325,11 @@ setup-custom-ca-certificates() (
rm -f "$store" "$opts_file" rm -f "$store" "$opts_file"
if [[ -n "$(ls "$stacks_ca_certs_path"/*.pem 2>/dev/null)" ]]; then if [[ -n "$(ls "$stacks_ca_certs_path"/*.pem 2>/dev/null)" ]]; then
echo "Looks like you have some '.pem' files in your 'ca-certs' folder. Please rename them to '.crt' to be picked up automatically.". tlog "Looks like you have some '.pem' files in your 'ca-certs' folder. Please rename them to '.crt' to be picked up automatically.".
fi fi
if ! [[ -d "$stacks_ca_certs_path" && "$(find "$stacks_ca_certs_path" -maxdepth 1 -type f -name '*.crt' | wc -l)" -gt 0 ]]; then if ! [[ -d "$stacks_ca_certs_path" && "$(find "$stacks_ca_certs_path" -maxdepth 1 -type f -name '*.crt' | wc -l)" -gt 0 ]]; then
echo "No custom CA certificates found." tlog "No custom CA certificates found."
return return
fi fi
@ -389,7 +389,7 @@ check_redis_compatible_page_size() {
--data '{ "userId": "'"$HOSTNAME"'", "event":"RedisCompile" }' \ --data '{ "userId": "'"$HOSTNAME"'", "event":"RedisCompile" }' \
https://api.segment.io/v1/track \ https://api.segment.io/v1/track \
|| true || true
echo "Compile Redis stable with page size of $page_size" tlog "Compile Redis stable with page size of $page_size"
apt-get update apt-get update
apt-get install --yes build-essential apt-get install --yes build-essential
curl --connect-timeout 5 --location https://download.redis.io/redis-stable.tar.gz | tar -xz -C /tmp curl --connect-timeout 5 --location https://download.redis.io/redis-stable.tar.gz | tar -xz -C /tmp
@ -399,15 +399,14 @@ check_redis_compatible_page_size() {
popd popd
rm -rf /tmp/redis-stable rm -rf /tmp/redis-stable
else else
echo "Redis is compatible with page size of $page_size" tlog "Redis is compatible with page size of $page_size"
fi fi
} }
init_postgres() { init_postgres() {
# Initialize embedded postgres by default; set APPSMITH_ENABLE_EMBEDDED_DB to 0, to use existing cloud postgres mockdb instance # Initialize embedded postgres by default; set APPSMITH_ENABLE_EMBEDDED_DB to 0, to use existing cloud postgres mockdb instance
if [[ ${APPSMITH_ENABLE_EMBEDDED_DB: -1} != 0 ]]; then if [[ ${APPSMITH_ENABLE_EMBEDDED_DB: -1} != 0 ]]; then
echo "" tlog "Checking initialized local postgres"
echo "Checking initialized local postgres"
POSTGRES_DB_PATH="$stacks_path/data/postgres/main" POSTGRES_DB_PATH="$stacks_path/data/postgres/main"
mkdir -p "$POSTGRES_DB_PATH" "$TMP/pg-runtime" mkdir -p "$POSTGRES_DB_PATH" "$TMP/pg-runtime"
@ -416,9 +415,9 @@ init_postgres() {
chown -R postgres:postgres "$POSTGRES_DB_PATH" "$TMP/pg-runtime" chown -R postgres:postgres "$POSTGRES_DB_PATH" "$TMP/pg-runtime"
if [[ -e "$POSTGRES_DB_PATH/PG_VERSION" ]]; then if [[ -e "$POSTGRES_DB_PATH/PG_VERSION" ]]; then
echo "Found existing Postgres, Skipping initialization" tlog "Found existing Postgres, Skipping initialization"
else else
echo "Initializing local postgresql database" tlog "Initializing local postgresql database"
mkdir -p "$POSTGRES_DB_PATH" mkdir -p "$POSTGRES_DB_PATH"
# Postgres does not allow it's server to be run with super user access, we use user postgres and the file system owner also needs to be the same user postgres # Postgres does not allow it's server to be run with super user access, we use user postgres and the file system owner also needs to be the same user postgres
@ -510,11 +509,11 @@ check_db_uri
if [[ -z "${DYNO}" ]]; then if [[ -z "${DYNO}" ]]; then
if [[ $isMongoUrl -eq 1 ]]; then if [[ $isMongoUrl -eq 1 ]]; then
# Setup MongoDB and initialize replica set # Setup MongoDB and initialize replica set
echo "Initializing MongoDB" tlog "Initializing MongoDB"
init_mongodb init_mongodb
init_replica_set init_replica_set
elif [[ $isPostgresUrl -eq 1 ]]; then elif [[ $isPostgresUrl -eq 1 ]]; then
echo "Initializing Postgres" tlog "Initializing Postgres"
# init_postgres # init_postgres
fi fi
else else

View File

@ -0,0 +1,5 @@
#!/bin/sh
# Running this with `sh` since it's very tiny, and doesn't need the big bash.
# Printing time in UTC.
echo "$(date -u +%FT%T.%3NZ)" "$@"