Merge branch 'release' into feat/4182-form-detect-changes
This commit is contained in:
commit
ff2bcc228a
|
|
@ -61,7 +61,7 @@
|
|||
"options": "[{'label':'Vegetarian','value':'VEG'},{'label':'Non-Vegetarian','value':'NON_VEG'},{'label':'Vegan','value':'VEGAN'}]",
|
||||
"widgetName": "Dropdown1",
|
||||
"defaultOptionValue": "VEG",
|
||||
"type": "DROP_DOWN_WIDGET",
|
||||
"type": "SELECT_WIDGET",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 74,
|
||||
"parentRowSpace": 40,
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@
|
|||
"topRow": 8,
|
||||
"bottomRow": 15,
|
||||
"parentRowSpace": 10,
|
||||
"type": "DROP_DOWN_WIDGET",
|
||||
"type": "SELECT_WIDGET",
|
||||
"serverSideFiltering": false,
|
||||
"hideCard": false,
|
||||
"defaultOptionValue": "GREEN",
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"parentRowSpace": 38,
|
||||
"isVisible": true,
|
||||
"label": "Test Dropdown",
|
||||
"type": "DROP_DOWN_WIDGET",
|
||||
"type": "SELECT_WIDGET",
|
||||
"dynamicBindingPathList": [],
|
||||
"isLoading": false,
|
||||
"selectionType": "",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ describe("API Panel request body", function() {
|
|||
cy.SelectAction(testdata.getAction);
|
||||
|
||||
cy.contains(apiEditor.bodyTab).click();
|
||||
cy.get(`[data-cy=${testdata.apiContentTypeNone}]`).click();
|
||||
cy.get(testdata.noBodyErrorMessageDiv).should("exist");
|
||||
cy.get(testdata.noBodyErrorMessageDiv).contains(
|
||||
testdata.noBodyErrorMessage,
|
||||
|
|
|
|||
|
|
@ -14,13 +14,11 @@ describe("Check debugger logs state when there are onPageLoad actions", function
|
|||
cy.CreateAPI("TestApi");
|
||||
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods);
|
||||
cy.SaveAndRunAPI();
|
||||
|
||||
cy.get(explorer.addWidget).click();
|
||||
|
||||
cy.reload();
|
||||
// Wait for the debugger icon to be visible
|
||||
cy.get(".t--debugger").should("be.visible");
|
||||
cy.get(debuggerLocators.errorCount).should("not.exist");
|
||||
//cy.get(debuggerLocators.errorCount).should("not.exist");
|
||||
cy.wait("@postExecute");
|
||||
cy.contains(debuggerLocators.errorCount, 1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -81,11 +81,12 @@ describe("Chart Widget Skeleton Loading Functionality", function() {
|
|||
//Step9:
|
||||
cy.get(".bp3-button-text")
|
||||
.first()
|
||||
.click();
|
||||
.click({ force: true });
|
||||
|
||||
//Step10:
|
||||
cy.get(".t--widget-chartwidget div[class*='bp3-skeleton']").should("exist");
|
||||
|
||||
/* This section is flaky hence commenting out
|
||||
//Step11:
|
||||
cy.reload();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
const dsl = require("../../../../fixtures/emptyDSL.json");
|
||||
const explorer = require("../../../../locators/explorerlocators.json");
|
||||
const formWidgetsPage = require("../../../../locators/FormWidgets.json");
|
||||
const commonlocators = require("../../../../locators/commonlocators.json");
|
||||
const publish = require("../../../../locators/publishWidgetspage.json");
|
||||
|
||||
describe("Dropdown Widget Functionality", function() {
|
||||
before(() => {
|
||||
cy.addDsl(dsl);
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.wait(7000);
|
||||
});
|
||||
|
||||
it("Add new dropdown widget", () => {
|
||||
cy.get(explorer.addWidget).click();
|
||||
|
|
@ -36,7 +42,7 @@ describe("Dropdown Widget Functionality", function() {
|
|||
);
|
||||
});
|
||||
|
||||
it("should check that more thatn empty value is not allowed in options", () => {
|
||||
it("should check that more than one empty value is not allowed in options", () => {
|
||||
cy.openPropertyPane("selectwidget");
|
||||
cy.updateCodeInput(
|
||||
".t--property-control-options",
|
||||
|
|
@ -59,4 +65,61 @@ describe("Dropdown Widget Functionality", function() {
|
|||
"exist",
|
||||
);
|
||||
});
|
||||
|
||||
it.skip("should check that Objects can be added to Select Widget default value", () => {
|
||||
cy.openPropertyPane("selectwidget");
|
||||
cy.updateCodeInput(
|
||||
".t--property-control-options",
|
||||
`[
|
||||
{
|
||||
"label": "Blue",
|
||||
"value": "BLUE"
|
||||
},
|
||||
{
|
||||
"label": "Green",
|
||||
"value": "GREEN"
|
||||
},
|
||||
{
|
||||
"label": "Red",
|
||||
"value": "RED"
|
||||
}
|
||||
]`,
|
||||
);
|
||||
cy.updateCodeInput(
|
||||
".t--property-control-defaultvalue",
|
||||
`
|
||||
{
|
||||
"label": "Green",
|
||||
"value": "GREEN"
|
||||
}
|
||||
`,
|
||||
);
|
||||
cy.get(".t--property-control-options .t--codemirror-has-error").should(
|
||||
"not.exist",
|
||||
);
|
||||
cy.get(".t--property-control-defaultvalue .t--codemirror-has-error").should(
|
||||
"not.exist",
|
||||
);
|
||||
cy.get(formWidgetsPage.dropdownDefaultButton).should("contain", "Green");
|
||||
});
|
||||
|
||||
it("Dropdown Functionality To Check disabled Widget", function() {
|
||||
cy.openPropertyPane("selectwidget");
|
||||
// Disable the visible JS
|
||||
cy.togglebarDisable(commonlocators.visibleCheckbox);
|
||||
cy.PublishtheApp();
|
||||
// Verify the disabled visible JS
|
||||
cy.get(publish.selectwidget + " " + "input").should("not.exist");
|
||||
cy.goToEditFromPublish();
|
||||
});
|
||||
|
||||
it("Dropdown Functionality To UnCheck disabled Widget", function() {
|
||||
cy.openPropertyPane("selectwidget");
|
||||
// Check the visible JS
|
||||
cy.togglebar(commonlocators.visibleCheckbox);
|
||||
cy.PublishtheApp();
|
||||
// Verify the checked visible JS
|
||||
cy.get(publish.selectwidget).should("exist");
|
||||
cy.goToEditFromPublish();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
const dsl = require("../../../../fixtures/emptyDSL.json");
|
||||
const explorer = require("../../../../locators/explorerlocators.json");
|
||||
const formWidgetsPage = require("../../../../locators/FormWidgets.json");
|
||||
|
||||
describe("MultiSelect Widget Functionality", function() {
|
||||
before(() => {
|
||||
cy.addDsl(dsl);
|
||||
});
|
||||
|
||||
it("Add new dropdown widget", () => {
|
||||
beforeEach(() => {
|
||||
cy.wait(7000);
|
||||
});
|
||||
it("Add new multiselect widget", () => {
|
||||
cy.get(explorer.addWidget).click();
|
||||
cy.dragAndDropToCanvas("multiselectwidgetv2", { x: 300, y: 300 });
|
||||
cy.get(".t--widget-multiselectwidgetv2").should("exist");
|
||||
|
|
@ -36,7 +39,7 @@ describe("MultiSelect Widget Functionality", function() {
|
|||
);
|
||||
});
|
||||
|
||||
it("should check that more thatn empty value is not allowed in options", () => {
|
||||
it("should check that more that one empty value is not allowed in options", () => {
|
||||
cy.openPropertyPane("multiselectwidgetv2");
|
||||
cy.updateCodeInput(
|
||||
".t--property-control-options",
|
||||
|
|
@ -59,4 +62,44 @@ describe("MultiSelect Widget Functionality", function() {
|
|||
"exist",
|
||||
);
|
||||
});
|
||||
it("should check that Objects can be added to multiselect Widget default value", () => {
|
||||
cy.openPropertyPane("multiselectwidgetv2");
|
||||
cy.updateCodeInput(
|
||||
".t--property-control-options",
|
||||
`[
|
||||
{
|
||||
"label": "Blue",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"label": "Green",
|
||||
"value": "GREEN"
|
||||
},
|
||||
{
|
||||
"label": "Red",
|
||||
"value": "RED"
|
||||
}
|
||||
]`,
|
||||
);
|
||||
cy.updateCodeInput(
|
||||
".t--property-control-defaultvalue",
|
||||
`[
|
||||
{
|
||||
"label": "Green",
|
||||
"value": "GREEN"
|
||||
}
|
||||
]`,
|
||||
);
|
||||
cy.get(".t--property-control-options .t--codemirror-has-error").should(
|
||||
"not.exist",
|
||||
);
|
||||
cy.get(".t--property-control-defaultvalue .t--codemirror-has-error").should(
|
||||
"not.exist",
|
||||
);
|
||||
cy.wait(100);
|
||||
cy.get(formWidgetsPage.multiselectwidgetv2)
|
||||
.find(".rc-select-selection-item-content")
|
||||
.first()
|
||||
.should("have.text", "Green");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
const dsl = require("../../../../fixtures/previewMode.json");
|
||||
const commonlocators = require("../../../../locators/commonlocators.json");
|
||||
const publishPage = require("../../../../locators/publishWidgetspage.json");
|
||||
|
||||
describe("Preview mode functionality", function() {
|
||||
before(() => {
|
||||
|
|
@ -26,4 +28,25 @@ describe("Preview mode functionality", function() {
|
|||
`${selector}:first-of-type .t--widget-propertypane-toggle > .t--widget-name`,
|
||||
).should("not.exist");
|
||||
});
|
||||
|
||||
it("check invisible widget should not show in proview mode and should show in edit mode", function() {
|
||||
cy.get(".t--switch-comment-mode-off").click();
|
||||
cy.openPropertyPane("buttonwidget");
|
||||
cy.UncheckWidgetProperties(commonlocators.visibleCheckbox);
|
||||
|
||||
// button should not show in preview mode
|
||||
cy.get(".t--switch-preview-mode-toggle").click();
|
||||
cy.get(`${publishPage.buttonWidget} button`).should("not.exist");
|
||||
|
||||
// Text widget should show
|
||||
cy.get(`${publishPage.textWidget} .bp3-ui-text`).should("exist");
|
||||
|
||||
// button should show in edit mode
|
||||
cy.get(".t--switch-comment-mode-off").click();
|
||||
cy.get(`${publishPage.buttonWidget} button`).should("exist");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// put your clean up code if any
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"checkboxWidget": ".t--draggable-checkboxwidget",
|
||||
"selectwidget": ".t--draggable-selectwidget",
|
||||
"dropdownWidget": ".t--draggable-dropdownwidget",
|
||||
"dropdownWidget": ".t--draggable-selectwidget",
|
||||
"menuButtonWidget": ".t--draggable-menubuttonwidget",
|
||||
"multiselectwidgetv2": ".t--draggable-multiselectwidgetv2",
|
||||
"multiselecttreeWidget": ".t--draggable-multiselecttreewidget",
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@
|
|||
"widgetName":"Dropdown1",
|
||||
"defaultOptionValue":"VEG",
|
||||
"version":1,
|
||||
"type":"DROP_DOWN_WIDGET",
|
||||
"type":"SELECT_WIDGET",
|
||||
"isLoading":false,
|
||||
"parentColumnSpace":60.131249999999994,
|
||||
"parentRowSpace":40,
|
||||
|
|
|
|||
|
|
@ -300,7 +300,7 @@ const TextInput = forwardRef(
|
|||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [inputValue, setInputValue] = useState(props.defaultValue);
|
||||
|
||||
const { trimValue = true } = props;
|
||||
const { trimValue = false } = props;
|
||||
|
||||
const setRightSideRef = useCallback((ref: HTMLDivElement) => {
|
||||
if (ref) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { previewModeSelector } from "selectors/editorSelectors";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
isVisible?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* render only visible components in preview mode
|
||||
*/
|
||||
function PreviewModeComponent({
|
||||
children,
|
||||
isVisible,
|
||||
}: Props): React.ReactElement {
|
||||
const isPreviewMode = useSelector(previewModeSelector);
|
||||
if (!isPreviewMode || isVisible) return children as React.ReactElement;
|
||||
else return <div />;
|
||||
}
|
||||
|
||||
export default PreviewModeComponent;
|
||||
|
|
@ -814,7 +814,7 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
value: "HEADER",
|
||||
},
|
||||
],
|
||||
"Send client credentials with",
|
||||
"Send client credentials with (on refresh token):",
|
||||
"",
|
||||
false,
|
||||
"",
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ import {
|
|||
} from "utils/ApiPaneUtils";
|
||||
import { updateReplayEntity } from "actions/pageActions";
|
||||
import { ENTITY_TYPE } from "entities/AppsmithConsole";
|
||||
import { getDisplayFormat } from "selectors/apiPaneSelectors";
|
||||
|
||||
function* syncApiParamsSaga(
|
||||
actionPayload: ReduxActionWithMeta<string, { field: string }>,
|
||||
|
|
@ -138,7 +139,8 @@ function* handleUpdateBodyContentType(
|
|||
) {
|
||||
const { apiId, title } = action.payload;
|
||||
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
||||
// this is a previous value gotten before the updated content type has been set
|
||||
|
||||
// this is the previous value gotten before the new content type has been set
|
||||
const previousContentType =
|
||||
values.actionConfiguration?.formData?.apiContentType;
|
||||
|
||||
|
|
@ -150,31 +152,37 @@ function* handleUpdateBodyContentType(
|
|||
return;
|
||||
}
|
||||
|
||||
// this is the update for the new api contentType
|
||||
// update the api content type so it can be persisted.
|
||||
// this is the update for the new apicontentType
|
||||
// Quick Context: APiContentype is the field that represents the content type the user wants while in RAW mode.
|
||||
// users should be able to set the content type to whatever they want.
|
||||
let formData = { ...values.actionConfiguration.formData };
|
||||
if (formData === undefined) formData = {};
|
||||
formData["apiContentType"] = title;
|
||||
formData["apiContentType"] =
|
||||
title === POST_BODY_FORMAT_OPTIONS.RAW ||
|
||||
title === POST_BODY_FORMAT_OPTIONS.NONE
|
||||
? previousContentType
|
||||
: title;
|
||||
|
||||
yield put(
|
||||
change(API_EDITOR_FORM_NAME, "actionConfiguration.formData", formData),
|
||||
);
|
||||
|
||||
if (displayFormatValue === POST_BODY_FORMAT_OPTIONS.RAW) {
|
||||
// update the content type header if raw has been selected
|
||||
yield put({
|
||||
type: ReduxActionTypes.SET_EXTRA_FORMDATA,
|
||||
payload: {
|
||||
id: apiId,
|
||||
values: {
|
||||
displayFormat: {
|
||||
label: displayFormatValue,
|
||||
value: displayFormatValue,
|
||||
},
|
||||
// Quick Context: The extra formadata action is responsible for updating the current multi switch mode you see on api editor body tab
|
||||
// whenever a user selects a new content type through the tab e.g application/json, this action is dispatched to update that value, which is then read in the PostDataBody file
|
||||
// to show the appropriate content type section.
|
||||
|
||||
yield put({
|
||||
type: ReduxActionTypes.SET_EXTRA_FORMDATA,
|
||||
payload: {
|
||||
id: apiId,
|
||||
values: {
|
||||
displayFormat: {
|
||||
label: title,
|
||||
value: title,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const headers = cloneDeep(values.actionConfiguration.headers);
|
||||
|
||||
|
|
@ -186,25 +194,19 @@ function* handleUpdateBodyContentType(
|
|||
);
|
||||
const indexToUpdate = getIndextoUpdate(headers, contentTypeHeaderIndex);
|
||||
|
||||
// If the user has selected "None" as the body type & there was a content-type
|
||||
// If the user has selected "None" or "Raw" as the body type & there was a content-type
|
||||
// header present in the API configuration, keep the previous content type header
|
||||
// but if the user has selected "raw", set the content header to text/plain
|
||||
// this is done to ensure user input isn't cleared off if they switch to raw or none mode.
|
||||
// however if the user types in a new value, we use the updated value (formValueChangeSaga - line 426).
|
||||
if (
|
||||
displayFormatValue === POST_BODY_FORMAT_OPTIONS.NONE &&
|
||||
(displayFormatValue === POST_BODY_FORMAT_OPTIONS.NONE ||
|
||||
displayFormatValue === POST_BODY_FORMAT_OPTIONS.RAW) &&
|
||||
indexToUpdate !== -1
|
||||
) {
|
||||
headers[indexToUpdate] = {
|
||||
key: previousContentType ? CONTENT_TYPE_HEADER_KEY : "",
|
||||
value: previousContentType ? previousContentType : "",
|
||||
};
|
||||
} else if (
|
||||
displayFormatValue === POST_BODY_FORMAT_OPTIONS.RAW &&
|
||||
indexToUpdate !== -1
|
||||
) {
|
||||
headers[indexToUpdate] = {
|
||||
key: CONTENT_TYPE_HEADER_KEY,
|
||||
value: POST_BODY_FORMAT_OPTIONS.RAW,
|
||||
};
|
||||
} else {
|
||||
headers[indexToUpdate] = {
|
||||
key: CONTENT_TYPE_HEADER_KEY,
|
||||
|
|
@ -212,6 +214,7 @@ function* handleUpdateBodyContentType(
|
|||
};
|
||||
}
|
||||
|
||||
// update the new header values.
|
||||
yield put(
|
||||
change(API_EDITOR_FORM_NAME, "actionConfiguration.headers", headers),
|
||||
);
|
||||
|
|
@ -235,18 +238,19 @@ function* handleUpdateBodyContentType(
|
|||
}
|
||||
|
||||
function* initializeExtraFormDataSaga() {
|
||||
const state = yield select();
|
||||
const { extraformData } = state.ui.apiPane;
|
||||
const formData = yield select(getFormData, API_EDITOR_FORM_NAME);
|
||||
const { values } = formData;
|
||||
// const headers = get(values, "actionConfiguration.headers");
|
||||
const apiContentType = get(
|
||||
values,
|
||||
"actionConfiguration.formData.apiContentType",
|
||||
);
|
||||
|
||||
if (!extraformData[values.id]) {
|
||||
yield call(setHeaderFormat, values.id, apiContentType);
|
||||
// when initializing, check if theres a display format present, if not use Json display format as default.
|
||||
const extraFormData = yield select(getDisplayFormat, values.id);
|
||||
|
||||
// as a fail safe, if no display format is present, use Raw mode
|
||||
const rawApiContentType = extraFormData?.displayFormat?.value
|
||||
? extraFormData?.displayFormat?.value
|
||||
: POST_BODY_FORMAT_OPTIONS.RAW;
|
||||
|
||||
if (!extraFormData) {
|
||||
yield call(setHeaderFormat, values.id, rawApiContentType);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -296,6 +300,7 @@ function* changeApiSaga(
|
|||
function* setHeaderFormat(apiId: string, apiContentType?: string) {
|
||||
// use the current apiContentType to set appropriate Headers for action
|
||||
let displayFormat;
|
||||
|
||||
if (apiContentType) {
|
||||
if (apiContentType === POST_BODY_FORMAT_OPTIONS.NONE) {
|
||||
displayFormat = {
|
||||
|
|
@ -336,8 +341,9 @@ export function* updateFormFields(
|
|||
const value = actionPayload.payload;
|
||||
log.debug("updateFormFields: " + JSON.stringify(value));
|
||||
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
||||
let apiContentType = values.actionConfiguration.formData.apiContentType;
|
||||
|
||||
// get current content type of the action
|
||||
let apiContentType = values.actionConfiguration.formData.apiContentType;
|
||||
if (field === "actionConfiguration.httpMethod") {
|
||||
const { actionConfiguration } = values;
|
||||
if (!actionConfiguration.headers) return;
|
||||
|
|
@ -370,14 +376,7 @@ export function* updateFormFields(
|
|||
};
|
||||
}
|
||||
}
|
||||
// change apiContentType when user changes api Http Method
|
||||
yield put(
|
||||
change(
|
||||
API_EDITOR_FORM_NAME,
|
||||
"actionConfiguration.formData.apiContentType",
|
||||
apiContentType,
|
||||
),
|
||||
);
|
||||
|
||||
yield put(
|
||||
change(
|
||||
API_EDITOR_FORM_NAME,
|
||||
|
|
@ -385,9 +384,6 @@ export function* updateFormFields(
|
|||
actionConfigurationHeaders,
|
||||
),
|
||||
);
|
||||
} else if (field.includes("actionConfiguration.headers")) {
|
||||
const apiId = get(values, "id");
|
||||
yield call(setHeaderFormat, apiId, apiContentType);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -424,29 +420,21 @@ function* formValueChangeSaga(
|
|||
}),
|
||||
);
|
||||
// when user types a content type value, update actionConfiguration.formData.apiContent type as well.
|
||||
// we don't do this initally because we want to specifically catch user editing the content-type value
|
||||
if (
|
||||
field === `actionConfiguration.headers[${contentTypeHeaderIndex}].value`
|
||||
) {
|
||||
if (
|
||||
// if the value is not a registered content type, make the default apiContentType raw but don't change header
|
||||
Object.values(POST_BODY_FORMAT_OPTIONS).includes(actionPayload.payload)
|
||||
) {
|
||||
yield put(
|
||||
change(
|
||||
API_EDITOR_FORM_NAME,
|
||||
"actionConfiguration.formData.apiContentType",
|
||||
actionPayload.payload,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
yield put(
|
||||
change(
|
||||
API_EDITOR_FORM_NAME,
|
||||
"actionConfiguration.formData.apiContentType",
|
||||
POST_BODY_FORMAT_OPTIONS.RAW,
|
||||
),
|
||||
);
|
||||
}
|
||||
yield put(
|
||||
change(
|
||||
API_EDITOR_FORM_NAME,
|
||||
"actionConfiguration.formData.apiContentType",
|
||||
actionPayload.payload,
|
||||
),
|
||||
);
|
||||
const apiId = get(values, "id");
|
||||
// when the user specifically sets a new content type value, we check if the input value is a supported post body type and switch to it
|
||||
// if it does not we set the default to Raw mode.
|
||||
yield call(setHeaderFormat, apiId, actionPayload.payload);
|
||||
}
|
||||
}
|
||||
yield all([
|
||||
|
|
|
|||
11
app/client/src/selectors/apiPaneSelectors.ts
Normal file
11
app/client/src/selectors/apiPaneSelectors.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { AppState } from "reducers";
|
||||
|
||||
type GetFormData = (
|
||||
state: AppState,
|
||||
apiId: string,
|
||||
) => { label: string; value: string };
|
||||
|
||||
export const getDisplayFormat: GetFormData = (state, apiId) => {
|
||||
const displayFormat = state.ui.apiPane.extraformData[apiId];
|
||||
return displayFormat;
|
||||
};
|
||||
|
|
@ -112,7 +112,7 @@ const useHorizontalResize = (
|
|||
unFocus(document, window);
|
||||
|
||||
if (ref.current) {
|
||||
const width = ref.current.getBoundingClientRect().width;
|
||||
const width = ref.current.clientWidth;
|
||||
const current = event.touches[0].clientX;
|
||||
const positionDelta = position - current;
|
||||
const widthDelta = inverse ? -positionDelta : positionDelta;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import OverlayCommentsWrapper from "comments/inlineComments/OverlayCommentsWrapp
|
|||
import PreventInteractionsOverlay from "components/editorComponents/PreventInteractionsOverlay";
|
||||
import AppsmithConsole from "utils/AppsmithConsole";
|
||||
import { ENTITY_TYPE } from "entities/AppsmithConsole";
|
||||
import PreviewModeComponent from "components/editorComponents/PreviewModeComponent";
|
||||
|
||||
/***
|
||||
* BaseWidget
|
||||
|
|
@ -313,12 +314,20 @@ abstract class BaseWidget<
|
|||
);
|
||||
}
|
||||
|
||||
addPreviewModeWidget(content: ReactNode): React.ReactElement {
|
||||
return (
|
||||
<PreviewModeComponent isVisible={this.props.isVisible}>
|
||||
{content}
|
||||
</PreviewModeComponent>
|
||||
);
|
||||
}
|
||||
|
||||
private getWidgetView(): ReactNode {
|
||||
let content: ReactNode;
|
||||
|
||||
switch (this.props.renderMode) {
|
||||
case RenderModes.CANVAS:
|
||||
content = this.getCanvasView();
|
||||
content = this.addPreviewModeWidget(content);
|
||||
content = this.addPreventInteractionOverlay(content);
|
||||
content = this.addOverlayComments(content);
|
||||
if (!this.props.detachFromLayout) {
|
||||
|
|
|
|||
|
|
@ -207,9 +207,7 @@ class InputWidget extends BaseInputWidget<InputWidgetProps, WidgetState> {
|
|||
isTriggerProperty: false,
|
||||
validation: {
|
||||
type: ValidationTypes.NUMBER,
|
||||
params: {
|
||||
min: 1,
|
||||
},
|
||||
params: { min: 1, natural: true },
|
||||
},
|
||||
hidden: (props: InputWidgetProps) => {
|
||||
return props.inputType !== InputTypes.TEXT;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import Icon from "components/ads/Icon";
|
|||
import { Button, Classes, InputGroup } from "@blueprintjs/core";
|
||||
import { WidgetContainerDiff } from "widgets/WidgetUtils";
|
||||
import { Colors } from "constants/Colors";
|
||||
import _ from "lodash";
|
||||
import { uniqBy } from "lodash";
|
||||
|
||||
const menuItemSelectedIcon = (props: { isSelected: boolean }) => {
|
||||
return <MenuItemCheckBox checked={props.isSelected} />;
|
||||
|
|
@ -121,13 +121,12 @@ function MultiSelectComponent({
|
|||
}),
|
||||
);
|
||||
// get unique selected values amongst SelectedAllValue and Value
|
||||
const allSelectedOptions = _.uniqBy(
|
||||
[...allOption, ...value],
|
||||
"value",
|
||||
).map((val) => ({
|
||||
...val,
|
||||
key: val.value,
|
||||
}));
|
||||
const allSelectedOptions = uniqBy([...allOption, ...value], "value").map(
|
||||
(val) => ({
|
||||
...val,
|
||||
key: val.value,
|
||||
}),
|
||||
);
|
||||
onChange(allSelectedOptions);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ export const CONFIG = {
|
|||
name: "MultiSelect",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
isFilterable: true,
|
||||
defaults: {
|
||||
rows: 7,
|
||||
columns: 20,
|
||||
|
|
@ -18,8 +17,9 @@ export const CONFIG = {
|
|||
{ label: "Red", value: "RED" },
|
||||
],
|
||||
widgetName: "MultiSelect",
|
||||
isFilterable: true,
|
||||
serverSideFiltering: false,
|
||||
defaultOptionValue: [{ label: "Green", value: "GREEN" }],
|
||||
defaultOptionValue: ["GREEN", "RED"],
|
||||
version: 1,
|
||||
isRequired: false,
|
||||
isDisabled: false,
|
||||
|
|
|
|||
249
app/client/src/widgets/MultiSelectWidgetV2/widget/index.test.tsx
Normal file
249
app/client/src/widgets/MultiSelectWidgetV2/widget/index.test.tsx
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
import _ from "lodash";
|
||||
import { defaultOptionValueValidation, MultiSelectWidgetProps } from ".";
|
||||
|
||||
describe("defaultOptionValueValidation - ", () => {
|
||||
it("should get tested with empty string", () => {
|
||||
const input = "";
|
||||
|
||||
expect(
|
||||
defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
|
||||
).toEqual({
|
||||
isValid: true,
|
||||
parsed: [],
|
||||
messages: [""],
|
||||
});
|
||||
});
|
||||
|
||||
it("should get tested with array of strings|number", () => {
|
||||
const input = ["green", "red"];
|
||||
|
||||
expect(
|
||||
defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
|
||||
).toEqual({
|
||||
isValid: true,
|
||||
parsed: input,
|
||||
messages: [""],
|
||||
});
|
||||
});
|
||||
|
||||
it("should get tested with array json string", () => {
|
||||
const input = `["green", "red"]`;
|
||||
|
||||
expect(
|
||||
defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
|
||||
).toEqual({
|
||||
isValid: true,
|
||||
parsed: ["green", "red"],
|
||||
messages: [""],
|
||||
});
|
||||
});
|
||||
|
||||
it("should get tested with array of object json string", () => {
|
||||
const input = `[
|
||||
{
|
||||
"label": "green",
|
||||
"value": "green"
|
||||
},
|
||||
{
|
||||
"label": "red",
|
||||
"value": "red"
|
||||
}
|
||||
]`;
|
||||
|
||||
expect(
|
||||
defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
|
||||
).toEqual({
|
||||
isValid: true,
|
||||
parsed: [
|
||||
{
|
||||
label: "green",
|
||||
value: "green",
|
||||
},
|
||||
{
|
||||
label: "red",
|
||||
value: "red",
|
||||
},
|
||||
],
|
||||
messages: [""],
|
||||
});
|
||||
});
|
||||
|
||||
it("should get tested with comma seperated strings", () => {
|
||||
const input = "green, red";
|
||||
|
||||
expect(
|
||||
defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
|
||||
).toEqual({
|
||||
isValid: true,
|
||||
parsed: ["green", "red"],
|
||||
messages: [""],
|
||||
});
|
||||
});
|
||||
|
||||
it("should get tested with simple string", () => {
|
||||
const input = "green";
|
||||
|
||||
expect(
|
||||
defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
|
||||
).toEqual({
|
||||
isValid: true,
|
||||
parsed: ["green"],
|
||||
messages: [""],
|
||||
});
|
||||
});
|
||||
|
||||
it("should get tested with simple string", () => {
|
||||
const input = `{"green"`;
|
||||
|
||||
expect(
|
||||
defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
|
||||
).toEqual({
|
||||
isValid: true,
|
||||
parsed: [`{"green"`],
|
||||
messages: [""],
|
||||
});
|
||||
});
|
||||
|
||||
it("should get tested with array of label, value", () => {
|
||||
const input = [
|
||||
{
|
||||
label: "green",
|
||||
value: "green",
|
||||
},
|
||||
{
|
||||
label: "red",
|
||||
value: "red",
|
||||
},
|
||||
];
|
||||
|
||||
expect(
|
||||
defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
|
||||
).toEqual({
|
||||
isValid: true,
|
||||
parsed: [
|
||||
{
|
||||
label: "green",
|
||||
value: "green",
|
||||
},
|
||||
{
|
||||
label: "red",
|
||||
value: "red",
|
||||
},
|
||||
],
|
||||
messages: [""],
|
||||
});
|
||||
});
|
||||
|
||||
it("should get tested with array of invalid values", () => {
|
||||
const testValues = [
|
||||
[
|
||||
undefined,
|
||||
{
|
||||
isValid: false,
|
||||
parsed: [],
|
||||
messages: [
|
||||
"value should match: Array<string | number> | Array<{label: string, value: string | number}>",
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
null,
|
||||
{
|
||||
isValid: false,
|
||||
parsed: [],
|
||||
messages: [
|
||||
"value should match: Array<string | number> | Array<{label: string, value: string | number}>",
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
true,
|
||||
{
|
||||
isValid: false,
|
||||
parsed: [],
|
||||
messages: [
|
||||
"value should match: Array<string | number> | Array<{label: string, value: string | number}>",
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
{},
|
||||
{
|
||||
isValid: false,
|
||||
parsed: [],
|
||||
messages: [
|
||||
"value should match: Array<string | number> | Array<{label: string, value: string | number}>",
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
[undefined],
|
||||
{
|
||||
isValid: false,
|
||||
parsed: [],
|
||||
messages: [
|
||||
"value should match: Array<string | number> | Array<{label: string, value: string | number}>",
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
[true],
|
||||
{
|
||||
isValid: false,
|
||||
parsed: [],
|
||||
messages: [
|
||||
"value should match: Array<string | number> | Array<{label: string, value: string | number}>",
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
["green", "green"],
|
||||
{
|
||||
isValid: false,
|
||||
parsed: [],
|
||||
messages: ["values must be unique. Duplicate values found"],
|
||||
},
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
label: "green",
|
||||
value: "green",
|
||||
},
|
||||
{
|
||||
label: "green",
|
||||
value: "green",
|
||||
},
|
||||
],
|
||||
{
|
||||
isValid: false,
|
||||
parsed: [],
|
||||
messages: ["path:value must be unique. Duplicate values found"],
|
||||
},
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
label: "green",
|
||||
},
|
||||
{
|
||||
label: "green",
|
||||
},
|
||||
],
|
||||
{
|
||||
isValid: false,
|
||||
parsed: [],
|
||||
messages: [
|
||||
"value should match: Array<string | number> | Array<{label: string, value: string | number}>",
|
||||
],
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
testValues.forEach(([input, expected]) => {
|
||||
expect(
|
||||
defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
|
||||
).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -2,8 +2,11 @@ import React from "react";
|
|||
import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
|
||||
import { WidgetType } from "constants/WidgetConstants";
|
||||
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import { isArray } from "lodash";
|
||||
import { ValidationTypes } from "constants/WidgetValidation";
|
||||
import { isArray, isString, isNumber } from "lodash";
|
||||
import {
|
||||
ValidationResponse,
|
||||
ValidationTypes,
|
||||
} from "constants/WidgetValidation";
|
||||
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||
import MultiSelectComponent from "../component";
|
||||
import {
|
||||
|
|
@ -12,6 +15,114 @@ import {
|
|||
} from "rc-select/lib/interface/generator";
|
||||
import { Layers } from "constants/Layers";
|
||||
import { MinimumPopupRows, GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
|
||||
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
|
||||
|
||||
export function defaultOptionValueValidation(
|
||||
value: unknown,
|
||||
props: MultiSelectWidgetProps,
|
||||
_: any,
|
||||
): ValidationResponse {
|
||||
let isValid;
|
||||
let parsed;
|
||||
let message = "";
|
||||
|
||||
/*
|
||||
* Function to check if the object has `label` and `value`
|
||||
*/
|
||||
const hasLabelValue = (obj: any) => {
|
||||
return (
|
||||
_.isPlainObject(obj) &&
|
||||
obj.hasOwnProperty("label") &&
|
||||
obj.hasOwnProperty("value") &&
|
||||
_.isString(obj.label) &&
|
||||
(_.isString(obj.value) || _.isFinite(obj.value))
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* Function to check for duplicate values in array
|
||||
*/
|
||||
const hasUniqueValues = (arr: Array<string>) => {
|
||||
const uniqueValues = new Set(arr);
|
||||
|
||||
return uniqueValues.size === arr.length;
|
||||
};
|
||||
|
||||
/*
|
||||
* When value is "['green', 'red']", "[{label: 'green', value: 'green'}]" and "green, red"
|
||||
*/
|
||||
if (_.isString(value) && (value as string).trim() !== "") {
|
||||
try {
|
||||
/*
|
||||
* when value is "['green', 'red']", "[{label: 'green', value: 'green'}]"
|
||||
*/
|
||||
value = JSON.parse(value as string);
|
||||
} catch (e) {
|
||||
/*
|
||||
* when value is "green, red", JSON.parse throws error
|
||||
*/
|
||||
const splitByComma = (value as string).split(",") || [];
|
||||
|
||||
value = splitByComma.map((s) => s.trim());
|
||||
}
|
||||
}
|
||||
|
||||
if (_.isString(value) && (value as string).trim() === "") {
|
||||
isValid = true;
|
||||
parsed = [];
|
||||
message = "";
|
||||
} else if (Array.isArray(value)) {
|
||||
if (value.every((val) => _.isString(val) || _.isFinite(val))) {
|
||||
/*
|
||||
* When value is ["green", "red"]
|
||||
*/
|
||||
if (hasUniqueValues(value as [])) {
|
||||
isValid = true;
|
||||
parsed = value;
|
||||
message = "";
|
||||
} else {
|
||||
isValid = false;
|
||||
parsed = [];
|
||||
message = "values must be unique. Duplicate values found";
|
||||
}
|
||||
} else if (value.every(hasLabelValue)) {
|
||||
/*
|
||||
* When value is [{label: "green", value: "red"}]
|
||||
*/
|
||||
if (hasUniqueValues(value.map((val) => val.value) as [])) {
|
||||
isValid = true;
|
||||
parsed = value;
|
||||
message = "";
|
||||
} else {
|
||||
isValid = false;
|
||||
parsed = [];
|
||||
message = "path:value must be unique. Duplicate values found";
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* When value is [true, false], [undefined, undefined] etc.
|
||||
*/
|
||||
isValid = false;
|
||||
parsed = [];
|
||||
message =
|
||||
"value should match: Array<string | number> | Array<{label: string, value: string | number}>";
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* When value is undefined, null, {} etc.
|
||||
*/
|
||||
isValid = false;
|
||||
parsed = [];
|
||||
message =
|
||||
"value should match: Array<string | number> | Array<{label: string, value: string | number}>";
|
||||
}
|
||||
|
||||
return {
|
||||
isValid,
|
||||
parsed,
|
||||
messages: [message],
|
||||
};
|
||||
}
|
||||
|
||||
class MultiSelectWidget extends BaseWidget<
|
||||
MultiSelectWidgetProps,
|
||||
|
|
@ -66,7 +177,7 @@ class MultiSelectWidget extends BaseWidget<
|
|||
EvaluationSubstitutionType.SMART_SUBSTITUTE,
|
||||
},
|
||||
{
|
||||
helpText: "Selects the option with value by default",
|
||||
helpText: "Selects the option(s) with value by default",
|
||||
propertyName: "defaultOptionValue",
|
||||
label: "Default Value",
|
||||
controlType: "INPUT_TEXT",
|
||||
|
|
@ -74,32 +185,13 @@ class MultiSelectWidget extends BaseWidget<
|
|||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: {
|
||||
type: ValidationTypes.ARRAY,
|
||||
type: ValidationTypes.FUNCTION,
|
||||
params: {
|
||||
unique: ["value"],
|
||||
children: {
|
||||
type: ValidationTypes.OBJECT,
|
||||
params: {
|
||||
required: true,
|
||||
allowedKeys: [
|
||||
{
|
||||
name: "label",
|
||||
type: ValidationTypes.TEXT,
|
||||
params: {
|
||||
default: "",
|
||||
requiredKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "value",
|
||||
type: ValidationTypes.TEXT,
|
||||
params: {
|
||||
default: "",
|
||||
requiredKey: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
fn: defaultOptionValueValidation,
|
||||
expected: {
|
||||
type: "Array of values",
|
||||
example: `['option1', 'option2'] | [{ "label": "label1", "value": "value1" }]`,
|
||||
autocompleteDataType: AutocompleteDataType.ARRAY,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -304,8 +396,8 @@ class MultiSelectWidget extends BaseWidget<
|
|||
|
||||
static getDerivedPropertiesMap() {
|
||||
return {
|
||||
selectedOptionLabels: `{{ this.selectedOptions ? this.selectedOptions.map((o) => o.label ) : [] }}`,
|
||||
selectedOptionValues: `{{ this.selectedOptions ? this.selectedOptions.map((o) => o.value ) : [] }}`,
|
||||
selectedOptionLabels: `{{ this.selectedOptions ? this.selectedOptions.map((o) => _.isNil(o.label) ? o : o.label ) : [] }}`,
|
||||
selectedOptionValues: `{{ this.selectedOptions ? this.selectedOptions.map((o) => _.isNil(o.value) ? o : o.value ) : [] }}`,
|
||||
isValid: `{{this.isRequired ? !!this.selectedOptionValues && this.selectedOptionValues.length > 0 : true}}`,
|
||||
isDirty: `{{ ((array1, array2) => {if (array1.length === array2.length) {return !array1.map((o) => o.value).every(element => array2.includes(element));} return true;})(this.defaultOptionValue, this.selectedOptionValues); }}`,
|
||||
};
|
||||
|
|
@ -329,7 +421,13 @@ class MultiSelectWidget extends BaseWidget<
|
|||
const options = isArray(this.props.options) ? this.props.options : [];
|
||||
const dropDownWidth = MinimumPopupRows * this.props.parentColumnSpace;
|
||||
const { componentWidth } = this.getComponentDimensions();
|
||||
|
||||
const values: LabelValueType[] = this.props.selectedOptions
|
||||
? this.props.selectedOptions.map((o) =>
|
||||
isString(o) || isNumber(o)
|
||||
? { label: o, value: o }
|
||||
: { label: o.label, value: o.value },
|
||||
)
|
||||
: [];
|
||||
return (
|
||||
<MultiSelectComponent
|
||||
allowSelectAll={this.props.allowSelectAll}
|
||||
|
|
@ -358,7 +456,7 @@ class MultiSelectWidget extends BaseWidget<
|
|||
options={options}
|
||||
placeholder={this.props.placeholderText as string}
|
||||
serverSideFiltering={this.props.serverSideFiltering}
|
||||
value={this.props.selectedOptions ?? []}
|
||||
value={values}
|
||||
widgetId={this.props.widgetId}
|
||||
width={componentWidth}
|
||||
/>
|
||||
|
|
@ -366,6 +464,10 @@ class MultiSelectWidget extends BaseWidget<
|
|||
}
|
||||
|
||||
onOptionChange = (value: DefaultValueType) => {
|
||||
if (!this.props.isDirty) {
|
||||
this.props.updateWidgetMetaProperty("isDirty", true);
|
||||
}
|
||||
|
||||
this.props.updateWidgetMetaProperty("selectedOptions", value, {
|
||||
triggerPropertyName: "onOptionChange",
|
||||
dynamicString: this.props.onOptionChange,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
BlueprintCSSTransform,
|
||||
createGlobalStyle,
|
||||
} from "constants/DefaultTheme";
|
||||
import { isEmptyOrNill } from ".";
|
||||
|
||||
export const TextLabelWrapper = styled.div<{
|
||||
compactMode: boolean;
|
||||
|
|
@ -145,7 +146,8 @@ export const StyledSingleDropDown = styled(SingleDropDown)<{
|
|||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
color: ${(props) => (props.value ? Colors.GREY_10 : Colors.GREY_6)};
|
||||
color: ${(props) =>
|
||||
!isEmptyOrNill(props.value) ? Colors.GREY_10 : Colors.GREY_6};
|
||||
}
|
||||
&& {
|
||||
.${Classes.ICON} {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { ComponentProps } from "widgets/BaseComponent";
|
|||
import { MenuItem, Button, Classes } from "@blueprintjs/core";
|
||||
import { DropdownOption } from "../constants";
|
||||
import { IItemRendererProps } from "@blueprintjs/select";
|
||||
import _ from "lodash";
|
||||
import { debounce, findIndex, isEmpty, isNil } from "lodash";
|
||||
import "../../../../node_modules/@blueprintjs/select/lib/css/blueprint-select.css";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { TextSize } from "constants/WidgetConstants";
|
||||
|
|
@ -19,6 +19,7 @@ import {
|
|||
import Fuse from "fuse.js";
|
||||
import { WidgetContainerDiff } from "widgets/WidgetUtils";
|
||||
import Icon, { IconSize } from "components/ads/Icon";
|
||||
import { isString } from "../../../utils/helpers";
|
||||
|
||||
const FUSE_OPTIONS = {
|
||||
shouldSort: true,
|
||||
|
|
@ -29,6 +30,10 @@ const FUSE_OPTIONS = {
|
|||
keys: ["label", "value"],
|
||||
};
|
||||
|
||||
export const isEmptyOrNill = (value: any) => {
|
||||
return isNil(value) || (isString(value) && value === "");
|
||||
};
|
||||
|
||||
const DEBOUNCE_TIMEOUT = 800;
|
||||
|
||||
interface SelectComponentState {
|
||||
|
|
@ -59,7 +64,7 @@ class SelectComponent extends React.Component<
|
|||
|
||||
handleActiveItemChange = (activeItem: DropdownOption | null) => {
|
||||
// find new index from options
|
||||
const activeItemIndex = _.findIndex(this.props.options, [
|
||||
const activeItemIndex = findIndex(this.props.options, [
|
||||
"label",
|
||||
activeItem?.label,
|
||||
]);
|
||||
|
|
@ -77,20 +82,21 @@ class SelectComponent extends React.Component<
|
|||
widgetId,
|
||||
} = this.props;
|
||||
// active focused item
|
||||
const activeItem = !_.isEmpty(this.props.options)
|
||||
const activeItem = !isEmpty(this.props.options)
|
||||
? this.props.options[this.state.activeItemIndex]
|
||||
: undefined;
|
||||
// get selected option label from selectedIndex
|
||||
const selectedOption =
|
||||
!_.isEmpty(this.props.options) &&
|
||||
!isEmpty(this.props.options) &&
|
||||
this.props.selectedIndex !== undefined &&
|
||||
this.props.selectedIndex > -1
|
||||
? this.props.options[this.props.selectedIndex].label
|
||||
: this.props.label;
|
||||
// for display selected option, there is no separate option to show placeholder
|
||||
const value = selectedOption
|
||||
? selectedOption
|
||||
: this.props.placeholder || "-- Select --";
|
||||
const value =
|
||||
!isNil(selectedOption) && selectedOption !== ""
|
||||
? selectedOption
|
||||
: this.props.placeholder || "-- Select --";
|
||||
|
||||
return (
|
||||
<DropdownContainer compactMode={compactMode}>
|
||||
|
|
@ -142,7 +148,7 @@ class SelectComponent extends React.Component<
|
|||
onClose: () => {
|
||||
if (!this.props.selectedIndex) return;
|
||||
return this.handleActiveItemChange(
|
||||
this.props.options[this.props.selectedIndex as number],
|
||||
this.props.options[this.props.selectedIndex],
|
||||
);
|
||||
},
|
||||
modifiers: {
|
||||
|
|
@ -160,7 +166,7 @@ class SelectComponent extends React.Component<
|
|||
disabled={this.props.disabled}
|
||||
rightIcon={
|
||||
<StyledDiv>
|
||||
{this.props.value ? (
|
||||
{!isEmptyOrNill(this.props.value) ? (
|
||||
<Icon
|
||||
className="dropdown-icon cancel-icon"
|
||||
fillColor={
|
||||
|
|
@ -201,7 +207,7 @@ class SelectComponent extends React.Component<
|
|||
};
|
||||
|
||||
isOptionSelected = (selectedOption: DropdownOption) => {
|
||||
const optionIndex = _.findIndex(this.props.options, (option) => {
|
||||
const optionIndex = findIndex(this.props.options, (option) => {
|
||||
return option.value === selectedOption.value;
|
||||
});
|
||||
return optionIndex === this.props.selectedIndex;
|
||||
|
|
@ -211,7 +217,7 @@ class SelectComponent extends React.Component<
|
|||
if (!this.props.serverSideFiltering) return;
|
||||
return this.serverSideSearch(filterValue);
|
||||
};
|
||||
serverSideSearch = _.debounce((filterValue: string) => {
|
||||
serverSideSearch = debounce((filterValue: string) => {
|
||||
this.props.onFilterChange(filterValue);
|
||||
}, DEBOUNCE_TIMEOUT);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ export const CONFIG = {
|
|||
],
|
||||
serverSideFiltering: false,
|
||||
widgetName: "Select",
|
||||
defaultOptionValue: { label: "Green", value: "GREEN" },
|
||||
defaultOptionValue: "GREEN",
|
||||
version: 1,
|
||||
isFilterable: false,
|
||||
isFilterable: true,
|
||||
isRequired: false,
|
||||
isDisabled: false,
|
||||
animateLoading: true,
|
||||
|
|
|
|||
109
app/client/src/widgets/SelectWidget/widget/index.test.tsx
Normal file
109
app/client/src/widgets/SelectWidget/widget/index.test.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import _ from "lodash";
|
||||
import { SelectWidgetProps, defaultOptionValueValidation } from ".";
|
||||
|
||||
describe("defaultOptionValueValidation - ", () => {
|
||||
it("should get tested with simple string", () => {
|
||||
const input = "";
|
||||
|
||||
expect(
|
||||
defaultOptionValueValidation(input, {} as SelectWidgetProps, _),
|
||||
).toEqual({
|
||||
isValid: true,
|
||||
parsed: "",
|
||||
messages: [""],
|
||||
});
|
||||
});
|
||||
|
||||
it("should get tested with simple string", () => {
|
||||
const input = "green";
|
||||
|
||||
expect(
|
||||
defaultOptionValueValidation(input, {} as SelectWidgetProps, _),
|
||||
).toEqual({
|
||||
isValid: true,
|
||||
parsed: "green",
|
||||
messages: [""],
|
||||
});
|
||||
});
|
||||
|
||||
it("should get tested with plain object", () => {
|
||||
const input = {
|
||||
label: "green",
|
||||
value: "green",
|
||||
};
|
||||
|
||||
expect(
|
||||
defaultOptionValueValidation(input, {} as SelectWidgetProps, _),
|
||||
).toEqual({
|
||||
isValid: true,
|
||||
parsed: {
|
||||
label: "green",
|
||||
value: "green",
|
||||
},
|
||||
messages: [""],
|
||||
});
|
||||
});
|
||||
|
||||
it("should get tested with invalid values", () => {
|
||||
const testValues = [
|
||||
[
|
||||
undefined,
|
||||
{
|
||||
isValid: false,
|
||||
parsed: {},
|
||||
messages: [
|
||||
`value does not evaluate to type: string | { "label": "label1", "value": "value1" }`,
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
null,
|
||||
{
|
||||
isValid: false,
|
||||
parsed: {},
|
||||
messages: [
|
||||
`value does not evaluate to type: string | { "label": "label1", "value": "value1" }`,
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
[],
|
||||
{
|
||||
isValid: false,
|
||||
parsed: {},
|
||||
messages: [
|
||||
`value does not evaluate to type: string | { "label": "label1", "value": "value1" }`,
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
true,
|
||||
{
|
||||
isValid: false,
|
||||
parsed: {},
|
||||
messages: [
|
||||
`value does not evaluate to type: string | { "label": "label1", "value": "value1" }`,
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
label: "green",
|
||||
},
|
||||
{
|
||||
isValid: false,
|
||||
parsed: {},
|
||||
messages: [
|
||||
`value does not evaluate to type: string | { "label": "label1", "value": "value1" }`,
|
||||
],
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
testValues.forEach(([input, expected]) => {
|
||||
expect(
|
||||
defaultOptionValueValidation(input, {} as SelectWidgetProps, _),
|
||||
).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -3,13 +3,70 @@ import BaseWidget, { WidgetProps, WidgetState } from "../../BaseWidget";
|
|||
import { WidgetType } from "constants/WidgetConstants";
|
||||
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import SelectComponent from "../component";
|
||||
import _ from "lodash";
|
||||
import { DropdownOption } from "../constants";
|
||||
import { ValidationTypes } from "constants/WidgetValidation";
|
||||
import {
|
||||
ValidationResponse,
|
||||
ValidationTypes,
|
||||
} from "constants/WidgetValidation";
|
||||
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||
import { MinimumPopupRows, GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
|
||||
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
|
||||
import { findIndex, isArray, isNumber, isString } from "lodash";
|
||||
|
||||
export function defaultOptionValueValidation(
|
||||
value: unknown,
|
||||
props: SelectWidgetProps,
|
||||
_: any,
|
||||
): ValidationResponse {
|
||||
let isValid;
|
||||
let parsed;
|
||||
let message = "";
|
||||
|
||||
/*
|
||||
* Function to check if the object has `label` and `value`
|
||||
*/
|
||||
const hasLabelValue = (obj: any) => {
|
||||
return (
|
||||
_.isPlainObject(value) &&
|
||||
obj.hasOwnProperty("label") &&
|
||||
obj.hasOwnProperty("value") &&
|
||||
_.isString(obj.label) &&
|
||||
(_.isString(obj.value) || _.isFinite(obj.value))
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* When value is "{label: 'green', value: 'green'}"
|
||||
*/
|
||||
if (typeof value === "string") {
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (_.isString(value) || _.isFinite(value) || hasLabelValue(value)) {
|
||||
/*
|
||||
* When value is "", "green", 444, {label: "green", value: "green"}
|
||||
*/
|
||||
isValid = true;
|
||||
parsed = value;
|
||||
} else {
|
||||
isValid = false;
|
||||
parsed = {};
|
||||
message = `value does not evaluate to type: string | { "label": "label1", "value": "value1" }`;
|
||||
}
|
||||
|
||||
return {
|
||||
isValid,
|
||||
parsed,
|
||||
messages: [message],
|
||||
};
|
||||
}
|
||||
|
||||
class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
|
||||
constructor(props: SelectWidgetProps) {
|
||||
super(props);
|
||||
}
|
||||
static getPropertyPaneConfig() {
|
||||
return [
|
||||
{
|
||||
|
|
@ -21,7 +78,7 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
|
|||
propertyName: "options",
|
||||
label: "Options",
|
||||
controlType: "INPUT_TEXT",
|
||||
placeholderText: '[{ "label": "Option1", "value": "Option2" }]',
|
||||
placeholderText: '[{ "label": "label1", "value": "value1" }]',
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: {
|
||||
|
|
@ -60,34 +117,24 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
|
|||
{
|
||||
helpText: "Selects the option with value by default",
|
||||
propertyName: "defaultOptionValue",
|
||||
label: "Default Option",
|
||||
label: "Default Value",
|
||||
controlType: "INPUT_TEXT",
|
||||
placeholderText: '{ "label": "Option1", "value": "Option2" }',
|
||||
placeholderText: '{ "label": "label1", "value": "value1" }',
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: {
|
||||
type: ValidationTypes.OBJECT,
|
||||
type: ValidationTypes.FUNCTION,
|
||||
params: {
|
||||
allowedKeys: [
|
||||
{
|
||||
name: "label",
|
||||
type: ValidationTypes.TEXT,
|
||||
params: {
|
||||
default: "",
|
||||
requiredKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "value",
|
||||
type: ValidationTypes.TEXT,
|
||||
params: {
|
||||
default: "",
|
||||
requiredKey: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
fn: defaultOptionValueValidation,
|
||||
expected: {
|
||||
type: 'value1 or { "label": "label1", "value": "value1" }',
|
||||
example: `value1 | { "label": "label1", "value": "value1" }`,
|
||||
autocompleteDataType: AutocompleteDataType.STRING,
|
||||
},
|
||||
},
|
||||
},
|
||||
evaluationSubstitutionType:
|
||||
EvaluationSubstitutionType.SMART_SUBSTITUTE,
|
||||
},
|
||||
{
|
||||
helpText: "Sets a Label Text",
|
||||
|
|
@ -273,57 +320,49 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
|
|||
];
|
||||
}
|
||||
|
||||
static getDerivedPropertiesMap() {
|
||||
return {
|
||||
isValid: `{{this.isRequired ? !!this.selectedOptionValue || this.selectedOptionValue === 0 : true}}`,
|
||||
selectedOptionLabel: `{{ this.optionValue.label ?? this.optionValue.value }}`,
|
||||
selectedOptionValue: `{{ this.optionValue.value }}`,
|
||||
isDirty: `{{ this.optionValue.value !== this.defaultValue.value }}`,
|
||||
};
|
||||
}
|
||||
|
||||
static getDefaultPropertiesMap(): Record<string, string> {
|
||||
return {
|
||||
defaultValue: "defaultOptionValue",
|
||||
optionValue: "defaultOptionValue",
|
||||
value: "defaultOptionValue",
|
||||
label: "defaultOptionValue",
|
||||
filterText: "",
|
||||
};
|
||||
}
|
||||
|
||||
static getMetaPropertiesMap(): Record<string, any> {
|
||||
return {
|
||||
defaultValue: undefined,
|
||||
optionValue: undefined,
|
||||
value: undefined,
|
||||
label: undefined,
|
||||
filterText: "",
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedPropertiesMap() {
|
||||
return {
|
||||
isValid: `{{this.isRequired ? !!this.selectedOptionValue || this.selectedOptionValue === 0 : true}}`,
|
||||
selectedOptionLabel: `{{(()=>{const label = _.isPlainObject(this.label) ? this.label?.label : this.label; return label; })()}}`,
|
||||
selectedOptionValue: `{{(()=>{const value = _.isPlainObject(this.value) ? this.value?.value : this.value; return value; })()}}`,
|
||||
isDirty: `{{ this.value !== this.defaultValue }}`,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
this.changeSelectedOption();
|
||||
}
|
||||
componentDidUpdate(prevProps: SelectWidgetProps): void {
|
||||
// removing selectedOptionValue if defaultValueChanges
|
||||
if (
|
||||
prevProps.defaultOptionValue?.value !==
|
||||
this.props.defaultOptionValue?.value ||
|
||||
prevProps.option !== this.props.option
|
||||
) {
|
||||
this.changeSelectedOption();
|
||||
}
|
||||
}
|
||||
|
||||
changeSelectedOption = () => {
|
||||
this.props.updateWidgetMetaProperty("optionValue", this.props.optionValue);
|
||||
};
|
||||
isStringOrNumber = (value: any): value is string | number =>
|
||||
isString(value) || isNumber(value);
|
||||
|
||||
getPageView() {
|
||||
const options = _.isArray(this.props.options) ? this.props.options : [];
|
||||
const options = isArray(this.props.options) ? this.props.options : [];
|
||||
const isInvalid =
|
||||
"isValid" in this.props && !this.props.isValid && !!this.props.isDirty;
|
||||
const dropDownWidth = MinimumPopupRows * this.props.parentColumnSpace;
|
||||
|
||||
const selectedIndex = _.findIndex(this.props.options, {
|
||||
const selectedIndex = findIndex(this.props.options, {
|
||||
value: this.props.selectedOptionValue,
|
||||
});
|
||||
|
||||
const { componentHeight, componentWidth } = this.getComponentDimensions();
|
||||
return (
|
||||
<SelectComponent
|
||||
|
|
@ -342,7 +381,7 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
|
|||
isFilterable={this.props.isFilterable}
|
||||
isLoading={this.props.isLoading}
|
||||
isValid={this.props.isValid}
|
||||
label={this.props.optionValue?.label}
|
||||
label={this.props.selectedOptionLabel}
|
||||
labelStyle={this.props.labelStyle}
|
||||
labelText={this.props.labelText}
|
||||
labelTextColor={this.props.labelTextColor}
|
||||
|
|
@ -353,7 +392,7 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
|
|||
placeholder={this.props.placeholderText}
|
||||
selectedIndex={selectedIndex > -1 ? selectedIndex : undefined}
|
||||
serverSideFiltering={this.props.serverSideFiltering}
|
||||
value={this.props.optionValue?.value}
|
||||
value={this.props.selectedOptionValue}
|
||||
widgetId={this.props.widgetId}
|
||||
width={componentWidth}
|
||||
/>
|
||||
|
|
@ -369,9 +408,11 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
|
|||
isChanged = !(this.props.selectedOptionValue === selectedOption.value);
|
||||
}
|
||||
if (isChanged) {
|
||||
this.props.updateWidgetMetaProperty("optionValue", selectedOption, {
|
||||
this.props.updateWidgetMetaProperty("label", selectedOption.label ?? "");
|
||||
|
||||
this.props.updateWidgetMetaProperty("value", selectedOption.value ?? "", {
|
||||
triggerPropertyName: "onOptionChange",
|
||||
dynamicString: this.props.onOptionChange as string,
|
||||
dynamicString: this.props.onOptionChange,
|
||||
event: {
|
||||
type: EventType.ON_OPTION_CHANGE,
|
||||
},
|
||||
|
|
@ -379,16 +420,29 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
|
|||
}
|
||||
};
|
||||
|
||||
changeSelectedOption = () => {
|
||||
const label = this.isStringOrNumber(this.props.label)
|
||||
? this.props.label
|
||||
: this.props.label?.label;
|
||||
const value = this.isStringOrNumber(this.props.value)
|
||||
? this.props.value
|
||||
: this.props.value?.value;
|
||||
this.props.updateWidgetMetaProperty("value", value);
|
||||
this.props.updateWidgetMetaProperty("label", label);
|
||||
};
|
||||
|
||||
onFilterChange = (value: string) => {
|
||||
this.props.updateWidgetMetaProperty("filterText", value);
|
||||
|
||||
super.executeAction({
|
||||
triggerPropertyName: "onFilterUpdate",
|
||||
dynamicString: this.props.onFilterUpdate,
|
||||
event: {
|
||||
type: EventType.ON_FILTER_UPDATE,
|
||||
},
|
||||
});
|
||||
if (this.props.onFilterUpdate && this.props.serverSideFiltering) {
|
||||
super.executeAction({
|
||||
triggerPropertyName: "onFilterUpdate",
|
||||
dynamicString: this.props.onFilterUpdate,
|
||||
event: {
|
||||
type: EventType.ON_FILTER_UPDATE,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static getWidgetType(): WidgetType {
|
||||
|
|
@ -398,13 +452,12 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
|
|||
|
||||
export interface SelectWidgetProps extends WidgetProps {
|
||||
placeholderText?: string;
|
||||
label?: string;
|
||||
selectedIndex?: number;
|
||||
selectedOption: DropdownOption;
|
||||
options?: DropdownOption[];
|
||||
onOptionChange?: string;
|
||||
defaultOptionValue?: { label?: string; value?: string };
|
||||
value?: string;
|
||||
defaultOptionValue?: any;
|
||||
value?: any;
|
||||
label?: any;
|
||||
isRequired: boolean;
|
||||
isFilterable: boolean;
|
||||
defaultValue: string;
|
||||
|
|
|
|||
|
|
@ -66,27 +66,20 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = {
|
|||
},
|
||||
metaProperties: {},
|
||||
},
|
||||
DROP_DOWN_WIDGET: {
|
||||
SELECT_WIDGET: {
|
||||
defaultProperties: {
|
||||
selectedOptionValue: "defaultOptionValue",
|
||||
selectedOptionValueArr: "defaultOptionValue",
|
||||
selectedOption: "defaultOptionValue",
|
||||
filterText: "",
|
||||
},
|
||||
derivedProperties: {
|
||||
isValid:
|
||||
"{{this.isRequired ? this.selectionType === 'SINGLE_SELECT' ? !!this.selectedOption : !!this.selectedIndexArr && this.selectedIndexArr.length > 0 : true}}",
|
||||
selectedOption:
|
||||
"{{ this.selectionType === 'SINGLE_SELECT' ? _.find(this.options, { value: this.selectedOptionValue }) : undefined}}",
|
||||
selectedOptionArr:
|
||||
'{{this.selectionType === "MULTI_SELECT" ? this.options.filter(opt => _.includes(this.selectedOptionValueArr, opt.value)) : undefined}}',
|
||||
selectedIndex:
|
||||
"{{ _.findIndex(this.options, { value: this.selectedOption.value } ) }}",
|
||||
selectedIndexArr:
|
||||
"{{ this.selectedOptionValueArr.map(o => _.findIndex(this.options, { value: o })) }}",
|
||||
value:
|
||||
"{{ this.selectionType === 'SINGLE_SELECT' ? this.selectedOptionValue : this.selectedOptionValueArr }}",
|
||||
selectedOptionValues: "{{ this.selectedOptionValueArr }}",
|
||||
selectedOptionLabel: `{{_.isPlainObject(this.selectedOption) ? this.selectedOption?.label : this.selectedOption}}`,
|
||||
selectedOptionValue: `{{_.isPlainObject(this.selectedOption) ? this.selectedOption?.value : this.selectedOption}}`,
|
||||
isValid: `{{this.isRequired ? !!this.selectedOptionValue || this.selectedOptionValue === 0 : true}}`,
|
||||
},
|
||||
metaProperties: {
|
||||
selectedOption: undefined,
|
||||
filterText: "",
|
||||
},
|
||||
metaProperties: {},
|
||||
},
|
||||
RADIO_GROUP_WIDGET: {
|
||||
defaultProperties: {
|
||||
|
|
@ -278,25 +271,25 @@ const mockDerived = jest.spyOn(WidgetFactory, "getWidgetDerivedPropertiesMap");
|
|||
const dependencyMap = {
|
||||
Dropdown1: [
|
||||
"Dropdown1.defaultOptionValue",
|
||||
"Dropdown1.filterText",
|
||||
"Dropdown1.isValid",
|
||||
"Dropdown1.selectedIndex",
|
||||
"Dropdown1.selectedIndexArr",
|
||||
"Dropdown1.meta",
|
||||
"Dropdown1.selectedOption",
|
||||
"Dropdown1.selectedOptionArr",
|
||||
"Dropdown1.selectedOptionLabel",
|
||||
"Dropdown1.selectedOptionValue",
|
||||
"Dropdown1.selectedOptionValueArr",
|
||||
"Dropdown1.selectedOptionValues",
|
||||
"Dropdown1.value",
|
||||
],
|
||||
"Dropdown1.isValid": [],
|
||||
"Dropdown1.selectedIndex": [],
|
||||
"Dropdown1.selectedIndexArr": [],
|
||||
"Dropdown1.selectedOption": [],
|
||||
"Dropdown1.selectedOptionArr": [],
|
||||
"Dropdown1.selectedOptionValue": ["Dropdown1.defaultOptionValue"],
|
||||
"Dropdown1.selectedOptionValueArr": ["Dropdown1.defaultOptionValue"],
|
||||
"Dropdown1.selectedOptionValues": [],
|
||||
"Dropdown1.value": [],
|
||||
"Dropdown1.filterText": ["Dropdown1.meta.filterText"],
|
||||
"Dropdown1.meta": [
|
||||
"Dropdown1.meta.filterText",
|
||||
"Dropdown1.meta.selectedOption",
|
||||
],
|
||||
"Dropdown1.selectedOption": [
|
||||
"Dropdown1.defaultOptionValue",
|
||||
"Dropdown1.meta.selectedOption",
|
||||
],
|
||||
"Dropdown1.selectedOptionLabel": [],
|
||||
"Dropdown1.selectedOptionValue": [],
|
||||
Table1: [
|
||||
"Table1.defaultSearchText",
|
||||
"Table1.defaultSelectedRow",
|
||||
|
|
@ -394,7 +387,7 @@ describe("DataTreeEvaluator", () => {
|
|||
value: "valueTest2",
|
||||
},
|
||||
],
|
||||
type: "DROP_DOWN_WIDGET",
|
||||
type: "SELECT_WIDGET",
|
||||
},
|
||||
{},
|
||||
),
|
||||
|
|
@ -492,7 +485,7 @@ describe("DataTreeEvaluator", () => {
|
|||
value: "valueTest2",
|
||||
},
|
||||
],
|
||||
type: "DROP_DOWN_WIDGET",
|
||||
type: "SELECT_WIDGET",
|
||||
bindingPaths: {
|
||||
options: EvaluationSubstitutionType.TEMPLATE,
|
||||
defaultOptionValue: EvaluationSubstitutionType.TEMPLATE,
|
||||
|
|
@ -501,11 +494,8 @@ describe("DataTreeEvaluator", () => {
|
|||
isDisabled: EvaluationSubstitutionType.TEMPLATE,
|
||||
isValid: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedOption: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedOptionArr: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedIndex: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedIndexArr: EvaluationSubstitutionType.TEMPLATE,
|
||||
value: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedOptionValues: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedOptionValue: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedOptionLabel: EvaluationSubstitutionType.TEMPLATE,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export const WidgetTypeFactories: Record<string, any> = {
|
|||
DATE_PICKER_WIDGET: OldDatepickerFactory,
|
||||
DATE_PICKER_WIDGET2: DatepickerFactory,
|
||||
TABLE_WIDGET: TableFactory,
|
||||
DROP_DOWN_WIDGET: DropdownFactory,
|
||||
SELECT_WIDGET: DropdownFactory,
|
||||
CHECKBOX_WIDGET: CheckboxFactory,
|
||||
RADIO_GROUP_WIDGET: RadiogroupFactory,
|
||||
TABS_WIDGET: TabsFactory,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.appsmith.server.domains.CommentThread;
|
|||
import com.appsmith.external.models.Datasource;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.Page;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.domains.User;
|
||||
import lombok.Getter;
|
||||
|
||||
|
|
@ -74,13 +75,15 @@ public enum AclPermission {
|
|||
READ_DATASOURCES("read:datasources", Datasource.class),
|
||||
EXECUTE_DATASOURCES("execute:datasources", Datasource.class),
|
||||
|
||||
COMMENT_ON_THREAD("canComment:commentThreads", CommentThread.class),
|
||||
READ_THREAD("read:commentThreads", CommentThread.class),
|
||||
MANAGE_THREAD("manage:commentThreads", CommentThread.class),
|
||||
COMMENT_ON_THREADS("canComment:commentThreads", CommentThread.class),
|
||||
READ_THREADS("read:commentThreads", CommentThread.class),
|
||||
MANAGE_THREADS("manage:commentThreads", CommentThread.class),
|
||||
|
||||
READ_COMMENT("read:comments", Comment.class),
|
||||
MANAGE_COMMENT("manage:comments", Comment.class),
|
||||
READ_COMMENTS("read:comments", Comment.class),
|
||||
MANAGE_COMMENTS("manage:comments", Comment.class),
|
||||
|
||||
READ_THEMES("read:themes", Theme.class),
|
||||
MANAGE_THEMES("manage:themes", Theme.class),
|
||||
;
|
||||
|
||||
private final String value;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.appsmith.server.acl.AclPermission.COMMENT_ON_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.COMMENT_ON_THREAD;
|
||||
import static com.appsmith.server.acl.AclPermission.COMMENT_ON_THREADS;
|
||||
import static com.appsmith.server.acl.AclPermission.EXECUTE_ACTIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.EXECUTE_DATASOURCES;
|
||||
import static com.appsmith.server.acl.AclPermission.EXPORT_APPLICATIONS;
|
||||
|
|
@ -30,6 +30,7 @@ import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
|
|||
import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_ORGANIZATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_THEMES;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_USERS;
|
||||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_EXPORT_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_MANAGE_APPLICATIONS;
|
||||
|
|
@ -38,11 +39,12 @@ import static com.appsmith.server.acl.AclPermission.ORGANIZATION_READ_APPLICATIO
|
|||
import static com.appsmith.server.acl.AclPermission.PUBLISH_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_ACTIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_COMMENT;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_COMMENTS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_DATASOURCES;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_ORGANIZATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_PAGES;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_THREAD;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_THEMES;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_THREADS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_USERS;
|
||||
import static com.appsmith.server.acl.AclPermission.USER_MANAGE_ORGANIZATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.USER_READ_ORGANIZATIONS;
|
||||
|
|
@ -81,6 +83,7 @@ public class PolicyGeneratorCE {
|
|||
createPagePolicyGraph();
|
||||
createActionPolicyGraph();
|
||||
createCommentPolicyGraph();
|
||||
createThemePolicyGraph();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -142,11 +145,17 @@ public class PolicyGeneratorCE {
|
|||
}
|
||||
|
||||
private void createCommentPolicyGraph() {
|
||||
hierarchyGraph.addEdge(COMMENT_ON_APPLICATIONS, COMMENT_ON_THREAD);
|
||||
hierarchyGraph.addEdge(COMMENT_ON_APPLICATIONS, COMMENT_ON_THREADS);
|
||||
|
||||
lateralGraph.addEdge(COMMENT_ON_THREAD, READ_THREAD);
|
||||
lateralGraph.addEdge(COMMENT_ON_THREADS, READ_THREADS);
|
||||
|
||||
hierarchyGraph.addEdge(COMMENT_ON_THREAD, READ_COMMENT);
|
||||
hierarchyGraph.addEdge(COMMENT_ON_THREADS, READ_COMMENTS);
|
||||
}
|
||||
|
||||
private void createThemePolicyGraph() {
|
||||
hierarchyGraph.addEdge(MANAGE_APPLICATIONS, MANAGE_THEMES);
|
||||
hierarchyGraph.addEdge(READ_APPLICATIONS, READ_THEMES);
|
||||
lateralGraph.addEdge(MANAGE_THEMES, READ_THEMES);
|
||||
}
|
||||
|
||||
public Set<Policy> getLateralPolicies(AclPermission permission, Set<String> userNames, Class<? extends BaseDomain> destinationEntity) {
|
||||
|
|
|
|||
|
|
@ -106,5 +106,6 @@ public class FieldName {
|
|||
public static final String ACTION_LIST = "actionList";
|
||||
public static final String ACTION_COLLECTION_LIST = "actionCollectionList";
|
||||
public static final String DECRYPTED_FIELDS = "decryptedFields";
|
||||
public static final String THEME = "theme";
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,18 +4,23 @@ import com.appsmith.server.constants.Url;
|
|||
import com.appsmith.server.domains.ApplicationMode;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.dtos.ResponseDTO;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.services.ThemeService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@RequestMapping(Url.THEME_URL)
|
||||
|
|
@ -24,15 +29,38 @@ public class ThemeControllerCE extends BaseController<ThemeService, Theme, Strin
|
|||
super(themeService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ResponseDTO<Theme>> create(Theme resource, String originHeader, ServerWebExchange exchange) {
|
||||
throw new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION);
|
||||
}
|
||||
|
||||
@GetMapping("applications/{applicationId}")
|
||||
public Mono<ResponseDTO<Theme>> getThemes(@PathVariable String applicationId, @RequestParam(required = false, defaultValue = "EDIT") ApplicationMode mode) {
|
||||
public Mono<ResponseDTO<List<Theme>>> getApplicationThemes(@PathVariable String applicationId) {
|
||||
return service.getApplicationThemes(applicationId).collectList()
|
||||
.map(themes -> new ResponseDTO<>(HttpStatus.OK.value(), themes, null));
|
||||
}
|
||||
|
||||
@GetMapping("applications/{applicationId}/current")
|
||||
public Mono<ResponseDTO<Theme>> getCurrentTheme(@PathVariable String applicationId, @RequestParam(required = false, defaultValue = "EDIT") ApplicationMode mode) {
|
||||
return service.getApplicationTheme(applicationId, mode)
|
||||
.map(theme -> new ResponseDTO<>(HttpStatus.OK.value(), theme, null));
|
||||
}
|
||||
|
||||
@PostMapping("applications/{applicationId}")
|
||||
@PutMapping("applications/{applicationId}")
|
||||
public Mono<ResponseDTO<Theme>> updateTheme(@PathVariable String applicationId, @Valid @RequestBody Theme resource) {
|
||||
return service.updateTheme(applicationId, resource)
|
||||
.map(theme -> new ResponseDTO<>(HttpStatus.OK.value(), theme, null));
|
||||
}
|
||||
|
||||
@PatchMapping("applications/{applicationId}")
|
||||
public Mono<ResponseDTO<Theme>> publishCurrentTheme(@PathVariable String applicationId, @RequestBody Theme resource) {
|
||||
return service.persistCurrentTheme(applicationId, resource)
|
||||
.map(theme -> new ResponseDTO<>(HttpStatus.OK.value(), theme, null));
|
||||
}
|
||||
|
||||
@PatchMapping("{themeId}")
|
||||
public Mono<ResponseDTO<Theme>> updateName(@PathVariable String themeId, @Valid @RequestBody Theme resource) {
|
||||
return service.updateName(themeId, resource)
|
||||
.map(theme -> new ResponseDTO<>(HttpStatus.OK.value(), theme, null));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.appsmith.server.domains;
|
|||
|
||||
import com.appsmith.external.models.BaseDomain;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
|
|
@ -11,7 +10,6 @@ import lombok.Setter;
|
|||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
|
|
@ -23,23 +21,15 @@ public class Theme extends BaseDomain {
|
|||
|
||||
@NotNull
|
||||
private String name;
|
||||
private Config config;
|
||||
private Properties properties;
|
||||
private Map<String, WidgetStyle> stylesheet;
|
||||
private String applicationId;
|
||||
private String organizationId;
|
||||
private Object config;
|
||||
private Object properties;
|
||||
private Map<String, Object> stylesheet;
|
||||
|
||||
@JsonProperty("isSystemTheme") // manually setting property name to make sure it's compatible with Gson
|
||||
private boolean isSystemTheme = false; // should be false by default
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Properties {
|
||||
private Colors colors;
|
||||
private BorderRadiusProperties borderRadius;
|
||||
private BoxShadowProperties boxShadow;
|
||||
private FontFamilyProperties fontFamily;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
|
|
@ -47,87 +37,4 @@ public class Theme extends BaseDomain {
|
|||
private String primaryColor;
|
||||
private String backgroundColor;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Config {
|
||||
private Colors colors;
|
||||
private BorderRadius borderRadius;
|
||||
private BoxShadow boxShadow;
|
||||
private FontFamily fontFamily;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ResponsiveAttributes {
|
||||
@JsonProperty("none")
|
||||
@SerializedName("none")
|
||||
private String noneValue;
|
||||
|
||||
@JsonProperty("DEFAULT")
|
||||
@SerializedName("DEFAULT")
|
||||
private String defaultValue;
|
||||
|
||||
@JsonProperty("md")
|
||||
@SerializedName("md")
|
||||
private String mdValue;
|
||||
|
||||
@JsonProperty("lg")
|
||||
@SerializedName("lg")
|
||||
private String lgValue;
|
||||
|
||||
@JsonProperty("xl")
|
||||
@SerializedName("xl")
|
||||
private String xlValue;
|
||||
|
||||
@JsonProperty("2xl")
|
||||
@SerializedName("2xl")
|
||||
private String doubleXlValue;
|
||||
|
||||
@JsonProperty("3xl")
|
||||
@SerializedName("3xl")
|
||||
private String tripleXlValue;
|
||||
|
||||
@JsonProperty("full")
|
||||
@SerializedName("full")
|
||||
private String fullValue;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class BorderRadius {
|
||||
private ResponsiveAttributes appBorderRadius;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class BoxShadow {
|
||||
private ResponsiveAttributes appBoxShadow;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FontFamily {
|
||||
private List<String> appFont;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FontFamilyProperties {
|
||||
private String appFont;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class WidgetStyle {
|
||||
private String backgroundColor;
|
||||
private String borderRadius;
|
||||
private String boxShadow;
|
||||
private String primaryColor;
|
||||
private String menuColor;
|
||||
private String buttonColor;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class BorderRadiusProperties {
|
||||
private String appBorderRadius;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class BoxShadowProperties {
|
||||
private String appBoxShadow;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.appsmith.server.domains.Application;
|
|||
import com.appsmith.server.domains.CommentThread;
|
||||
import com.appsmith.server.domains.NewAction;
|
||||
import com.appsmith.server.domains.NewPage;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.repositories.ActionCollectionRepository;
|
||||
import com.appsmith.server.repositories.ApplicationRepository;
|
||||
|
|
@ -17,9 +18,11 @@ import com.appsmith.server.repositories.CommentThreadRepository;
|
|||
import com.appsmith.server.repositories.DatasourceRepository;
|
||||
import com.appsmith.server.repositories.NewActionRepository;
|
||||
import com.appsmith.server.repositories.NewPageRepository;
|
||||
import com.appsmith.server.repositories.ThemeRepository;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
|
@ -35,6 +38,7 @@ import java.util.function.Function;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_THEMES;
|
||||
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
|
|
@ -47,6 +51,7 @@ public class PolicyUtils {
|
|||
private final NewActionRepository newActionRepository;
|
||||
private final CommentThreadRepository commentThreadRepository;
|
||||
private final ActionCollectionRepository actionCollectionRepository;
|
||||
private final ThemeRepository themeRepository;
|
||||
|
||||
public <T extends BaseDomain> T addPoliciesToExistingObject(Map<String, Policy> policyMap, T obj) {
|
||||
// Making a deep copy here so we don't modify the `policyMap` object.
|
||||
|
|
@ -231,12 +236,37 @@ public class PolicyUtils {
|
|||
.saveAll(updatedPages));
|
||||
}
|
||||
|
||||
public Flux<Theme> updateThemePolicies(Application application, Map<String, Policy> themePolicyMap, boolean addPolicyToObject) {
|
||||
Flux<Theme> applicationThemes = themeRepository.getApplicationThemes(application.getId(), READ_THEMES);
|
||||
if(StringUtils.hasLength(application.getEditModeThemeId())) {
|
||||
applicationThemes = applicationThemes.concatWith(
|
||||
themeRepository.findById(application.getEditModeThemeId(), READ_THEMES)
|
||||
);
|
||||
}
|
||||
if(StringUtils.hasLength(application.getPublishedModeThemeId())) {
|
||||
applicationThemes = applicationThemes.concatWith(
|
||||
themeRepository.findById(application.getPublishedModeThemeId(), READ_THEMES)
|
||||
);
|
||||
}
|
||||
return applicationThemes
|
||||
.filter(theme -> !theme.isSystemTheme()) // skip the system themes
|
||||
.map(theme -> {
|
||||
if (addPolicyToObject) {
|
||||
return addPoliciesToExistingObject(themePolicyMap, theme);
|
||||
} else {
|
||||
return removePoliciesFromExistingObject(themePolicyMap, theme);
|
||||
}
|
||||
})
|
||||
.collectList()
|
||||
.flatMapMany(themeRepository::saveAll);
|
||||
}
|
||||
|
||||
public Flux<CommentThread> updateCommentThreadPermissions(
|
||||
String applicationId, Map<String, Policy> commentThreadPolicyMap, String username, boolean addPolicyToObject) {
|
||||
|
||||
return
|
||||
// fetch comment threads with read permissions
|
||||
commentThreadRepository.findByApplicationId(applicationId, AclPermission.READ_THREAD)
|
||||
commentThreadRepository.findByApplicationId(applicationId, AclPermission.READ_THREADS)
|
||||
.switchIfEmpty(Mono.empty())
|
||||
.map(thread -> {
|
||||
if(!Boolean.TRUE.equals(thread.getIsPrivate())) {
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ import static com.appsmith.server.acl.AclPermission.MAKE_PUBLIC_APPLICATIONS;
|
|||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_EXPORT_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_INVITE_USERS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_ACTIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_THEMES;
|
||||
import static com.appsmith.server.constants.FieldName.DEFAULT_RESOURCES;
|
||||
import static com.appsmith.server.constants.FieldName.DYNAMIC_TRIGGER_PATH_LIST;
|
||||
import static com.appsmith.server.helpers.CollectionUtils.isNullOrEmpty;
|
||||
|
|
@ -4744,37 +4745,6 @@ public class DatabaseChangelog {
|
|||
mongockTemplate.save(firestorePlugin);
|
||||
}
|
||||
|
||||
@ChangeSet(order = "108", id = "create-system-themes", author = "")
|
||||
public void createSystemThemes(MongockTemplate mongockTemplate) throws IOException {
|
||||
Index uniqueApplicationIdIndex = new Index()
|
||||
.on(fieldName(QTheme.theme.isSystemTheme), Sort.Direction.ASC)
|
||||
.named("system_theme_index");
|
||||
|
||||
ensureIndexes(mongockTemplate, Theme.class, uniqueApplicationIdIndex);
|
||||
|
||||
final String themesJson = StreamUtils.copyToString(
|
||||
new DefaultResourceLoader().getResource("system-themes.json").getInputStream(),
|
||||
Charset.defaultCharset()
|
||||
);
|
||||
Theme[] themes = new Gson().fromJson(themesJson, Theme[].class);
|
||||
|
||||
Theme legacyTheme = null;
|
||||
for (Theme theme : themes) {
|
||||
theme.setSystemTheme(true);
|
||||
Theme savedTheme = mongockTemplate.save(theme);
|
||||
if(savedTheme.getName().equalsIgnoreCase(Theme.LEGACY_THEME_NAME)) {
|
||||
legacyTheme = savedTheme;
|
||||
}
|
||||
}
|
||||
|
||||
// migrate all applications and set legacy theme to them in both mode
|
||||
Update update = new Update().set(fieldName(QApplication.application.publishedModeThemeId), legacyTheme.getId())
|
||||
.set(fieldName(QApplication.application.editModeThemeId), legacyTheme.getId());
|
||||
mongockTemplate.updateMulti(
|
||||
new Query(where(fieldName(QApplication.application.deleted)).is(false)), update, Application.class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the key formData.aggregate.limit to 101 for all Mongo plugin actions.
|
||||
* It iterates over each action id one by one to avoid out of memory error.
|
||||
|
|
@ -4817,6 +4787,11 @@ public class DatabaseChangelog {
|
|||
return true;
|
||||
}
|
||||
|
||||
@ChangeSet(order = "108", id = "create-system-themes", author = "")
|
||||
public void createSystemThemes(MongockTemplate mongockTemplate) throws IOException {
|
||||
createSystemThemes2(mongockTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* This migration adds a new field to Mongo aggregate command to set batchSize: formData.aggregate.limit. Its value
|
||||
* is set by this migration to 101 for all existing actions since this is the default `batchSize` used by
|
||||
|
|
@ -5025,4 +5000,75 @@ public class DatabaseChangelog {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adding this migration again because we've added permission to themes.
|
||||
* Also there are couple of changes in the system theme properties.
|
||||
* @param mongockTemplate
|
||||
* @throws IOException
|
||||
*/
|
||||
@ChangeSet(order = "117", id = "create-system-themes-v2", author = "")
|
||||
public void createSystemThemes2(MongockTemplate mongockTemplate) throws IOException {
|
||||
Index systemThemeIndex = new Index()
|
||||
.on(fieldName(QTheme.theme.isSystemTheme), Sort.Direction.ASC)
|
||||
.named("system_theme_index")
|
||||
.background();
|
||||
|
||||
Index applicationIdIndex = new Index()
|
||||
.on(fieldName(QTheme.theme.applicationId), Sort.Direction.ASC)
|
||||
.on(fieldName(QTheme.theme.deleted), Sort.Direction.ASC)
|
||||
.named("application_id_index")
|
||||
.background();
|
||||
|
||||
dropIndexIfExists(mongockTemplate, Theme.class, "system_theme_index");
|
||||
dropIndexIfExists(mongockTemplate, Theme.class, "application_id_index");
|
||||
ensureIndexes(mongockTemplate, Theme.class, systemThemeIndex, applicationIdIndex);
|
||||
|
||||
final String themesJson = StreamUtils.copyToString(
|
||||
new DefaultResourceLoader().getResource("system-themes.json").getInputStream(),
|
||||
Charset.defaultCharset()
|
||||
);
|
||||
Theme[] themes = new Gson().fromJson(themesJson, Theme[].class);
|
||||
|
||||
Theme legacyTheme = null;
|
||||
boolean themeExists = false;
|
||||
|
||||
Policy policyWithCurrentPermission = Policy.builder().permission(READ_THEMES.getValue())
|
||||
.users(Set.of(FieldName.ANONYMOUS_USER)).build();
|
||||
|
||||
for (Theme theme : themes) {
|
||||
theme.setSystemTheme(true);
|
||||
theme.setCreatedAt(Instant.now());
|
||||
theme.setPolicies(Set.of(policyWithCurrentPermission));
|
||||
Query query = new Query(Criteria.where(fieldName(QTheme.theme.name)).is(theme.getName())
|
||||
.and(fieldName(QTheme.theme.isSystemTheme)).is(true));
|
||||
|
||||
Theme savedTheme = mongockTemplate.findOne(query, Theme.class);
|
||||
if(savedTheme == null) { // this theme does not exist, create it
|
||||
savedTheme = mongockTemplate.save(theme);
|
||||
} else { // theme already found, update
|
||||
themeExists = true;
|
||||
savedTheme.setPolicies(theme.getPolicies());
|
||||
savedTheme.setConfig(theme.getConfig());
|
||||
savedTheme.setProperties(theme.getProperties());
|
||||
savedTheme.setStylesheet(theme.getStylesheet());
|
||||
if(savedTheme.getCreatedAt() == null) {
|
||||
savedTheme.setCreatedAt(Instant.now());
|
||||
}
|
||||
mongockTemplate.save(savedTheme);
|
||||
}
|
||||
|
||||
if(theme.getName().equalsIgnoreCase(Theme.LEGACY_THEME_NAME)) {
|
||||
legacyTheme = savedTheme;
|
||||
}
|
||||
}
|
||||
|
||||
if(!themeExists) { // this is the first time we're running the migration
|
||||
// migrate all applications and set legacy theme to them in both mode
|
||||
Update update = new Update().set(fieldName(QApplication.application.publishedModeThemeId), legacyTheme.getId())
|
||||
.set(fieldName(QApplication.application.editModeThemeId), legacyTheme.getId());
|
||||
mongockTemplate.updateMulti(
|
||||
new Query(where(fieldName(QApplication.application.deleted)).is(false)), update, Application.class
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,13 +58,13 @@ public class CustomCommentThreadRepositoryCEImpl extends BaseAppsmithRepositoryI
|
|||
where(fieldName(QCommentThread.commentThread.applicationId)).is(applicationId),
|
||||
where(fieldName(QCommentThread.commentThread.isPrivate)).is(TRUE)
|
||||
);
|
||||
return queryOne(criteria, AclPermission.READ_THREAD);
|
||||
return queryOne(criteria, AclPermission.READ_THREADS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UpdateResult> removeSubscriber(String threadId, String username) {
|
||||
Update update = new Update().pull(fieldName(QCommentThread.commentThread.subscribers), username);
|
||||
return this.updateById(threadId, update, AclPermission.READ_THREAD);
|
||||
return this.updateById(threadId, update, AclPermission.READ_THREADS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -92,7 +92,7 @@ public class CustomCommentThreadRepositoryCEImpl extends BaseAppsmithRepositoryI
|
|||
where(fieldName(QCommentThread.commentThread.applicationId)).is(applicationId),
|
||||
where(resolvedActiveFieldKey).is(false)
|
||||
);
|
||||
return count(criteriaList, AclPermission.READ_THREAD);
|
||||
return count(criteriaList, AclPermission.READ_THREADS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package com.appsmith.server.repositories.ce;
|
||||
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.repositories.AppsmithRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface CustomThemeRepositoryCE extends AppsmithRepository<Theme> {
|
||||
Flux<Theme> getApplicationThemes(String applicationId, AclPermission aclPermission);
|
||||
Flux<Theme> getSystemThemes();
|
||||
Mono<Theme> getSystemThemeByName(String themeName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.appsmith.server.repositories.ce;
|
||||
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.domains.QTheme;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.repositories.BaseAppsmithRepositoryImpl;
|
||||
|
|
@ -24,10 +25,18 @@ public class CustomThemeRepositoryCEImpl extends BaseAppsmithRepositoryImpl<Them
|
|||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Flux<Theme> getApplicationThemes(String applicationId, AclPermission aclPermission) {
|
||||
Criteria appThemeCriteria = Criteria.where(fieldName(QTheme.theme.applicationId)).is(applicationId);
|
||||
Criteria systemThemeCriteria = Criteria.where(fieldName(QTheme.theme.isSystemTheme)).is(Boolean.TRUE);
|
||||
Criteria criteria = new Criteria().orOperator(appThemeCriteria, systemThemeCriteria);
|
||||
return queryAll(List.of(criteria), aclPermission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Theme> getSystemThemes() {
|
||||
Criteria criteria = Criteria.where(fieldName(QTheme.theme.isSystemTheme)).is(Boolean.TRUE);
|
||||
return queryAll(List.of(criteria), null);
|
||||
Criteria systemThemeCriteria = Criteria.where(fieldName(QTheme.theme.isSystemTheme)).is(Boolean.TRUE);
|
||||
return queryAll(List.of(systemThemeCriteria), AclPermission.READ_THEMES);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -35,6 +44,6 @@ public class CustomThemeRepositoryCEImpl extends BaseAppsmithRepositoryImpl<Them
|
|||
String findNameRegex = String.format("^%s$", Pattern.quote(themeName));
|
||||
Criteria criteria = where(fieldName(QTheme.theme.name)).regex(findNameRegex, "i")
|
||||
.and(fieldName(QTheme.theme.isSystemTheme)).is(true);
|
||||
return queryOne(List.of(criteria), null);
|
||||
return queryOne(List.of(criteria), AclPermission.READ_THEMES);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.appsmith.server.services;
|
||||
|
||||
import com.appsmith.server.acl.PolicyGenerator;
|
||||
import com.appsmith.server.repositories.ApplicationRepository;
|
||||
import com.appsmith.server.repositories.ThemeRepository;
|
||||
import com.appsmith.server.services.ce.ThemeServiceCEImpl;
|
||||
|
|
@ -14,7 +15,7 @@ import javax.validation.Validator;
|
|||
@Slf4j
|
||||
@Service
|
||||
public class ThemeServiceImpl extends ThemeServiceCEImpl implements ThemeService {
|
||||
public ThemeServiceImpl(Scheduler scheduler, Validator validator, MongoConverter mongoConverter, ReactiveMongoTemplate reactiveMongoTemplate, ThemeRepository repository, AnalyticsService analyticsService, ApplicationRepository applicationRepository) {
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService, applicationRepository);
|
||||
public ThemeServiceImpl(Scheduler scheduler, Validator validator, MongoConverter mongoConverter, ReactiveMongoTemplate reactiveMongoTemplate, ThemeRepository repository, AnalyticsService analyticsService, ApplicationRepository applicationRepository, PolicyGenerator policyGenerator) {
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService, applicationRepository, policyGenerator);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -697,16 +697,6 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
|
|||
application1.setModifiedBy(applicationUserTuple2.getT2().getUsername()); // setting modified by to current user
|
||||
return applicationService.createDefault(application1);
|
||||
})
|
||||
// duplicate the source application's themes if required i.e. if they were customized
|
||||
.flatMap(application ->
|
||||
themeService.cloneThemeToApplication(sourceApplication.getEditModeThemeId(), application.getId())
|
||||
.zipWith(themeService.cloneThemeToApplication(sourceApplication.getPublishedModeThemeId(), application.getId()))
|
||||
.map(themesZip -> {
|
||||
application.setEditModeThemeId(themesZip.getT1().getId());
|
||||
application.setPublishedModeThemeId(themesZip.getT2().getId());
|
||||
return application;
|
||||
})
|
||||
)
|
||||
// Now fetch the pages of the source application, clone and add them to this new application
|
||||
.flatMap(savedApplication -> Flux.fromIterable(sourceApplication.getPages())
|
||||
.flatMap(applicationPage -> {
|
||||
|
|
@ -728,6 +718,20 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
|
|||
savedApplication.setPages(clonedPages);
|
||||
return applicationService.save(savedApplication);
|
||||
})
|
||||
)
|
||||
// duplicate the source application's themes if required i.e. if they were customized
|
||||
.flatMap(application ->
|
||||
themeService.cloneThemeToApplication(sourceApplication.getEditModeThemeId(), application)
|
||||
.zipWith(themeService.cloneThemeToApplication(sourceApplication.getPublishedModeThemeId(), application))
|
||||
.flatMap(themesZip -> {
|
||||
String editModeThemeId = themesZip.getT1().getId();
|
||||
String publishedModeThemeId = themesZip.getT2().getId();
|
||||
application.setEditModeThemeId(editModeThemeId);
|
||||
application.setPublishedModeThemeId(publishedModeThemeId);
|
||||
return applicationService.setAppTheme(
|
||||
application.getId(), editModeThemeId, publishedModeThemeId, MANAGE_APPLICATIONS
|
||||
).thenReturn(application);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -842,9 +846,9 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
|
|||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION, applicationId)))
|
||||
.cache();
|
||||
|
||||
Mono<Theme> publishThemeMono = applicationMono.flatMap(application -> themeService.publishTheme(
|
||||
application.getEditModeThemeId(), application.getPublishedModeThemeId(), application.getId()
|
||||
));
|
||||
Mono<Theme> publishThemeMono = applicationMono.flatMap(
|
||||
application -> themeService.publishTheme(application.getId())
|
||||
);
|
||||
|
||||
Flux<NewPage> publishApplicationAndPages = applicationMono
|
||||
//Return all the pages in the Application
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.appsmith.server.domains.Application;
|
|||
import com.appsmith.server.domains.GitAuth;
|
||||
import com.appsmith.server.dtos.ApplicationAccessDTO;
|
||||
import com.appsmith.server.services.CrudService;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
|
@ -62,4 +63,6 @@ public interface ApplicationServiceCE extends CrudService<Application, String> {
|
|||
|
||||
String getRandomAppCardColor();
|
||||
|
||||
Mono<UpdateResult> setAppTheme(String applicationId, String editModeThemeId, String publishedModeThemeId, AclPermission aclPermission);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.appsmith.server.domains.GitAuth;
|
|||
import com.appsmith.server.domains.NewAction;
|
||||
import com.appsmith.server.domains.NewPage;
|
||||
import com.appsmith.server.domains.Page;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.dtos.ActionDTO;
|
||||
import com.appsmith.server.dtos.ApplicationAccessDTO;
|
||||
|
|
@ -28,6 +29,7 @@ import com.appsmith.server.services.AnalyticsService;
|
|||
import com.appsmith.server.services.BaseService;
|
||||
import com.appsmith.server.services.ConfigService;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
|
|
@ -308,18 +310,23 @@ public class ApplicationServiceCEImpl extends BaseService<ApplicationRepository,
|
|||
Map<String, Policy> pagePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(applicationPolicyMap, Application.class, Page.class);
|
||||
Map<String, Policy> actionPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(pagePolicyMap, Page.class, Action.class);
|
||||
Map<String, Policy> datasourcePolicyMap = policyUtils.generatePolicyFromPermission(Set.of(EXECUTE_DATASOURCES), user);
|
||||
Map<String, Policy> themePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(
|
||||
applicationPolicyMap, Application.class, Theme.class
|
||||
);
|
||||
|
||||
final Flux<NewPage> updatedPagesFlux = policyUtils
|
||||
.updateWithApplicationPermissionsToAllItsPages(application.getId(), pagePolicyMap, isPublic);
|
||||
// Use the same policy map as actions for action collections since action collections have the same kind of permissions
|
||||
final Flux<ActionCollection> updatedActionCollectionsFlux = policyUtils
|
||||
.updateWithPagePermissionsToAllItsActionCollections(application.getId(), actionPolicyMap, isPublic);
|
||||
|
||||
Flux<Theme> updatedThemesFlux = policyUtils.updateThemePolicies(application, themePolicyMap, isPublic);
|
||||
final Flux<NewAction> updatedActionsFlux = updatedPagesFlux
|
||||
.collectList()
|
||||
.thenMany(updatedActionCollectionsFlux)
|
||||
.collectList()
|
||||
.then(Mono.justOrEmpty(application.getId()))
|
||||
.thenMany(updatedThemesFlux)
|
||||
.collectList()
|
||||
.flatMapMany(applicationId -> policyUtils.updateWithPagePermissionsToAllItsActions(application.getId(), actionPolicyMap, isPublic));
|
||||
|
||||
return updatedActionsFlux
|
||||
|
|
@ -547,4 +554,8 @@ public class ApplicationServiceCEImpl extends BaseService<ApplicationRepository,
|
|||
return ApplicationConstants.APP_CARD_COLORS[randomColorIndex];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UpdateResult> setAppTheme(String applicationId, String editModeThemeId, String publishedModeThemeId, AclPermission aclPermission) {
|
||||
return repository.setAppTheme(applicationId, editModeThemeId, publishedModeThemeId, aclPermission);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@ import static com.appsmith.server.acl.AclPermission.COMMENT_ON_APPLICATIONS;
|
|||
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_COMMENT;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_COMMENTS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_PAGES;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_THREAD;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_THREADS;
|
||||
import static com.appsmith.server.constants.CommentConstants.APPSMITH_BOT_NAME;
|
||||
import static com.appsmith.server.constants.CommentConstants.APPSMITH_BOT_USERNAME;
|
||||
import static java.lang.Boolean.FALSE;
|
||||
|
|
@ -177,7 +177,7 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
comment.setPageId(branchedPageId);
|
||||
comment.setApplicationId(branchedApplicationId);
|
||||
return threadRepository
|
||||
.findById(threadId, AclPermission.COMMENT_ON_THREAD)
|
||||
.findById(threadId, AclPermission.COMMENT_ON_THREADS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.COMMENT_THREAD, threadId)))
|
||||
.flatMap(commentThread -> updateThreadOnAddComment(commentThread, comment, user))
|
||||
.flatMap(commentThread -> create(commentThread, user, comment, originHeader));
|
||||
|
|
@ -188,7 +188,7 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
@Override
|
||||
public Mono<Comment> findByIdAndBranchName(String id, String branchName) {
|
||||
// Ignore branch name as comments are not shared across git branches
|
||||
return repository.findById(id, READ_COMMENT)
|
||||
return repository.findById(id, READ_COMMENTS)
|
||||
.map(responseUtils::updatePageAndAppIdWithDefaultResourcesForComments);
|
||||
}
|
||||
|
||||
|
|
@ -234,9 +234,9 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
CommentThread.class
|
||||
));
|
||||
policies.add(policyUtils.generatePolicyFromPermission(
|
||||
Set.of(AclPermission.MANAGE_THREAD),
|
||||
Set.of(AclPermission.MANAGE_THREADS),
|
||||
commentThread.getAuthorUsername()
|
||||
).get(AclPermission.MANAGE_THREAD.getValue()));
|
||||
).get(AclPermission.MANAGE_THREADS.getValue()));
|
||||
commentThread.setPolicies(policies);
|
||||
return commentThread;
|
||||
});
|
||||
|
|
@ -266,9 +266,9 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
Comment.class
|
||||
);
|
||||
policies.add(policyUtils.generatePolicyFromPermission(
|
||||
Set.of(AclPermission.MANAGE_COMMENT),
|
||||
Set.of(AclPermission.MANAGE_COMMENTS),
|
||||
user
|
||||
).get(AclPermission.MANAGE_COMMENT.getValue()));
|
||||
).get(AclPermission.MANAGE_COMMENTS.getValue()));
|
||||
comment.setPolicies(policies);
|
||||
|
||||
Mono<Comment> commentMono;
|
||||
|
|
@ -429,7 +429,7 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
// Comments and threads can't be moved between the pages and applications.
|
||||
comment.setApplicationId(null);
|
||||
comment.setPageId(null);
|
||||
return repository.updateById(id, comment, AclPermission.MANAGE_COMMENT)
|
||||
return repository.updateById(id, comment, AclPermission.MANAGE_COMMENTS)
|
||||
.flatMap(analyticsService::sendUpdateEvent)
|
||||
.map(responseUtils::updatePageAndAppIdWithDefaultResourcesForComments);
|
||||
}
|
||||
|
|
@ -447,7 +447,7 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
} else {
|
||||
return Mono.just(user);
|
||||
}
|
||||
}).zipWith(threadRepository.findById(threadId, AclPermission.READ_THREAD))
|
||||
}).zipWith(threadRepository.findById(threadId, AclPermission.READ_THREADS))
|
||||
.switchIfEmpty(Mono.error(
|
||||
new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, "comment thread", threadId))
|
||||
)
|
||||
|
|
@ -489,7 +489,7 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
}
|
||||
|
||||
return threadRepository
|
||||
.updateById(threadId, commentThread, AclPermission.READ_THREAD)
|
||||
.updateById(threadId, commentThread, AclPermission.READ_THREADS)
|
||||
.flatMap(updatedThread -> {
|
||||
updatedThread.setIsViewed(true);
|
||||
// Update branched applicationId and pageId with default Ids
|
||||
|
|
@ -520,7 +520,7 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
public Flux<Comment> get(MultiValueMap<String, String> params) {
|
||||
// Remove branch name as comments are not shared across branches
|
||||
params.remove(FieldName.DEFAULT_RESOURCES + "." + FieldName.BRANCH_NAME);
|
||||
return super.getWithPermission(params, READ_COMMENT)
|
||||
return super.getWithPermission(params, READ_COMMENTS)
|
||||
.map(responseUtils::updatePageAndAppIdWithDefaultResourcesForComments);
|
||||
}
|
||||
|
||||
|
|
@ -580,7 +580,7 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
// user is app viewer, show only PUBLISHED comment threads
|
||||
commentThreadFilterDTO.setMode(ApplicationMode.PUBLISHED);
|
||||
}
|
||||
return threadRepository.find(commentThreadFilterDTO, AclPermission.READ_THREAD)
|
||||
return threadRepository.find(commentThreadFilterDTO, AclPermission.READ_THREADS)
|
||||
.collectList()
|
||||
.flatMap(threads -> {
|
||||
final Map<String, CommentThread> threadsByThreadId = new HashMap<>();
|
||||
|
|
@ -626,10 +626,10 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
*/
|
||||
@Override
|
||||
public Mono<Comment> deleteComment(String id) {
|
||||
return repository.findById(id, AclPermission.MANAGE_COMMENT)
|
||||
return repository.findById(id, AclPermission.MANAGE_COMMENTS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.COMMENT, id)))
|
||||
.flatMap(repository::archive)
|
||||
.flatMap(comment -> threadRepository.findById(comment.getThreadId(), READ_THREAD).flatMap(commentThread ->
|
||||
.flatMap(comment -> threadRepository.findById(comment.getThreadId(), READ_THREADS).flatMap(commentThread ->
|
||||
sendCommentNotifications(commentThread.getSubscribers(), comment, CommentNotificationEvent.DELETED)
|
||||
.thenReturn(comment)
|
||||
))
|
||||
|
|
@ -639,7 +639,7 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
|
||||
@Override
|
||||
public Mono<CommentThread> deleteThread(String threadId) {
|
||||
return threadRepository.findById(threadId, AclPermission.MANAGE_THREAD)
|
||||
return threadRepository.findById(threadId, AclPermission.MANAGE_THREADS)
|
||||
.flatMap(threadRepository::archive)
|
||||
.flatMap(commentThread ->
|
||||
notificationService.createNotification(
|
||||
|
|
@ -653,7 +653,7 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
@Override
|
||||
public Mono<Boolean> createReaction(String commentId, Comment.Reaction reaction) {
|
||||
return Mono.zip(
|
||||
repository.findById(commentId, READ_COMMENT),
|
||||
repository.findById(commentId, READ_COMMENTS),
|
||||
sessionUserService.getCurrentUser()
|
||||
)
|
||||
.flatMap(tuple -> {
|
||||
|
|
@ -671,7 +671,7 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
@Override
|
||||
public Mono<Boolean> deleteReaction(String commentId, Comment.Reaction reaction) {
|
||||
return Mono.zip(
|
||||
repository.findById(commentId, READ_COMMENT),
|
||||
repository.findById(commentId, READ_COMMENTS),
|
||||
sessionUserService.getCurrentUser()
|
||||
)
|
||||
.flatMap(tuple -> {
|
||||
|
|
@ -702,7 +702,7 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
Mono<Long> commentSeq;
|
||||
if (TRUE.equals(commentThread.getIsPrivate())) {
|
||||
Collection<Policy> policyCollection = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(AclPermission.MANAGE_THREAD, AclPermission.COMMENT_ON_THREAD),
|
||||
Set.of(AclPermission.MANAGE_THREADS, AclPermission.COMMENT_ON_THREADS),
|
||||
user
|
||||
).values();
|
||||
policies.addAll(policyCollection);
|
||||
|
|
@ -714,9 +714,9 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
CommentThread.class
|
||||
));
|
||||
policies.add(policyUtils.generatePolicyFromPermission(
|
||||
Set.of(AclPermission.MANAGE_THREAD),
|
||||
Set.of(AclPermission.MANAGE_THREADS),
|
||||
user
|
||||
).get(AclPermission.MANAGE_THREAD.getValue()));
|
||||
).get(AclPermission.MANAGE_THREADS.getValue()));
|
||||
commentSeq = sequenceService.getNext(CommentThread.class, application.getId());
|
||||
}
|
||||
commentThread.setPolicies(policies);
|
||||
|
|
@ -740,9 +740,9 @@ public class CommentServiceCEImpl extends BaseService<CommentRepository, Comment
|
|||
Comment.class
|
||||
);
|
||||
policies.add(policyUtils.generatePolicyFromPermission(
|
||||
Set.of(AclPermission.MANAGE_COMMENT),
|
||||
Set.of(AclPermission.MANAGE_COMMENTS),
|
||||
user
|
||||
).get(AclPermission.MANAGE_COMMENT.getValue()));
|
||||
).get(AclPermission.MANAGE_COMMENTS.getValue()));
|
||||
comment.setPolicies(policies);
|
||||
|
||||
Comment.Block block = new Comment.Block();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
package com.appsmith.server.services.ce;
|
||||
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationMode;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.services.CrudService;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface ThemeServiceCE extends CrudService<Theme, String> {
|
||||
Mono<Theme> getApplicationTheme(String applicationId, ApplicationMode applicationMode);
|
||||
Flux<Theme> getApplicationThemes(String applicationId);
|
||||
Flux<Theme> getSystemThemes();
|
||||
Mono<Theme> getSystemTheme(String themeName);
|
||||
Mono<Theme> updateTheme(String applicationId, Theme resource);
|
||||
Mono<Theme> changeCurrentTheme(String themeId, String applicationId);
|
||||
|
||||
|
|
@ -18,14 +24,17 @@ public interface ThemeServiceCE extends CrudService<Theme, String> {
|
|||
Mono<String> getDefaultThemeId();
|
||||
|
||||
/**
|
||||
* Duplicates a theme if the theme is customized one. It'll set the application id to the new theme.
|
||||
* Duplicates a theme if the theme is customized one.
|
||||
* If the source theme is a system theme, it'll skip creating a new theme and return the system theme instead.
|
||||
* @param srcThemeId ID of source theme that needs to be duplicated
|
||||
* @param destApplicationId ID of the application for which theme'll be created
|
||||
* @return newly created theme if source is not system theme, otherwise return the system theme
|
||||
*/
|
||||
Mono<Theme> cloneThemeToApplication(String srcThemeId, String destApplicationId);
|
||||
|
||||
Mono<Theme> publishTheme(String editModeThemeId, String publishedThemeId, String applicationId);
|
||||
void resetDefaultThemeIdCache();
|
||||
Mono<Theme> cloneThemeToApplication(String srcThemeId, Application destApplication);
|
||||
Mono<Theme> publishTheme(String applicationId);
|
||||
Mono<Theme> persistCurrentTheme(String applicationId, Theme theme);
|
||||
Mono<Theme> getThemeById(String themeId, AclPermission permission);
|
||||
Mono<Theme> save(Theme theme);
|
||||
Mono<Theme> updateName(String id, Theme theme);
|
||||
Mono<Theme> getOrSaveTheme(Theme theme, Application destApplication);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.appsmith.server.services.ce;
|
||||
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.acl.PolicyGenerator;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationMode;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
|
|
@ -19,43 +21,48 @@ import org.springframework.util.StringUtils;
|
|||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
import javax.validation.Validator;
|
||||
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_THEMES;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_THEMES;
|
||||
|
||||
@Slf4j
|
||||
public class ThemeServiceCEImpl extends BaseService<ThemeRepositoryCE, Theme, String> implements ThemeServiceCE {
|
||||
|
||||
private final ApplicationRepository applicationRepository;
|
||||
private final PolicyGenerator policyGenerator;
|
||||
private String defaultThemeId; // acts as a simple cache so that we don't need to fetch from DB always
|
||||
|
||||
public ThemeServiceCEImpl(Scheduler scheduler, Validator validator, MongoConverter mongoConverter, ReactiveMongoTemplate reactiveMongoTemplate, ThemeRepository repository, AnalyticsService analyticsService, ApplicationRepository applicationRepository) {
|
||||
public ThemeServiceCEImpl(Scheduler scheduler, Validator validator, MongoConverter mongoConverter, ReactiveMongoTemplate reactiveMongoTemplate, ThemeRepository repository, AnalyticsService analyticsService, ApplicationRepository applicationRepository, PolicyGenerator policyGenerator) {
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
|
||||
this.applicationRepository = applicationRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Theme> get(MultiValueMap<String, String> params) {
|
||||
return repository.getSystemThemes(); // return the list of system themes
|
||||
this.policyGenerator = policyGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Theme> create(Theme resource) {
|
||||
// user can get the list of themes under an application only
|
||||
throw new UnsupportedOperationException();
|
||||
return repository.save(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Theme> update(String s, Theme resource) {
|
||||
// we don't allow to update a theme by id, user can only update a theme under their application
|
||||
throw new UnsupportedOperationException();
|
||||
throw new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Theme> getById(String s) {
|
||||
// TODO: better to add permission check
|
||||
return repository.findById(s);
|
||||
// we don't allow to get a theme by id from DB
|
||||
throw new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Theme> get(MultiValueMap<String, String> params) {
|
||||
// we return all system themes
|
||||
return repository.getSystemThemes();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -69,45 +76,75 @@ public class ThemeServiceCEImpl extends BaseService<ThemeRepositoryCE, Theme, St
|
|||
if(applicationMode == ApplicationMode.PUBLISHED) {
|
||||
themeId = application.getPublishedModeThemeId();
|
||||
}
|
||||
if(!StringUtils.isEmpty(themeId)) {
|
||||
return repository.findById(themeId);
|
||||
if(StringUtils.hasLength(themeId)) {
|
||||
return repository.findById(themeId, READ_THEMES);
|
||||
} else { // theme id is not present, return default theme
|
||||
return repository.getSystemThemeByName(Theme.DEFAULT_THEME_NAME);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Theme> getApplicationThemes(String applicationId) {
|
||||
return repository.getApplicationThemes(applicationId, READ_THEMES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Theme> getSystemThemes() {
|
||||
return repository.getSystemThemes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Theme> updateTheme(String applicationId, Theme resource) {
|
||||
return applicationRepository.findById(applicationId, AclPermission.MANAGE_APPLICATIONS)
|
||||
.flatMap(application -> {
|
||||
// makes sure user has permission to edit application and an application exists by this applicationId
|
||||
// check if this application has already a customized them
|
||||
return saveThemeForApplication(application.getEditModeThemeId(), resource, applicationId, ApplicationMode.EDIT);
|
||||
return saveThemeForApplication(application.getEditModeThemeId(), resource, application, ApplicationMode.EDIT);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Theme> changeCurrentTheme(String newThemeId, String applicationId) {
|
||||
// set provided theme to application and return that theme object
|
||||
Mono<Theme> setAppThemeMono = applicationRepository.setAppTheme(
|
||||
applicationId, newThemeId,null, MANAGE_APPLICATIONS
|
||||
).then(repository.findById(newThemeId));
|
||||
|
||||
// in case a customized theme was set to application, we need to delete that
|
||||
return applicationRepository.findById(applicationId, AclPermission.MANAGE_APPLICATIONS)
|
||||
.switchIfEmpty(Mono.error(
|
||||
new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION, applicationId))
|
||||
)
|
||||
.flatMap(application -> repository.findById(application.getEditModeThemeId())
|
||||
.flatMap(application -> repository.findById(application.getEditModeThemeId(), READ_THEMES)
|
||||
.defaultIfEmpty(new Theme())
|
||||
.flatMap(currentTheme -> {
|
||||
if (!StringUtils.isEmpty(currentTheme.getId()) && !currentTheme.isSystemTheme()) {
|
||||
// current theme is not a system theme but customized one, delete this
|
||||
return repository.delete(currentTheme).then(setAppThemeMono);
|
||||
.zipWith(repository.findById(newThemeId, READ_THEMES))
|
||||
.flatMap(themeTuple2 -> {
|
||||
Theme currentTheme = themeTuple2.getT1();
|
||||
Theme newTheme = themeTuple2.getT2();
|
||||
Mono<Theme> saveThemeMono;
|
||||
if(!newTheme.isSystemTheme()) {
|
||||
// we'll create a copy of newTheme
|
||||
newTheme.setId(null);
|
||||
newTheme.setApplicationId(null);
|
||||
newTheme.setOrganizationId(null);
|
||||
newTheme.setPolicies(policyGenerator.getAllChildPolicies(
|
||||
application.getPolicies(), Application.class, Theme.class
|
||||
));
|
||||
saveThemeMono = repository.save(newTheme);
|
||||
} else {
|
||||
saveThemeMono = Mono.just(newTheme);
|
||||
}
|
||||
return setAppThemeMono;
|
||||
}));
|
||||
|
||||
return saveThemeMono.flatMap(savedTheme -> {
|
||||
if (StringUtils.hasLength(currentTheme.getId()) && !currentTheme.isSystemTheme()
|
||||
&& !StringUtils.hasLength(currentTheme.getApplicationId())) {
|
||||
// current theme is neither a system theme nor app theme, delete the user customizations
|
||||
return repository.delete(currentTheme).then(applicationRepository.setAppTheme(
|
||||
applicationId, savedTheme.getId(),null, MANAGE_APPLICATIONS
|
||||
)).thenReturn(savedTheme);
|
||||
} else {
|
||||
return applicationRepository.setAppTheme(
|
||||
applicationId, savedTheme.getId(),null, MANAGE_APPLICATIONS
|
||||
).thenReturn(savedTheme);
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -122,79 +159,97 @@ public class ThemeServiceCEImpl extends BaseService<ThemeRepositoryCE, Theme, St
|
|||
}
|
||||
|
||||
@Override
|
||||
public Mono<Theme> cloneThemeToApplication(String srcThemeId, String destApplicationId) {
|
||||
return applicationRepository.findById(destApplicationId, MANAGE_APPLICATIONS).then(
|
||||
// make sure the current user has permission to manage application
|
||||
repository.findById(srcThemeId).flatMap(theme -> {
|
||||
if (theme.isSystemTheme()) { // it's a system theme, no need to copy
|
||||
return Mono.just(theme);
|
||||
} else { // it's a customized theme, create a copy and return the copy
|
||||
theme.setId(null); // setting id to null so that save method will create a new instance
|
||||
return repository.save(theme);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Theme> publishTheme(String editModeThemeId, String publishedThemeId, String applicationId) {
|
||||
Mono<Theme> editModeThemeMono;
|
||||
if(!StringUtils.hasLength(editModeThemeId)) { // theme id is empty, use the default theme
|
||||
editModeThemeMono = repository.getSystemThemeByName(Theme.LEGACY_THEME_NAME);
|
||||
} else { // theme id is not empty, fetch it by id
|
||||
editModeThemeMono = repository.findById(editModeThemeId);
|
||||
}
|
||||
|
||||
Mono<Theme> publishThemeMono = editModeThemeMono.flatMap(editModeTheme -> {
|
||||
if (editModeTheme.isSystemTheme()) { // system theme is set as edit mode theme
|
||||
// just set the system theme id as edit and published mode theme id to application object
|
||||
return applicationRepository.setAppTheme(
|
||||
applicationId, editModeTheme.getId(), editModeTheme.getId(), MANAGE_APPLICATIONS
|
||||
).thenReturn(editModeTheme);
|
||||
} else { // a customized theme is set as edit mode theme, copy that theme for published mode
|
||||
return saveThemeForApplication(publishedThemeId, editModeTheme, applicationId, ApplicationMode.PUBLISHED);
|
||||
public Mono<Theme> cloneThemeToApplication(String srcThemeId, Application destApplication) {
|
||||
return repository.findById(srcThemeId, READ_THEMES).flatMap(theme -> {
|
||||
if (theme.isSystemTheme()) { // it's a system theme, no need to copy
|
||||
return Mono.just(theme);
|
||||
} else { // it's a customized theme, create a copy and return the copy
|
||||
theme.setId(null); // setting id to null so that save method will create a new instance
|
||||
theme.setApplicationId(null);
|
||||
theme.setOrganizationId(null);
|
||||
theme.setPolicies(policyGenerator.getAllChildPolicies(
|
||||
destApplication.getPolicies(), Application.class, Theme.class
|
||||
));
|
||||
return repository.save(theme);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes a theme from edit mode to published mode
|
||||
* @param applicationId application id
|
||||
* @return Mono of theme object that was set in published mode
|
||||
*/
|
||||
@Override
|
||||
public Mono<Theme> publishTheme(String applicationId) {
|
||||
// fetch application to make sure user has permission to manage this application
|
||||
return applicationRepository.findById(applicationId, MANAGE_APPLICATIONS).then(publishThemeMono);
|
||||
return applicationRepository.findById(applicationId, MANAGE_APPLICATIONS).flatMap(application -> {
|
||||
Mono<Theme> editModeThemeMono;
|
||||
if(!StringUtils.hasLength(application.getEditModeThemeId())) { // theme id is empty, use the default theme
|
||||
editModeThemeMono = repository.getSystemThemeByName(Theme.LEGACY_THEME_NAME);
|
||||
} else { // theme id is not empty, fetch it by id
|
||||
editModeThemeMono = repository.findById(application.getEditModeThemeId(), READ_THEMES);
|
||||
}
|
||||
|
||||
return editModeThemeMono.flatMap(editModeTheme -> {
|
||||
if (editModeTheme.isSystemTheme()) { // system theme is set as edit mode theme
|
||||
// Delete published mode theme if it was a copy of custom theme
|
||||
return deletePublishedCustomizedThemeCopy(application.getPublishedModeThemeId()).then(
|
||||
// Set the system theme id as edit and published mode theme id to application object
|
||||
applicationRepository.setAppTheme(
|
||||
applicationId, editModeTheme.getId(), editModeTheme.getId(), MANAGE_APPLICATIONS
|
||||
)
|
||||
).thenReturn(editModeTheme);
|
||||
} else { // a customized theme is set as edit mode theme, copy that theme for published mode
|
||||
return saveThemeForApplication(
|
||||
application.getPublishedModeThemeId(), editModeTheme, application, ApplicationMode.PUBLISHED
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new theme if Theme with provided themeId is a system theme.
|
||||
* It sets the properties from the provided theme resource to the existing or newly created theme.
|
||||
* It'll also update the application if a new theme was created.
|
||||
* @param themeId ID of the existing theme that might be updated
|
||||
* @param resource new theme DTO that'll be stored as a new theme or override the existing theme
|
||||
* @param applicationId Application that contains the theme
|
||||
* @param currentThemeId ID of the existing theme that might be updated
|
||||
* @param targetThemeResource new theme DTO that'll be stored as a new theme or override the existing theme
|
||||
* @param application Application that contains the theme
|
||||
* @param applicationMode In which mode this theme will be set
|
||||
* @return Updated or newly created theme Publisher
|
||||
*/
|
||||
private Mono<Theme> saveThemeForApplication(String themeId, Theme resource, String applicationId, ApplicationMode applicationMode) {
|
||||
return repository.findById(themeId)
|
||||
.flatMap(theme -> {
|
||||
private Mono<Theme> saveThemeForApplication(String currentThemeId, Theme targetThemeResource, Application application, ApplicationMode applicationMode) {
|
||||
return repository.findById(currentThemeId, READ_THEMES)
|
||||
.flatMap(currentTheme -> {
|
||||
// set the edit mode values to published mode theme
|
||||
theme.setConfig(resource.getConfig());
|
||||
theme.setStylesheet(resource.getStylesheet());
|
||||
theme.setProperties(resource.getProperties());
|
||||
theme.setName(resource.getName());
|
||||
currentTheme.setConfig(targetThemeResource.getConfig());
|
||||
currentTheme.setStylesheet(targetThemeResource.getStylesheet());
|
||||
currentTheme.setProperties(targetThemeResource.getProperties());
|
||||
if(StringUtils.hasLength(targetThemeResource.getName())) {
|
||||
currentTheme.setName(targetThemeResource.getName());
|
||||
}
|
||||
boolean newThemeCreated = false;
|
||||
if (theme.isSystemTheme()) {
|
||||
if (currentTheme.isSystemTheme()) {
|
||||
// if this is a system theme, create a new one
|
||||
theme.setId(null); // setting id to null will create a new theme
|
||||
theme.setSystemTheme(false);
|
||||
currentTheme.setId(null); // setting id to null will create a new theme
|
||||
currentTheme.setSystemTheme(false);
|
||||
currentTheme.setPolicies(policyGenerator.getAllChildPolicies(
|
||||
application.getPolicies(), Application.class, Theme.class
|
||||
));
|
||||
newThemeCreated = true;
|
||||
}
|
||||
return repository.save(theme).zipWith(Mono.just(newThemeCreated));
|
||||
return repository.save(currentTheme).zipWith(Mono.just(newThemeCreated));
|
||||
}).flatMap(savedThemeTuple -> {
|
||||
Theme theme = savedThemeTuple.getT1();
|
||||
if (savedThemeTuple.getT2()) { // new published theme created, update the application
|
||||
if (savedThemeTuple.getT2()) { // new theme created, update the application
|
||||
if(applicationMode == ApplicationMode.EDIT) {
|
||||
return applicationRepository.setAppTheme(
|
||||
applicationId, theme.getId(), null, MANAGE_APPLICATIONS
|
||||
application.getId(), theme.getId(), null, MANAGE_APPLICATIONS
|
||||
).thenReturn(theme);
|
||||
} else {
|
||||
return applicationRepository.setAppTheme(
|
||||
applicationId, null, theme.getId(), MANAGE_APPLICATIONS
|
||||
application.getId(), null, theme.getId(), MANAGE_APPLICATIONS
|
||||
).thenReturn(theme);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -204,7 +259,117 @@ public class ThemeServiceCEImpl extends BaseService<ThemeRepositoryCE, Theme, St
|
|||
}
|
||||
|
||||
@Override
|
||||
public void resetDefaultThemeIdCache() {
|
||||
defaultThemeId = null;
|
||||
public Mono<Theme> persistCurrentTheme(String applicationId, Theme resource) {
|
||||
return applicationRepository.findById(applicationId, MANAGE_APPLICATIONS)
|
||||
.switchIfEmpty(Mono.error(
|
||||
new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION, applicationId))
|
||||
)
|
||||
.flatMap(application -> {
|
||||
String themeId = application.getEditModeThemeId();
|
||||
if(!StringUtils.hasLength(themeId)) { // theme id is not present, raise error
|
||||
return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION));
|
||||
} else {
|
||||
return repository.findById(themeId, READ_THEMES)
|
||||
.map(theme -> Tuples.of(theme, application));
|
||||
}
|
||||
})
|
||||
.flatMap(themeAndApplicationTuple -> {
|
||||
Theme theme = themeAndApplicationTuple.getT1();
|
||||
Application application = themeAndApplicationTuple.getT2();
|
||||
theme.setId(null); // we'll create a copy so setting id to null
|
||||
theme.setSystemTheme(false);
|
||||
theme.setApplicationId(applicationId);
|
||||
theme.setOrganizationId(application.getOrganizationId());
|
||||
theme.setPolicies(policyGenerator.getAllChildPolicies(
|
||||
application.getPolicies(), Application.class, Theme.class
|
||||
));
|
||||
|
||||
if(StringUtils.hasLength(resource.getName())) {
|
||||
theme.setName(resource.getName());
|
||||
} else {
|
||||
theme.setName(theme.getName() + " copy");
|
||||
}
|
||||
return repository.save(theme);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will fetch a theme by id and delete this if it's not a system theme.
|
||||
* When an app is published with a customized theme, we store a copy of that theme so that changes are available
|
||||
* in published mode even user has changed the theme in edit mode. When user switches back to another theme and
|
||||
* publish the application where that app was previously published with a custom theme, we should delete that copy.
|
||||
* Otherwise there'll be a lot of orphan theme copies that were set a published mode once but are used no more.
|
||||
* @param themeId id of the theme that'll be deleted
|
||||
* @return deleted theme mono
|
||||
*/
|
||||
private Mono<Theme> deletePublishedCustomizedThemeCopy(String themeId) {
|
||||
if(!StringUtils.hasLength(themeId)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return repository.findById(themeId).flatMap(theme -> {
|
||||
if(!theme.isSystemTheme()) {
|
||||
return repository.deleteById(themeId).thenReturn(theme);
|
||||
}
|
||||
return Mono.just(theme);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Theme> delete(String themeId) {
|
||||
return repository.findById(themeId, MANAGE_THEMES)
|
||||
.switchIfEmpty(Mono.error(
|
||||
new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION, FieldName.THEME))
|
||||
).flatMap(theme -> {
|
||||
if (StringUtils.hasLength(theme.getApplicationId())) { // only persisted themes are allowed to delete
|
||||
return repository.archive(theme);
|
||||
} else {
|
||||
return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Theme> getSystemTheme(String themeName) {
|
||||
return repository.getSystemThemeByName(themeName);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Theme> getThemeById(String themeId, AclPermission permission) {
|
||||
return repository.findById(themeId, permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Theme> save(Theme theme) {
|
||||
return repository.save(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Theme> updateName(String id, Theme themeDto) {
|
||||
return repository.findById(id, MANAGE_THEMES)
|
||||
.switchIfEmpty(Mono.error(
|
||||
new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.THEME, id))
|
||||
).flatMap(theme -> {
|
||||
if(StringUtils.hasLength(themeDto.getName())) {
|
||||
theme.setName(themeDto.getName());
|
||||
}
|
||||
return repository.save(theme);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Theme> getOrSaveTheme(Theme theme, Application destApplication) {
|
||||
if(theme == null) { // this application was exported without theme, assign the legacy theme to it
|
||||
return repository.getSystemThemeByName(Theme.LEGACY_THEME_NAME); // return the default theme
|
||||
} else if (theme.isSystemTheme()) {
|
||||
return repository.getSystemThemeByName(theme.getName());
|
||||
} else {
|
||||
theme.setApplicationId(null);
|
||||
theme.setOrganizationId(null);
|
||||
theme.setPolicies(policyGenerator.getAllChildPolicies(
|
||||
destApplication.getPolicies(), Application.class, Theme.class
|
||||
));
|
||||
return repository.save(theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.appsmith.server.domains.NewAction;
|
|||
import com.appsmith.server.domains.NewPage;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.Page;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.domains.UserRole;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
|
|
@ -172,6 +173,9 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
|
|||
Map<String, Policy> commentThreadPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(
|
||||
applicationPolicyMap, Application.class, CommentThread.class
|
||||
);
|
||||
Map<String, Policy> themePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(
|
||||
applicationPolicyMap, Application.class, Theme.class
|
||||
);
|
||||
//Now update the organization policies
|
||||
Organization updatedOrganization = policyUtils.addPoliciesToExistingObject(orgPolicyMap, organization);
|
||||
updatedOrganization.setUserRoles(userRoles);
|
||||
|
|
@ -179,7 +183,7 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
|
|||
// Update the underlying application/page/action
|
||||
Flux<Datasource> updatedDatasourcesFlux = policyUtils.updateWithNewPoliciesToDatasourcesByOrgId(updatedOrganization.getId(), datasourcePolicyMap, true);
|
||||
Flux<Application> updatedApplicationsFlux = policyUtils.updateWithNewPoliciesToApplicationsByOrgId(updatedOrganization.getId(), applicationPolicyMap, true)
|
||||
.cache();
|
||||
.cache(); // .cache is very important, as we will execute once and reuse the results multiple times
|
||||
Flux<NewPage> updatedPagesFlux = updatedApplicationsFlux
|
||||
.flatMap(application -> policyUtils.updateWithApplicationPermissionsToAllItsPages(application.getId(), pagePolicyMap, true));
|
||||
Flux<NewAction> updatedActionsFlux = updatedApplicationsFlux
|
||||
|
|
@ -188,14 +192,18 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
|
|||
.flatMap(application -> policyUtils.updateWithPagePermissionsToAllItsActionCollections(application.getId(), actionPolicyMap, true));
|
||||
Flux<CommentThread> updatedThreadsFlux = updatedApplicationsFlux
|
||||
.flatMap(application -> policyUtils.updateCommentThreadPermissions(application.getId(), commentThreadPolicyMap, user.getUsername(), true));
|
||||
|
||||
Flux<Theme> updatedThemesFlux = updatedApplicationsFlux
|
||||
.flatMap(application -> policyUtils.updateThemePolicies(
|
||||
application, themePolicyMap, true
|
||||
));
|
||||
return Mono.zip(
|
||||
updatedDatasourcesFlux.collectList(),
|
||||
updatedPagesFlux.collectList(),
|
||||
updatedActionsFlux.collectList(),
|
||||
updatedActionCollectionsFlux.collectList(),
|
||||
Mono.just(updatedOrganization),
|
||||
updatedThreadsFlux.collectList()
|
||||
updatedThreadsFlux.collectList(),
|
||||
updatedThemesFlux.collectList()
|
||||
)
|
||||
.flatMap(tuple -> {
|
||||
//By now all the datasources/applications/pages/actions have been updated. Just save the organization now
|
||||
|
|
@ -256,6 +264,9 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
|
|||
Map<String, Policy> commentThreadPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(
|
||||
applicationPolicyMap, Application.class, CommentThread.class
|
||||
);
|
||||
Map<String, Policy> themePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(
|
||||
applicationPolicyMap, Application.class, Theme.class
|
||||
);
|
||||
|
||||
//Now update the organization policies
|
||||
Organization updatedOrganization = policyUtils.removePoliciesFromExistingObject(orgPolicyMap, organization);
|
||||
|
|
@ -264,7 +275,7 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
|
|||
// Update the underlying application/page/action
|
||||
Flux<Datasource> updatedDatasourcesFlux = policyUtils.updateWithNewPoliciesToDatasourcesByOrgId(updatedOrganization.getId(), datasourcePolicyMap, false);
|
||||
Flux<Application> updatedApplicationsFlux = policyUtils.updateWithNewPoliciesToApplicationsByOrgId(updatedOrganization.getId(), applicationPolicyMap, false)
|
||||
.cache();
|
||||
.cache(); // .cache is very important, as we will execute once and reuse the results multiple times
|
||||
Flux<NewPage> updatedPagesFlux = updatedApplicationsFlux
|
||||
.flatMap(application -> policyUtils.updateWithApplicationPermissionsToAllItsPages(application.getId(), pagePolicyMap, false));
|
||||
Flux<NewAction> updatedActionsFlux = updatedApplicationsFlux
|
||||
|
|
@ -275,6 +286,10 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
|
|||
.flatMap(application -> policyUtils.updateCommentThreadPermissions(
|
||||
application.getId(), commentThreadPolicyMap, user.getUsername(), false
|
||||
));
|
||||
Flux<Theme> updatedThemesFlux = updatedApplicationsFlux
|
||||
.flatMap(application -> policyUtils.updateThemePolicies(
|
||||
application, themePolicyMap, false
|
||||
));
|
||||
|
||||
return Mono.zip(
|
||||
updatedDatasourcesFlux.collectList(),
|
||||
|
|
@ -282,7 +297,8 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
|
|||
updatedActionsFlux.collectList(),
|
||||
updatedActionCollectionsFlux.collectList(),
|
||||
updatedThreadsFlux.collectList(),
|
||||
Mono.just(updatedOrganization)
|
||||
Mono.just(updatedOrganization),
|
||||
updatedThemesFlux.collectList()
|
||||
).flatMap(tuple -> {
|
||||
//By now all the datasources/applications/pages/actions have been updated. Just save the organization now
|
||||
Organization updatedOrgBeforeSave = tuple.getT6();
|
||||
|
|
@ -435,6 +451,9 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
|
|||
applicationPolicyMap, Application.class, CommentThread.class
|
||||
);
|
||||
Map<String, Policy> actionPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(pagePolicyMap, Page.class, Action.class);
|
||||
Map<String, Policy> themePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(
|
||||
applicationPolicyMap, Application.class, Theme.class
|
||||
);
|
||||
|
||||
// Now update the organization policies
|
||||
Organization updatedOrganization = policyUtils.addPoliciesToExistingObject(orgPolicyMap, organization);
|
||||
|
|
@ -454,13 +473,19 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
|
|||
.flatMap(application -> policyUtils.updateCommentThreadPermissions(
|
||||
application.getId(), commentThreadPolicyMap, null, true
|
||||
));
|
||||
Flux<Theme> updatedThemesFlux = updatedApplicationsFlux
|
||||
.flatMap(application -> policyUtils.updateThemePolicies(
|
||||
application, themePolicyMap, true
|
||||
));
|
||||
|
||||
return Mono.when(
|
||||
updatedDatasourcesFlux.collectList(),
|
||||
updatedPagesFlux.collectList(),
|
||||
updatedActionsFlux.collectList(),
|
||||
updatedActionCollectionsFlux.collectList(),
|
||||
updatedThreadsFlux.collectList())
|
||||
updatedDatasourcesFlux.collectList(),
|
||||
updatedPagesFlux.collectList(),
|
||||
updatedActionsFlux.collectList(),
|
||||
updatedActionCollectionsFlux.collectList(),
|
||||
updatedThreadsFlux.collectList(),
|
||||
updatedThemesFlux.collectList()
|
||||
)
|
||||
// By now all the
|
||||
// data sources/applications/pages/actions/action collections/comment threads
|
||||
// have been updated. Just save the organization now
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.appsmith.server.services.LayoutCollectionService;
|
|||
import com.appsmith.server.services.NewActionService;
|
||||
import com.appsmith.server.services.OrganizationService;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.appsmith.server.services.ThemeService;
|
||||
import com.appsmith.server.services.UserService;
|
||||
import com.appsmith.server.solutions.ce.ExamplesOrganizationClonerCEImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -35,10 +36,11 @@ public class ExamplesOrganizationClonerImpl extends ExamplesOrganizationClonerCE
|
|||
NewActionService newActionService,
|
||||
LayoutActionService layoutActionService,
|
||||
ActionCollectionService actionCollectionService,
|
||||
LayoutCollectionService layoutCollectionService) {
|
||||
LayoutCollectionService layoutCollectionService,
|
||||
ThemeService themeService) {
|
||||
|
||||
super(organizationService, organizationRepository, datasourceService, datasourceRepository, configService,
|
||||
sessionUserService, userService, applicationService, applicationPageService, newPageRepository,
|
||||
newActionService, layoutActionService, actionCollectionService, layoutCollectionService);
|
||||
newActionService, layoutActionService, actionCollectionService, layoutCollectionService, themeService);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import com.appsmith.server.repositories.DatasourceRepository;
|
|||
import com.appsmith.server.repositories.NewActionRepository;
|
||||
import com.appsmith.server.repositories.NewPageRepository;
|
||||
import com.appsmith.server.repositories.PluginRepository;
|
||||
import com.appsmith.server.repositories.ThemeRepository;
|
||||
import com.appsmith.server.services.ActionCollectionService;
|
||||
import com.appsmith.server.services.ApplicationPageService;
|
||||
import com.appsmith.server.services.ApplicationService;
|
||||
|
|
@ -15,6 +14,7 @@ import com.appsmith.server.services.NewPageService;
|
|||
import com.appsmith.server.services.OrganizationService;
|
||||
import com.appsmith.server.services.SequenceService;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.appsmith.server.services.ThemeService;
|
||||
import com.appsmith.server.solutions.ce.ImportExportApplicationServiceCEImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
|
@ -38,11 +38,11 @@ public class ImportExportApplicationServiceImpl extends ImportExportApplicationS
|
|||
ExamplesOrganizationCloner examplesOrganizationCloner,
|
||||
ActionCollectionRepository actionCollectionRepository,
|
||||
ActionCollectionService actionCollectionService,
|
||||
ThemeRepository themeRepository) {
|
||||
ThemeService themeService) {
|
||||
|
||||
super(datasourceService, sessionUserService, newActionRepository, datasourceRepository, pluginRepository,
|
||||
organizationService, applicationService, newPageService, applicationPageService, newPageRepository,
|
||||
newActionService, sequenceService, examplesOrganizationCloner, actionCollectionRepository,
|
||||
actionCollectionService, themeRepository);
|
||||
actionCollectionService, themeService);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.appsmith.external.models.AuthenticationDTO;
|
|||
import com.appsmith.external.models.BaseDomain;
|
||||
import com.appsmith.external.models.Datasource;
|
||||
import com.appsmith.external.models.DefaultResources;
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.ActionCollection;
|
||||
import com.appsmith.server.domains.Application;
|
||||
|
|
@ -13,6 +14,7 @@ import com.appsmith.server.domains.ApplicationPage;
|
|||
import com.appsmith.server.domains.Layout;
|
||||
import com.appsmith.server.domains.NewPage;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.dtos.ActionCollectionDTO;
|
||||
import com.appsmith.server.dtos.ActionDTO;
|
||||
|
|
@ -33,7 +35,9 @@ import com.appsmith.server.services.LayoutCollectionService;
|
|||
import com.appsmith.server.services.NewActionService;
|
||||
import com.appsmith.server.services.OrganizationService;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.appsmith.server.services.ThemeService;
|
||||
import com.appsmith.server.services.UserService;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bson.types.ObjectId;
|
||||
|
|
@ -68,6 +72,7 @@ public class ExamplesOrganizationClonerCEImpl implements ExamplesOrganizationClo
|
|||
private final LayoutActionService layoutActionService;
|
||||
private final ActionCollectionService actionCollectionService;
|
||||
private final LayoutCollectionService layoutCollectionService;
|
||||
private final ThemeService themeService;
|
||||
|
||||
public Mono<Organization> cloneExamplesOrganization() {
|
||||
return sessionUserService
|
||||
|
|
@ -417,17 +422,35 @@ public class ExamplesOrganizationClonerCEImpl implements ExamplesOrganizationClo
|
|||
.flatMapMany(
|
||||
savedApplication -> {
|
||||
applicationIds.add(savedApplication.getId());
|
||||
return newPageRepository
|
||||
.findByApplicationId(templateApplicationId)
|
||||
.map(newPage -> {
|
||||
log.info("Preparing page for cloning {} {}.", newPage.getUnpublishedPage().getName(), newPage.getId());
|
||||
newPage.setApplicationId(savedApplication.getId());
|
||||
return newPage;
|
||||
});
|
||||
return forkThemes(application, savedApplication).thenMany(
|
||||
newPageRepository
|
||||
.findByApplicationId(templateApplicationId)
|
||||
.map(newPage -> {
|
||||
log.info("Preparing page for cloning {} {}.", newPage.getUnpublishedPage().getName(), newPage.getId());
|
||||
newPage.setApplicationId(savedApplication.getId());
|
||||
return newPage;
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private Mono<UpdateResult> forkThemes(Application srcApplication, Application destApplication) {
|
||||
return Mono.zip(
|
||||
themeService.cloneThemeToApplication(srcApplication.getEditModeThemeId(), destApplication),
|
||||
themeService.cloneThemeToApplication(srcApplication.getPublishedModeThemeId(), destApplication)
|
||||
).flatMap(themes -> {
|
||||
Theme editModeTheme = themes.getT1();
|
||||
Theme publishedModeTheme = themes.getT2();
|
||||
return applicationService.setAppTheme(
|
||||
destApplication.getId(),
|
||||
editModeTheme.getId(),
|
||||
publishedModeTheme.getId(),
|
||||
AclPermission.MANAGE_APPLICATIONS
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Application> cloneApplicationDocument(Application application) {
|
||||
if (!StringUtils.hasText(application.getName())) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.NAME));
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ import com.appsmith.server.repositories.DatasourceRepository;
|
|||
import com.appsmith.server.repositories.NewActionRepository;
|
||||
import com.appsmith.server.repositories.NewPageRepository;
|
||||
import com.appsmith.server.repositories.PluginRepository;
|
||||
import com.appsmith.server.repositories.ThemeRepository;
|
||||
import com.appsmith.server.services.ActionCollectionService;
|
||||
import com.appsmith.server.services.ApplicationPageService;
|
||||
import com.appsmith.server.services.ApplicationService;
|
||||
|
|
@ -47,6 +46,7 @@ import com.appsmith.server.services.NewPageService;
|
|||
import com.appsmith.server.services.OrganizationService;
|
||||
import com.appsmith.server.services.SequenceService;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.appsmith.server.services.ThemeService;
|
||||
import com.appsmith.server.solutions.ExamplesOrganizationCloner;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
|
@ -77,6 +77,7 @@ import static com.appsmith.server.acl.AclPermission.EXPORT_APPLICATIONS;
|
|||
import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_THEMES;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
|
|
@ -97,7 +98,7 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
private final ExamplesOrganizationCloner examplesOrganizationCloner;
|
||||
private final ActionCollectionRepository actionCollectionRepository;
|
||||
private final ActionCollectionService actionCollectionService;
|
||||
private final ThemeRepository themeRepository;
|
||||
private final ThemeService themeService;
|
||||
|
||||
private static final Set<MediaType> ALLOWED_CONTENT_TYPES = Set.of(MediaType.APPLICATION_JSON);
|
||||
private static final String INVALID_JSON_FILE = "invalid json file";
|
||||
|
|
@ -153,8 +154,8 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
return plugin;
|
||||
})
|
||||
.then(applicationMono)
|
||||
.flatMap(application -> themeRepository.findById(application.getEditModeThemeId())
|
||||
.zipWith(themeRepository.findById(application.getPublishedModeThemeId()))
|
||||
.flatMap(application -> themeService.getThemeById(application.getEditModeThemeId(), READ_THEMES)
|
||||
.zipWith(themeService.getThemeById(application.getPublishedModeThemeId(), READ_THEMES))
|
||||
.map(themesTuple -> {
|
||||
Theme editModeTheme = exportTheme(themesTuple.getT1());
|
||||
Theme publishedModeTheme = exportTheme(themesTuple.getT2());
|
||||
|
|
@ -581,7 +582,6 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
pluginMap.put(pluginReference, plugin.getId());
|
||||
return plugin;
|
||||
})
|
||||
.then(importThemes(importedApplication, importedDoc))
|
||||
.then(organizationService.findById(organizationId, AclPermission.ORGANIZATION_MANAGE_APPLICATIONS))
|
||||
.switchIfEmpty(Mono.error(
|
||||
new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.ORGANIZATION, organizationId))
|
||||
|
|
@ -707,6 +707,7 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
.then(applicationService.save(importedApplication));
|
||||
})
|
||||
)
|
||||
.flatMap(savedAPP -> importThemes(savedAPP, importedDoc))
|
||||
.flatMap(savedApp -> {
|
||||
importedApplication.setId(savedApp.getId());
|
||||
if (savedApp.getGitApplicationMetadata() != null) {
|
||||
|
|
@ -1524,26 +1525,23 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
}
|
||||
|
||||
private Mono<Application> importThemes(Application application, ApplicationJson importedApplicationJson) {
|
||||
Mono<Theme> importedEditModeTheme = getOrSaveTheme(importedApplicationJson.getEditModeTheme());
|
||||
Mono<Theme> importedPublishedModeTheme = getOrSaveTheme(importedApplicationJson.getPublishedTheme());
|
||||
Mono<Theme> importedEditModeTheme = themeService.getOrSaveTheme(importedApplicationJson.getEditModeTheme(), application);
|
||||
Mono<Theme> importedPublishedModeTheme = themeService.getOrSaveTheme(importedApplicationJson.getPublishedTheme(), application);
|
||||
|
||||
return Mono.zip(importedEditModeTheme, importedPublishedModeTheme).map(importedThemesTuple -> {
|
||||
application.setEditModeThemeId(importedThemesTuple.getT1().getId());
|
||||
application.setPublishedModeThemeId(importedThemesTuple.getT2().getId());
|
||||
return application;
|
||||
return Mono.zip(importedEditModeTheme, importedPublishedModeTheme).flatMap(importedThemesTuple -> {
|
||||
String editModeThemeId = importedThemesTuple.getT1().getId();
|
||||
String publishedModeThemeId = importedThemesTuple.getT2().getId();
|
||||
|
||||
application.setEditModeThemeId(editModeThemeId);
|
||||
application.setPublishedModeThemeId(publishedModeThemeId);
|
||||
// this will update the theme id in DB
|
||||
// also returning the updated application object so that theme id are available to the next pipeline
|
||||
return applicationService.setAppTheme(
|
||||
application.getId(), editModeThemeId, publishedModeThemeId, MANAGE_APPLICATIONS
|
||||
).thenReturn(application);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Theme> getOrSaveTheme(Theme theme) {
|
||||
if(theme == null) { // this application was exported without theme, assign the legacy theme to it
|
||||
return themeRepository.getSystemThemeByName(Theme.LEGACY_THEME_NAME); // return the default theme
|
||||
} else if (theme.isSystemTheme()) {
|
||||
return themeRepository.getSystemThemeByName(theme.getName());
|
||||
} else {
|
||||
return themeRepository.save(theme);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeUnwantedFieldsFromApplicationDuringExport(Application application) {
|
||||
application.setOrganizationId(null);
|
||||
application.setPages(null);
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@
|
|||
"appBorderRadius": {
|
||||
"none": "0px",
|
||||
"md": "0.375rem",
|
||||
"lg": "1.5rem",
|
||||
"full": "9999px"
|
||||
"lg": "1.5rem"
|
||||
}
|
||||
},
|
||||
"boxShadow": {
|
||||
|
|
@ -41,135 +40,216 @@
|
|||
"AUDIO_RECORDER_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"BUTTON_WIDGET": {
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"BUTTON_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CAMERA_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CHART_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"CHECKBOX_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CHECKBOX_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CONTAINER_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"CIRCULAR_PROGRESS_WIDGET": {
|
||||
"fillColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
},
|
||||
"CURRENCY_INPUT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"PHONE_INPUT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"DATE_PICKER_WIDGET2": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"FILE_PICKER_WIDGET_V2": {
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"FORM_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"FORM_BUTTON_WIDGET": {
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"ICON_BUTTON_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"IFRAME_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"IMAGE_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"INPUT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"INPUT_WIDGET_V2": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"LIST_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"MAP_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"MAP_CHART_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"MENU_BUTTON_WIDGET": {
|
||||
"menuColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"MODAL_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"MULTI_SELECT_TREE_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"MULTI_SELECT_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"DROP_DOWN_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"PROGRESSBAR_WIDGET": {
|
||||
"fillColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
},
|
||||
"RATE_WIDGET": {
|
||||
"activeColor": "{{appsmith.theme.colors.primaryColor}}"
|
||||
},
|
||||
"RADIO_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"RICH_TEXT_EDITOR_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"STATBOX_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"SWITCH_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"SWITCH_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
|
||||
},
|
||||
"SELECT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"TABLE_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"TABS_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"TEXT_WIDGET": {
|
||||
},
|
||||
"VIDEO_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"SINGLE_SELECT_TREE_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
|
@ -199,8 +279,7 @@
|
|||
"appBorderRadius": {
|
||||
"none": "0px",
|
||||
"md": "0.375rem",
|
||||
"lg": "1.5rem",
|
||||
"full": "9999px"
|
||||
"lg": "1.5rem"
|
||||
}
|
||||
},
|
||||
"boxShadow": {
|
||||
|
|
@ -230,135 +309,216 @@
|
|||
"AUDIO_RECORDER_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"BUTTON_WIDGET": {
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"BUTTON_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CAMERA_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CHART_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"CHECKBOX_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CHECKBOX_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CONTAINER_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"CIRCULAR_PROGRESS_WIDGET": {
|
||||
"fillColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
},
|
||||
"CURRENCY_INPUT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"PHONE_INPUT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"DATE_PICKER_WIDGET2": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"FILE_PICKER_WIDGET_V2": {
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"FORM_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"FORM_BUTTON_WIDGET": {
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"ICON_BUTTON_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"IFRAME_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"IMAGE_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"INPUT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"INPUT_WIDGET_V2": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"LIST_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"MAP_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"MAP_CHART_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"MENU_BUTTON_WIDGET": {
|
||||
"menuColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"MODAL_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"MULTI_SELECT_TREE_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"MULTI_SELECT_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"DROP_DOWN_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"PROGRESSBAR_WIDGET": {
|
||||
"fillColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
},
|
||||
"RATE_WIDGET": {
|
||||
"activeColor": "{{appsmith.theme.colors.primaryColor}}"
|
||||
},
|
||||
"RADIO_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"RICH_TEXT_EDITOR_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"STATBOX_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"SWITCH_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"SWITCH_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
|
||||
},
|
||||
"SELECT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"TABLE_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"TABS_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"TEXT_WIDGET": {
|
||||
},
|
||||
"VIDEO_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"SINGLE_SELECT_TREE_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
|
@ -388,8 +548,7 @@
|
|||
"appBorderRadius": {
|
||||
"none": "0px",
|
||||
"md": "0.375rem",
|
||||
"lg": "1.5rem",
|
||||
"full": "9999px"
|
||||
"lg": "1.5rem"
|
||||
}
|
||||
},
|
||||
"boxShadow": {
|
||||
|
|
@ -419,135 +578,216 @@
|
|||
"AUDIO_RECORDER_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"BUTTON_WIDGET": {
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"BUTTON_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CAMERA_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CHART_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"CHECKBOX_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CHECKBOX_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CONTAINER_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"CIRCULAR_PROGRESS_WIDGET": {
|
||||
"fillColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
},
|
||||
"CURRENCY_INPUT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"PHONE_INPUT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"DATE_PICKER_WIDGET2": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"FILE_PICKER_WIDGET_V2": {
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"FORM_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"FORM_BUTTON_WIDGET": {
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"ICON_BUTTON_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"IFRAME_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"IMAGE_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"INPUT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"INPUT_WIDGET_V2": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"LIST_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"MAP_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"MAP_CHART_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"MENU_BUTTON_WIDGET": {
|
||||
"menuColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"MODAL_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"MULTI_SELECT_TREE_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"MULTI_SELECT_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"DROP_DOWN_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"PROGRESSBAR_WIDGET": {
|
||||
"fillColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
},
|
||||
"RATE_WIDGET": {
|
||||
"activeColor": "{{appsmith.theme.colors.primaryColor}}"
|
||||
},
|
||||
"RADIO_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"RICH_TEXT_EDITOR_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"STATBOX_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"SWITCH_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"SWITCH_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
|
||||
},
|
||||
"SELECT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"TABLE_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"TABS_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"TEXT_WIDGET": {
|
||||
},
|
||||
"VIDEO_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"SINGLE_SELECT_TREE_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
|
@ -577,8 +817,7 @@
|
|||
"appBorderRadius": {
|
||||
"none": "0px",
|
||||
"md": "0.375rem",
|
||||
"lg": "1.5rem",
|
||||
"full": "9999px"
|
||||
"lg": "1.5rem"
|
||||
}
|
||||
},
|
||||
"boxShadow": {
|
||||
|
|
@ -608,135 +847,216 @@
|
|||
"AUDIO_RECORDER_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"BUTTON_WIDGET": {
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"BUTTON_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CAMERA_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CHART_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"CHECKBOX_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CHECKBOX_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"CONTAINER_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"CIRCULAR_PROGRESS_WIDGET": {
|
||||
"fillColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
},
|
||||
"CURRENCY_INPUT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"PHONE_INPUT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"DATE_PICKER_WIDGET2": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"FILE_PICKER_WIDGET_V2": {
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"FORM_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"FORM_BUTTON_WIDGET": {
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"ICON_BUTTON_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"IFRAME_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"IMAGE_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"INPUT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"INPUT_WIDGET_V2": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"LIST_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"MAP_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"MAP_CHART_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"MENU_BUTTON_WIDGET": {
|
||||
"menuColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"MODAL_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"MULTI_SELECT_TREE_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"MULTI_SELECT_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"DROP_DOWN_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"PROGRESSBAR_WIDGET": {
|
||||
"fillColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
},
|
||||
"RATE_WIDGET": {
|
||||
"activeColor": "{{appsmith.theme.colors.primaryColor}}"
|
||||
},
|
||||
"RADIO_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"RICH_TEXT_EDITOR_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"STATBOX_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"SWITCH_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"SWITCH_GROUP_WIDGET": {
|
||||
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
|
||||
},
|
||||
"SELECT_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
},
|
||||
"TABLE_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"TABS_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"TEXT_WIDGET": {
|
||||
},
|
||||
"VIDEO_WIDGET": {
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
|
||||
},
|
||||
"SINGLE_SELECT_TREE_WIDGET": {
|
||||
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
|
||||
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
||||
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
|
||||
"boxShadow": "none"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
|
@ -745,7 +1065,7 @@
|
|||
"backgroundColor": "#F6F6F6"
|
||||
},
|
||||
"borderRadius": {
|
||||
"appBorderRadius": "1rem"
|
||||
"appBorderRadius": "1.5rem"
|
||||
},
|
||||
"boxShadow": {
|
||||
"appBoxShadow": "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)"
|
||||
|
|
@ -755,4 +1075,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
@ -46,7 +46,7 @@ public class PolicyUtilsTest {
|
|||
CommentThread commentThread = new CommentThread();
|
||||
commentThread.setApplicationId(testApplicationId);
|
||||
Map<String, Policy> commentThreadPolicies = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(AclPermission.MANAGE_THREAD, AclPermission.COMMENT_ON_THREAD), "api_user"
|
||||
Set.of(AclPermission.MANAGE_THREADS, AclPermission.COMMENT_ON_THREADS), "api_user"
|
||||
);
|
||||
commentThread.setPolicies(Set.copyOf(commentThreadPolicies.values()));
|
||||
Mono<CommentThread> saveThreadMono = commentThreadRepository.save(commentThread);
|
||||
|
|
@ -54,7 +54,7 @@ public class PolicyUtilsTest {
|
|||
// add a new user and update the policies of the new user
|
||||
String newUserName = "new_test_user";
|
||||
Map<String, Policy> commentThreadPoliciesForNewUser = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(AclPermission.COMMENT_ON_THREAD), newUserName
|
||||
Set.of(AclPermission.COMMENT_ON_THREADS), newUserName
|
||||
);
|
||||
Flux<CommentThread> updateCommentThreads = policyUtils.updateCommentThreadPermissions(
|
||||
testApplicationId, commentThreadPoliciesForNewUser, newUserName, true
|
||||
|
|
@ -64,7 +64,7 @@ public class PolicyUtilsTest {
|
|||
Mono<List<CommentThread>> applicationCommentList = saveThreadMono
|
||||
.thenMany(updateCommentThreads)
|
||||
.collectList()
|
||||
.thenMany(commentThreadRepository.findByApplicationId(testApplicationId, AclPermission.READ_THREAD))
|
||||
.thenMany(commentThreadRepository.findByApplicationId(testApplicationId, AclPermission.READ_THREADS))
|
||||
.collectList();
|
||||
|
||||
StepVerifier.create(applicationCommentList)
|
||||
|
|
@ -72,12 +72,12 @@ public class PolicyUtilsTest {
|
|||
assertThat(commentThreads.size()).isEqualTo(1);
|
||||
CommentThread commentThread1 = commentThreads.get(0);
|
||||
Set<Policy> policies = commentThread1.getPolicies();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREAD.getValue(), "api_user")).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREAD.getValue(), newUserName)).isFalse();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREAD.getValue(), "api_user")).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREAD.getValue(), newUserName)).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREAD.getValue(), "api_user")).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREAD.getValue(), newUserName)).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREADS.getValue(), "api_user")).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREADS.getValue(), newUserName)).isFalse();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREADS.getValue(), "api_user")).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREADS.getValue(), newUserName)).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREADS.getValue(), "api_user")).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREADS.getValue(), newUserName)).isTrue();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
@ -100,7 +100,7 @@ public class PolicyUtilsTest {
|
|||
user2.setEmail(newUserName);
|
||||
|
||||
Map<String, Policy> commentThreadPolicies = policyUtils.generatePolicyFromPermissionForMultipleUsers(
|
||||
Set.of(AclPermission.MANAGE_THREAD, AclPermission.COMMENT_ON_THREAD), List.of(user1, user2)
|
||||
Set.of(AclPermission.MANAGE_THREADS, AclPermission.COMMENT_ON_THREADS), List.of(user1, user2)
|
||||
);
|
||||
|
||||
commentThread.setPolicies(Set.copyOf(commentThreadPolicies.values()));
|
||||
|
|
@ -108,7 +108,7 @@ public class PolicyUtilsTest {
|
|||
|
||||
// remove an user and update the policies of the user
|
||||
Map<String, Policy> commentThreadPoliciesForNewUser = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(AclPermission.MANAGE_THREAD, AclPermission.COMMENT_ON_THREAD), newUserName
|
||||
Set.of(AclPermission.MANAGE_THREADS, AclPermission.COMMENT_ON_THREADS), newUserName
|
||||
);
|
||||
Flux<CommentThread> updateCommentThreads = policyUtils.updateCommentThreadPermissions(
|
||||
testApplicationId, commentThreadPoliciesForNewUser, newUserName, false
|
||||
|
|
@ -118,7 +118,7 @@ public class PolicyUtilsTest {
|
|||
Mono<List<CommentThread>> applicationCommentList = saveThreadMono
|
||||
.thenMany(updateCommentThreads)
|
||||
.collectList()
|
||||
.thenMany(commentThreadRepository.findByApplicationId(testApplicationId, AclPermission.READ_THREAD))
|
||||
.thenMany(commentThreadRepository.findByApplicationId(testApplicationId, AclPermission.READ_THREADS))
|
||||
.collectList();
|
||||
|
||||
StepVerifier.create(applicationCommentList)
|
||||
|
|
@ -126,12 +126,12 @@ public class PolicyUtilsTest {
|
|||
assertThat(commentThreads.size()).isEqualTo(1);
|
||||
CommentThread commentThread1 = commentThreads.get(0);
|
||||
Set<Policy> policies = commentThread1.getPolicies();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREAD.getValue(), "api_user")).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREAD.getValue(), newUserName)).isFalse();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREAD.getValue(), "api_user")).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREAD.getValue(), newUserName)).isFalse();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREAD.getValue(), "api_user")).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREAD.getValue(), newUserName)).isFalse();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREADS.getValue(), "api_user")).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREADS.getValue(), newUserName)).isFalse();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREADS.getValue(), "api_user")).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREADS.getValue(), newUserName)).isFalse();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREADS.getValue(), "api_user")).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREADS.getValue(), newUserName)).isFalse();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public class CustomCommentThreadRepositoryImplTest {
|
|||
User user = new User();
|
||||
user.setEmail(userEmail);
|
||||
|
||||
Map<String, Policy> policyMap = policyUtils.generatePolicyFromPermission(Set.of(AclPermission.READ_THREAD), user);
|
||||
Map<String, Policy> policyMap = policyUtils.generatePolicyFromPermission(Set.of(AclPermission.READ_THREADS), user);
|
||||
thread.setPolicies(Set.copyOf(policyMap.values()));
|
||||
return thread;
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ public class CustomCommentThreadRepositoryImplTest {
|
|||
user.setEmail(userEmail);
|
||||
|
||||
Map<String, Policy> policyMap = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(AclPermission.MANAGE_THREAD, AclPermission.COMMENT_ON_THREAD), user);
|
||||
Set.of(AclPermission.MANAGE_THREADS, AclPermission.COMMENT_ON_THREADS), user);
|
||||
HashSet<Policy> policySet = new HashSet<>();
|
||||
|
||||
// not using Set.of here because the caller function may need to add more policies
|
||||
|
|
@ -167,7 +167,7 @@ public class CustomCommentThreadRepositoryImplTest {
|
|||
CommentThreadFilterDTO filterDTO = new CommentThreadFilterDTO();
|
||||
filterDTO.setApplicationId("sample-application-id-1");
|
||||
filterDTO.setResolved(false);
|
||||
return commentThreadRepository.find(filterDTO, AclPermission.READ_THREAD).collectList();
|
||||
return commentThreadRepository.find(filterDTO, AclPermission.READ_THREADS).collectList();
|
||||
});
|
||||
|
||||
StepVerifier.create(listMono).assertNext(
|
||||
|
|
@ -255,7 +255,7 @@ public class CustomCommentThreadRepositoryImplTest {
|
|||
Mono<Map<String, Collection<CommentThread>>> pageIdThreadMono = commentThreadRepository.saveAll(threads)
|
||||
.collectList()
|
||||
.then(commentThreadRepository.archiveByPageId(pageOneId, ApplicationMode.EDIT))
|
||||
.thenMany(commentThreadRepository.findByApplicationId(applicationId, AclPermission.READ_THREAD))
|
||||
.thenMany(commentThreadRepository.findByApplicationId(applicationId, AclPermission.READ_THREADS))
|
||||
.collectMultimap(CommentThread::getPageId);
|
||||
|
||||
StepVerifier.create(pageIdThreadMono)
|
||||
|
|
@ -282,7 +282,7 @@ public class CustomCommentThreadRepositoryImplTest {
|
|||
|
||||
// add api_user to thread policy with read thread permission
|
||||
for(Policy policy: thread.getPolicies()) {
|
||||
if(policy.getPermission().equals(AclPermission.READ_THREAD.getValue())) {
|
||||
if(policy.getPermission().equals(AclPermission.READ_THREADS.getValue())) {
|
||||
Set<String> users = new HashSet<>();
|
||||
users.addAll(policy.getUsers());
|
||||
users.add("api_user");
|
||||
|
|
@ -292,7 +292,7 @@ public class CustomCommentThreadRepositoryImplTest {
|
|||
|
||||
Mono<Map<String, Collection<CommentThread>>> pageIdThreadMono = commentThreadRepository.save(thread)
|
||||
.then(commentThreadRepository.archiveByPageId(testPageId, ApplicationMode.EDIT)) // this will do nothing
|
||||
.thenMany(commentThreadRepository.findByApplicationId(applicationId, AclPermission.READ_THREAD))
|
||||
.thenMany(commentThreadRepository.findByApplicationId(applicationId, AclPermission.READ_THREADS))
|
||||
.collectMultimap(CommentThread::getPageId);
|
||||
|
||||
StepVerifier.create(pageIdThreadMono)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.appsmith.server.repositories;
|
||||
|
||||
import com.appsmith.external.models.Policy;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.helpers.PolicyUtils;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
|
@ -11,7 +13,10 @@ import reactor.core.publisher.Mono;
|
|||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.appsmith.server.acl.AclPermission.READ_THEMES;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest
|
||||
|
|
@ -21,10 +26,20 @@ public class CustomThemeRepositoryTest {
|
|||
@Autowired
|
||||
ThemeRepository themeRepository;
|
||||
|
||||
@Autowired
|
||||
PolicyUtils policyUtils;
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void getSystemThemes_WhenThemesExists_ReturnsSystemThemes() {
|
||||
Mono<List<Theme>> systemThemesMono = themeRepository.save(new Theme())
|
||||
String testAppId = "second-app-id";
|
||||
Theme firstAppTheme = new Theme();
|
||||
firstAppTheme.setApplicationId("first-app-id");
|
||||
|
||||
Theme secondAppTheme = new Theme();
|
||||
secondAppTheme.setApplicationId(testAppId);
|
||||
|
||||
Mono<List<Theme>> systemThemesMono = themeRepository.saveAll(List.of(firstAppTheme, secondAppTheme))
|
||||
.then(themeRepository.getSystemThemes().collectList());
|
||||
|
||||
StepVerifier.create(systemThemesMono).assertNext(themes -> {
|
||||
|
|
@ -32,6 +47,28 @@ public class CustomThemeRepositoryTest {
|
|||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void getApplicationThemes_WhenThemesExists_ReturnsAppThemes() {
|
||||
Map<String, Policy> themePolicies = policyUtils.generatePolicyFromPermission(Set.of(READ_THEMES), "api_user");
|
||||
|
||||
String testAppId = "second-app-id";
|
||||
Theme firstAppTheme = new Theme();
|
||||
firstAppTheme.setApplicationId("first-app-id");
|
||||
firstAppTheme.setPolicies(Set.of(themePolicies.get(READ_THEMES.getValue())));
|
||||
|
||||
Theme secondAppTheme = new Theme();
|
||||
secondAppTheme.setApplicationId(testAppId);
|
||||
secondAppTheme.setPolicies(Set.of(themePolicies.get(READ_THEMES.getValue())));
|
||||
|
||||
Mono<List<Theme>> systemThemesMono = themeRepository.saveAll(List.of(firstAppTheme, secondAppTheme))
|
||||
.then(themeRepository.getApplicationThemes(testAppId, READ_THEMES).collectList());
|
||||
|
||||
StepVerifier.create(systemThemesMono).assertNext(themes -> {
|
||||
assertThat(themes.size()).isEqualTo(5); // 4 system themes were created from db migration
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void getSystemThemeByName_WhenNameMatches_ReturnsTheme() {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.appsmith.server.domains.NewPage;
|
|||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.Plugin;
|
||||
import com.appsmith.server.domains.PluginType;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.dtos.ActionCollectionDTO;
|
||||
import com.appsmith.server.dtos.ActionDTO;
|
||||
|
|
@ -82,6 +83,7 @@ import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS;
|
|||
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_THEMES;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_ACTIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_DATASOURCES;
|
||||
|
|
@ -2252,4 +2254,42 @@ public class ApplicationServiceTest {
|
|||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void cloneApplication_WithCustomSavedTheme_ThemesAlsoCopied() {
|
||||
Application testApplication = new Application();
|
||||
String appName = "cloneApplication_WithCustomSavedTheme_ThemesAlsoCopied";
|
||||
testApplication.setName(appName);
|
||||
|
||||
Theme theme = new Theme();
|
||||
theme.setName("Custom theme");
|
||||
|
||||
Mono<Theme> createTheme = themeService.create(theme);
|
||||
|
||||
Mono<Tuple2<Theme, Tuple2<Application, Application>>> tuple2Application = createTheme
|
||||
.then(applicationPageService.createApplication(testApplication, orgId))
|
||||
.flatMap(application ->
|
||||
themeService.updateTheme(application.getId(), theme).then(
|
||||
themeService.persistCurrentTheme(application.getId(), new Theme())
|
||||
.flatMap(theme1 -> Mono.zip(
|
||||
applicationPageService.cloneApplication(application.getId(), null),
|
||||
Mono.just(application))
|
||||
)
|
||||
)
|
||||
).flatMap(objects ->
|
||||
themeService.getThemeById(objects.getT1().getEditModeThemeId(), MANAGE_THEMES)
|
||||
.zipWith(Mono.just(objects))
|
||||
);
|
||||
|
||||
StepVerifier.create(tuple2Application)
|
||||
.assertNext(objects -> {
|
||||
Theme clonedTheme = objects.getT1();
|
||||
Application clonedApp = objects.getT2().getT1();
|
||||
Application srcApp = objects.getT2().getT2();
|
||||
assertThat(clonedApp.getEditModeThemeId()).isNotEqualTo(srcApp.getEditModeThemeId());
|
||||
assertThat(clonedTheme.getApplicationId()).isNull();
|
||||
assertThat(clonedTheme.getOrganizationId()).isNull();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,15 +141,15 @@ public class CommentServiceTest {
|
|||
assertThat(thread.getId()).isNotEmpty();
|
||||
//assertThat(thread.getResolved()).isNull();
|
||||
assertThat(thread.getPolicies()).containsExactlyInAnyOrder(
|
||||
Policy.builder().permission(AclPermission.READ_THREAD.getValue()).users(Set.of("api_user")).groups(Collections.emptySet()).build(),
|
||||
Policy.builder().permission(AclPermission.MANAGE_THREAD.getValue()).users(Set.of("api_user")).groups(Collections.emptySet()).build(),
|
||||
Policy.builder().permission(AclPermission.COMMENT_ON_THREAD.getValue()).users(Set.of("api_user")).groups(Collections.emptySet()).build()
|
||||
Policy.builder().permission(AclPermission.READ_THREADS.getValue()).users(Set.of("api_user")).groups(Collections.emptySet()).build(),
|
||||
Policy.builder().permission(AclPermission.MANAGE_THREADS.getValue()).users(Set.of("api_user")).groups(Collections.emptySet()).build(),
|
||||
Policy.builder().permission(AclPermission.COMMENT_ON_THREADS.getValue()).users(Set.of("api_user")).groups(Collections.emptySet()).build()
|
||||
);
|
||||
assertThat(thread.getComments()).hasSize(2); // one comment is from bot
|
||||
assertThat(thread.getComments().get(0).getBody()).isEqualTo(makePlainTextComment("comment one").getBody());
|
||||
assertThat(thread.getComments().get(0).getPolicies()).containsExactlyInAnyOrder(
|
||||
Policy.builder().permission(AclPermission.MANAGE_COMMENT.getValue()).users(Set.of("api_user")).groups(Collections.emptySet()).build(),
|
||||
Policy.builder().permission(AclPermission.READ_COMMENT.getValue()).users(Set.of("api_user")).groups(Collections.emptySet()).build()
|
||||
Policy.builder().permission(AclPermission.MANAGE_COMMENTS.getValue()).users(Set.of("api_user")).groups(Collections.emptySet()).build(),
|
||||
Policy.builder().permission(AclPermission.READ_COMMENTS.getValue()).users(Set.of("api_user")).groups(Collections.emptySet()).build()
|
||||
);
|
||||
|
||||
assertThat(threadsInApp).hasSize(1);
|
||||
|
|
@ -325,7 +325,7 @@ public class CommentServiceTest {
|
|||
User user = new User();
|
||||
user.setEmail("api_user");
|
||||
Map<String, Policy> stringPolicyMap = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(AclPermission.READ_THREAD),
|
||||
Set.of(AclPermission.READ_THREADS),
|
||||
user
|
||||
);
|
||||
Set<Policy> policies = Set.copyOf(stringPolicyMap.values());
|
||||
|
|
@ -369,7 +369,7 @@ public class CommentServiceTest {
|
|||
public void create_WhenThreadIsResolvedAndAlreadyViewed_ThreadIsUnresolvedAndUnread() {
|
||||
// create a thread first with resolved=true
|
||||
Collection<Policy> threadPolicies = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(AclPermission.COMMENT_ON_THREAD),
|
||||
Set.of(AclPermission.COMMENT_ON_THREADS),
|
||||
"api_user"
|
||||
).values();
|
||||
|
||||
|
|
@ -587,7 +587,7 @@ public class CommentServiceTest {
|
|||
|
||||
// create a thread first with resolved=true
|
||||
Collection<Policy> threadPolicies = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(AclPermission.COMMENT_ON_THREAD),
|
||||
Set.of(AclPermission.COMMENT_ON_THREADS),
|
||||
"api_user"
|
||||
).values();
|
||||
CommentThread commentThread = new CommentThread();
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ package com.appsmith.server.services;
|
|||
|
||||
import com.appsmith.external.models.Policy;
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationMode;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.dtos.ApplicationAccessDTO;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.PolicyUtils;
|
||||
import com.appsmith.server.repositories.ApplicationRepository;
|
||||
import com.appsmith.server.repositories.ThemeRepository;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
|
@ -19,12 +21,17 @@ import reactor.core.publisher.Mono;
|
|||
import reactor.test.StepVerifier;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuple3;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.appsmith.server.acl.AclPermission.MAKE_PUBLIC_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_THEMES;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_THEMES;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest
|
||||
|
|
@ -38,7 +45,7 @@ public class ThemeServiceTest {
|
|||
ApplicationRepository applicationRepository;
|
||||
|
||||
@Autowired
|
||||
ThemeRepository themeRepository;
|
||||
ApplicationService applicationService;
|
||||
|
||||
@Autowired
|
||||
private ThemeService themeService;
|
||||
|
|
@ -55,20 +62,20 @@ public class ThemeServiceTest {
|
|||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void getApplicationTheme_WhenThemeIsSet_ThemesReturned() {
|
||||
Mono<Tuple2<Theme, Theme>> applicationThemesMono = themeRepository.getSystemThemeByName("Classic")
|
||||
.zipWith(themeRepository.getSystemThemeByName("Sharp"))
|
||||
Mono<Tuple2<Theme, Theme>> applicationThemesMono = themeService.getSystemTheme("Classic")
|
||||
.zipWith(themeService.getSystemTheme("Sharp"))
|
||||
.flatMap(themesTuple -> {
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setEditModeThemeId(themesTuple.getT1().getId());
|
||||
application.setPublishedModeThemeId(themesTuple.getT2().getId());
|
||||
return applicationRepository.save(application);
|
||||
})
|
||||
.flatMap(application -> {
|
||||
return Mono.zip(
|
||||
themeService.getApplicationTheme(application.getId(), ApplicationMode.EDIT),
|
||||
themeService.getApplicationTheme(application.getId(), ApplicationMode.PUBLISHED)
|
||||
);
|
||||
});
|
||||
.flatMap(application ->
|
||||
Mono.zip(
|
||||
themeService.getApplicationTheme(application.getId(), ApplicationMode.EDIT),
|
||||
themeService.getApplicationTheme(application.getId(), ApplicationMode.PUBLISHED)
|
||||
)
|
||||
);
|
||||
|
||||
StepVerifier.create(applicationThemesMono)
|
||||
.assertNext(themesTuple -> {
|
||||
|
|
@ -82,20 +89,18 @@ public class ThemeServiceTest {
|
|||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void getApplicationTheme_WhenUserHasNoPermission_ExceptionThrows() {
|
||||
Mono<Tuple2<Theme, Theme>> applicationThemesMono = themeRepository.getSystemThemeByName("Classic")
|
||||
.zipWith(themeRepository.getSystemThemeByName("Sharp"))
|
||||
Mono<Tuple2<Theme, Theme>> applicationThemesMono = themeService.getSystemTheme("Classic")
|
||||
.zipWith(themeService.getSystemTheme("Sharp"))
|
||||
.flatMap(themesTuple -> {
|
||||
Application application = createApplication("random_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setEditModeThemeId(themesTuple.getT1().getId());
|
||||
application.setPublishedModeThemeId(themesTuple.getT2().getId());
|
||||
return applicationRepository.save(application);
|
||||
})
|
||||
.flatMap(application -> {
|
||||
return Mono.zip(
|
||||
themeService.getApplicationTheme(application.getId(), ApplicationMode.EDIT),
|
||||
themeService.getApplicationTheme(application.getId(), ApplicationMode.PUBLISHED)
|
||||
);
|
||||
});
|
||||
.flatMap(application -> Mono.zip(
|
||||
themeService.getApplicationTheme(application.getId(), ApplicationMode.EDIT),
|
||||
themeService.getApplicationTheme(application.getId(), ApplicationMode.PUBLISHED)
|
||||
));
|
||||
|
||||
StepVerifier.create(applicationThemesMono)
|
||||
.expectError(AppsmithException.class)
|
||||
|
|
@ -105,7 +110,7 @@ public class ThemeServiceTest {
|
|||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void changeCurrentTheme_WhenUserHasPermission_ThemesSetInEditMode() {
|
||||
Mono<Tuple2<Application, Application>> tuple2Mono = themeRepository.getSystemThemeByName("Classic")
|
||||
Mono<Tuple2<Application, Application>> tuple2Mono = themeService.getSystemTheme("Classic")
|
||||
.flatMap(theme -> {
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setEditModeThemeId(theme.getId());
|
||||
|
|
@ -113,7 +118,7 @@ public class ThemeServiceTest {
|
|||
// setting classic theme to edit mode and published mode
|
||||
return applicationRepository.save(application);
|
||||
})
|
||||
.zipWith(themeRepository.getSystemThemeByName("Rounded"))
|
||||
.zipWith(themeService.getSystemTheme("Rounded"))
|
||||
.flatMap(tuple -> {
|
||||
Application application = tuple.getT1();
|
||||
Theme theme = tuple.getT2();
|
||||
|
|
@ -141,7 +146,7 @@ public class ThemeServiceTest {
|
|||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void changeCurrentTheme_WhenUserHasNoPermission_ThrowsException() {
|
||||
Mono<Theme> themeMono = themeRepository.getSystemThemeByName("Classic")
|
||||
Mono<Theme> themeMono = themeService.getSystemTheme("Classic")
|
||||
.flatMap(theme -> {
|
||||
Application application = createApplication("some_other_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setEditModeThemeId(theme.getId());
|
||||
|
|
@ -157,16 +162,84 @@ public class ThemeServiceTest {
|
|||
StepVerifier.create(themeMono).expectError(AppsmithException.class).verify();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void changeCurrentTheme_WhenSystemThemeSet_NoNewThemeCreated() {
|
||||
Mono<String> defaultThemeIdMono = themeService.getDefaultThemeId().cache();
|
||||
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setOrganizationId("theme-test-org-id");
|
||||
Mono<Theme> applicationThemeMono = defaultThemeIdMono
|
||||
.flatMap(defaultThemeId -> {
|
||||
application.setEditModeThemeId(defaultThemeId);
|
||||
return applicationRepository.save(application);
|
||||
})
|
||||
.flatMap(savedApplication ->
|
||||
defaultThemeIdMono.flatMap(themeId ->
|
||||
themeService.changeCurrentTheme(themeId, savedApplication.getId())
|
||||
.then(themeService.getApplicationTheme(savedApplication.getId(), ApplicationMode.EDIT))
|
||||
)
|
||||
);
|
||||
|
||||
StepVerifier.create(applicationThemeMono).assertNext(theme -> {
|
||||
assertThat(theme.isSystemTheme()).isTrue();
|
||||
assertThat(theme.getApplicationId()).isNull();
|
||||
assertThat(theme.getOrganizationId()).isNull();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void changeCurrentTheme_WhenSystemThemeSetOverCustomTheme_NewThemeNotCreatedAndOldOneDeleted() {
|
||||
Collection<Policy> themePolicies = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(MANAGE_THEMES), "api_user"
|
||||
).values();
|
||||
|
||||
Theme customTheme = new Theme();
|
||||
customTheme.setName("my-custom-theme");
|
||||
customTheme.setPolicies(Set.copyOf(themePolicies));
|
||||
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setOrganizationId("theme-test-org-id");
|
||||
|
||||
Mono<Tuple2<Theme, Theme>> tuple2Mono = themeService.save(customTheme)
|
||||
.flatMap(savedTheme -> {
|
||||
application.setEditModeThemeId(savedTheme.getId());
|
||||
return applicationRepository.save(application);
|
||||
})
|
||||
.flatMap(savedApplication ->
|
||||
themeService.getDefaultThemeId()
|
||||
.flatMap(themeId -> themeService.changeCurrentTheme(themeId, savedApplication.getId()))
|
||||
.thenReturn(savedApplication)
|
||||
).flatMap(application1 ->
|
||||
// get old theme and new
|
||||
Mono.zip(
|
||||
themeService.getApplicationTheme(application1.getId(), ApplicationMode.EDIT),
|
||||
themeService.getThemeById(application1.getEditModeThemeId(), READ_THEMES)
|
||||
.defaultIfEmpty(new Theme()) // this should be deleted, return empty theme
|
||||
)
|
||||
);
|
||||
|
||||
StepVerifier.create(tuple2Mono).assertNext(themeTuple2 -> {
|
||||
Theme currentTheme = themeTuple2.getT1();
|
||||
Theme oldTheme = themeTuple2.getT2();
|
||||
assertThat(currentTheme.isSystemTheme()).isTrue();
|
||||
assertThat(currentTheme.getApplicationId()).isNull();
|
||||
assertThat(currentTheme.getOrganizationId()).isNull();
|
||||
assertThat(oldTheme.getId()).isNull();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void cloneThemeToApplication_WhenSrcThemeIsSystemTheme_NoNewThemeCreated() {
|
||||
Application newApplication = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
Mono<Tuple2<Theme, Theme>> newAndOldThemeMono = applicationRepository.save(newApplication)
|
||||
.zipWith(themeRepository.getSystemThemeByName("Classic"))
|
||||
.zipWith(themeService.getSystemTheme("Classic"))
|
||||
.flatMap(applicationAndTheme -> {
|
||||
Theme theme = applicationAndTheme.getT2();
|
||||
Application application = applicationAndTheme.getT1();
|
||||
return themeService.cloneThemeToApplication(theme.getId(), application.getId()).zipWith(Mono.just(theme));
|
||||
return themeService.cloneThemeToApplication(theme.getId(), application).zipWith(Mono.just(theme));
|
||||
});
|
||||
|
||||
StepVerifier.create(newAndOldThemeMono)
|
||||
|
|
@ -182,13 +255,16 @@ public class ThemeServiceTest {
|
|||
Application newApplication = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
Theme customTheme = new Theme();
|
||||
customTheme.setName("custom theme");
|
||||
customTheme.setPolicies(Set.copyOf(
|
||||
policyUtils.generatePolicyFromPermission(Set.of(MANAGE_THEMES), "api_user").values()
|
||||
));
|
||||
|
||||
Mono<Tuple2<Theme, Theme>> newAndOldThemeMono = applicationRepository.save(newApplication)
|
||||
.zipWith(themeRepository.save(customTheme))
|
||||
.zipWith(themeService.save(customTheme))
|
||||
.flatMap(applicationAndTheme -> {
|
||||
Theme theme = applicationAndTheme.getT2();
|
||||
Application application = applicationAndTheme.getT1();
|
||||
return themeService.cloneThemeToApplication(theme.getId(), application.getId()).zipWith(Mono.just(theme));
|
||||
return themeService.cloneThemeToApplication(theme.getId(), application).zipWith(Mono.just(theme));
|
||||
});
|
||||
|
||||
StepVerifier.create(newAndOldThemeMono)
|
||||
|
|
@ -199,14 +275,57 @@ public class ThemeServiceTest {
|
|||
.verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void cloneThemeToApplication_WhenSrcThemeIsCustomSavedTheme_NewCustomThemeCreated() {
|
||||
Application srcApplication = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
|
||||
Mono<Tuple2<Theme, Theme>> newAndOldThemeMono = applicationRepository.save(srcApplication)
|
||||
.flatMap(application -> {
|
||||
Theme srcCustomTheme = new Theme();
|
||||
srcCustomTheme.setName("custom theme");
|
||||
srcCustomTheme.setApplicationId(application.getId());
|
||||
srcCustomTheme.setPolicies(Set.copyOf(
|
||||
policyUtils.generatePolicyFromPermission(Set.of(MANAGE_THEMES), "api_user").values()
|
||||
));
|
||||
return themeService.save(srcCustomTheme);
|
||||
})
|
||||
.zipWith(applicationRepository.save(createApplication("api_user", Set.of(MANAGE_APPLICATIONS))))
|
||||
.flatMap(objects -> {
|
||||
Theme srcTheme = objects.getT1();
|
||||
Application destApp = objects.getT2();
|
||||
return Mono.zip(
|
||||
themeService.cloneThemeToApplication(srcTheme.getId(), destApp),
|
||||
Mono.just(srcTheme)
|
||||
);
|
||||
});
|
||||
|
||||
StepVerifier.create(newAndOldThemeMono)
|
||||
.assertNext(objects -> {
|
||||
Theme clonnedTheme = objects.getT1();
|
||||
Theme srcTheme = objects.getT2();
|
||||
|
||||
assertThat(clonnedTheme.getId()).isNotEqualTo(srcTheme.getId());
|
||||
assertThat(clonnedTheme.getName()).isEqualTo(srcTheme.getName());
|
||||
assertThat(clonnedTheme.getApplicationId()).isNull();
|
||||
assertThat(clonnedTheme.getOrganizationId()).isNull();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void getApplicationTheme_WhenUserHasPermission_ThemeReturned() {
|
||||
Collection<Policy> themePolicies = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(MANAGE_THEMES), "api_user"
|
||||
).values();
|
||||
|
||||
Theme customTheme = new Theme();
|
||||
customTheme.setName("custom theme for edit mode");
|
||||
customTheme.setPolicies(Set.copyOf(themePolicies));
|
||||
|
||||
Mono<Tuple2<Theme, Theme>> applicationThemesMono = themeRepository.save(customTheme)
|
||||
.zipWith(themeRepository.getSystemThemeByName("classic"))
|
||||
Mono<Tuple2<Theme, Theme>> applicationThemesMono = themeService.save(customTheme)
|
||||
.zipWith(themeService.getSystemTheme("classic"))
|
||||
.flatMap(themes -> {
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setEditModeThemeId(themes.getT1().getId());
|
||||
|
|
@ -230,7 +349,7 @@ public class ThemeServiceTest {
|
|||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void publishTheme_WhenSystemThemeIsSet_NoNewThemeCreated() {
|
||||
Mono<Theme> classicThemeMono = themeRepository.getSystemThemeByName("classic").cache();
|
||||
Mono<Theme> classicThemeMono = themeService.getSystemTheme("classic").cache();
|
||||
|
||||
Mono<Tuple2<Application, Theme>> appAndThemeTuple = classicThemeMono
|
||||
.flatMap(theme -> {
|
||||
|
|
@ -239,9 +358,8 @@ public class ThemeServiceTest {
|
|||
application.setPublishedModeThemeId("this-id-should-be-overridden");
|
||||
return applicationRepository.save(application);
|
||||
}).flatMap(savedApplication ->
|
||||
themeService.publishTheme(savedApplication.getEditModeThemeId(),
|
||||
savedApplication.getPublishedModeThemeId(), savedApplication.getId()
|
||||
).then(applicationRepository.findById(savedApplication.getId()))
|
||||
themeService.publishTheme(savedApplication.getId())
|
||||
.then(applicationRepository.findById(savedApplication.getId()))
|
||||
)
|
||||
.zipWith(classicThemeMono);
|
||||
|
||||
|
|
@ -254,23 +372,53 @@ public class ThemeServiceTest {
|
|||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void publishTheme_WhenSystemThemeInEditModeAndCustomThemeInPublishedMode_PublisedCopyDeleted() {
|
||||
Mono<Theme> classicThemeMono = themeService.getSystemTheme("classic").cache();
|
||||
|
||||
Theme customTheme = new Theme();
|
||||
customTheme.setName("published-theme-copy");
|
||||
Mono<Theme> publishedCustomThemeMono = themeService.save(customTheme);
|
||||
|
||||
Mono<Theme> deletedThemeMono = classicThemeMono
|
||||
.zipWith(publishedCustomThemeMono)
|
||||
.flatMap(themesTuple -> {
|
||||
Theme systemTheme = themesTuple.getT1();
|
||||
Theme savedCustomTheme = themesTuple.getT2();
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setEditModeThemeId(systemTheme.getId());
|
||||
application.setPublishedModeThemeId(savedCustomTheme.getId());
|
||||
return applicationRepository.save(application);
|
||||
}).flatMap(savedApplication ->
|
||||
themeService.publishTheme(savedApplication.getId())
|
||||
.then(themeService.getThemeById(savedApplication.getPublishedModeThemeId(), READ_THEMES))
|
||||
);
|
||||
|
||||
StepVerifier.create(deletedThemeMono)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void publishTheme_WhenCustomThemeIsSet_ThemeCopiedForPublishedMode() {
|
||||
Collection<Policy> themePolicies = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(MANAGE_THEMES), "api_user"
|
||||
).values();
|
||||
|
||||
Theme customTheme = new Theme();
|
||||
customTheme.setName("my-custom-theme");
|
||||
customTheme.setPolicies(Set.copyOf(themePolicies));
|
||||
|
||||
Mono<Tuple2<Theme, Theme>> appThemesMono = themeRepository.save(customTheme)
|
||||
.zipWith(themeRepository.getSystemThemeByName("classic"))
|
||||
Mono<Tuple2<Theme, Theme>> appThemesMono = themeService.save(customTheme)
|
||||
.zipWith(themeService.getSystemTheme("classic"))
|
||||
.flatMap(themes -> {
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setEditModeThemeId(themes.getT1().getId()); // custom theme
|
||||
application.setPublishedModeThemeId(themes.getT2().getId()); // system theme
|
||||
return applicationRepository.save(application);
|
||||
}).flatMap(application ->
|
||||
themeService.publishTheme(application.getEditModeThemeId(),
|
||||
application.getPublishedModeThemeId(), application.getId()
|
||||
).then(Mono.zip(
|
||||
themeService.publishTheme(application.getId()).then(Mono.zip(
|
||||
themeService.getApplicationTheme(application.getId(), ApplicationMode.EDIT),
|
||||
themeService.getApplicationTheme(application.getId(), ApplicationMode.PUBLISHED)
|
||||
))
|
||||
|
|
@ -291,7 +439,7 @@ public class ThemeServiceTest {
|
|||
Theme customTheme = new Theme();
|
||||
customTheme.setName("My custom theme");
|
||||
|
||||
Mono<Tuple2<Theme, Theme>> appThemesMono = themeRepository.getSystemThemeByName("classic")
|
||||
Mono<Tuple2<Theme, Theme>> appThemesMono = themeService.getSystemTheme("classic")
|
||||
.flatMap(theme -> {
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setEditModeThemeId(theme.getId()); // system theme
|
||||
|
|
@ -319,12 +467,17 @@ public class ThemeServiceTest {
|
|||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void updateTheme_WhenCustomThemeIsSet_ThemeIsOverridden() {
|
||||
Collection<Policy> themePolicies = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(MANAGE_THEMES), "api_user"
|
||||
).values();
|
||||
Theme customTheme = new Theme();
|
||||
customTheme.setName("My custom theme");
|
||||
Mono<Theme> saveCustomThemeMono = themeRepository.save(customTheme);
|
||||
customTheme.setPolicies(Set.copyOf(themePolicies));
|
||||
|
||||
Mono<Theme> saveCustomThemeMono = themeService.save(customTheme);
|
||||
|
||||
Mono<Tuple3<Theme, Theme, Application>> appThemesMono = saveCustomThemeMono
|
||||
.zipWith(themeRepository.getSystemThemeByName("classic"))
|
||||
.zipWith(themeService.getSystemTheme("classic"))
|
||||
.flatMap(themes -> {
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setEditModeThemeId(themes.getT1().getId()); // custom theme
|
||||
|
|
@ -361,15 +514,14 @@ public class ThemeServiceTest {
|
|||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void publishTheme_WhenNoThemeIsSet_SystemDefaultThemeIsSetToPublishedMode() {
|
||||
Mono<Theme> classicThemeMono = themeRepository.getSystemThemeByName(Theme.LEGACY_THEME_NAME);
|
||||
Mono<Theme> classicThemeMono = themeService.getSystemTheme(Theme.LEGACY_THEME_NAME);
|
||||
|
||||
Mono<Tuple2<Application, Theme>> appAndThemeTuple = applicationRepository.save(
|
||||
createApplication("api_user", Set.of(MANAGE_APPLICATIONS))
|
||||
)
|
||||
.flatMap(savedApplication ->
|
||||
themeService.publishTheme(savedApplication.getEditModeThemeId(),
|
||||
savedApplication.getPublishedModeThemeId(), savedApplication.getId()
|
||||
).then(applicationRepository.findById(savedApplication.getId()))
|
||||
themeService.publishTheme(savedApplication.getId())
|
||||
.then(applicationRepository.findById(savedApplication.getId()))
|
||||
)
|
||||
.zipWith(classicThemeMono);
|
||||
|
||||
|
|
@ -380,4 +532,214 @@ public class ThemeServiceTest {
|
|||
assertThat(application.getPublishedModeThemeId()).isEqualTo(classicSystemTheme.getId());
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void publishTheme_WhenApplicationIsPublic_PublishedThemeIsPublic() {
|
||||
Collection<Policy> themePolicies = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(MANAGE_THEMES), "api_user"
|
||||
).values();
|
||||
|
||||
Theme customTheme = new Theme();
|
||||
customTheme.setName("my-custom-theme");
|
||||
customTheme.setPolicies(Set.copyOf(themePolicies));
|
||||
|
||||
Mono<Theme> appThemesMono = themeService.save(customTheme)
|
||||
.zipWith(themeService.getSystemTheme("classic"))
|
||||
.flatMap(themes -> {
|
||||
Application application = createApplication("api_user",
|
||||
Set.of(MAKE_PUBLIC_APPLICATIONS, MANAGE_APPLICATIONS));
|
||||
application.setEditModeThemeId(themes.getT1().getId()); // custom theme
|
||||
application.setPublishedModeThemeId(themes.getT2().getId()); // system theme
|
||||
return applicationRepository.save(application);
|
||||
})
|
||||
.flatMap(application -> {
|
||||
// make the application public
|
||||
ApplicationAccessDTO accessDTO = new ApplicationAccessDTO();
|
||||
accessDTO.setPublicAccess(true);
|
||||
return applicationService.changeViewAccess(application.getId(), accessDTO);
|
||||
})
|
||||
.flatMap(application ->
|
||||
themeService.publishTheme(application.getId()).then(
|
||||
themeService.getApplicationTheme(application.getId(), ApplicationMode.PUBLISHED)
|
||||
)
|
||||
);
|
||||
|
||||
StepVerifier.create(appThemesMono)
|
||||
.assertNext(publishedModeTheme -> {
|
||||
Boolean permissionPresentForAnonymousUser = policyUtils.isPermissionPresentForUser(
|
||||
publishedModeTheme.getPolicies(), READ_THEMES.getValue(), FieldName.ANONYMOUS_USER
|
||||
);
|
||||
assertThat(permissionPresentForAnonymousUser).isTrue();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void persistCurrentTheme_WhenCustomThemeIsSet_NewApplicationThemeCreated() {
|
||||
Collection<Policy> themePolicies = policyUtils.generatePolicyFromPermission(
|
||||
Set.of(MANAGE_THEMES), "api_user"
|
||||
).values();
|
||||
|
||||
Theme customTheme = new Theme();
|
||||
customTheme.setName("Classic");
|
||||
customTheme.setPolicies(Set.copyOf(themePolicies));
|
||||
|
||||
Mono<Tuple3<List<Theme>, Theme, Application>> tuple3Mono = themeService.save(customTheme).flatMap(theme -> {
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setEditModeThemeId(theme.getId());
|
||||
application.setOrganizationId("theme-test-org-id");
|
||||
return applicationRepository.save(application);
|
||||
}).flatMap(application -> {
|
||||
Theme theme = new Theme();
|
||||
theme.setName("My custom theme");
|
||||
return themeService.persistCurrentTheme(application.getId(), theme)
|
||||
.map(theme1 -> Tuples.of(theme1, application));
|
||||
}).flatMap(persistedThemeAndApp ->
|
||||
themeService.getApplicationThemes(persistedThemeAndApp.getT2().getId()).collectList()
|
||||
.map(themes -> Tuples.of(themes, persistedThemeAndApp.getT1(), persistedThemeAndApp.getT2()))
|
||||
);
|
||||
|
||||
StepVerifier.create(tuple3Mono).assertNext(tuple3 -> {
|
||||
List<Theme> availableThemes = tuple3.getT1();
|
||||
Theme persistedTheme = tuple3.getT2();
|
||||
Application application = tuple3.getT3();
|
||||
assertThat(availableThemes.size()).isEqualTo(5); // one custom theme + 4 system themes
|
||||
assertThat(persistedTheme.getApplicationId()).isNotEmpty(); // theme should have application id set
|
||||
assertThat(persistedTheme.getOrganizationId()).isEqualTo("theme-test-org-id"); // theme should have org id set
|
||||
assertThat(policyUtils.isPermissionPresentForUser(
|
||||
persistedTheme.getPolicies(), READ_THEMES.getValue(), "api_user")
|
||||
).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(
|
||||
persistedTheme.getPolicies(), MANAGE_THEMES.getValue(), "api_user")
|
||||
).isTrue();
|
||||
assertThat(application.getEditModeThemeId()).isNotEqualTo(persistedTheme.getId()); // a new copy should be created
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void persistCurrentTheme_WhenSystemThemeIsSet_NewApplicationThemeCreated() {
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setOrganizationId("theme-test-org-id");
|
||||
Mono<Tuple2<List<Theme>, Theme>> tuple2Mono = themeService.getDefaultThemeId()
|
||||
.flatMap(defaultThemeId -> {
|
||||
application.setEditModeThemeId(defaultThemeId);
|
||||
return applicationRepository.save(application);
|
||||
})
|
||||
.flatMap(savedApplication -> {
|
||||
Theme theme = new Theme();
|
||||
theme.setName("My custom theme");
|
||||
return themeService.persistCurrentTheme(savedApplication.getId(), theme)
|
||||
.map(theme1 -> Tuples.of(theme1, savedApplication.getId()));
|
||||
}).flatMap(persistedThemeAndAppId ->
|
||||
themeService.getApplicationThemes(persistedThemeAndAppId.getT2()).collectList()
|
||||
.map(themes -> Tuples.of(themes, persistedThemeAndAppId.getT1()))
|
||||
);
|
||||
|
||||
StepVerifier.create(tuple2Mono).assertNext(tuple2 -> {
|
||||
List<Theme> availableThemes = tuple2.getT1();
|
||||
Theme currentTheme = tuple2.getT2();
|
||||
assertThat(availableThemes.size()).isEqualTo(5); // one custom theme + 4 system themes
|
||||
assertThat(currentTheme.isSystemTheme()).isFalse();
|
||||
assertThat(currentTheme.getApplicationId()).isNotEmpty(); // theme should have application id set
|
||||
assertThat(currentTheme.getOrganizationId()).isEqualTo("theme-test-org-id"); // theme should have org id set
|
||||
assertThat(policyUtils.isPermissionPresentForUser(currentTheme.getPolicies(), READ_THEMES.getValue(), "api_user")).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(currentTheme.getPolicies(), MANAGE_THEMES.getValue(), "api_user")).isTrue();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void delete_WhenSystemTheme_NotAllowed() {
|
||||
StepVerifier.create(themeService.getDefaultThemeId().flatMap(themeService::delete))
|
||||
.expectError(AppsmithException.class)
|
||||
.verify();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void delete_WhenUnsavedCustomizedTheme_NotAllowed() {
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
|
||||
Mono<Theme> deleteThemeMono = themeService.getDefaultThemeId()
|
||||
.flatMap(s -> {
|
||||
application.setEditModeThemeId(s);
|
||||
return applicationRepository.save(application);
|
||||
})
|
||||
.flatMap(savedApplication -> {
|
||||
Theme themeCustomization = new Theme();
|
||||
themeCustomization.setName("Updated name");
|
||||
return themeService.updateTheme(savedApplication.getId(), themeCustomization);
|
||||
}).flatMap(customizedTheme -> themeService.delete(customizedTheme.getId()));
|
||||
|
||||
StepVerifier.create(deleteThemeMono)
|
||||
.expectErrorMessage(AppsmithError.UNSUPPORTED_OPERATION.getMessage())
|
||||
.verify();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void delete_WhenSavedCustomizedTheme_ThemeIsDeleted() {
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
|
||||
Mono<Theme> deleteThemeMono = themeService.getDefaultThemeId()
|
||||
.flatMap(s -> {
|
||||
application.setEditModeThemeId(s);
|
||||
return applicationRepository.save(application);
|
||||
})
|
||||
.flatMap(savedApplication -> {
|
||||
Theme themeCustomization = new Theme();
|
||||
themeCustomization.setName("Updated name");
|
||||
return themeService.persistCurrentTheme(savedApplication.getId(), themeCustomization);
|
||||
})
|
||||
.flatMap(customizedTheme -> themeService.delete(customizedTheme.getId())
|
||||
.then(themeService.getThemeById(customizedTheme.getId(), READ_THEMES)));
|
||||
|
||||
StepVerifier.create(deleteThemeMono).verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void updateName_WhenSystemTheme_NotAllowed() {
|
||||
Mono<Theme> updateThemeNameMono = themeService.getDefaultThemeId().flatMap(themeId -> {
|
||||
Theme theme = new Theme();
|
||||
theme.setName("My theme");
|
||||
return themeService.updateName(themeId, theme);
|
||||
});
|
||||
StepVerifier.create(updateThemeNameMono).expectError(AppsmithException.class).verify();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void updateName_WhenCustomTheme_NameUpdated() {
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setOrganizationId("test-org");
|
||||
|
||||
Mono<Theme> updateThemeNameMono = themeService.getDefaultThemeId()
|
||||
.flatMap(s -> {
|
||||
application.setEditModeThemeId(s);
|
||||
return applicationRepository.save(application);
|
||||
})
|
||||
.flatMap(savedApplication -> {
|
||||
Theme themeCustomization = new Theme();
|
||||
themeCustomization.setName("old name");
|
||||
return themeService.persistCurrentTheme(savedApplication.getId(), themeCustomization);
|
||||
})
|
||||
.flatMap(customizedTheme -> {
|
||||
Theme theme = new Theme();
|
||||
theme.setName("new name");
|
||||
return themeService.updateName(customizedTheme.getId(), theme)
|
||||
.then(themeService.getThemeById(customizedTheme.getId(), READ_THEMES));
|
||||
});
|
||||
|
||||
StepVerifier.create(updateThemeNameMono).assertNext(theme -> {
|
||||
assertThat(theme.getName()).isEqualTo("new name");
|
||||
assertThat(theme.isSystemTheme()).isFalse();
|
||||
assertThat(theme.getApplicationId()).isNotNull();
|
||||
assertThat(theme.getOrganizationId()).isEqualTo("test-org");
|
||||
assertThat(theme.getConfig()).isNotNull();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -277,10 +277,10 @@ class UserOrganizationServiceTest {
|
|||
StepVerifier.create(commentThreadMono).assertNext(commentThread -> {
|
||||
Set<Policy> policies = commentThread.getPolicies();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(
|
||||
policies, AclPermission.READ_THREAD.getValue(), "test_developer"
|
||||
policies, AclPermission.READ_THREADS.getValue(), "test_developer"
|
||||
)).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(
|
||||
policies, AclPermission.READ_THREAD.getValue(), "api_user"
|
||||
policies, AclPermission.READ_THREADS.getValue(), "api_user"
|
||||
)).isTrue();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
|
@ -308,10 +308,10 @@ class UserOrganizationServiceTest {
|
|||
StepVerifier.create(commentThreadMono).assertNext(commentThread -> {
|
||||
Set<Policy> policies = commentThread.getPolicies();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(
|
||||
policies, AclPermission.READ_THREAD.getValue(), "test_developer"
|
||||
policies, AclPermission.READ_THREADS.getValue(), "test_developer"
|
||||
)).isFalse();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(
|
||||
policies, AclPermission.READ_THREAD.getValue(), "api_user"
|
||||
policies, AclPermission.READ_THREADS.getValue(), "api_user"
|
||||
)).isTrue();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
|
@ -346,13 +346,13 @@ class UserOrganizationServiceTest {
|
|||
StepVerifier.create(saveUserMono.then(commentThreadMono)).assertNext(commentThread -> {
|
||||
Set<Policy> policies = commentThread.getPolicies();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(
|
||||
policies, AclPermission.READ_THREAD.getValue(), "test_developer"
|
||||
policies, AclPermission.READ_THREADS.getValue(), "test_developer"
|
||||
)).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(
|
||||
policies, AclPermission.READ_THREAD.getValue(), "new_test_user"
|
||||
policies, AclPermission.READ_THREADS.getValue(), "new_test_user"
|
||||
)).isTrue();
|
||||
assertThat(policyUtils.isPermissionPresentForUser(
|
||||
policies, AclPermission.READ_THREAD.getValue(), "api_user"
|
||||
policies, AclPermission.READ_THREADS.getValue(), "api_user"
|
||||
)).isTrue();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,14 @@ import com.appsmith.server.acl.AppsmithRole;
|
|||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.ActionCollection;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationMode;
|
||||
import com.appsmith.server.domains.Layout;
|
||||
import com.appsmith.server.domains.NewAction;
|
||||
import com.appsmith.server.domains.NewPage;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.Plugin;
|
||||
import com.appsmith.server.domains.PluginType;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.dtos.ActionCollectionDTO;
|
||||
import com.appsmith.server.dtos.ActionDTO;
|
||||
import com.appsmith.server.dtos.InviteUsersDTO;
|
||||
|
|
@ -34,6 +36,7 @@ import com.appsmith.server.services.NewActionService;
|
|||
import com.appsmith.server.services.NewPageService;
|
||||
import com.appsmith.server.services.OrganizationService;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.appsmith.server.services.ThemeService;
|
||||
import com.appsmith.server.services.UserService;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
|
@ -60,6 +63,8 @@ import org.springframework.util.LinkedMultiValueMap;
|
|||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.util.function.Tuple3;
|
||||
import reactor.util.function.Tuple4;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -67,6 +72,7 @@ import java.util.Collections;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.appsmith.server.acl.AclPermission.READ_ACTIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
|
||||
|
|
@ -137,6 +143,9 @@ public class ApplicationForkingServiceTests {
|
|||
@Autowired
|
||||
private LayoutCollectionService layoutCollectionService;
|
||||
|
||||
@Autowired
|
||||
private ThemeService themeService;
|
||||
|
||||
private static String sourceAppId;
|
||||
|
||||
private static String testUserOrgId;
|
||||
|
|
@ -484,6 +493,192 @@ public class ApplicationForkingServiceTests {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails("api_user")
|
||||
public void forkApplicationToOrganization_WhenAppHasUnsavedThemeCustomization_ForkedWithCustomizations() {
|
||||
String uniqueString = UUID.randomUUID().toString();
|
||||
Organization organization = new Organization();
|
||||
organization.setName("org_" + uniqueString);
|
||||
|
||||
Mono<Tuple4<Theme, Theme, Application, Application>> tuple4Mono = organizationService.create(organization)
|
||||
.flatMap(createdOrg -> {
|
||||
Application application = new Application();
|
||||
application.setName("app_" + uniqueString);
|
||||
return applicationPageService.createApplication(application, createdOrg.getId());
|
||||
}).flatMap(srcApplication -> {
|
||||
Theme theme = new Theme();
|
||||
theme.setName("theme_" + uniqueString);
|
||||
return themeService.updateTheme(srcApplication.getId(), theme)
|
||||
.then(applicationService.findById(srcApplication.getId()));
|
||||
}).flatMap(srcApplication -> {
|
||||
Organization desOrg = new Organization();
|
||||
desOrg.setName("org_dest_" + uniqueString);
|
||||
return organizationService.create(desOrg).flatMap(createdOrg ->
|
||||
applicationForkingService.forkApplicationToOrganization(srcApplication.getId(), createdOrg.getId())
|
||||
).zipWith(Mono.just(srcApplication));
|
||||
}).flatMap(applicationTuple2 -> {
|
||||
Application forkedApp = applicationTuple2.getT1();
|
||||
Application srcApp = applicationTuple2.getT2();
|
||||
return Mono.zip(
|
||||
themeService.getApplicationTheme(forkedApp.getId(), ApplicationMode.EDIT),
|
||||
themeService.getApplicationTheme(forkedApp.getId(), ApplicationMode.PUBLISHED),
|
||||
Mono.just(forkedApp),
|
||||
Mono.just(srcApp)
|
||||
);
|
||||
});
|
||||
|
||||
StepVerifier.create(tuple4Mono).assertNext(objects -> {
|
||||
Theme editModeTheme = objects.getT1();
|
||||
Theme publishedModeTheme = objects.getT2();
|
||||
Application forkedApp = objects.getT3();
|
||||
Application srcApp = objects.getT4();
|
||||
|
||||
assertThat(forkedApp.getEditModeThemeId()).isEqualTo(editModeTheme.getId());
|
||||
assertThat(forkedApp.getPublishedModeThemeId()).isEqualTo(publishedModeTheme.getId());
|
||||
assertThat(forkedApp.getEditModeThemeId()).isNotEqualTo(forkedApp.getPublishedModeThemeId());
|
||||
|
||||
// published mode should have the custom theme as we publish after forking the app
|
||||
assertThat(publishedModeTheme.isSystemTheme()).isFalse();
|
||||
// published mode theme will have no application id and org id set as the customizations were not saved
|
||||
assertThat(publishedModeTheme.getOrganizationId()).isNullOrEmpty();
|
||||
assertThat(publishedModeTheme.getApplicationId()).isNullOrEmpty();
|
||||
|
||||
// edit mode theme should be a custom one
|
||||
assertThat(editModeTheme.isSystemTheme()).isFalse();
|
||||
// edit mode theme will have no application id and org id set as the customizations were not saved
|
||||
assertThat(editModeTheme.getOrganizationId()).isNullOrEmpty();
|
||||
assertThat(editModeTheme.getApplicationId()).isNullOrEmpty();
|
||||
|
||||
// forked theme should have the same name as src theme
|
||||
assertThat(editModeTheme.getName()).isEqualTo("theme_" + uniqueString);
|
||||
assertThat(publishedModeTheme.getName()).isEqualTo("theme_" + uniqueString);
|
||||
|
||||
// forked application should have a new edit mode theme created, should not be same as src app theme
|
||||
assertThat(srcApp.getEditModeThemeId()).isNotEqualTo(forkedApp.getEditModeThemeId());
|
||||
assertThat(srcApp.getPublishedModeThemeId()).isNotEqualTo(forkedApp.getPublishedModeThemeId());
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails("api_user")
|
||||
public void forkApplicationToOrganization_WhenAppHasSystemTheme_SystemThemeSet() {
|
||||
String uniqueString = UUID.randomUUID().toString();
|
||||
Organization organization = new Organization();
|
||||
organization.setName("org_" + uniqueString);
|
||||
|
||||
Mono<Tuple3<Theme, Application, Application>> tuple3Mono = organizationService.create(organization)
|
||||
.flatMap(createdOrg -> {
|
||||
Application application = new Application();
|
||||
application.setName("app_" + uniqueString);
|
||||
return applicationPageService.createApplication(application, createdOrg.getId());
|
||||
}).flatMap(srcApplication -> {
|
||||
Organization desOrg = new Organization();
|
||||
desOrg.setName("org_dest_" + uniqueString);
|
||||
return organizationService.create(desOrg).flatMap(createdOrg ->
|
||||
applicationForkingService.forkApplicationToOrganization(srcApplication.getId(), createdOrg.getId())
|
||||
).zipWith(Mono.just(srcApplication));
|
||||
}).flatMap(applicationTuple2 -> {
|
||||
Application forkedApp = applicationTuple2.getT1();
|
||||
Application srcApp = applicationTuple2.getT2();
|
||||
return Mono.zip(
|
||||
themeService.getApplicationTheme(forkedApp.getId(), ApplicationMode.EDIT),
|
||||
Mono.just(forkedApp),
|
||||
Mono.just(srcApp)
|
||||
);
|
||||
});
|
||||
|
||||
StepVerifier.create(tuple3Mono).assertNext(objects -> {
|
||||
Theme editModeTheme = objects.getT1();
|
||||
Application forkedApp = objects.getT2();
|
||||
Application srcApp = objects.getT3();
|
||||
|
||||
// same theme should be set to edit mode and published mode
|
||||
assertThat(forkedApp.getEditModeThemeId()).isEqualTo(editModeTheme.getId());
|
||||
assertThat(forkedApp.getPublishedModeThemeId()).isEqualTo(editModeTheme.getId());
|
||||
|
||||
// edit mode theme should be system theme
|
||||
assertThat(editModeTheme.isSystemTheme()).isTrue();
|
||||
// edit mode theme will have no application id and org id set as it's system theme
|
||||
assertThat(editModeTheme.getOrganizationId()).isNullOrEmpty();
|
||||
assertThat(editModeTheme.getApplicationId()).isNullOrEmpty();
|
||||
|
||||
// forked theme should be default theme
|
||||
assertThat(editModeTheme.getName()).isEqualToIgnoringCase(Theme.DEFAULT_THEME_NAME);
|
||||
|
||||
// forked application should have same theme set
|
||||
assertThat(srcApp.getEditModeThemeId()).isEqualTo(forkedApp.getEditModeThemeId());
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails("api_user")
|
||||
public void forkApplicationToOrganization_WhenAppHasCustomSavedTheme_NewCustomThemeCreated() {
|
||||
String uniqueString = UUID.randomUUID().toString();
|
||||
Organization organization = new Organization();
|
||||
organization.setName("org_" + uniqueString);
|
||||
|
||||
Mono<Tuple4<Theme, Theme, Application, Application>> tuple4Mono = organizationService.create(organization)
|
||||
.flatMap(createdOrg -> {
|
||||
Application application = new Application();
|
||||
application.setName("app_" + uniqueString);
|
||||
return applicationPageService.createApplication(application, createdOrg.getId());
|
||||
}).flatMap(srcApplication -> {
|
||||
Theme theme = new Theme();
|
||||
theme.setName("theme_" + uniqueString);
|
||||
return themeService.updateTheme(srcApplication.getId(), theme)
|
||||
.then(themeService.persistCurrentTheme(srcApplication.getId(), theme))
|
||||
.then(applicationService.findById(srcApplication.getId()));
|
||||
}).flatMap(srcApplication -> {
|
||||
Organization desOrg = new Organization();
|
||||
desOrg.setName("org_dest_" + uniqueString);
|
||||
return organizationService.create(desOrg).flatMap(createdOrg ->
|
||||
applicationForkingService.forkApplicationToOrganization(srcApplication.getId(), createdOrg.getId())
|
||||
).zipWith(Mono.just(srcApplication));
|
||||
}).flatMap(applicationTuple2 -> {
|
||||
Application forkedApp = applicationTuple2.getT1();
|
||||
Application srcApp = applicationTuple2.getT2();
|
||||
return Mono.zip(
|
||||
themeService.getApplicationTheme(forkedApp.getId(), ApplicationMode.EDIT),
|
||||
themeService.getApplicationTheme(forkedApp.getId(), ApplicationMode.PUBLISHED),
|
||||
Mono.just(forkedApp),
|
||||
Mono.just(srcApp)
|
||||
);
|
||||
});
|
||||
|
||||
StepVerifier.create(tuple4Mono).assertNext(objects -> {
|
||||
Theme editModeTheme = objects.getT1();
|
||||
Theme publishedModeTheme = objects.getT2();
|
||||
Application forkedApp = objects.getT3();
|
||||
Application srcApp = objects.getT4();
|
||||
|
||||
assertThat(forkedApp.getEditModeThemeId()).isEqualTo(editModeTheme.getId());
|
||||
assertThat(forkedApp.getPublishedModeThemeId()).isEqualTo(publishedModeTheme.getId());
|
||||
assertThat(forkedApp.getEditModeThemeId()).isNotEqualTo(forkedApp.getPublishedModeThemeId());
|
||||
|
||||
// published mode should have the custom theme as we publish after forking the app
|
||||
assertThat(publishedModeTheme.isSystemTheme()).isFalse();
|
||||
|
||||
// published mode theme will have no application id and org id set as it's a copy
|
||||
assertThat(publishedModeTheme.getOrganizationId()).isNullOrEmpty();
|
||||
assertThat(publishedModeTheme.getApplicationId()).isNullOrEmpty();
|
||||
|
||||
// edit mode theme should be a custom one
|
||||
assertThat(editModeTheme.isSystemTheme()).isFalse();
|
||||
|
||||
// edit mode theme will have application id and org id set as the customizations were saved
|
||||
assertThat(editModeTheme.getOrganizationId()).isNullOrEmpty();
|
||||
assertThat(editModeTheme.getApplicationId()).isNullOrEmpty();
|
||||
|
||||
// forked theme should have the same name as src theme
|
||||
assertThat(editModeTheme.getName()).isEqualTo("theme_" + uniqueString);
|
||||
assertThat(publishedModeTheme.getName()).isEqualTo("theme_" + uniqueString);
|
||||
|
||||
// forked application should have a new edit mode theme created, should not be same as src app theme
|
||||
assertThat(srcApp.getEditModeThemeId()).isNotEqualTo(forkedApp.getEditModeThemeId());
|
||||
assertThat(srcApp.getPublishedModeThemeId()).isNotEqualTo(forkedApp.getPublishedModeThemeId());
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
private Flux<ActionDTO> getActionsInOrganization(Organization organization) {
|
||||
return applicationService
|
||||
.findByOrganizationId(organization.getId(), READ_APPLICATIONS)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import com.appsmith.server.migrations.JsonSchemaVersions;
|
|||
import com.appsmith.server.repositories.ApplicationRepository;
|
||||
import com.appsmith.server.repositories.NewPageRepository;
|
||||
import com.appsmith.server.repositories.PluginRepository;
|
||||
import com.appsmith.server.repositories.ThemeRepository;
|
||||
import com.appsmith.server.services.ActionCollectionService;
|
||||
import com.appsmith.server.services.ApplicationPageService;
|
||||
import com.appsmith.server.services.DatasourceService;
|
||||
|
|
@ -42,6 +41,7 @@ import com.appsmith.server.services.NewActionService;
|
|||
import com.appsmith.server.services.NewPageService;
|
||||
import com.appsmith.server.services.OrganizationService;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.appsmith.server.services.ThemeService;
|
||||
import com.appsmith.server.services.UserService;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
|
@ -90,6 +90,7 @@ import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS;
|
|||
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_THEMES;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_ACTIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_PAGES;
|
||||
|
|
@ -148,7 +149,7 @@ public class ImportExportApplicationServiceTests {
|
|||
PluginExecutorHelper pluginExecutorHelper;
|
||||
|
||||
@Autowired
|
||||
ThemeRepository themeRepository;
|
||||
ThemeService themeService;
|
||||
|
||||
private static final String INVALID_JSON_FILE = "invalid json file";
|
||||
private static Plugin installedPlugin;
|
||||
|
|
@ -859,8 +860,8 @@ public class ImportExportApplicationServiceTests {
|
|||
.create(resultMono
|
||||
.flatMap(application -> Mono.zip(
|
||||
Mono.just(application),
|
||||
themeRepository.findById(application.getEditModeThemeId()),
|
||||
themeRepository.findById(application.getPublishedModeThemeId())
|
||||
themeService.getThemeById(application.getEditModeThemeId(), MANAGE_THEMES),
|
||||
themeService.getThemeById(application.getPublishedModeThemeId(), MANAGE_THEMES)
|
||||
)))
|
||||
.assertNext(tuple -> {
|
||||
final Application application = tuple.getT1();
|
||||
|
|
@ -869,9 +870,13 @@ public class ImportExportApplicationServiceTests {
|
|||
|
||||
assertThat(editTheme.isSystemTheme()).isFalse();
|
||||
assertThat(editTheme.getName()).isEqualTo("Custom edit theme");
|
||||
assertThat(editTheme.getOrganizationId()).isNull();
|
||||
assertThat(editTheme.getApplicationId()).isNull();
|
||||
|
||||
assertThat(publishedTheme.isSystemTheme()).isFalse();
|
||||
assertThat(publishedTheme.getName()).isEqualTo("Custom published theme");
|
||||
assertThat(publishedTheme.getOrganizationId()).isNullOrEmpty();
|
||||
assertThat(publishedTheme.getApplicationId()).isNullOrEmpty();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -632,11 +632,15 @@
|
|||
"editModeTheme": {
|
||||
"name": "Custom edit theme",
|
||||
"new": true,
|
||||
"isSystemTheme": false
|
||||
"isSystemTheme": false,
|
||||
"applicationId": "dummy-app-id",
|
||||
"organizationId": "dummy-org-id"
|
||||
},
|
||||
"publishedTheme": {
|
||||
"name": "Custom published theme",
|
||||
"new": true,
|
||||
"isSystemTheme": false
|
||||
"isSystemTheme": false,
|
||||
"applicationId": "dummy-app-id",
|
||||
"organizationId": "dummy-org-id"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user