From f376d362e4f6e11dce23063a04d692b28cb5eae9 Mon Sep 17 00:00:00 2001 From: balajisoundar Date: Wed, 17 Jan 2024 19:44:14 +0530 Subject: [PATCH 01/16] chore: custom widget onReady warning and template updates (#30335) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Update tempaltes and add warning message when onReady function is missing. > if no issue exists, please create an issue and ask the maintainers about this first > > #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change > Please delete options that are not relevant. - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) - Breaking change (fix or feature that would cause existing functionality to not work as expected) - Chore (housekeeping or task changes that don't impact user perception) - This change requires a documentation update > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [ ] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed ## Summary by CodeRabbit - **New Features** - Introduced a new Vanilla JavaScript template for custom widgets. - Enhanced Vue.js custom widget template with new design and functionality. - Added a warning system to alert users when certain expected code patterns are missing. - **Bug Fixes** - Updated the `appsmithConsole` to include error handling. - **Documentation** - Added new documentation URLs for custom widget development guidance. - **Refactor** - Replaced the `blank` module with `vanillaJs` in code templates. - Removed unused styles and code comments from React template. - Streamlined default context values for widget development. --- app/client/src/ce/constants/messages.ts | 3 + app/client/src/ce/utils/analyticsUtilTypes.ts | 5 +- .../Header/CodeTemplates/Templates/blank.ts | 13 -- .../Header/CodeTemplates/Templates/index.ts | 4 +- .../Header/CodeTemplates/Templates/react.ts | 98 ++----------- .../CodeTemplates/Templates/vanillaJs.ts | 129 ++++++++++++++++++ .../Header/CodeTemplates/Templates/vue.ts | 125 ++++++++++++++--- .../Editor/CustomWidgetBuilder/constants.ts | 15 +- .../CustomWidgetBuilder/utility.test.ts | 4 +- .../Editor/CustomWidgetBuilder/utility.ts | 25 ++++ .../CustomWidget/component/appsmithConsole.js | 2 +- .../widgets/CustomWidget/component/index.tsx | 11 ++ .../widgets/CustomWidget/widget/defaultApp.ts | 7 + .../src/widgets/CustomWidget/widget/index.tsx | 10 ++ 14 files changed, 317 insertions(+), 134 deletions(-) delete mode 100644 app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/blank.ts create mode 100644 app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/vanillaJs.ts diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index c7e018b77c..72a538d10d 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -2356,6 +2356,7 @@ export const CUSTOM_WIDGET_FEATURE = { }, templateKey: { blank: () => "Blank", + vanillaJs: () => "Vanilla JS", react: () => "React", vue: () => "Vue", }, @@ -2411,6 +2412,8 @@ export const CUSTOM_WIDGET_FEATURE = { helpDropdown: { stackoverflow: () => "Search StackOverflow", }, + noOnReadyWarning: (url: string) => + `Missing appsmith.onReady() function call. Initiate your component inside 'appsmith.onReady()' for your custom widget to work as expected. For more information - ${url}`, }, preview: { eventFired: () => "Event fired:", diff --git a/app/client/src/ce/utils/analyticsUtilTypes.ts b/app/client/src/ce/utils/analyticsUtilTypes.ts index 5c586903af..832299c110 100644 --- a/app/client/src/ce/utils/analyticsUtilTypes.ts +++ b/app/client/src/ce/utils/analyticsUtilTypes.ts @@ -432,6 +432,7 @@ export type VERSION_UPDATE_EVENTS = | "VERSION_UPDATED_FAILED"; export type CUSTOM_WIDGET_EVENTS = + | "CUSTOM_WIDGET_LOAD_INIT" | "CUSTOM_WIDGET_EDIT_SOURCE_CLICKED" | "CUSTOM_WIDGET_ADD_EVENT_CLICKED" | "CUSTOM_WIDGET_ADD_EVENT_CANCEL_CLICKED" @@ -450,4 +451,6 @@ export type CUSTOM_WIDGET_EVENTS = | "CUSTOM_WIDGET_BUILDER_REFERENCE_VISIBILITY_CHANGED" | "CUSTOM_WIDGET_BUILDER_REFERENCE_EVENT_OPENED" | "CUSTOM_WIDGET_BUILDER_DEBUGGER_CLEARED" - | "CUSTOM_WIDGET_BUILDER_DEBUGGER_VISIBILITY_CHANGED"; + | "CUSTOM_WIDGET_BUILDER_DEBUGGER_VISIBILITY_CHANGED" + | "CUSTOM_WIDGET_API_TRIGGER_EVENT" + | "CUSTOM_WIDGET_API_UPDATE_MODEL"; diff --git a/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/blank.ts b/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/blank.ts deleted file mode 100644 index 4b930114d7..0000000000 --- a/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/blank.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { - CUSTOM_WIDGET_FEATURE, - createMessage, -} from "@appsmith/constants/messages"; - -export default { - key: createMessage(CUSTOM_WIDGET_FEATURE.templateKey.blank), - uncompiledSrcDoc: { - html: "", - css: "", - js: "", - }, -}; diff --git a/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/index.ts b/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/index.ts index 9825b6ec1e..8174339fad 100644 --- a/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/index.ts +++ b/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/index.ts @@ -1,5 +1,5 @@ -import blank from "./blank"; +import vanillaJs from "./vanillaJs"; import react from "./react"; import vue from "./vue"; -export default [blank, react, vue]; +export default [vanillaJs, react, vue]; diff --git a/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/react.ts b/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/react.ts index 8f944018c8..251c42d166 100644 --- a/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/react.ts +++ b/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/react.ts @@ -2,6 +2,7 @@ import { CUSTOM_WIDGET_FEATURE, createMessage, } from "@appsmith/constants/messages"; +import { CUSTOM_WIDGET_ONREADY_DOC_URL } from "pages/Editor/CustomWidgetBuilder/constants"; export default { key: createMessage(CUSTOM_WIDGET_FEATURE.templateKey.react), @@ -43,6 +44,7 @@ export default { .button-container button { margin: 0 10px; + border-radius: var(--appsmith-theme-borderRadius); } .button-container button.primary { @@ -58,10 +60,6 @@ import reactDom from 'https://cdn.jsdelivr.net/npm/react-dom@18.2.0/+esm' import { Button, Card } from 'https://cdn.jsdelivr.net/npm/antd@5.11.1/+esm' import Markdown from 'https://cdn.jsdelivr.net/npm/react-markdown@9.0.1/+esm' -const style = { - maxWidth: "400px", -} - function App() { const [currentIndex, setCurrentIndex] = React.useState(0); @@ -75,7 +73,7 @@ function App() { }; return ( - +

Custom Widget

@@ -92,92 +90,12 @@ function App() { } appsmith.onReady(() => { + /* + * This handler function will get called when parent application is ready. + * Initialize your component here + * more info - ${CUSTOM_WIDGET_ONREADY_DOC_URL} + */ reactDom.render(, document.getElementById("root")); -});`, - }, - srcDoc: { - html: ` -
-`, - css: `.app { - height: calc(var(--appsmith-ui-height) * 1px); - width: calc(var(--appsmith-ui-width) * 1px); - justify-content: center; - border-radius: var(--appsmith-theme-borderRadius); - box-shadow: var(--appsmith-theme-boxShadow); -} - -.tip-container { - margin-bottom: 20px; -} - -.tip-container h2 { - margin-bottom: 20px; - font-size: 16px; - font-weight: 700; -} - -.tip-header { - display: flex; - justify-content: space-between; - align-items: baseline; -} - -.tip-header div { - color: #999; -} - -.button-container { - text-align: right; -} - -.button-container button { - margin: 0 10px; -} - -.button-container button.primary { - background: var(--appsmith-theme-primaryColor) !important; -} - -.button-container button.reset { - color: var(--appsmith-theme-primaryColor) !important; - border-color: var(--appsmith-theme-primaryColor) !important; -}`, - js: `import React from 'https://cdn.jsdelivr.net/npm/react@18.2.0/+esm'; -import reactDom from 'https://cdn.jsdelivr.net/npm/react-dom@18.2.0/+esm'; -import { Button, Card } from 'https://cdn.jsdelivr.net/npm/antd@5.11.1/+esm'; -import Markdown from 'https://cdn.jsdelivr.net/npm/react-markdown@9.0.1/+esm'; -const style = { - maxWidth: "400px" -}; -function App() { - const [currentIndex, setCurrentIndex] = React.useState(0); - const handleNext = () => { - setCurrentIndex(prevIndex => (prevIndex + 1) % appsmith.model.tips.length); - }; - const handleReset = () => { - setCurrentIndex(0); - appsmith.triggerEvent("onReset"); - }; - return /*#__PURE__*/React.createElement(Card, { - className: "app", - style: style - }, /*#__PURE__*/React.createElement("div", { - className: "tip-container" - }, /*#__PURE__*/React.createElement("div", { - className: "tip-header" - }, /*#__PURE__*/React.createElement("h2", null, "Custom Widget"), /*#__PURE__*/React.createElement("div", null, currentIndex + 1, " / ", appsmith.model.tips.length, " ")), /*#__PURE__*/React.createElement(Markdown, null, appsmith.model.tips[currentIndex])), /*#__PURE__*/React.createElement("div", { - className: "button-container" - }, /*#__PURE__*/React.createElement(Button, { - className: "primary", - onClick: handleNext, - type: "primary" - }, "Next Tip"), /*#__PURE__*/React.createElement(Button, { - onClick: handleReset - }, "Reset"))); -} -appsmith.onReady(() => { - reactDom.render( /*#__PURE__*/React.createElement(App, null), document.getElementById("root")); });`, }, }; diff --git a/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/vanillaJs.ts b/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/vanillaJs.ts new file mode 100644 index 0000000000..e86786d4c6 --- /dev/null +++ b/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/vanillaJs.ts @@ -0,0 +1,129 @@ +import { + CUSTOM_WIDGET_FEATURE, + createMessage, +} from "@appsmith/constants/messages"; +import { CUSTOM_WIDGET_ONREADY_DOC_URL } from "pages/Editor/CustomWidgetBuilder/constants"; + +export default { + key: createMessage(CUSTOM_WIDGET_FEATURE.templateKey.vanillaJs), + uncompiledSrcDoc: { + html: `
+
+
+

Custom Widget

+
+
+
+
+
+ + +
+
`, + css: `.app { + height: calc(var(--appsmith-ui-height) * 1px); + width: calc(var(--appsmith-ui-width) * 1px); + justify-content: center; + border-radius: var(--appsmith-theme-borderRadius); + box-shadow: var(--appsmith-theme-boxShadow); + padding: 29px 25px; + box-sizing: border-box; + font-family: system-ui; + background: #fff; +} + +.tip-container { + margin-bottom: 20px; + font-size: 14px; + line-height: 1.571429; +} + +.tip-container h2 { + margin-bottom: 20px; + font-size: 16px; + font-weight: 700; +} + +.tip-header { + display: flex; + justify-content: space-between; + align-items: baseline; + margin-bottom: 9px; +} + +.tip-header div { + color: #999; +} + +.button-container { + text-align: right; + padding-top: 4px; +} + +.button-container button { + margin: 0 10px; + cursor: pointer; + border-radius: var(--appsmith-theme-borderRadius); + padding: 6px 16px; + background: none; +} + +.button-container button#next { + background: var(--appsmith-theme-primaryColor) !important; + color: #fff; + border:1px solid var(--appsmith-theme-primaryColor) !important; +} + +.button-container button#reset { + border: 1px solid #999; + color: #999; + outline: none; + box-shadow: none; +} + +.button-container button#reset:hover:not(:disabled) { + color: var(--appsmith-theme-primaryColor); + border-color: var(--appsmith-theme-primaryColor); +} + +.button-container button#reset:disabled { + cursor: default; +}`, + js: `function initApp() { + const index = document.getElementById("index"); + const tip = document.getElementById("tip"); + const next = document.getElementById("next"); + const reset = document.getElementById("reset"); + + let currentIndex = 0; + + const updateDom = () => { + tip.innerHTML = appsmith.model.tips[currentIndex]; + index.innerHTML = (currentIndex + 1) + " / " + appsmith.model.tips.length; + reset.disabled = currentIndex === 0; + }; + + next.addEventListener("click", () => { + currentIndex = (currentIndex + 1) % appsmith.model.tips.length; + updateDom(); + }); + + reset.addEventListener("click", () => { + currentIndex = 0; + updateDom(); + appsmith.triggerEvent("onReset"); + }); + + updateDom(); +} + +appsmith.onReady(() => { + /* + * This handler function will get called when parent application is ready. + * Initialize your component here + * more info - ${CUSTOM_WIDGET_ONREADY_DOC_URL} + */ + initApp(); +});`, + }, +}; diff --git a/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/vue.ts b/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/vue.ts index 8506b6b9c4..440e54d3a5 100644 --- a/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/vue.ts +++ b/app/client/src/pages/Editor/CustomWidgetBuilder/Editor/Header/CodeTemplates/Templates/vue.ts @@ -2,31 +2,118 @@ import { CUSTOM_WIDGET_FEATURE, createMessage, } from "@appsmith/constants/messages"; +import { CUSTOM_WIDGET_ONREADY_DOC_URL } from "pages/Editor/CustomWidgetBuilder/constants"; export default { key: createMessage(CUSTOM_WIDGET_FEATURE.templateKey.vue), uncompiledSrcDoc: { - html: `
-

{{ msg }}

+ html: `
+
+
+

Custom Widget

+
{{ currentIndex + 1 }} / {{ tips.length }}
+
+
{{ tips[currentIndex] }}
+
+
+ + +
-`, - css: `#hello-world-app { - font-family: monospace; - width: 100vw; - height: 100vh; - display: flex; - justify-content: center; - align-items: center; +`, + css: `#app { + height: calc(var(--appsmith-ui-height) * 1px); + width: calc(var(--appsmith-ui-width) * 1px); + justify-content: center; + border-radius: var(--appsmith-theme-borderRadius); + box-shadow: var(--appsmith-theme-boxShadow); + padding: 29px 25px; + box-sizing: border-box; + font-family: system-ui; + background: #fff; +} + +.tip-container { + margin-bottom: 20px; + font-size: 14px; + line-height: 1.571429; +} + +.tip-container h2 { + margin-bottom: 20px; + font-size: 16px; + font-weight: 700; +} + +.tip-header { + display: flex; + justify-content: space-between; + align-items: baseline; + margin-bottom: 9px; +} + +.tip-header div { + color: #999; +} + +.button-container { + text-align: right; + padding-top: 4px; +} + +.button-container button { + margin: 0 10px; + cursor: pointer; + border-radius: var(--appsmith-theme-borderRadius); + padding: 6px 16px; + background: none; +} + +.button-container button#next { + background: var(--appsmith-theme-primaryColor) !important; + color: #fff; + border:1px solid var(--appsmith-theme-primaryColor) !important; +} + +.button-container button#reset { + border: 1px solid #999; + color: #999; + outline: none; + box-shadow: none; +} + +.button-container button#reset:hover:not(:disabled) { + color: var(--appsmith-theme-primaryColor); + border-color: var(--appsmith-theme-primaryColor); +} + +.button-container button#reset:disabled { + cursor: default; }`, - js: `new Vue({ - el: "#hello-world-app", - data() { - return { - msg: "Hello World by Vue!" - } - } + js: `appsmith.onReady(() => { + /* + * This handler function will get called when parent application is ready. + * Initialize your component here + * more info - ${CUSTOM_WIDGET_ONREADY_DOC_URL} + */ + new Vue({ + el: "#app", + data() { + return { + currentIndex: 0, + tips: appsmith.model.tips, + }; + }, + methods: { + next() { + this.currentIndex = (this.currentIndex + 1) % this.tips.length; + }, + reset() { + this.currentIndex = 0; + appsmith.triggerEvent("onReset"); + }, + }, + }); });`, }, }; diff --git a/app/client/src/pages/Editor/CustomWidgetBuilder/constants.ts b/app/client/src/pages/Editor/CustomWidgetBuilder/constants.ts index 42941c6bbd..23a74e2500 100644 --- a/app/client/src/pages/Editor/CustomWidgetBuilder/constants.ts +++ b/app/client/src/pages/Editor/CustomWidgetBuilder/constants.ts @@ -21,14 +21,14 @@ export const DEFAULT_CONTEXT_VALUE = { name: "", widgetId: "", srcDoc: { - html: "
Hello World
", - js: "function test() {console.log('Hello World');}", - css: "div {color: red;}", + html: "", + js: "", + css: "", }, uncompiledSrcDoc: { - html: "
Hello World
", - js: "function test() {console.log('Hello World');}", - css: "div {color: red;}", + html: "", + js: "", + css: "", }, model: {}, events: {}, @@ -59,3 +59,6 @@ export const CUSTOM_WIDGET_DOC_URL = export const CUSTOM_WIDGET_DEFAULT_MODEL_DOC_URL = "https://docs.appsmith.com/reference/widgets/custom#default-model"; + +export const CUSTOM_WIDGET_ONREADY_DOC_URL = + "https://docs.appsmith.com/reference/widgets/custom#onready"; diff --git a/app/client/src/pages/Editor/CustomWidgetBuilder/utility.test.ts b/app/client/src/pages/Editor/CustomWidgetBuilder/utility.test.ts index e1abfb4e7b..fa44efd508 100644 --- a/app/client/src/pages/Editor/CustomWidgetBuilder/utility.test.ts +++ b/app/client/src/pages/Editor/CustomWidgetBuilder/utility.test.ts @@ -56,14 +56,14 @@ describe("compileSrcDoc", () => { const result = compileSrcDoc(validSrcDoc); expect(result.code).toEqual(validSrcDoc); - expect(result.warnings).toHaveLength(0); + expect(result.warnings).toHaveLength(1); expect(result.errors).toHaveLength(0); }); it("should handle Babel compilation errors", () => { const srcDocWithErrors = { html: "
Hello World
", - js: "const a = 5 )", + js: "appsmith.onReady(() => {const a = 5 )})", css: "div { color: red; }", }; diff --git a/app/client/src/pages/Editor/CustomWidgetBuilder/utility.ts b/app/client/src/pages/Editor/CustomWidgetBuilder/utility.ts index 0a2f3f0b32..9803273187 100644 --- a/app/client/src/pages/Editor/CustomWidgetBuilder/utility.ts +++ b/app/client/src/pages/Editor/CustomWidgetBuilder/utility.ts @@ -1,5 +1,10 @@ import { transform } from "@babel/standalone/"; import type { DebuggerLogItem, SrcDoc } from "./types"; +import { + CUSTOM_WIDGET_FEATURE, + createMessage, +} from "@appsmith/constants/messages"; +import { CUSTOM_WIDGET_ONREADY_DOC_URL } from "./constants"; interface CompiledResult { code: SrcDoc; @@ -14,6 +19,8 @@ export const compileSrcDoc = (srcDoc: SrcDoc): CompiledResult => { errors: [], }; + checkForWarnings(compiledResult); + try { const result = transform(srcDoc.js, { sourceType: "module", @@ -34,6 +41,24 @@ export const compileSrcDoc = (srcDoc: SrcDoc): CompiledResult => { return compiledResult; }; +function checkForWarnings(compiledResult: CompiledResult) { + const code = compiledResult.code.js; + + if (code?.length > 0) { + /* + * We are keeping this check as a simple string check instead of using AST + * because we want to keep the custom widget compile process as simple as possible. + */ + !code.includes("appsmith.onReady(") && + compiledResult.warnings.push({ + message: createMessage( + CUSTOM_WIDGET_FEATURE.debugger.noOnReadyWarning, + CUSTOM_WIDGET_ONREADY_DOC_URL, + ), + }); + } +} + export interface BabelError { reasonCode: string; message: string; diff --git a/app/client/src/widgets/CustomWidget/component/appsmithConsole.js b/app/client/src/widgets/CustomWidget/component/appsmithConsole.js index 1c27b1b5eb..1f68e5ed4a 100644 --- a/app/client/src/widgets/CustomWidget/component/appsmithConsole.js +++ b/app/client/src/widgets/CustomWidget/component/appsmithConsole.js @@ -25,7 +25,7 @@ }, }); - ["log", "warn", "info"].forEach((method) => { + ["log", "warn", "info", "error"].forEach((method) => { nativeConsole[method] = createProxy(method); }); diff --git a/app/client/src/widgets/CustomWidget/component/index.tsx b/app/client/src/widgets/CustomWidget/component/index.tsx index 82f29e6b0a..149b9b7612 100644 --- a/app/client/src/widgets/CustomWidget/component/index.tsx +++ b/app/client/src/widgets/CustomWidget/component/index.tsx @@ -23,6 +23,7 @@ import { combinedPreviewModeSelector } from "selectors/editorSelectors"; import { getAppMode } from "@appsmith/selectors/applicationSelectors"; import { APP_MODE } from "entities/App"; import { getWidgetPropsForPropertyPane } from "selectors/propertyPaneSelectors"; +import AnalyticsUtil from "utils/AnalyticsUtil"; const StyledIframe = styled.iframe<{ width: number; height: number }>` width: ${(props) => props.width - 8}px; @@ -101,6 +102,16 @@ function CustomComponent(props: CustomComponentProps) { }, "*", ); + + if ( + props.renderMode === "DEPLOYED" || + props.renderMode === "EDITOR" + ) { + AnalyticsUtil.logEvent("CUSTOM_WIDGET_LOAD_INIT", { + widgetId: props.widgetId, + renderMode: props.renderMode, + }); + } break; case EVENTS.CUSTOM_WIDGET_UPDATE_MODEL: props.update(message.data); diff --git a/app/client/src/widgets/CustomWidget/widget/defaultApp.ts b/app/client/src/widgets/CustomWidget/widget/defaultApp.ts index d98fb154eb..4451d46fd0 100644 --- a/app/client/src/widgets/CustomWidget/widget/defaultApp.ts +++ b/app/client/src/widgets/CustomWidget/widget/defaultApp.ts @@ -1,3 +1,5 @@ +import { CUSTOM_WIDGET_ONREADY_DOC_URL } from "pages/Editor/CustomWidgetBuilder/constants"; + export default { uncompiledSrcDoc: { html: ` @@ -82,6 +84,11 @@ function App() { } appsmith.onReady(() => { + /* + * This handler function will get called when parent application is ready. + * Initialize your component here + * more info - ${CUSTOM_WIDGET_ONREADY_DOC_URL} + */ reactDom.render(, document.getElementById("root")); });`, }, diff --git a/app/client/src/widgets/CustomWidget/widget/index.tsx b/app/client/src/widgets/CustomWidget/widget/index.tsx index 2b370b5ff0..d943782935 100644 --- a/app/client/src/widgets/CustomWidget/widget/index.tsx +++ b/app/client/src/widgets/CustomWidget/widget/index.tsx @@ -31,6 +31,7 @@ import { Link } from "design-system"; import styled from "styled-components"; import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; import { Colors } from "constants/Colors"; +import AnalyticsUtil from "utils/AnalyticsUtil"; const StyledLink = styled(Link)` display: inline-block; @@ -332,6 +333,11 @@ class CustomWidget extends BaseWidget { }, globalContext: contextObj, }); + + AnalyticsUtil.logEvent("CUSTOM_WIDGET_API_TRIGGER_EVENT", { + widgetId: this.props.widgetId, + eventName, + }); } }; @@ -340,6 +346,10 @@ class CustomWidget extends BaseWidget { ...this.props.model, ...data, }); + + AnalyticsUtil.logEvent("CUSTOM_WIDGET_API_UPDATE_MODEL", { + widgetId: this.props.widgetId, + }); }; getRenderMode = () => { From f52e4781c111cec9e10c042ffe362ec1f55f77cb Mon Sep 17 00:00:00 2001 From: Jacques Ikot Date: Wed, 17 Jan 2024 15:48:57 +0100 Subject: [PATCH 02/16] feat: update canvas building blocks (#30311) ## Description **Goal** 1. To remove the dashboard canvas starter building block and replace it with the Sort and Filter Table building block from the templates gallery. 2. To replace all canvas starter icons with the corresponding building blocks icons. **Steps** - Add a new page to the Starter Templates app called Sort and Filter Table - Export the existing Sort and Filter Template and import into the new page in Starter Templates using PIE - Change the copy for title and description in the code - Take a screenshot of the Sort and Filter table home page, upload to Contentful and update the screenshot URL. - Download and add new Icons for all 3 canvas starter blocks - Deploy updated Starter Templates application and upload new JSON to S3 under the same name #### PR fixes following issue(s) Fixes #30261 #### Type of change > Please delete options that are not relevant. - New feature (non-breaking change which adds functionality) ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [ ] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed ## Summary by CodeRabbit - **New Features** - Updated the naming and descriptions of template page layouts for improved clarity. - Reorganized and renamed SVG icons for consistency across the platform. - **Style** - Enhanced the styling of template layout titles and descriptions for better readability. --- .../canvas-starter-record-details.svg | 72 +++++++++++++++++++ .../templates/canvas-starter-record-edit.svg | 68 ++++++++++++++++++ .../canvas-starter-sort-filter-table.svg | 56 +++++++++++++++ .../templates/starter-template-dashboard.svg | 35 --------- .../icons/templates/starter-template-form.svg | 19 ----- .../starter-template-record-details.svg | 22 ------ .../starter-template-record-edit.svg | 20 ------ app/client/src/ce/constants/messages.ts | 6 +- .../src/constants/TemplatesConstants.tsx | 18 ++--- .../StyledComponents.tsx | 33 +-------- .../starterBuildingBlocks/index.tsx | 39 +++++++--- 11 files changed, 239 insertions(+), 149 deletions(-) create mode 100644 app/client/src/assets/icons/templates/canvas-starter-record-details.svg create mode 100644 app/client/src/assets/icons/templates/canvas-starter-record-edit.svg create mode 100644 app/client/src/assets/icons/templates/canvas-starter-sort-filter-table.svg delete mode 100644 app/client/src/assets/icons/templates/starter-template-dashboard.svg delete mode 100644 app/client/src/assets/icons/templates/starter-template-form.svg delete mode 100644 app/client/src/assets/icons/templates/starter-template-record-details.svg delete mode 100644 app/client/src/assets/icons/templates/starter-template-record-edit.svg diff --git a/app/client/src/assets/icons/templates/canvas-starter-record-details.svg b/app/client/src/assets/icons/templates/canvas-starter-record-details.svg new file mode 100644 index 0000000000..a99ab3a45f --- /dev/null +++ b/app/client/src/assets/icons/templates/canvas-starter-record-details.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/client/src/assets/icons/templates/canvas-starter-record-edit.svg b/app/client/src/assets/icons/templates/canvas-starter-record-edit.svg new file mode 100644 index 0000000000..1ddd1825ad --- /dev/null +++ b/app/client/src/assets/icons/templates/canvas-starter-record-edit.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/client/src/assets/icons/templates/canvas-starter-sort-filter-table.svg b/app/client/src/assets/icons/templates/canvas-starter-sort-filter-table.svg new file mode 100644 index 0000000000..4251d635c9 --- /dev/null +++ b/app/client/src/assets/icons/templates/canvas-starter-sort-filter-table.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/client/src/assets/icons/templates/starter-template-dashboard.svg b/app/client/src/assets/icons/templates/starter-template-dashboard.svg deleted file mode 100644 index 7ca68acc1b..0000000000 --- a/app/client/src/assets/icons/templates/starter-template-dashboard.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/client/src/assets/icons/templates/starter-template-form.svg b/app/client/src/assets/icons/templates/starter-template-form.svg deleted file mode 100644 index a1b35aaca7..0000000000 --- a/app/client/src/assets/icons/templates/starter-template-form.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/app/client/src/assets/icons/templates/starter-template-record-details.svg b/app/client/src/assets/icons/templates/starter-template-record-details.svg deleted file mode 100644 index 7fdb6e5930..0000000000 --- a/app/client/src/assets/icons/templates/starter-template-record-details.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/client/src/assets/icons/templates/starter-template-record-edit.svg b/app/client/src/assets/icons/templates/starter-template-record-edit.svg deleted file mode 100644 index 80f8a3d5e9..0000000000 --- a/app/client/src/assets/icons/templates/starter-template-record-edit.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index 72a538d10d..5e29aa561f 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -2229,9 +2229,9 @@ export const DATASOURCE_BLANK_STATE_MESSAGE = () => "No datasources to display"; export const STARTER_TEMPLATE_PAGE_LAYOUTS = { header: () => "Choose a template", layouts: { - dashboard: { - name: () => "Visualize your data", - description: () => "Use to see your data in charts", + sortFilterTable: { + name: () => "Filter your data", + description: () => "Use to filter and sort your data", }, form: { name: () => "Form", diff --git a/app/client/src/constants/TemplatesConstants.tsx b/app/client/src/constants/TemplatesConstants.tsx index c7288d20ab..f53726e28d 100644 --- a/app/client/src/constants/TemplatesConstants.tsx +++ b/app/client/src/constants/TemplatesConstants.tsx @@ -12,15 +12,15 @@ export const COMMUNITY_PORTAL = { const RecordEdit = importSvg( async () => - import("../assets/icons/templates/starter-template-record-edit.svg"), + import("../assets/icons/templates/canvas-starter-record-edit.svg"), ); const RecordDetails = importSvg( async () => - import("../assets/icons/templates/starter-template-record-details.svg"), + import("../assets/icons/templates/canvas-starter-record-details.svg"), ); -const Dashboard = importSvg( +const SortFilterTable = importSvg( async () => - import("../assets/icons/templates/starter-template-dashboard.svg"), + import("../assets/icons/templates/canvas-starter-sort-filter-table.svg"), ); export const STARTER_BUILDING_BLOCK_TEMPLATE_NAME = "Starter Building Block"; @@ -61,17 +61,17 @@ export const STARTER_BUILDING_BLOCKS = { { id: 3, title: createMessage( - STARTER_BUILDING_BLOCKS_LAYOUTS.layouts.dashboard.name, + STARTER_BUILDING_BLOCKS_LAYOUTS.layouts.sortFilterTable.name, ), description: createMessage( - STARTER_BUILDING_BLOCKS_LAYOUTS.layouts.dashboard.description, + STARTER_BUILDING_BLOCKS_LAYOUTS.layouts.sortFilterTable.description, ), - icon: , + icon: , screenshot: - "https://s3.us-east-2.amazonaws.com/template.appsmith.com/canvas-starter-page-layout-dashboard.png", + "https://images.ctfassets.net/lpvian6u6i39/55ERsTeUvbAzJVaBBsInZr/0009fee0adb710b91c18a5bdc989deeb/canvas-starter-building-block-sort-filter-table.png?fm=png&q=50", templateId: "6530e343fa63b553e4be0266", templateName: STARTER_BUILDING_BLOCK_TEMPLATE_NAME, - templatePageName: "Dashboard", + templatePageName: "Sort and Filter Table", }, ], }; diff --git a/app/client/src/layoutSystems/common/dropTarget/starterBuildingBlocks/StyledComponents.tsx b/app/client/src/layoutSystems/common/dropTarget/starterBuildingBlocks/StyledComponents.tsx index 32b0ed5224..132faaf4ac 100644 --- a/app/client/src/layoutSystems/common/dropTarget/starterBuildingBlocks/StyledComponents.tsx +++ b/app/client/src/layoutSystems/common/dropTarget/starterBuildingBlocks/StyledComponents.tsx @@ -1,5 +1,4 @@ import styled from "styled-components"; -import { Text } from "design-system"; import { Colors } from "constants/Colors"; @@ -49,35 +48,6 @@ export const TemplateLayoutContainer = styled.div` } `; -export const TemplateLayoutHeaderText = styled(Text)<{ layoutActive: boolean }>` - font-size: 16px; - font-weight: 600; - line-height: 24px; - margin-bottom: 16px; - color: var(--colors-semantics-text-emphasis); - opacity: ${(props) => (props.layoutActive ? "1" : "0.7")}; -`; - -export const TemplateLayoutRowItemTitle = styled.p<{ layoutActive: boolean }>` - font-size: 14px; - line-height: 20px; - text-align: center; - font-weight: 500; - color: var(--colors-ui-content-heading-sub-section-heading); - opacity: ${(props) => (props.layoutActive ? "1" : "0.7")}; -`; - -export const TemplateLayoutRowItemDescription = styled.p<{ - layoutActive: boolean; -}>` - font-size: 12px; - line-height: 16px; - text-align: center; - font-weight: 400; - color: var(--colors-ui-content-supplementary); - opacity: ${(props) => (props.layoutActive ? "1" : "0.7")}; -`; - export const TemplateLayoutContentGrid = styled.div` display: flex; justify-content: center; @@ -116,7 +86,8 @@ export const TemplateLayoutContentItemContent = styled.div` export const IconContainer = styled.div<{ layoutItemActive: boolean }>` border-width: 1px; border-radius: 4px; - margin-bottom: 8px; + margin-bottom: 16px; + margin-top: 8px; border-color: ${(props) => props.layoutItemActive ? Colors.PRIMARY_ORANGE : "transparent"}; `; diff --git a/app/client/src/layoutSystems/common/dropTarget/starterBuildingBlocks/index.tsx b/app/client/src/layoutSystems/common/dropTarget/starterBuildingBlocks/index.tsx index 25e247a3e9..9c1e714e71 100644 --- a/app/client/src/layoutSystems/common/dropTarget/starterBuildingBlocks/index.tsx +++ b/app/client/src/layoutSystems/common/dropTarget/starterBuildingBlocks/index.tsx @@ -15,7 +15,7 @@ import { STARTER_BUILDING_BLOCKS, STARTER_BUILDING_BLOCK_TEMPLATE_NAME, } from "constants/TemplatesConstants"; -import { Button } from "design-system"; +import { Button, Text } from "design-system"; import LoadingScreen from "pages/Templates/TemplatesModal/LoadingScreen"; import { useDispatch, useSelector } from "react-redux"; import { @@ -31,9 +31,6 @@ import { TemplateLayoutContentItem, TemplateLayoutContentItemContent, TemplateLayoutFrame, - TemplateLayoutHeaderText, - TemplateLayoutRowItemDescription, - TemplateLayoutRowItemTitle, } from "./StyledComponents"; function StarterBuildingBlocks() { @@ -128,9 +125,16 @@ function StarterBuildingBlocks() { onMouseEnter={() => setLayoutActive(true)} onMouseLeave={() => setLayoutActive(false)} > - + {createMessage(STARTER_TEMPLATE_PAGE_LAYOUTS.header)} - + {layoutItems.map((item, index) => ( @@ -155,12 +159,27 @@ function StarterBuildingBlocks() { - + {item.title} - - + + {item.description} - + ))} From 186d9934e8b584bd1e002aaa041316b7011153a0 Mon Sep 17 00:00:00 2001 From: Anagh Hegde Date: Wed, 17 Jan 2024 21:09:44 +0530 Subject: [PATCH 03/16] fix: Queries do not get exported in a git connected app, by using partial export. (#30368) ## Description The issue happens on a feature branch for a git connected application. Works fine on master branch. The reason is how the ids are handled for a git connected application. We use the defaultResource and map the actual id with defaultResourceId when sending the data to client. So for a git connected app the backend code was using the mongo _id to filter the selected action but the client sent the defaultResourceId in the request payload. Hence even though the mongo query returned the correct result the filter on the list was returning the empty list. Used the default resource id for filtering #### PR fixes following issue(s) Fixes https://github.com/appsmithorg/appsmith/issues/29917 #### Type of change - Bug fix (non-breaking change which fixes an issue) ## Testing #### How Has This Been Tested? - [ ] Manual - [ ] JUnit #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed ## Summary by CodeRabbit - **New Features** - Enhanced export functionality in git-connected applications to allow filtering by branch name. --- .../internal/PartialExportServiceCEImpl.java | 26 +++++-- .../services/PartialExportServiceTest.java | 71 +++++++++++++++++++ 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/PartialExportServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/PartialExportServiceCEImpl.java index ea103a46be..80f99d269b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/PartialExportServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/PartialExportServiceCEImpl.java @@ -136,7 +136,8 @@ public class PartialExportServiceCEImpl implements PartialExportServiceCE { branchedPageId, partialExportFileDTO.getActionCollectionList(), applicationJson, - mappedResourcesDTO) + mappedResourcesDTO, + branchName) .then(Mono.just(branchedPageId)); } return Mono.just(branchedPageId); @@ -148,7 +149,8 @@ public class PartialExportServiceCEImpl implements PartialExportServiceCE { branchedPageId, partialExportFileDTO.getActionList(), applicationJson, - mappedResourcesDTO) + mappedResourcesDTO, + branchName) .then(Mono.just(branchedPageId)); } return Mono.just(branchedPageId); @@ -212,11 +214,17 @@ public class PartialExportServiceCEImpl implements PartialExportServiceCE { String pageId, List validActions, ApplicationJson applicationJson, - MappedExportableResourcesDTO mappedResourcesDTO) { + MappedExportableResourcesDTO mappedResourcesDTO, + String branchName) { return newActionService.findByPageId(pageId).collectList().flatMap(actions -> { + // For git connected app, the filtering has to be done on the default action id + // since the client is not aware of the branched resource id List updatedActionList = actions.stream() - .filter(action -> validActions.contains(action.getId())) + .filter(action -> branchName != null + ? validActions.contains(action.getDefaultResources().getActionId()) + : validActions.contains(action.getId())) .toList(); + // Map name to id for exportable entities newActionExportableService.mapNameToIdForExportableEntities(mappedResourcesDTO, updatedActionList); // Make it exportable by removing the ids @@ -232,10 +240,16 @@ public class PartialExportServiceCEImpl implements PartialExportServiceCE { String pageId, List validActions, ApplicationJson applicationJson, - MappedExportableResourcesDTO mappedResourcesDTO) { + MappedExportableResourcesDTO mappedResourcesDTO, + String branchName) { return actionCollectionService.findByPageId(pageId).collectList().flatMap(actionCollections -> { + // For git connected app, the filtering has to be done on the default actionCollection id + // since the client is not aware of the branched resource id List updatedActionCollectionList = actionCollections.stream() - .filter(actionCollection -> validActions.contains(actionCollection.getId())) + .filter(actionCollection -> branchName != null + ? validActions.contains( + actionCollection.getDefaultResources().getCollectionId()) + : validActions.contains(actionCollection.getId())) .toList(); // Map name to id for exportable entities actionCollectionExportableService.mapNameToIdForExportableEntities( diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PartialExportServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PartialExportServiceTest.java index 8bebe5e3c2..ed40c09bc0 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PartialExportServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PartialExportServiceTest.java @@ -340,4 +340,75 @@ public class PartialExportServiceTest { }) .verifyComplete(); } + + @Test + @WithUserDetails(value = "api_user") + public void testGetPartialExport_gitConnectedApp_featureBranchResourceExported() { + Mockito.when(pluginService.findAllByIdsWithoutPermission(Mockito.any(), Mockito.anyList())) + .thenReturn(Flux.fromIterable(List.of(installedPlugin, installedJsPlugin))); + + Application application = + createGitConnectedApp("testGetPartialExport_gitConnectedApp_featureBranchResourceExported"); + + // update git branch name for page + PageDTO savedPage = new PageDTO(); + savedPage.setName("Page 2"); + savedPage.setApplicationId(application.getId()); + DefaultResources defaultResources = new DefaultResources(); + defaultResources.setApplicationId(application.getId()); + defaultResources.setBranchName("master"); + savedPage.setDefaultResources(defaultResources); + savedPage = applicationPageService + .createPageWithBranchName(savedPage, "master") + .block(); + + // Create Action + ActionDTO action = new ActionDTO(); + action.setName("validAction"); + action.setPageId(savedPage.getId()); + action.setExecuteOnLoad(true); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + actionConfiguration.setTimeoutInMillisecond("6000"); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasourceMap.get("DS1")); + DefaultResources defaultResource = new DefaultResources(); + defaultResource.setApplicationId(application.getId()); + defaultResource.setBranchName("master"); + defaultResource.setActionId("testActionId"); + action.setDefaultResources(defaultResource); + + ActionDTO savedAction = + layoutActionService.createSingleAction(action, Boolean.FALSE).block(); + + PartialExportFileDTO partialExportFileDTO = new PartialExportFileDTO(); + partialExportFileDTO.setDatasourceList(List.of( + datasourceMap.get("DS1").getId(), datasourceMap.get("DS2").getId())); + // For a feature branch the resources in the client always get the default resource id + partialExportFileDTO.setActionList(List.of("testActionId")); + + // Get the partial export resources + Mono partialExportFileDTOMono = partialExportService.getPartialExportResources( + application.getId(), savedPage.getId(), "master", partialExportFileDTO); + + StepVerifier.create(partialExportFileDTOMono) + .assertNext(applicationJson -> { + assertThat(applicationJson.getDatasourceList().size()).isEqualTo(2); + List dsNames = applicationJson.getDatasourceList().stream() + .map(DatasourceStorage::getName) + .toList(); + assertThat(dsNames).containsAll(List.of("DS1", "DS2")); + assertThat(applicationJson.getDatasourceList().get(0).getPluginId()) + .isEqualTo("installed-plugin"); + assertThat(applicationJson.getDatasourceList().get(1).getPluginId()) + .isEqualTo("installed-plugin"); + assertThat(applicationJson.getActionList().size()).isEqualTo(1); + + NewAction newAction = applicationJson.getActionList().get(0); + assertThat(newAction.getUnpublishedAction().getName()).isEqualTo("validAction"); + assertThat(newAction.getUnpublishedAction().getPageId()).isEqualTo("Page 2"); + assertThat(newAction.getId()).isEqualTo("Page 2_validAction"); + }) + .verifyComplete(); + } } From 81561a8c52d41c4edec92cbec1b718e61ad749ab Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Wed, 17 Jan 2024 21:15:30 +0530 Subject: [PATCH 04/16] fix: Allow request headers up to 16KB (#30405) This is so that large headers like JWT tokens can be accepted. [Relevant Slack conversation](https://theappsmith.slack.com/archives/C023V5Y0STH/p1705472948243609?thread_ts=1705395384.537429&cid=C023V5Y0STH). --- .../appsmith-server/src/main/resources/application.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/server/appsmith-server/src/main/resources/application.properties b/app/server/appsmith-server/src/main/resources/application.properties index 949752ecc3..b680f8c262 100644 --- a/app/server/appsmith-server/src/main/resources/application.properties +++ b/app/server/appsmith-server/src/main/resources/application.properties @@ -2,6 +2,8 @@ server.port=${PORT:8080} # Allow the Spring context to close all active requests before shutting down the server # Please ref: https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/reference/html/spring-boot-features.html#boot-features-graceful-shutdown server.shutdown=graceful +server.max-http-request-header-size=16KB + spring.lifecycle.timeout-per-shutdown-phase=20s spring.profiles.active=${ACTIVE_PROFILE:production} From c7eb8c115c1ab0b59b38c552090d9e537d81a435 Mon Sep 17 00:00:00 2001 From: Rishabh Rathod Date: Thu, 18 Jan 2024 00:20:48 +0530 Subject: [PATCH 05/16] fix: JSModule action selector bug (#30400) ## Description JSModule action selector name issue #### PR fixes following issue(s) Fixes # (issue number) #### Type of change - Bug fix (non-breaking change which fixes an issue) ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [ ] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed ## Summary by CodeRabbit - **Refactor** - Updated the naming convention for JavaScript functions in the action creator. --- .../src/components/editorComponents/ActionCreator/helpers.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/src/components/editorComponents/ActionCreator/helpers.tsx b/app/client/src/components/editorComponents/ActionCreator/helpers.tsx index 359e3ca3ae..066c761f62 100644 --- a/app/client/src/components/editorComponents/ActionCreator/helpers.tsx +++ b/app/client/src/components/editorComponents/ActionCreator/helpers.tsx @@ -604,7 +604,7 @@ export function getJSOptions( const jsFunction = { label: js.name, id: js.id, - value: jsModuleInstance.config.name + "." + js.name, + value: jsModuleInstance.name + "." + js.name, type: jsOption.value, icon: , args: argValue, From b6726e223f907f31cfe44cf02ee1a3b512ed7d52 Mon Sep 17 00:00:00 2001 From: balajisoundar Date: Thu, 18 Jan 2024 11:29:55 +0530 Subject: [PATCH 06/16] chore: fix custom widget selection issue in widget builder (#30408) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change > Please delete options that are not relevant. - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) - Breaking change (fix or feature that would cause existing functionality to not work as expected) - Chore (housekeeping or task changes that don't impact user perception) - This change requires a documentation update > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [x] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed ## Summary by CodeRabbit - **Refactor** - Improved the logic for overlay requirements in the Custom Widget based on the rendering mode. --- app/client/src/widgets/CustomWidget/component/index.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/client/src/widgets/CustomWidget/component/index.tsx b/app/client/src/widgets/CustomWidget/component/index.tsx index 149b9b7612..6ece788f7d 100644 --- a/app/client/src/widgets/CustomWidget/component/index.tsx +++ b/app/client/src/widgets/CustomWidget/component/index.tsx @@ -20,8 +20,6 @@ import type { Color } from "constants/Colors"; import { connect } from "react-redux"; import type { AppState } from "@appsmith/reducers"; import { combinedPreviewModeSelector } from "selectors/editorSelectors"; -import { getAppMode } from "@appsmith/selectors/applicationSelectors"; -import { APP_MODE } from "entities/App"; import { getWidgetPropsForPropertyPane } from "selectors/propertyPaneSelectors"; import AnalyticsUtil from "utils/AnalyticsUtil"; @@ -267,11 +265,10 @@ export const mapStateToProps = ( ownProps: CustomComponentProps, ) => { const isPreviewMode = combinedPreviewModeSelector(state); - const appMode = getAppMode(state); return { needsOverlay: - appMode == APP_MODE.EDIT && + ownProps.renderMode === "EDITOR" && !isPreviewMode && ownProps.widgetId !== getWidgetPropsForPropertyPane(state)?.widgetId, }; From d6c4ec642680a0e5c3f17a70c97151d498bed76d Mon Sep 17 00:00:00 2001 From: Nikhil Nandagopal Date: Thu, 18 Jan 2024 11:48:59 +0530 Subject: [PATCH 07/16] Updated Label Config --- .github/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/config.json b/.github/config.json index 0189b527aa..0f323c185a 100644 --- a/.github/config.json +++ b/.github/config.json @@ -1 +1 @@ -{"runners":[{"versioning":{"source":"milestones","type":"SemVer"},"prereleaseName":"alpha","issue":{"labels":{"Error Handling":{"conditions":[],"requires":1},"Templates pod":{"conditions":[{"label":"Templates","type":"hasLabel","value":true},{"label":"Community template","type":"hasLabel","value":true},{"label":"Partial-import-export","type":"hasLabel","value":true}],"requires":1},"Team Managers Pod":{"conditions":[{"label":"Settings","type":"hasLabel","value":true},{"label":"Home Page","type":"hasLabel","value":true},{"label":"Realtime Commenting","type":"hasLabel","value":true},{"label":"SSO","type":"hasLabel","value":true},{"label":"Multi User Realtime","type":"hasLabel","value":true},{"label":"RBAC","type":"hasLabel","value":true},{"label":"ABAC","type":"hasLabel","value":true},{"label":"Audit Logs","type":"hasLabel","value":true},{"label":"Multitenancy","type":"hasLabel","value":true},{"label":"Airgap","type":"hasLabel","value":true},{"label":"Enterprise Edition","type":"hasLabel","value":true},{"label":"SCIM","type":"hasLabel","value":true},{"label":"Invite flow","type":"hasLabel","value":true}],"requires":1},"New Developers Pod":{"conditions":[{"label":"Omnibar","type":"hasLabel","value":true},{"label":"Telemetry","type":"hasLabel","value":true},{"label":"Entity Explorer","type":"hasLabel","value":true},{"label":"IDE","type":"hasLabel","value":true},{"label":"Example Apps","type":"hasLabel","value":true},{"label":"i18n","type":"hasLabel","value":true},{"label":"IDE Navigation","type":"hasLabel","value":true},{"label":"Clean URLs","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"App setting","type":"hasLabel","value":true}],"requires":1},"BE Coders Pod":{"conditions":[{"label":"SAAS Plugins","type":"hasLabel","value":true},{"label":"SAAS Manager App","type":"hasLabel","value":true},{"label":"Data Platform Pod","type":"hasLabel","value":true},{"label":"Integrations Pod","type":"hasLabel","value":true}],"requires":1},"FE Coders Pod":{"conditions":[{"label":"JS Linting & Errors","type":"hasLabel","value":true},{"label":"Debugger","type":"hasLabel","value":true},{"label":"JS Snippets","type":"hasLabel","value":true},{"label":"Autocomplete","type":"hasLabel","value":true},{"label":"Evaluated Value","type":"hasLabel","value":true},{"label":"Slash Command","type":"hasLabel","value":true},{"label":"New JS Function","type":"hasLabel","value":true},{"label":"JS Promises","type":"hasLabel","value":true},{"label":"JS Usability","type":"hasLabel","value":true},{"label":"Code Refactoring","type":"hasLabel","value":true},{"label":"storeValue","type":"hasLabel","value":true},{"label":"OnPageLoad","type":"hasLabel","value":true},{"label":"Framework Functions","type":"hasLabel","value":true},{"label":"Code Editor","type":"hasLabel","value":true},{"label":"JS Objects","type":"hasLabel","value":true},{"label":"JS Evaluation","type":"hasLabel","value":true},{"label":"AST-frontend","type":"hasLabel","value":true},{"label":"Custom JS Libraries","type":"hasLabel","value":true},{"label":"Action Selector","type":"hasLabel","value":true},{"label":"JS Function execution","type":"hasLabel","value":true},{"label":"Widget setter method","type":"hasLabel","value":true},{"label":"Error Handling","type":"hasLabel","value":true}],"requires":1},"App Viewers Pod":{"conditions":[{"label":"Button Widget","type":"hasLabel","value":true},{"label":"Chart Widget","type":"hasLabel","value":true},{"label":"Container Widget","type":"hasLabel","value":true},{"label":"Date Picker Widget","type":"hasLabel","value":true},{"label":"Select Widget","type":"hasLabel","value":true},{"label":"File Picker Widget","type":"hasLabel","value":true},{"label":"Form Widget","type":"hasLabel","value":true},{"label":"Image Widget","type":"hasLabel","value":true},{"label":"Input Widget","type":"hasLabel","value":true},{"label":"List Widget","type":"hasLabel","value":true},{"label":"MultiSelect Widget","type":"hasLabel","value":true},{"label":"Map Widget","type":"hasLabel","value":true},{"label":"Modal Widget","type":"hasLabel","value":true},{"label":"Radio Widget","type":"hasLabel","value":true},{"label":"Rich Text Editor Widget","type":"hasLabel","value":true},{"label":"Tab Widget","type":"hasLabel","value":true},{"label":"Table Widget","type":"hasLabel","value":true},{"label":"Text Widget","type":"hasLabel","value":true},{"label":"Video Widget","type":"hasLabel","value":true},{"label":"iFrame","type":"hasLabel","value":true},{"label":"Menu Button","type":"hasLabel","value":true},{"label":"Rating","type":"hasLabel","value":true},{"label":"Widget Validation","type":"hasLabel","value":true},{"label":"reallabel","type":"hasLabel","value":true},{"label":"New Widget","type":"hasLabel","value":true},{"label":"Switch widget","type":"hasLabel","value":true},{"label":"Audio Widget","type":"hasLabel","value":true},{"label":"Icon Button Widget","type":"hasLabel","value":true},{"label":"Stat Box Widget","type":"hasLabel","value":true},{"label":"Voice Recorder Widget","type":"hasLabel","value":true},{"label":"Calendar Widget","type":"hasLabel","value":true},{"label":"Menu Button Widget","type":"hasLabel","value":true},{"label":"Divider Widget","type":"hasLabel","value":true},{"label":"Rating Widget","type":"hasLabel","value":true},{"label":"App Navigation","type":"hasLabel","value":true},{"label":"View Mode","type":"hasLabel","value":true},{"label":"Widget Property","type":"hasLabel","value":true},{"label":"Document Viewer Widget","type":"hasLabel","value":true},{"label":"Radio Group Widget","type":"hasLabel","value":true},{"label":"Currency Input Widget","type":"hasLabel","value":true},{"label":"TreeSelect","type":"hasLabel","value":true},{"label":"MultiTree Select Widget","type":"hasLabel","value":true},{"label":"Phone Input Widget","type":"hasLabel","value":true},{"label":"JSON Form","type":"hasLabel","value":true},{"label":"All Widgets","type":"hasLabel","value":true},{"label":"Button Group widget","type":"hasLabel","value":true},{"label":"Progress bar widget","type":"hasLabel","value":true},{"label":"Audio Recorder Widget","type":"hasLabel","value":true},{"label":"Camera Widget","type":"hasLabel","value":true},{"label":"Table Widget V2","type":"hasLabel","value":true},{"label":"Branding","type":"hasLabel","value":true},{"label":"Map Chart Widget","type":"hasLabel","value":true},{"label":"Code Scanner Widget","type":"hasLabel","value":true},{"label":"Widget keyboard accessibility","type":"hasLabel","value":true},{"label":"List Widget V2","type":"hasLabel","value":true},{"label":"Slider Widget","type":"hasLabel","value":true},{"label":"One-click Binding","type":"hasLabel","value":true},{"label":"Old widget version","type":"hasLabel","value":true},{"label":"Widget Discoverability","type":"hasLabel","value":true},{"label":"Custom widgets","type":"hasLabel","value":true}],"requires":1},"UI Builders Pod":{"conditions":[{"label":"Property Pane","type":"hasLabel","value":true},{"label":"Pages","type":"hasLabel","value":true},{"label":"Copy Paste","type":"hasLabel","value":true},{"label":"Drag & Drop","type":"hasLabel","value":true},{"label":"Undo/Redo","type":"hasLabel","value":true},{"label":"Widgets Pane","type":"hasLabel","value":true},{"label":"UI Performance","type":"hasLabel","value":true},{"label":"Widget Grouping","type":"hasLabel","value":true},{"label":"Reflow & Resize","type":"hasLabel","value":true},{"label":"Canvas / Grid","type":"hasLabel","value":true},{"label":"Canvas Zooms","type":"hasLabel","value":true},{"label":"Frontend Libraries Upgrade","type":"hasLabel","value":true},{"label":"Auto Height","type":"hasLabel","value":true},{"label":"Responsive Canvas","type":"hasLabel","value":true},{"label":"Responsive Widget","type":"hasLabel","value":true},{"label":"Responsive Viewport","type":"hasLabel","value":true},{"label":"Conversion Algorithm","type":"hasLabel","value":true},{"label":"Spacing","type":"hasLabel","value":true},{"label":"Browser specific","type":"hasLabel","value":true},{"label":"widget vertical alignment","type":"hasLabel","value":true},{"label":"Auto Layout","type":"hasLabel","value":true},{"label":"Fixed layout","type":"hasLabel","value":true},{"label":"Anvil layout","type":"hasLabel","value":true}],"requires":1},"User Education Pod":{"conditions":[{"label":"Content","type":"hasLabel","value":true},{"label":"Documentation","type":"hasLabel","value":true}],"requires":1},"DevOps Pod":{"conditions":[{"label":"Docker","type":"hasLabel","value":true},{"label":"Super Admin","type":"hasLabel","value":true},{"label":"Deployment","type":"hasLabel","value":true},{"label":"K8s","type":"hasLabel","value":true},{"label":"Email Config","type":"hasLabel","value":true},{"label":"Backup & Restore","type":"hasLabel","value":true},{"label":"AWS AMI","type":"hasLabel","value":true},{"label":"Observability","type":"hasLabel","value":true},{"label":"Heroku","type":"hasLabel","value":true},{"label":"New Deployment Mode","type":"hasLabel","value":true}],"requires":1},"Design System Pod":{"conditions":[{"label":"Design System Pod","type":"hasLabel","value":true},{"label":"ADS Component Issue","type":"hasLabel","value":true},{"label":"Keyboard accessibility ","type":"hasLabel","value":true},{"label":"Toggle button","type":"hasLabel","value":true},{"label":"ADS Category Token","type":"hasLabel","value":true},{"label":"ADS Component Documentation","type":"hasLabel","value":true},{"label":"ADS Migration","type":"hasLabel","value":true},{"label":"ADS Deduplication ","type":"hasLabel","value":true},{"label":"ADS Revamp","type":"hasLabel","value":true},{"label":"ADS Deduplication","type":"hasLabel","value":true},{"label":"ADS Unit Test","type":"hasLabel","value":true},{"label":"ADS Components","type":"hasLabel","value":true},{"label":"ADS Grayscale","type":"hasLabel","value":true},{"label":"Design System","type":"hasLabel","value":true},{"label":"ADS Typography","type":"hasLabel","value":true},{"label":"ADS Visual Styles","type":"hasLabel","value":true},{"label":"ADS Component Design","type":"hasLabel","value":true},{"label":"Modal Component","type":"hasLabel","value":true}],"requires":1},"Data Platform Pod":{"conditions":[{"label":"Datasource Environments","type":"hasLabel","value":true},{"label":"Datatype issue","type":"hasLabel","value":true},{"label":"Entity Refactor","type":"hasLabel","value":true},{"label":"Core Query Execution","type":"hasLabel","value":true},{"label":"Query Management","type":"hasLabel","value":true},{"label":"Query Settings","type":"hasLabel","value":true},{"label":"SmartSubstitution","type":"hasLabel","value":true},{"label":"Query Generation","type":"hasLabel","value":true},{"label":"Query performance","type":"hasLabel","value":true},{"label":"Suggested Widgets","type":"hasLabel","value":true},{"label":"Page load executions","type":"hasLabel","value":true},{"label":"DSL Update","type":"hasLabel","value":true},{"label":"AST-backend","type":"hasLabel","value":true},{"label":"Deploy App","type":"hasLabel","value":true},{"label":"File upload issues","type":"hasLabel","value":true},{"label":"Datasources","type":"hasLabel","value":true},{"label":"DocumentDB","type":"hasLabel","value":true},{"label":"Multiple Environments","type":"hasLabel","value":true},{"label":"Platformization","type":"hasLabel","value":true},{"label":"Custom environments","type":"hasLabel","value":true},{"label":"Schema","type":"hasLabel","value":true}],"requires":1},"Integrations Pod":{"conditions":[{"label":"New Datasource","type":"hasLabel","value":true},{"label":"Firestore","type":"hasLabel","value":true},{"label":"Google Sheets","type":"hasLabel","value":true},{"label":"Mongo","type":"hasLabel","value":true},{"label":"Redshift","type":"hasLabel","value":true},{"label":"snowflake","type":"hasLabel","value":true},{"label":"S3","type":"hasLabel","value":true},{"label":"Redis","type":"hasLabel","value":true},{"label":"Postgres","type":"hasLabel","value":true},{"label":"GraphQL Plugin","type":"hasLabel","value":true},{"label":"ArangoDB","type":"hasLabel","value":true},{"label":"MsSQL","type":"hasLabel","value":true},{"label":"REST API plugin","type":"hasLabel","value":true},{"label":"Elastic Search","type":"hasLabel","value":true},{"label":"OAuth","type":"hasLabel","value":true},{"label":"Airtable","type":"hasLabel","value":true},{"label":"CURL","type":"hasLabel","value":true},{"label":"DynamoDB","type":"hasLabel","value":true},{"label":"Zendesk","type":"hasLabel","value":true},{"label":"Hubspot","type":"hasLabel","value":true},{"label":"Query Forms","type":"hasLabel","value":true},{"label":"Twilio","type":"hasLabel","value":true},{"label":"MySQL","type":"hasLabel","value":true},{"label":"Connection pool","type":"hasLabel","value":true},{"label":"MariaDB","type":"hasLabel","value":true},{"label":"Integrations Pod General","type":"hasLabel","value":true},{"label":"SMTP plugin","type":"hasLabel","value":true},{"label":"Oracle SQL DB","type":"hasLabel","value":true},{"label":"Query filter","type":"hasLabel","value":true},{"label":"Activation - datasources","type":"hasLabel","value":true},{"label":"Onboarding","type":"hasLabel","value":true},{"label":"Generate Page","type":"hasLabel","value":true},{"label":"Sniping Mode","type":"hasLabel","value":true},{"label":"Welcome Screen","type":"hasLabel","value":true},{"label":"Login / Signup","type":"hasLabel","value":true}],"requires":1},"Git Pod":{"conditions":[{"label":"Git Version Control","type":"hasLabel","value":true},{"label":"Import-Export-App","type":"hasLabel","value":true},{"label":"Fork App","type":"hasLabel","value":true}],"requires":1},"Mobile Pod":{"conditions":[],"requires":1},"Billing & Usage Pod":{"conditions":[{"label":"CE Instance","type":"hasLabel","value":true},{"label":"Customer Portal","type":"hasLabel","value":true},{"label":"Cloud Services","type":"hasLabel","value":true},{"label":"Billing Integrations","type":"hasLabel","value":true},{"label":"Billing","type":"hasLabel","value":true},{"label":"Self Serve","type":"hasLabel","value":true},{"label":"Enterprise Billing","type":"hasLabel","value":true},{"label":"In-app ramps","type":"hasLabel","value":true},{"label":"Analytics Improvements","type":"hasLabel","value":true},{"label":"Self Serve 1.0","type":"hasLabel","value":true},{"label":"License","type":"hasLabel","value":true},{"label":"1-click upgrade","type":"hasLabel","value":true},{"label":"Appsmith Business Cloud","type":"hasLabel","value":true},{"label":"BE instance","type":"hasLabel","value":true},{"label":"Embedding Apps","type":"hasLabel","value":true},{"label":"TM_BU","type":"hasLabel","value":true},{"label":"Homepage Experience V2","type":"hasLabel","value":true},{"label":"Feature Flagging","type":"hasLabel","value":true},{"label":"Invite flow","type":"hasLabel","value":true},{"label":"Invite users","type":"hasLabel","value":true}],"requires":1},"Performance Pod":{"conditions":[{"label":"Performance","type":"hasLabel","value":true},{"label":"Performance infra","type":"hasLabel","value":true}],"requires":1},"Widget design system":{"conditions":[{"label":"App Theming","type":"hasLabel","value":true},{"label":"Widget Styling","type":"hasLabel","value":true},{"label":"Checkbox Group widget","type":"hasLabel","value":true},{"label":"Checkbox Widget","type":"hasLabel","value":true},{"label":"Checkbox Component","type":"hasLabel","value":true},{"label":"WDS team","type":"hasLabel","value":true},{"label":"Widget design system","type":"hasLabel","value":true}],"requires":1},"IDE Pod":{"conditions":[],"requires":1},"Appsmith Labs":{"conditions":[{"label":"AI","type":"hasLabel","value":true}],"requires":1},"Workflows Pod":{"conditions":[],"requires":1}}},"root":"."}],"labels":{"Tab Widget":{"color":"e2c76c","name":"Tab Widget","description":""},"Dont merge":{"color":"ADB39C","name":"Dont merge","description":""},"Epic":{"color":"3E4B9E","name":"Epic","description":"A zenhub epic that describes a project"},"Menu Button Widget":{"color":"235708","name":"Menu Button Widget","description":"Issues related to Menu Button widget"},"Checkbox Group widget":{"color":"88054d","name":"Checkbox Group widget","description":"Issues related to Checkbox Group Widget"},"Input Widget":{"color":"ae65d8","name":"Input Widget","description":""},"Security":{"color":"99139C","name":"Security","description":""},"QA":{"color":"e2ca68","name":"QA","description":""},"Verified":{"color":"9bf416","name":"Verified","description":""},"Wont Fix":{"color":"ffffff","name":"Wont Fix","description":"This will not be worked on"},"MySQL":{"color":"c9ddc6","name":"MySQL","description":"Issues related to MySQL plugin"},"Development":{"color":"9F8A02","name":"Development","description":""},"Help Wanted":{"color":"008672","name":"Help Wanted","description":"Extra attention is needed"},"Home Page":{"color":"9c0c8e","name":"Home Page","description":"Issues related to the application home page"},"Rating Widget":{"color":"235708","name":"Rating Widget","description":"Issues related to the rating widget"},"Stat Box Widget":{"color":"f1c9ce","name":"Stat Box Widget","description":"Issues related to stat box"},"Enhancement":{"color":"a2eeef","name":"Enhancement","description":"New feature or request"},"Settings":{"color":"f7ff60","name":"Settings","description":"organization, team & user settings"},"Fork App":{"color":"30c76d","name":"Fork App","description":"Issues related to forking apps"},"Container Widget":{"color":"19AD0D","name":"Container Widget","description":"Container widget"},"Papercut":{"color":"B562F6","name":"Papercut","description":""},"Needs Design":{"color":"bfd4f2","name":"Needs Design","description":"needs design or changes to design"},"i18n":{"color":"1799b0","name":"i18n","description":"Represents issues that need to be tackled to handle internationalization"},"Rich Text Editor Widget":{"color":"f72cac","name":"Rich Text Editor Widget","description":""},"Onboarding":{"color":"30c76d","name":"Onboarding","description":"Issues related to onboarding new developers"},"Pages":{"color":"d7fd80","name":"Pages","description":"Issues related to configuring pages"},"skip-changelog":{"color":"06086F","name":"skip-changelog","description":"Adding this label to a PR prevents it from being listed in the changelog"},"Low":{"color":"79e53b","name":"Low","description":"An issue that is neither critical nor breaks a user flow"},"potential-duplicate":{"color":"d3cb2e","name":"potential-duplicate","description":"This label marks issues that are potential duplicates of already open issues"},"Audio Widget":{"color":"447B9A","name":"Audio Widget","description":"Issues related to Audio Widget"},"Firestore":{"color":"8078b0","name":"Firestore","description":"Issues related to the firestore Integration"},"New Widget":{"color":"be4cf2","name":"New Widget","description":"A request for a new widget"},"Modal Widget":{"color":"03846f","name":"Modal Widget","description":""},"UX Improvement":{"color":"f4a089","name":"UX Improvement","description":""},"S3":{"color":"8078b0","name":"S3","description":"Issues related to the S3 plugin"},"Release Blocker":{"color":"5756bf","name":"Release Blocker","description":"This issue must be resolved before the release"},"safari":{"color":"51C6AA","name":"safari","description":"Bugs seen on safari browser"},"Example Apps":{"color":"1799b0","name":"Example Apps","description":"Example apps created for new signups"},"MultiSelect Widget":{"color":"AB62D4","name":"MultiSelect Widget","description":"Issues related to MultiSelect Widget"},"Widget Styling":{"color":"905420","name":"Widget Styling","description":"all about widget styling"},"Calendar Widget":{"color":"8c6644","name":"Calendar Widget","description":""},"Website":{"color":"151720","name":"Website","description":"Related to www.appsmith.com website"},"Low effort":{"color":"8B59F0","name":"Low effort","description":"Something that'll take a few days to build"},"App Viewers Pod":{"color":"cd8ef9","name":"App Viewers Pod","description":"This label assigns issues to the app viewers pod"},"Checkbox Widget":{"color":"88054d","name":"Checkbox Widget","description":""},"Spam":{"color":"620faf","name":"Spam","description":""},"Voice Recorder Widget":{"color":"85bc87","name":"Voice Recorder Widget","description":""},"Select Widget":{"color":"0c669e","name":"Select Widget","description":"Select or dropdown widget"},"Bug":{"color":"d73a4a","name":"Bug","description":"Something isn't working"},"Widget Validation":{"color":"6990BC","name":"Widget Validation","description":"Issues related to widget property validation"},"Generate Page":{"color":"30c76d","name":"Generate Page","description":"Issures related to page generation"},"File Picker Widget":{"color":"6ae4f2","name":"File Picker Widget","description":""},"snowflake":{"color":"8078b0","name":"snowflake","description":"Issues related to the snowflake Integration"},"Automation":{"color":"CCAF60","name":"Automation","description":""},"hotfix":{"color":"BA3F1D","name":"hotfix","description":""},"Team Managers Pod":{"color":"bddb81","name":"Team Managers Pod","description":"Issues that team managers care about for the security and efficiency of their teams"},"Import-Export-App":{"color":"15076d","name":"Import-Export-App","description":"Issues related to importing and exporting apps"},"High effort":{"color":"A7E87B","name":"High effort","description":"Something that'll take more than a month to build"},"Telemetry":{"color":"bc70f9","name":"Telemetry","description":"Issues related to instrumenting appsmith"},"Radio Widget":{"color":"91ef15","name":"Radio Widget","description":""},"Omnibar":{"color":"10b5ce","name":"Omnibar","description":"Issues related to the omnibar for navigation"},"Button Widget":{"color":"34efae","name":"Button Widget","description":""},"Switch widget":{"color":"33A8CE","name":"Switch widget","description":"The switch widget"},"Map Widget":{"color":"7eef7a","name":"Map Widget","description":""},"Task":{"color":"085630","name":"Task","description":"A simple Todo"},"Design System":{"color":"2958a4","name":"Design System","description":"Design system"},"opera":{"color":"C63F5B","name":"opera","description":"Any issues identified on the opera browser"},"Login / Signup":{"color":"30c76d","name":"Login / Signup","description":"Authentication flows"},"Image Widget":{"color":"8de8ad","name":"Image Widget","description":""},"firefox":{"color":"6d56e2","name":"firefox","description":""},"Property Pane":{"color":"b356ff","name":"Property Pane","description":"Issues related to the behaviour of the property pane"},"Deployment":{"color":"93491f","name":"Deployment","description":"Installation process of appsmith"},"Critical":{"color":"9b1b28","name":"Critical","description":"This issue needs immediate attention. Drop everything else"},"IDE":{"color":"61b2ee","name":"IDE","description":"Issues related to the IDE"},"Production":{"color":"b60205","name":"Production","description":""},"Dependencies":{"color":"0366d6","name":"Dependencies","description":"Pull requests that update a dependency file"},"Google Sheets":{"color":"8078b0","name":"Google Sheets","description":"Issues related to Google Sheets"},"Icon Button Widget":{"color":"D319CE","name":"Icon Button Widget","description":"Issues related to the icon button widget"},"Mongo":{"color":"8078b0","name":"Mongo","description":"Issues related to Mongo DB plugin"},"Documentation":{"color":"a8dff7","name":"Documentation","description":"Improvements or additions to documentation"},"TestGap":{"color":"f28253","name":"TestGap","description":"Issues identified for test plan improvement"},"keyboard shortcut":{"color":"0688B6","name":"keyboard shortcut","description":""},"Git Version Control":{"color":"858172","name":"Git Version Control","description":"Issues related to version control"},"Reopen":{"color":"897548","name":"Reopen","description":""},"Redshift":{"color":"8078b0","name":"Redshift","description":"Issues related to the redshift integration"},"Date Picker Widget":{"color":"ef1ce1","name":"Date Picker Widget","description":""},"Entity Explorer":{"color":"a2e2f9","name":"Entity Explorer","description":"Issues related to navigation using the entity explorer"},"JS Linting & Errors":{"color":"E56AA5","name":"JS Linting & Errors","description":"Issues related to JS Linting and errors"},"iFrame":{"color":"3CD1DB","name":"iFrame","description":"Issues related to iFrame"},"Stale":{"color":"ededed","name":"Stale","description":null},"Debugger":{"color":"e79062","name":"Debugger","description":"Issues related to the debugger"},"Quick effort":{"color":"95ED65","name":"Quick effort","description":"Something that'll take a few hours to build"},"Text Widget":{"color":"d130d1","name":"Text Widget","description":""},"Video Widget":{"color":"23dd4b","name":"Video Widget","description":""},"Datasources":{"color":"5052f6","name":"Datasources","description":"Issues related to configuring datasource on appsmith"},"error":{"color":"B66773","name":"error","description":"All issues connected to error messages"},"Form Widget":{"color":"09ed77","name":"Form Widget","description":""},"Needs Triaging":{"color":"e8b851","name":"Needs Triaging","description":"Needs attention from maintainers to triage"},"Autocomplete":{"color":"235708","name":"Autocomplete","description":"Issues related to the autocomplete"},"hacktoberfest":{"color":"0052cc","name":"hacktoberfest","description":"All issues that can be solved by the community during Hacktoberfest"},"Medium effort":{"color":"D31156","name":"Medium effort","description":"Something that'll take more than a week but less than a month to build"},"Release":{"color":"57e5e0","name":"Release","description":""},"High":{"color":"c94d14","name":"High","description":"This issue blocks a user from building or impacts a lot of users"},"UI Performance":{"color":"1799b0","name":"UI Performance","description":"Issues related to UI performance"},"UI Builders Pod":{"color":"517fba","name":"UI Builders Pod","description":"Issues that UI Builders face using appsmith"},"Deploy Preview":{"color":"bfdadc","name":"Deploy Preview","description":"Issues found in Deploy Preview"},"Needs Tests":{"color":"8ee263","name":"Needs Tests","description":"Needs automated tests to assert a feature/bug fix"},"Refactor":{"color":"B96662","name":"Refactor","description":"needs refactoring of code"},"Divider Widget":{"color":"235708","name":"Divider Widget","description":"Issues related to the divider widget"},"Table Widget":{"color":"2eead1","name":"Table Widget","description":""},"Needs More Info":{"color":"e54c10","name":"Needs More Info","description":"Needs additional information"},"Good First Issue":{"color":"7057ff","name":"Good First Issue","description":"Good for newcomers"},"UI Improvement":{"color":"9aeef4","name":"UI Improvement","description":""},"Backend":{"color":"d4c5f9","name":"Backend","description":"This marks the issue or pull request to reference server code"},"Frontend":{"color":"87c7f2","name":"Frontend","description":"This label marks the issue or pull request to reference client code"},"In App Comms":{"name":"In App Comms","description":"Issues around communication with appsmith instances","color":"463cca"},"Chart Widget":{"color":"616ecc","name":"Chart Widget","description":""},"List Widget":{"color":"8508A0","name":"List Widget","description":"Issues related to the list widget"},"Duplicate":{"color":"cfd3d7","name":"Duplicate","description":"This issue or pull request already exists"},"JS Snippets":{"color":"8d62d2","name":"JS Snippets","description":"issues related to JS Snippets"},"Copy Paste":{"name":"Copy Paste","description":"Issues related to copy paste","color":"b4f0a9"},"Drag & Drop":{"name":"Drag & Drop","description":"Issues related to the drag & drop experience","color":"92115a"},"BE Coders Pod":{"color":"5d9848","name":"BE Coders Pod","description":"Issues related to users writing code to fetch and update data"},"FE Coders Pod":{"color":"a7effc","name":"FE Coders Pod","description":"Issues related to users writing javascript in appsmith"},"New Developers Pod":{"color":"6310da","name":"New Developers Pod","description":"Issues that new developers face while exploring the IDE"},"Sniping Mode":{"name":"Sniping Mode","description":"Issues related to sniping mode","color":"30c76d"},"Redis":{"name":"Redis","description":"Issues related to Redis","color":"8078b0"},"New Datasource":{"color":"60b14c","name":"New Datasource","description":"Requests for new datasources"},"Evaluated Value":{"name":"Evaluated Value","description":"Issues related to evaluated values","color":"39f6e7"},"Undo/Redo":{"name":"Undo/Redo","description":"Issues related to undo/redo","color":"f25880"},"App Navigation":{"name":"App Navigation","description":"Issues related to the topbar navigation and configuring it","color":"12b715"},"Responsive Viewport":{"color":"d12d2e","name":"Responsive Viewport","description":"Issues seen on different viewports like mobile"},"Widgets Pane":{"name":"Widgets Pane","description":"Issues related to the discovery and organisation of widgets","color":"ad5d78"},"View Mode":{"color":"1799b0","name":"View Mode","description":"Issues related to the view mode"},"User Education Pod":{"name":"User Education Pod","description":"Issues related to user education","color":"1799b0"},"Content":{"name":"Content","description":"For content related topics i.e blogs, templates, videos","color":"a8dff7"},"Embedding Apps":{"name":"Embedding Apps","description":"Issues related to embedding","color":"30c76d"},"Slash Command":{"name":"Slash Command","description":"Issues related to the slash command","color":"a0608e"},"Widget Property":{"name":"Widget Property","description":"Issues related to adding / modifying widget properties across widgets","color":"5e92cb"},"Windows":{"name":"Windows","description":"Issues related exclusively to Windows systems","color":"b4cb8a"},"Old App Issues":{"name":"Old App Issues","description":"Issues related to apps old apps a few weeks old and app issues in stale browser session","color":"87ab18"},"Document Viewer Widget":{"name":"Document Viewer Widget","description":"Issues related to Document Viewer Widget","color":"899d4b"},"Radio Group Widget":{"name":"Radio Group Widget","description":"Issues related to radio group widget","color":"b68495"},"Super Admin":{"name":"Super Admin","description":"Issues related to the super admin page","color":"aa95cf"},"Postgres":{"name":"Postgres","description":"Postgres related issues","color":"8078b0"},"REST API plugin":{"name":"REST API plugin","description":"REST API plugin related issues","color":"8078b0"},"New JS Function":{"name":"New JS Function","description":"Issues related to adding a JS Function","color":"8e8aa4"},"Cannot Reproduce Issue":{"color":"93c9cc","name":"Cannot Reproduce Issue","description":"Issues that cannot be reproduced"},"Widget Grouping":{"name":"Widget Grouping","description":"Issues related to Widget Grouping","color":"a49951"},"K8s":{"name":"K8s","description":"Kubernetes related issues","color":"5f318a"},"Docker":{"name":"Docker","description":"Issues related to docker","color":"89b808"},"Camera Widget":{"name":"Camera Widget","description":"Issues and enhancements related to camera widget","color":"e6038e"},"SAAS Plugins":{"name":"SAAS Plugins","description":"Issues related to SAAS Plugins","color":"ef9c9d"},"JS Promises":{"name":"JS Promises","description":"Issues related to promises","color":"d7771f"},"OnPageLoad":{"name":"OnPageLoad","description":"OnPageLoad issues on functions and queries","color":"50559d"},"JS Usability":{"name":"JS Usability","description":"usability issues with JS editor and JS elsewhere","color":"a302b0"},"Currency Input Widget":{"name":"Currency Input Widget","description":"Issues related to currency input widget","color":"b2164f"},"TreeSelect":{"name":"TreeSelect","description":"Issues related to TreeSelect Widget","color":"a1633e"},"MultiTree Select Widget":{"name":"MultiTree Select Widget","description":"Issues related to MultiTree Select Widget","color":"a1633e"},"Welcome Screen":{"name":"Welcome Screen","description":"Issues related to the welcome screen","color":"30c76d"},"Realtime Commenting":{"color":"a70b86","name":"Realtime Commenting","description":"In-app communication between teams"},"Phone Input Widget":{"name":"Phone Input Widget","description":"Issues related to the Phone Input widget","color":"a70b86"},"JSON Form":{"name":"JSON Form","description":"Issue / features related to the JSON form wiget","color":"46b209"},"All Widgets":{"name":"All Widgets","description":"Issues related to all widgets","color":"972b36"},"V1":{"name":"V1","description":"V1","color":"67ab2e"},"Reflow & Resize":{"name":"Reflow & Resize","description":"All issues related to reflow and resize experience","color":"748a13"},"App Theming":{"name":"App Theming","description":"Items that are related to the App level theming controls epic","color":"905420"},"SSO":{"name":"SSO","description":"Issues, requests and enhancements around Single sign-on.","color":"bf019b"},"Multi User Realtime":{"name":"Multi User Realtime","description":"Issues related to multiple users using or editing an application","color":"e7b6ce"},"Templates":{"name":"Templates","description":"Issues related to templates","color":"b7e568"},"Ready for design":{"name":"Ready for design","description":"this issue is ready for design: it contains clear problem statements and other required information","color":"ebf442"},"Support":{"name":"Support","description":"Issues created by the A-force team to address user queries","color":"1740f3"},"Button Group widget":{"name":"Button Group widget","description":"Issue and enhancements related to the button group widget","color":"f17025"},"GraphQL Plugin":{"name":"GraphQL Plugin","description":"Issues related to GraphQL plugin","color":"8078b0"},"DevOps Pod":{"name":"DevOps Pod","description":"Issues related to devops","color":"d956c7"},"medium":{"name":"medium","description":"Issues that frustrate users due to poor UX","color":"23dfd9"},"ArangoDB":{"name":"ArangoDB","description":"Issues related to arangoDB","color":"8078b0"},"Code Refactoring":{"name":"Code Refactoring","description":"Issues related to code refactoring","color":"76310e"},"Progress bar widget":{"name":"Progress bar widget","description":"To track issues related to progress bar","color":"2d7abf"},"Audio Recorder Widget":{"name":"Audio Recorder Widget","description":"Issues related to Audio Recorder Widget","color":"9accef"},"Airtable":{"name":"Airtable","description":"Issues for Airtable","color":"60885f"},"RBAC":{"name":"RBAC","description":"Issues, requests and enhancements around RBAC.","color":"9211c3"},"Canvas / Grid":{"name":"Canvas / Grid","description":"Issues related to the canvas","color":"16b092"},"Email Config":{"name":"Email Config","description":"Issues related to configuring the email service","color":"2a21d1"},"CURL":{"name":"CURL","description":"Issues related to CURL impor","color":"60885f"},"Canvas Zooms":{"name":"Canvas Zooms","description":"Issues related to zooming the canvas","color":"e6038e"},"business":{"name":"business","description":"Features that will be a part of our business edition","color":"cd59eb"},"Action Pod":{"name":"Action Pod","description":"","color":"ee2e36"},"AutomationGap1":{"color":"a5e07c","name":"AutomationGap1","description":"Issues that needs automated tests"},"A-Force11":{"name":"A-Force11","description":"Issues raised by A-Force team","color":"d667b6"},"Business Edition":{"name":"Business Edition","description":"Features that will be a part of our business edition","color":"89bb6c"},"storeValue":{"name":"storeValue","description":"Issues related to the store value function","color":"5d3e66"},"Tests":{"name":"Tests","description":"test item","color":"1c6990"},"DynamoDB":{"name":"DynamoDB","description":"Issues that are related to DynamoDB should have this label","color":"60885f"},"Design System Pod":{"name":"Design System Pod","description":"Appsmith design system related issues","color":"706f03"},"ABAC":{"color":"e009a5","name":"ABAC","description":"User permissions and access controls"},"Backup & Restore":{"name":"Backup & Restore","description":"Issues related to backup and restore","color":"86874d"},"Billing":{"name":"Billing","description":"Billing infrastructure and flows for Business Edition and Trial users","color":"d2bc40"},"Datatype issue":{"name":"Datatype issue","description":"Issues that have risen because data types weren't handled","color":"60885f"},"OAuth":{"name":"OAuth","description":"OAuth related bugs or features","color":"60885f"},"Table Widget V2":{"name":"Table Widget V2","description":"Issues related to Table Widget V2","color":"3a7192"},"IDE Navigation":{"name":"IDE Navigation","description":"Issues/feature requests related to IDE navigation, and context switching","color":"bc0cba"},"Query performance":{"name":"Query performance","description":"Issues that have to do with lack in performance of query execution","color":"e4d966"},"SAAS Manager App":{"name":"SAAS Manager App","description":"Issues with the SAAS manager app","color":"d427db"},"Twilio":{"name":"Twilio","description":"Issues related to Twilio integration","color":"23ba8d"},"Hubspot":{"name":"Hubspot","description":"Issues related to Hubspot integration","color":"60885f"},"Zendesk":{"name":"Zendesk","description":"Issues related to Zendesk integration","color":"60885f"},"Entity Refactor":{"name":"Entity Refactor","description":"Issues related to refactor logic","color":"418fa4"},"Branding":{"name":"Branding","description":"All issues under branding and whitelabelling appsmith ecosystem","color":"7aaaf1"},"Map Chart Widget":{"name":"Map Chart Widget","description":"Issues related to Map Chart Widgets","color":"c8397f"},"Product Catchup":{"name":"Product Catchup","description":"Issues created in the product catchup","color":"29cd2c"},"Framework Functions":{"name":"Framework Functions","description":"Issues related to internal functions like showAlert(), navigateTo() etc...","color":"c25a09"},"Frontend Libraries Upgrade":{"name":"Frontend Libraries Upgrade","description":"Issues related to frontend libraries upgrade","color":"ede1fc"},"Audit Logs":{"name":"Audit Logs","description":"Audit trails to ensure data security","color":"f3fd62"},"MsSQL":{"name":"MsSQL","description":"Issues related to MsSQL plugin","color":"8078b0"},"Data Platform Pod":{"name":"Data Platform Pod","description":"Issues related to the underlying data platform","color":"3f8c3a"},"Integrations Pod":{"name":"Integrations Pod","description":"Issues related to a specific integration","color":"5dbbb1"},"Datasource Environments":{"name":"Datasource Environments","description":"Issues related to datasource environments","color":"bb7a14"},"Elastic Search":{"name":"Elastic Search","description":"Issues related to the elastic search datasource","color":"8078b0"},"Core Query Execution":{"color":"418fa4","name":"Core Query Execution","description":"Issues related to the execution of all queries"},"Query Management":{"name":"Query Management","description":"Issues related to the CRUD of actions or queries","color":"6a5b42"},"Query Settings":{"name":"Query Settings","description":"Issues related to the settings of all queries","color":"c7da7a"},"Code Editor":{"name":"Code Editor","description":"Issues related to the code editor","color":"4ca16e"},"Query Forms":{"color":"12b253","name":"Query Forms","description":"Isuses related to the query forms"},"JS Objects":{"color":"22962c","name":"JS Objects","description":"Issues related to JS Objects"},"JS Evaluation":{"color":"22962c","name":"JS Evaluation","description":"Issues related to JS evaluation on the platform"},"SmartSubstitution":{"name":"SmartSubstitution","description":"Issues related to Smart substitution of mustache bindings in queries","color":"e4d966"},"Query Generation":{"name":"Query Generation","description":"Issues related to query generation","color":"e4d966"},"Suggested Widgets":{"name":"Suggested Widgets","description":"Issues related to suggesting widgets based on query response","color":"e4d966"},"Page load executions":{"name":"Page load executions","description":"Issues related to page load execution","color":"5696b2"},"Code Scanner Widget":{"name":"Code Scanner Widget","description":"Issues related to code scanner widget","color":"9bc1a0"},"Clean URLs":{"name":"Clean URLs","description":"Issues related to clean URLs epic","color":"112623"},"Widget keyboard accessibility":{"name":"Widget keyboard accessibility","description":"All issues related to keyboard accessibility in widgets","color":"b626fd"},"Connection pool":{"name":"Connection pool","description":"issues to do with connection pooling of various plugins","color":"94fe36"},"List Widget V2":{"name":"List Widget V2","description":"Issues related to the list widget v2","color":"adaaf7"},"Auto Height":{"name":"Auto Height","description":"Issues related to dynamic height of widgets","color":"5149cf"},"cypress_failed_test":{"name":"cypress_failed_test","description":"Cypress failed tests","color":"4745d5"},"Needs validation":{"name":"Needs validation","description":"Needs problem validation before being picked up","color":"66673d"},"Slider Widget":{"name":"Slider Widget","description":"Issues raised for slider widgets.","color":"2eef5f"},"Multitenancy":{"name":"Multitenancy","description":"Support multitenancy within single appsmith instance","color":"8c49a9"},"Git Pod":{"name":"Git Pod","description":"Anything related to git sync","color":"2e5ba4"},"Mobile Pod":{"name":"Mobile Pod","description":"All issues related to mobile responsiveness","color":"6c97fd"},"Responsive Widget":{"name":"Responsive Widget","description":"All issues related to widget responsiveness","color":"d12d2e"},"Responsive Canvas":{"name":"Responsive Canvas","description":"All issues related to canvas responsiveness","color":"45a0a8"},"Conversion Algorithm":{"name":"Conversion Algorithm","description":"All issue related to converting app from fixed to flex mode & vice versa","color":"d12d2e"},"Spacing":{"name":"Spacing","description":"All issue related to spacing between widgets in auto layout","color":"d12d2e"},"Browser specific":{"name":"Browser specific","description":"All issue related to browser","color":"d12d2e"},"Error Handling":{"name":"Error Handling","description":"Issues related to error handling","color":"4e1872"},"Performance infra":{"name":"Performance infra","description":"all issue related to the performance infra","color":"8a60f6"},"DSL Update":{"name":"DSL Update","description":"Issues related to storing and updating the DSL","color":"e16cf3"},"AST-frontend":{"name":"AST-frontend","description":"Issues related to maintaining AST logic","color":"434a3a"},"AST-backend":{"name":"AST-backend","description":"Backend issues related to AST parsing","color":"c476eb"},"MariaDB":{"name":"MariaDB","description":"MariaDB datasource","color":"8428c3"},"Billing & Usage Pod":{"name":"Billing & Usage Pod","description":"Issues pertaining to licensing, billing, usage across self serve and enterprise customers","color":"256808"},"ADS Component Issue":{"name":"ADS Component Issue","description":"Issues which are caused due to ADS components","color":"d89119"},"Regressed":{"color":"723fd0","name":"Regressed","description":"Scenarios that were working before but have now regressed"},"Needs RCA":{"name":"Needs RCA","description":"a critical or high priority issue that needs an RCA","color":"2cc68f"},"Custom JS Libraries":{"name":"Custom JS Libraries","description":"Issues related to adding custom JS library","color":"bacb6d"},"Integrations Pod General":{"name":"Integrations Pod General","description":"Issues related to the Integrations Pod that don't fit into other tags.","color":"287823"},"Performance Pod":{"name":"Performance Pod","description":"All things related to Appsmith performance","color":"b5a25d"},"Performance":{"name":"Performance","description":"Issues related to performance","color":"9a18d7"},"File upload issues":{"name":"File upload issues","description":"Issues related to uploading any type of files from within Appsmith","color":"8154df"},"Action Selector":{"name":"Action Selector","description":"Issues related to action selector on the property pane","color":"2f9e20"},"Widget design system":{"name":"Widget design system","description":"","color":"cb6188"},"Deploy App":{"name":"Deploy App","description":"Issues related to app deployment","color":"6f6152"},"Community Reported":{"name":"Community Reported","description":"issues reported by community members","color":"1402e5"},"JS Function execution":{"name":"JS Function execution","description":"JS function execution","color":"7c2de1"},"Self Serve":{"name":"Self Serve","description":"For all issues related to self-serve flow for business edition","color":"4dacfc"},"Self Serve 1.0":{"name":"Self Serve 1.0","description":"For all issues related to v1 of the self serve project","color":"ae839e"},"CE Instance":{"name":"CE Instance","description":"For all issues relating to usage, licensing or billing on the CE instance","color":"d2bc40"},"Customer Portal":{"name":"Customer Portal","description":"For all tasks/issues pertaining to customer.appsmith.com","color":"d2bc40"},"Cloud Services":{"name":"Cloud Services","description":"For all tasks/issues on Appsmith cloud-services relating to licensing, usage and billing","color":"d2bc40"},"Billing Integrations":{"name":"Billing Integrations","description":"For all issues relating to 3P integrations Appsmith is using for billing & usage","color":"d2bc40"},"One-click Binding":{"name":"One-click Binding","description":"Issues related to the One click binding epic","color":"f1661c"},"Airgap":{"name":"Airgap","description":"Tickets related to supporting air-gapped Appsmith instances","color":"1cb294"},"SMTP plugin":{"name":"SMTP plugin","description":"Issues related to SMTP plugin","color":"541457"},"AWS AMI":{"name":"AWS AMI","description":"Issues Related to AWS AMI","color":"b44680"},"Old widget version":{"name":"Old widget version","description":"Use this label to raise issue specific only to an older version of a widget","color":"ff3814"},"Enterprise Billing":{"name":"Enterprise Billing","description":"To track all tasks/issues related to licensing & billing for enterprise customers","color":"14c156"},"Appsmith Business Cloud":{"name":"Appsmith Business Cloud","description":"Issues related to our business cloud offering","color":"89bb6c"},"Oracle SQL DB":{"name":"Oracle SQL DB","description":"Issues related to the Oracle plugin","color":"cbabcb"},"Community Contributor":{"name":"Community Contributor","description":"Meant to track issues that are assigned to external contributors","color":"149ab6"},"widget vertical alignment":{"name":"widget vertical alignment","description":"All issue related widget vertical alignment on the auto layout canvas","color":"d12d2e"},"Observability":{"name":"Observability","description":"Issues related to observability on the Appsmith instance","color":"dff913"},"Checkbox Component":{"name":"Checkbox Component","description":"This labels deals with checkbox component in wds package","color":"75a401"},"In-app ramps":{"name":"In-app ramps","description":"For all tasks/issues relating to adding in-app ramps in the community edition of the product","color":"8abae0"},"Analytics Improvements":{"name":"Analytics Improvements","description":"For all tasks focused on improving our overall analytics and fixing any issues ","color":"29b8ed"},"WDS team":{"name":"WDS team","description":"","color":"8d675a"},"Enterprise Edition":{"name":"Enterprise Edition","description":"Features that will be supported in Enterprise Edition only","color":"984f5e"},"Query filter":{"name":"Query filter","description":"Issues related to query filtering, e.g., WHERE clause","color":"a15134"},"Keyboard accessibility ":{"name":"Keyboard accessibility ","description":"All issue related to ADS component keyboard accessibility","color":"2ba696"},"Toggle button":{"name":"Toggle button","description":"All issue related to ADS toggle button","color":"edc47f"},"1-click upgrade":{"name":"1-click upgrade","description":"For all issues/tasks related to 1-click upgrade & downgrade project","color":"129082"},"Feature Flagging":{"name":"Feature Flagging","description":"Anything related feature flagging","color":"8d8a09"},"SCIM":{"name":"SCIM","description":"Label to collate our SCIM issues","color":"61a852"},"ADS Category Token":{"name":"ADS Category Token","description":"All issues related appsmith design system category tokens","color":"920961"},"ADS Component Documentation":{"name":"ADS Component Documentation","description":"All issues Appsmith design system component documentation","color":"64c46a"},"ADS Migration":{"name":"ADS Migration","description":"All issues related to Appsmith design system migration","color":"b082d6"},"ADS Deduplication ":{"name":"ADS Deduplication ","description":"Replacing component with ADS components","color":"b082d6"},"ADS Revamp":{"name":"ADS Revamp","description":"All issues related to ads revamp. ","color":"b082d6"},"ADS Deduplication":{"name":"ADS Deduplication","description":"Replacing component with ADS components","color":"b082d6"},"ADS Grayscale":{"name":"ADS Grayscale","description":"Support grayscale color changes","color":"b03577"},"ADS Unit Test":{"name":"ADS Unit Test","description":"All issue related ads unit cases ","color":"b082d6"},"ADS Components":{"name":"ADS Components","description":"All issues related ADS components","color":"b082d6"},"Widget Discoverability":{"name":"Widget Discoverability","description":"Issues related to Widget Discoverability","color":"7b55ce"},"Widget setter method":{"name":"Widget setter method","description":"Issues with widget property setters","color":"8dce87"},"License":{"name":"License","description":"For all issues/tasks related to licensing of appsmith-ee edition","color":"90ee98"},"Templates pod":{"name":"Templates pod","description":"Issues related to Templates","color":"b7e568"},"Community template":{"name":"Community template","description":"Label for development of community templates and its integration to platform","color":"8a0510"},"DocumentDB":{"name":"DocumentDB","description":"Issues related to support DocumentDB in Appsmith Data layer","color":"2c8b56"},"Multiple Environments":{"name":"Multiple Environments","description":"Issues or tasks related to multiple environments","color":"4e972b"},"Platformization":{"name":"Platformization","description":"Issues or tasks related to platformization of Appsmith codebase","color":"4e972b"},"Activation - datasources":{"name":"Activation - datasources","description":"issues related to activation projects","color":"7c7ace"},"Partial-import-export":{"name":"Partial-import-export","description":"Label for granular reusability.","color":"1e439c"},"AI":{"name":"AI","description":"All tasks related to AI","color":"75c4ce"},"Custom environments":{"name":"Custom environments","description":"Issues with creating or working with custom environments","color":"2137d6"},"ADS Typography":{"name":"ADS Typography","description":"All issue related typographical changes","color":"2dbe8d"},"Auto Layout":{"name":"Auto Layout","description":"Issues relates to auto layout","color":"92cf8c"},"Heroku":{"name":"Heroku","description":"Issues related to Heroku","color":"a81b69"},"ADS Visual Styles":{"name":"ADS Visual Styles","description":"All issues related to ADS visual styles","color":"d3da89"},"ADS Component Design":{"name":"ADS Component Design","description":"All issue related to component design","color":"5cc91e"},"Modal Component":{"name":"Modal Component","description":"All issue related to ads modal component","color":"ee63f3"},"App setting":{"name":"App setting","description":"Related to app settings panel within the app","color":"144206"},"BE instance":{"name":"BE instance","description":"For all issues related to license, billing on BE instance","color":"ae8f98"},"Schema":{"name":"Schema","description":"Issues related to database schema","color":"c470c2"},"Fixed layout":{"name":"Fixed layout","description":"issues related to fixed layout","color":"b66681"},"Anvil layout":{"name":"Anvil layout","description":"issues related to the new layout system anvil","color":"722bf0"},"New Deployment Mode":{"name":"New Deployment Mode","description":"Support a new mode of deployment","color":"108033"},"Custom widgets":{"name":"Custom widgets","description":"For all issues related to the custom widget project","color":"c9db9c"},"IDE Pod":{"name":"IDE Pod","description":"https://app.zenhub.com/workspaces/new-developers-pod-60507ad1d4b98d00150a2858/board","color":"d3d248"},"TM_BU":{"name":"TM_BU","description":"The issues on Team Manager which needs to be taken up by Billing & Usage","color":"198cdf"},"Homepage Experience V2":{"name":"Homepage Experience V2","description":"Label for reporting new tasks and bug fixes related to revamped homepage experience","color":"c55d54"},"Appsmith Labs":{"name":"Appsmith Labs","description":"All things related to AI and other new initiatives ","color":"712d51"},"Customer Success":{"name":"Customer Success","description":"Issues that the success team cares about","color":"6ccabd"},"Invite flow":{"name":"Invite flow","description":"Invite users flow and any associated actions","color":"881b35"},"Invite users":{"name":"Invite users","description":"Invite users flow and any associated actions","color":"23e6d6"},"Workflows Pod":{"name":"Workflows Pod","description":"For all issues related to the Workflows feature","color":"2c1f93"}},"success":true} \ No newline at end of file +{"runners":[{"versioning":{"source":"milestones","type":"SemVer"},"prereleaseName":"alpha","issue":{"labels":{"Error Handling":{"conditions":[],"requires":1},"Templates pod":{"conditions":[{"label":"Templates","type":"hasLabel","value":true},{"label":"Community template","type":"hasLabel","value":true},{"label":"Partial-import-export","type":"hasLabel","value":true}],"requires":1},"Team Managers Pod":{"conditions":[{"label":"Settings","type":"hasLabel","value":true},{"label":"Home Page","type":"hasLabel","value":true},{"label":"Realtime Commenting","type":"hasLabel","value":true},{"label":"SSO","type":"hasLabel","value":true},{"label":"Multi User Realtime","type":"hasLabel","value":true},{"label":"RBAC","type":"hasLabel","value":true},{"label":"ABAC","type":"hasLabel","value":true},{"label":"Audit Logs","type":"hasLabel","value":true},{"label":"Multitenancy","type":"hasLabel","value":true},{"label":"Airgap","type":"hasLabel","value":true},{"label":"Enterprise Edition","type":"hasLabel","value":true},{"label":"SCIM","type":"hasLabel","value":true},{"label":"Invite flow","type":"hasLabel","value":true}],"requires":1},"New Developers Pod":{"conditions":[{"label":"Omnibar","type":"hasLabel","value":true},{"label":"Telemetry","type":"hasLabel","value":true},{"label":"Entity Explorer","type":"hasLabel","value":true},{"label":"IDE","type":"hasLabel","value":true},{"label":"Example Apps","type":"hasLabel","value":true},{"label":"i18n","type":"hasLabel","value":true},{"label":"IDE Navigation","type":"hasLabel","value":true},{"label":"Clean URLs","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"App setting","type":"hasLabel","value":true}],"requires":1},"BE Coders Pod":{"conditions":[{"label":"SAAS Plugins","type":"hasLabel","value":true},{"label":"SAAS Manager App","type":"hasLabel","value":true},{"label":"Data Platform Pod","type":"hasLabel","value":true},{"label":"Integrations Pod","type":"hasLabel","value":true}],"requires":1},"FE Coders Pod":{"conditions":[{"label":"JS Linting & Errors","type":"hasLabel","value":true},{"label":"Debugger","type":"hasLabel","value":true},{"label":"JS Snippets","type":"hasLabel","value":true},{"label":"Autocomplete","type":"hasLabel","value":true},{"label":"Evaluated Value","type":"hasLabel","value":true},{"label":"Slash Command","type":"hasLabel","value":true},{"label":"New JS Function","type":"hasLabel","value":true},{"label":"JS Promises","type":"hasLabel","value":true},{"label":"JS Usability","type":"hasLabel","value":true},{"label":"Code Refactoring","type":"hasLabel","value":true},{"label":"storeValue","type":"hasLabel","value":true},{"label":"OnPageLoad","type":"hasLabel","value":true},{"label":"Framework Functions","type":"hasLabel","value":true},{"label":"Code Editor","type":"hasLabel","value":true},{"label":"JS Objects","type":"hasLabel","value":true},{"label":"JS Evaluation","type":"hasLabel","value":true},{"label":"AST-frontend","type":"hasLabel","value":true},{"label":"Custom JS Libraries","type":"hasLabel","value":true},{"label":"Action Selector","type":"hasLabel","value":true},{"label":"JS Function execution","type":"hasLabel","value":true},{"label":"Widget setter method","type":"hasLabel","value":true},{"label":"Error Handling","type":"hasLabel","value":true}],"requires":1},"App Viewers Pod":{"conditions":[{"label":"Button Widget","type":"hasLabel","value":true},{"label":"Chart Widget","type":"hasLabel","value":true},{"label":"Container Widget","type":"hasLabel","value":true},{"label":"Date Picker Widget","type":"hasLabel","value":true},{"label":"Select Widget","type":"hasLabel","value":true},{"label":"File Picker Widget","type":"hasLabel","value":true},{"label":"Form Widget","type":"hasLabel","value":true},{"label":"Image Widget","type":"hasLabel","value":true},{"label":"Input Widget","type":"hasLabel","value":true},{"label":"List Widget","type":"hasLabel","value":true},{"label":"MultiSelect Widget","type":"hasLabel","value":true},{"label":"Map Widget","type":"hasLabel","value":true},{"label":"Modal Widget","type":"hasLabel","value":true},{"label":"Radio Widget","type":"hasLabel","value":true},{"label":"Rich Text Editor Widget","type":"hasLabel","value":true},{"label":"Tab Widget","type":"hasLabel","value":true},{"label":"Table Widget","type":"hasLabel","value":true},{"label":"Text Widget","type":"hasLabel","value":true},{"label":"Video Widget","type":"hasLabel","value":true},{"label":"iFrame","type":"hasLabel","value":true},{"label":"Menu Button","type":"hasLabel","value":true},{"label":"Rating","type":"hasLabel","value":true},{"label":"Widget Validation","type":"hasLabel","value":true},{"label":"reallabel","type":"hasLabel","value":true},{"label":"New Widget","type":"hasLabel","value":true},{"label":"Switch widget","type":"hasLabel","value":true},{"label":"Audio Widget","type":"hasLabel","value":true},{"label":"Icon Button Widget","type":"hasLabel","value":true},{"label":"Stat Box Widget","type":"hasLabel","value":true},{"label":"Voice Recorder Widget","type":"hasLabel","value":true},{"label":"Calendar Widget","type":"hasLabel","value":true},{"label":"Menu Button Widget","type":"hasLabel","value":true},{"label":"Divider Widget","type":"hasLabel","value":true},{"label":"Rating Widget","type":"hasLabel","value":true},{"label":"App Navigation","type":"hasLabel","value":true},{"label":"View Mode","type":"hasLabel","value":true},{"label":"Widget Property","type":"hasLabel","value":true},{"label":"Document Viewer Widget","type":"hasLabel","value":true},{"label":"Radio Group Widget","type":"hasLabel","value":true},{"label":"Currency Input Widget","type":"hasLabel","value":true},{"label":"TreeSelect","type":"hasLabel","value":true},{"label":"MultiTree Select Widget","type":"hasLabel","value":true},{"label":"Phone Input Widget","type":"hasLabel","value":true},{"label":"JSON Form","type":"hasLabel","value":true},{"label":"All Widgets","type":"hasLabel","value":true},{"label":"Button Group widget","type":"hasLabel","value":true},{"label":"Progress bar widget","type":"hasLabel","value":true},{"label":"Audio Recorder Widget","type":"hasLabel","value":true},{"label":"Camera Widget","type":"hasLabel","value":true},{"label":"Table Widget V2","type":"hasLabel","value":true},{"label":"Branding","type":"hasLabel","value":true},{"label":"Map Chart Widget","type":"hasLabel","value":true},{"label":"Code Scanner Widget","type":"hasLabel","value":true},{"label":"Widget keyboard accessibility","type":"hasLabel","value":true},{"label":"List Widget V2","type":"hasLabel","value":true},{"label":"Slider Widget","type":"hasLabel","value":true},{"label":"One-click Binding","type":"hasLabel","value":true},{"label":"Old widget version","type":"hasLabel","value":true},{"label":"Widget Discoverability","type":"hasLabel","value":true},{"label":"Custom widgets","type":"hasLabel","value":true}],"requires":1},"UI Builders Pod":{"conditions":[{"label":"Property Pane","type":"hasLabel","value":true},{"label":"Pages","type":"hasLabel","value":true},{"label":"Copy Paste","type":"hasLabel","value":true},{"label":"Drag & Drop","type":"hasLabel","value":true},{"label":"Undo/Redo","type":"hasLabel","value":true},{"label":"Widgets Pane","type":"hasLabel","value":true},{"label":"UI Performance","type":"hasLabel","value":true},{"label":"Widget Grouping","type":"hasLabel","value":true},{"label":"Reflow & Resize","type":"hasLabel","value":true},{"label":"Canvas / Grid","type":"hasLabel","value":true},{"label":"Canvas Zooms","type":"hasLabel","value":true},{"label":"Frontend Libraries Upgrade","type":"hasLabel","value":true},{"label":"Auto Height","type":"hasLabel","value":true},{"label":"Responsive Canvas","type":"hasLabel","value":true},{"label":"Responsive Widget","type":"hasLabel","value":true},{"label":"Responsive Viewport","type":"hasLabel","value":true},{"label":"Conversion Algorithm","type":"hasLabel","value":true},{"label":"Spacing","type":"hasLabel","value":true},{"label":"Browser specific","type":"hasLabel","value":true},{"label":"widget vertical alignment","type":"hasLabel","value":true},{"label":"Auto Layout","type":"hasLabel","value":true},{"label":"Fixed layout","type":"hasLabel","value":true},{"label":"Anvil layout","type":"hasLabel","value":true}],"requires":1},"User Education Pod":{"conditions":[{"label":"Content","type":"hasLabel","value":true},{"label":"Documentation","type":"hasLabel","value":true}],"requires":1},"DevOps Pod":{"conditions":[{"label":"Docker","type":"hasLabel","value":true},{"label":"Super Admin","type":"hasLabel","value":true},{"label":"Deployment","type":"hasLabel","value":true},{"label":"K8s","type":"hasLabel","value":true},{"label":"Email Config","type":"hasLabel","value":true},{"label":"Backup & Restore","type":"hasLabel","value":true},{"label":"AWS AMI","type":"hasLabel","value":true},{"label":"Observability","type":"hasLabel","value":true},{"label":"Heroku","type":"hasLabel","value":true},{"label":"New Deployment Mode","type":"hasLabel","value":true}],"requires":1},"Design System Pod":{"conditions":[{"label":"Design System Pod","type":"hasLabel","value":true},{"label":"ADS Component Issue","type":"hasLabel","value":true},{"label":"Keyboard accessibility ","type":"hasLabel","value":true},{"label":"Toggle button","type":"hasLabel","value":true},{"label":"ADS Category Token","type":"hasLabel","value":true},{"label":"ADS Component Documentation","type":"hasLabel","value":true},{"label":"ADS Migration","type":"hasLabel","value":true},{"label":"ADS Deduplication ","type":"hasLabel","value":true},{"label":"ADS Revamp","type":"hasLabel","value":true},{"label":"ADS Deduplication","type":"hasLabel","value":true},{"label":"ADS Unit Test","type":"hasLabel","value":true},{"label":"ADS Components","type":"hasLabel","value":true},{"label":"ADS Grayscale","type":"hasLabel","value":true},{"label":"Design System","type":"hasLabel","value":true},{"label":"ADS Typography","type":"hasLabel","value":true},{"label":"ADS Visual Styles","type":"hasLabel","value":true},{"label":"ADS Component Design","type":"hasLabel","value":true},{"label":"Modal Component","type":"hasLabel","value":true}],"requires":1},"Data Platform Pod":{"conditions":[{"label":"Datasource Environments","type":"hasLabel","value":true},{"label":"Datatype issue","type":"hasLabel","value":true},{"label":"Entity Refactor","type":"hasLabel","value":true},{"label":"Core Query Execution","type":"hasLabel","value":true},{"label":"Query Management","type":"hasLabel","value":true},{"label":"Query Settings","type":"hasLabel","value":true},{"label":"SmartSubstitution","type":"hasLabel","value":true},{"label":"Query Generation","type":"hasLabel","value":true},{"label":"Query performance","type":"hasLabel","value":true},{"label":"Suggested Widgets","type":"hasLabel","value":true},{"label":"Page load executions","type":"hasLabel","value":true},{"label":"DSL Update","type":"hasLabel","value":true},{"label":"AST-backend","type":"hasLabel","value":true},{"label":"Deploy App","type":"hasLabel","value":true},{"label":"File upload issues","type":"hasLabel","value":true},{"label":"Datasources","type":"hasLabel","value":true},{"label":"DocumentDB","type":"hasLabel","value":true},{"label":"Multiple Environments","type":"hasLabel","value":true},{"label":"Platformization","type":"hasLabel","value":true},{"label":"Custom environments","type":"hasLabel","value":true},{"label":"Schema","type":"hasLabel","value":true}],"requires":1},"Integrations Pod":{"conditions":[{"label":"New Datasource","type":"hasLabel","value":true},{"label":"Firestore","type":"hasLabel","value":true},{"label":"Google Sheets","type":"hasLabel","value":true},{"label":"Mongo","type":"hasLabel","value":true},{"label":"Redshift","type":"hasLabel","value":true},{"label":"snowflake","type":"hasLabel","value":true},{"label":"S3","type":"hasLabel","value":true},{"label":"Redis","type":"hasLabel","value":true},{"label":"Postgres","type":"hasLabel","value":true},{"label":"GraphQL Plugin","type":"hasLabel","value":true},{"label":"ArangoDB","type":"hasLabel","value":true},{"label":"MsSQL","type":"hasLabel","value":true},{"label":"REST API plugin","type":"hasLabel","value":true},{"label":"Elastic Search","type":"hasLabel","value":true},{"label":"OAuth","type":"hasLabel","value":true},{"label":"Airtable","type":"hasLabel","value":true},{"label":"CURL","type":"hasLabel","value":true},{"label":"DynamoDB","type":"hasLabel","value":true},{"label":"Zendesk","type":"hasLabel","value":true},{"label":"Hubspot","type":"hasLabel","value":true},{"label":"Query Forms","type":"hasLabel","value":true},{"label":"Twilio","type":"hasLabel","value":true},{"label":"MySQL","type":"hasLabel","value":true},{"label":"Connection pool","type":"hasLabel","value":true},{"label":"MariaDB","type":"hasLabel","value":true},{"label":"Integrations Pod General","type":"hasLabel","value":true},{"label":"SMTP plugin","type":"hasLabel","value":true},{"label":"Oracle SQL DB","type":"hasLabel","value":true},{"label":"Query filter","type":"hasLabel","value":true},{"label":"Activation - datasources","type":"hasLabel","value":true},{"label":"Onboarding","type":"hasLabel","value":true},{"label":"Generate Page","type":"hasLabel","value":true},{"label":"Sniping Mode","type":"hasLabel","value":true},{"label":"Welcome Screen","type":"hasLabel","value":true},{"label":"Login / Signup","type":"hasLabel","value":true}],"requires":1},"Git Pod":{"conditions":[{"label":"Git Version Control","type":"hasLabel","value":true},{"label":"Import-Export-App","type":"hasLabel","value":true},{"label":"Fork App","type":"hasLabel","value":true}],"requires":1},"Mobile Pod":{"conditions":[],"requires":1},"Billing & Usage Pod":{"conditions":[{"label":"CE Instance","type":"hasLabel","value":true},{"label":"Customer Portal","type":"hasLabel","value":true},{"label":"Cloud Services","type":"hasLabel","value":true},{"label":"Billing Integrations","type":"hasLabel","value":true},{"label":"Billing","type":"hasLabel","value":true},{"label":"Self Serve","type":"hasLabel","value":true},{"label":"Enterprise Billing","type":"hasLabel","value":true},{"label":"In-app ramps","type":"hasLabel","value":true},{"label":"Analytics Improvements","type":"hasLabel","value":true},{"label":"Self Serve 1.0","type":"hasLabel","value":true},{"label":"License","type":"hasLabel","value":true},{"label":"1-click upgrade","type":"hasLabel","value":true},{"label":"Appsmith Business Cloud","type":"hasLabel","value":true},{"label":"BE instance","type":"hasLabel","value":true},{"label":"Embedding Apps","type":"hasLabel","value":true},{"label":"TM_BU","type":"hasLabel","value":true},{"label":"Homepage Experience V2","type":"hasLabel","value":true},{"label":"Feature Flagging","type":"hasLabel","value":true},{"label":"Invite flow","type":"hasLabel","value":true},{"label":"Invite users","type":"hasLabel","value":true}],"requires":1},"Performance Pod":{"conditions":[{"label":"Performance","type":"hasLabel","value":true},{"label":"Performance infra","type":"hasLabel","value":true}],"requires":1},"Widget design system":{"conditions":[{"label":"App Theming","type":"hasLabel","value":true},{"label":"Widget Styling","type":"hasLabel","value":true},{"label":"Checkbox Group widget","type":"hasLabel","value":true},{"label":"Checkbox Widget","type":"hasLabel","value":true},{"label":"Checkbox Component","type":"hasLabel","value":true},{"label":"WDS team","type":"hasLabel","value":true},{"label":"Widget design system","type":"hasLabel","value":true}],"requires":1},"IDE Pod":{"conditions":[],"requires":1},"Appsmith Labs":{"conditions":[{"label":"AI","type":"hasLabel","value":true},{"label":"AI Assistant","type":"hasLabel","value":true}],"requires":1},"Workflows Pod":{"conditions":[],"requires":1}}},"root":"."}],"labels":{"Tab Widget":{"color":"e2c76c","name":"Tab Widget","description":""},"Dont merge":{"color":"ADB39C","name":"Dont merge","description":""},"Epic":{"color":"3E4B9E","name":"Epic","description":"A zenhub epic that describes a project"},"Menu Button Widget":{"color":"235708","name":"Menu Button Widget","description":"Issues related to Menu Button widget"},"Checkbox Group widget":{"color":"88054d","name":"Checkbox Group widget","description":"Issues related to Checkbox Group Widget"},"Input Widget":{"color":"ae65d8","name":"Input Widget","description":""},"Security":{"color":"99139C","name":"Security","description":""},"QA":{"color":"e2ca68","name":"QA","description":""},"Verified":{"color":"9bf416","name":"Verified","description":""},"Wont Fix":{"color":"ffffff","name":"Wont Fix","description":"This will not be worked on"},"MySQL":{"color":"c9ddc6","name":"MySQL","description":"Issues related to MySQL plugin"},"Development":{"color":"9F8A02","name":"Development","description":""},"Help Wanted":{"color":"008672","name":"Help Wanted","description":"Extra attention is needed"},"Home Page":{"color":"9c0c8e","name":"Home Page","description":"Issues related to the application home page"},"Rating Widget":{"color":"235708","name":"Rating Widget","description":"Issues related to the rating widget"},"Stat Box Widget":{"color":"f1c9ce","name":"Stat Box Widget","description":"Issues related to stat box"},"Enhancement":{"color":"a2eeef","name":"Enhancement","description":"New feature or request"},"Settings":{"color":"f7ff60","name":"Settings","description":"organization, team & user settings"},"Fork App":{"color":"30c76d","name":"Fork App","description":"Issues related to forking apps"},"Container Widget":{"color":"19AD0D","name":"Container Widget","description":"Container widget"},"Papercut":{"color":"B562F6","name":"Papercut","description":""},"Needs Design":{"color":"bfd4f2","name":"Needs Design","description":"needs design or changes to design"},"i18n":{"color":"1799b0","name":"i18n","description":"Represents issues that need to be tackled to handle internationalization"},"Rich Text Editor Widget":{"color":"f72cac","name":"Rich Text Editor Widget","description":""},"Onboarding":{"color":"30c76d","name":"Onboarding","description":"Issues related to onboarding new developers"},"Pages":{"color":"d7fd80","name":"Pages","description":"Issues related to configuring pages"},"skip-changelog":{"color":"06086F","name":"skip-changelog","description":"Adding this label to a PR prevents it from being listed in the changelog"},"Low":{"color":"79e53b","name":"Low","description":"An issue that is neither critical nor breaks a user flow"},"potential-duplicate":{"color":"d3cb2e","name":"potential-duplicate","description":"This label marks issues that are potential duplicates of already open issues"},"Audio Widget":{"color":"447B9A","name":"Audio Widget","description":"Issues related to Audio Widget"},"Firestore":{"color":"8078b0","name":"Firestore","description":"Issues related to the firestore Integration"},"New Widget":{"color":"be4cf2","name":"New Widget","description":"A request for a new widget"},"Modal Widget":{"color":"03846f","name":"Modal Widget","description":""},"UX Improvement":{"color":"f4a089","name":"UX Improvement","description":""},"S3":{"color":"8078b0","name":"S3","description":"Issues related to the S3 plugin"},"Release Blocker":{"color":"5756bf","name":"Release Blocker","description":"This issue must be resolved before the release"},"safari":{"color":"51C6AA","name":"safari","description":"Bugs seen on safari browser"},"Example Apps":{"color":"1799b0","name":"Example Apps","description":"Example apps created for new signups"},"MultiSelect Widget":{"color":"AB62D4","name":"MultiSelect Widget","description":"Issues related to MultiSelect Widget"},"Widget Styling":{"color":"905420","name":"Widget Styling","description":"all about widget styling"},"Calendar Widget":{"color":"8c6644","name":"Calendar Widget","description":""},"Website":{"color":"151720","name":"Website","description":"Related to www.appsmith.com website"},"Low effort":{"color":"8B59F0","name":"Low effort","description":"Something that'll take a few days to build"},"App Viewers Pod":{"color":"cd8ef9","name":"App Viewers Pod","description":"This label assigns issues to the app viewers pod"},"Checkbox Widget":{"color":"88054d","name":"Checkbox Widget","description":""},"Spam":{"color":"620faf","name":"Spam","description":""},"Voice Recorder Widget":{"color":"85bc87","name":"Voice Recorder Widget","description":""},"Select Widget":{"color":"0c669e","name":"Select Widget","description":"Select or dropdown widget"},"Bug":{"color":"d73a4a","name":"Bug","description":"Something isn't working"},"Widget Validation":{"color":"6990BC","name":"Widget Validation","description":"Issues related to widget property validation"},"Generate Page":{"color":"30c76d","name":"Generate Page","description":"Issures related to page generation"},"File Picker Widget":{"color":"6ae4f2","name":"File Picker Widget","description":""},"snowflake":{"color":"8078b0","name":"snowflake","description":"Issues related to the snowflake Integration"},"Automation":{"color":"CCAF60","name":"Automation","description":""},"hotfix":{"color":"BA3F1D","name":"hotfix","description":""},"Team Managers Pod":{"color":"bddb81","name":"Team Managers Pod","description":"Issues that team managers care about for the security and efficiency of their teams"},"Import-Export-App":{"color":"15076d","name":"Import-Export-App","description":"Issues related to importing and exporting apps"},"High effort":{"color":"A7E87B","name":"High effort","description":"Something that'll take more than a month to build"},"Telemetry":{"color":"bc70f9","name":"Telemetry","description":"Issues related to instrumenting appsmith"},"Radio Widget":{"color":"91ef15","name":"Radio Widget","description":""},"Omnibar":{"color":"10b5ce","name":"Omnibar","description":"Issues related to the omnibar for navigation"},"Button Widget":{"color":"34efae","name":"Button Widget","description":""},"Switch widget":{"color":"33A8CE","name":"Switch widget","description":"The switch widget"},"Map Widget":{"color":"7eef7a","name":"Map Widget","description":""},"Task":{"color":"085630","name":"Task","description":"A simple Todo"},"Design System":{"color":"2958a4","name":"Design System","description":"Design system"},"opera":{"color":"C63F5B","name":"opera","description":"Any issues identified on the opera browser"},"Login / Signup":{"color":"30c76d","name":"Login / Signup","description":"Authentication flows"},"Image Widget":{"color":"8de8ad","name":"Image Widget","description":""},"firefox":{"color":"6d56e2","name":"firefox","description":""},"Property Pane":{"color":"b356ff","name":"Property Pane","description":"Issues related to the behaviour of the property pane"},"Deployment":{"color":"93491f","name":"Deployment","description":"Installation process of appsmith"},"Critical":{"color":"9b1b28","name":"Critical","description":"This issue needs immediate attention. Drop everything else"},"IDE":{"color":"61b2ee","name":"IDE","description":"Issues related to the IDE"},"Production":{"color":"b60205","name":"Production","description":""},"Dependencies":{"color":"0366d6","name":"Dependencies","description":"Pull requests that update a dependency file"},"Google Sheets":{"color":"8078b0","name":"Google Sheets","description":"Issues related to Google Sheets"},"Icon Button Widget":{"color":"D319CE","name":"Icon Button Widget","description":"Issues related to the icon button widget"},"Mongo":{"color":"8078b0","name":"Mongo","description":"Issues related to Mongo DB plugin"},"Documentation":{"color":"a8dff7","name":"Documentation","description":"Improvements or additions to documentation"},"TestGap":{"color":"f28253","name":"TestGap","description":"Issues identified for test plan improvement"},"keyboard shortcut":{"color":"0688B6","name":"keyboard shortcut","description":""},"Git Version Control":{"color":"858172","name":"Git Version Control","description":"Issues related to version control"},"Reopen":{"color":"897548","name":"Reopen","description":""},"Redshift":{"color":"8078b0","name":"Redshift","description":"Issues related to the redshift integration"},"Date Picker Widget":{"color":"ef1ce1","name":"Date Picker Widget","description":""},"Entity Explorer":{"color":"a2e2f9","name":"Entity Explorer","description":"Issues related to navigation using the entity explorer"},"JS Linting & Errors":{"color":"E56AA5","name":"JS Linting & Errors","description":"Issues related to JS Linting and errors"},"iFrame":{"color":"3CD1DB","name":"iFrame","description":"Issues related to iFrame"},"Stale":{"color":"ededed","name":"Stale","description":null},"Debugger":{"color":"e79062","name":"Debugger","description":"Issues related to the debugger"},"Quick effort":{"color":"95ED65","name":"Quick effort","description":"Something that'll take a few hours to build"},"Text Widget":{"color":"d130d1","name":"Text Widget","description":""},"Video Widget":{"color":"23dd4b","name":"Video Widget","description":""},"Datasources":{"color":"5052f6","name":"Datasources","description":"Issues related to configuring datasource on appsmith"},"error":{"color":"B66773","name":"error","description":"All issues connected to error messages"},"Form Widget":{"color":"09ed77","name":"Form Widget","description":""},"Needs Triaging":{"color":"e8b851","name":"Needs Triaging","description":"Needs attention from maintainers to triage"},"Autocomplete":{"color":"235708","name":"Autocomplete","description":"Issues related to the autocomplete"},"hacktoberfest":{"color":"0052cc","name":"hacktoberfest","description":"All issues that can be solved by the community during Hacktoberfest"},"Medium effort":{"color":"D31156","name":"Medium effort","description":"Something that'll take more than a week but less than a month to build"},"Release":{"color":"57e5e0","name":"Release","description":""},"High":{"color":"c94d14","name":"High","description":"This issue blocks a user from building or impacts a lot of users"},"UI Performance":{"color":"1799b0","name":"UI Performance","description":"Issues related to UI performance"},"UI Builders Pod":{"color":"517fba","name":"UI Builders Pod","description":"Issues that UI Builders face using appsmith"},"Deploy Preview":{"color":"bfdadc","name":"Deploy Preview","description":"Issues found in Deploy Preview"},"Needs Tests":{"color":"8ee263","name":"Needs Tests","description":"Needs automated tests to assert a feature/bug fix"},"Refactor":{"color":"B96662","name":"Refactor","description":"needs refactoring of code"},"Divider Widget":{"color":"235708","name":"Divider Widget","description":"Issues related to the divider widget"},"Table Widget":{"color":"2eead1","name":"Table Widget","description":""},"Needs More Info":{"color":"e54c10","name":"Needs More Info","description":"Needs additional information"},"Good First Issue":{"color":"7057ff","name":"Good First Issue","description":"Good for newcomers"},"UI Improvement":{"color":"9aeef4","name":"UI Improvement","description":""},"Backend":{"color":"d4c5f9","name":"Backend","description":"This marks the issue or pull request to reference server code"},"Frontend":{"color":"87c7f2","name":"Frontend","description":"This label marks the issue or pull request to reference client code"},"In App Comms":{"name":"In App Comms","description":"Issues around communication with appsmith instances","color":"463cca"},"Chart Widget":{"color":"616ecc","name":"Chart Widget","description":""},"List Widget":{"color":"8508A0","name":"List Widget","description":"Issues related to the list widget"},"Duplicate":{"color":"cfd3d7","name":"Duplicate","description":"This issue or pull request already exists"},"JS Snippets":{"color":"8d62d2","name":"JS Snippets","description":"issues related to JS Snippets"},"Copy Paste":{"name":"Copy Paste","description":"Issues related to copy paste","color":"b4f0a9"},"Drag & Drop":{"name":"Drag & Drop","description":"Issues related to the drag & drop experience","color":"92115a"},"BE Coders Pod":{"color":"5d9848","name":"BE Coders Pod","description":"Issues related to users writing code to fetch and update data"},"FE Coders Pod":{"color":"a7effc","name":"FE Coders Pod","description":"Issues related to users writing javascript in appsmith"},"New Developers Pod":{"color":"6310da","name":"New Developers Pod","description":"Issues that new developers face while exploring the IDE"},"Sniping Mode":{"name":"Sniping Mode","description":"Issues related to sniping mode","color":"30c76d"},"Redis":{"name":"Redis","description":"Issues related to Redis","color":"8078b0"},"New Datasource":{"color":"60b14c","name":"New Datasource","description":"Requests for new datasources"},"Evaluated Value":{"name":"Evaluated Value","description":"Issues related to evaluated values","color":"39f6e7"},"Undo/Redo":{"name":"Undo/Redo","description":"Issues related to undo/redo","color":"f25880"},"App Navigation":{"name":"App Navigation","description":"Issues related to the topbar navigation and configuring it","color":"12b715"},"Responsive Viewport":{"color":"d12d2e","name":"Responsive Viewport","description":"Issues seen on different viewports like mobile"},"Widgets Pane":{"name":"Widgets Pane","description":"Issues related to the discovery and organisation of widgets","color":"ad5d78"},"View Mode":{"color":"1799b0","name":"View Mode","description":"Issues related to the view mode"},"User Education Pod":{"name":"User Education Pod","description":"Issues related to user education","color":"1799b0"},"Content":{"name":"Content","description":"For content related topics i.e blogs, templates, videos","color":"a8dff7"},"Embedding Apps":{"name":"Embedding Apps","description":"Issues related to embedding","color":"30c76d"},"Slash Command":{"name":"Slash Command","description":"Issues related to the slash command","color":"a0608e"},"Widget Property":{"name":"Widget Property","description":"Issues related to adding / modifying widget properties across widgets","color":"5e92cb"},"Windows":{"name":"Windows","description":"Issues related exclusively to Windows systems","color":"b4cb8a"},"Old App Issues":{"name":"Old App Issues","description":"Issues related to apps old apps a few weeks old and app issues in stale browser session","color":"87ab18"},"Document Viewer Widget":{"name":"Document Viewer Widget","description":"Issues related to Document Viewer Widget","color":"899d4b"},"Radio Group Widget":{"name":"Radio Group Widget","description":"Issues related to radio group widget","color":"b68495"},"Super Admin":{"name":"Super Admin","description":"Issues related to the super admin page","color":"aa95cf"},"Postgres":{"name":"Postgres","description":"Postgres related issues","color":"8078b0"},"REST API plugin":{"name":"REST API plugin","description":"REST API plugin related issues","color":"8078b0"},"New JS Function":{"name":"New JS Function","description":"Issues related to adding a JS Function","color":"8e8aa4"},"Cannot Reproduce Issue":{"color":"93c9cc","name":"Cannot Reproduce Issue","description":"Issues that cannot be reproduced"},"Widget Grouping":{"name":"Widget Grouping","description":"Issues related to Widget Grouping","color":"a49951"},"K8s":{"name":"K8s","description":"Kubernetes related issues","color":"5f318a"},"Docker":{"name":"Docker","description":"Issues related to docker","color":"89b808"},"Camera Widget":{"name":"Camera Widget","description":"Issues and enhancements related to camera widget","color":"e6038e"},"SAAS Plugins":{"name":"SAAS Plugins","description":"Issues related to SAAS Plugins","color":"ef9c9d"},"JS Promises":{"name":"JS Promises","description":"Issues related to promises","color":"d7771f"},"OnPageLoad":{"name":"OnPageLoad","description":"OnPageLoad issues on functions and queries","color":"50559d"},"JS Usability":{"name":"JS Usability","description":"usability issues with JS editor and JS elsewhere","color":"a302b0"},"Currency Input Widget":{"name":"Currency Input Widget","description":"Issues related to currency input widget","color":"b2164f"},"TreeSelect":{"name":"TreeSelect","description":"Issues related to TreeSelect Widget","color":"a1633e"},"MultiTree Select Widget":{"name":"MultiTree Select Widget","description":"Issues related to MultiTree Select Widget","color":"a1633e"},"Welcome Screen":{"name":"Welcome Screen","description":"Issues related to the welcome screen","color":"30c76d"},"Realtime Commenting":{"color":"a70b86","name":"Realtime Commenting","description":"In-app communication between teams"},"Phone Input Widget":{"name":"Phone Input Widget","description":"Issues related to the Phone Input widget","color":"a70b86"},"JSON Form":{"name":"JSON Form","description":"Issue / features related to the JSON form wiget","color":"46b209"},"All Widgets":{"name":"All Widgets","description":"Issues related to all widgets","color":"972b36"},"V1":{"name":"V1","description":"V1","color":"67ab2e"},"Reflow & Resize":{"name":"Reflow & Resize","description":"All issues related to reflow and resize experience","color":"748a13"},"App Theming":{"name":"App Theming","description":"Items that are related to the App level theming controls epic","color":"905420"},"SSO":{"name":"SSO","description":"Issues, requests and enhancements around Single sign-on.","color":"bf019b"},"Multi User Realtime":{"name":"Multi User Realtime","description":"Issues related to multiple users using or editing an application","color":"e7b6ce"},"Templates":{"name":"Templates","description":"Issues related to templates","color":"b7e568"},"Ready for design":{"name":"Ready for design","description":"this issue is ready for design: it contains clear problem statements and other required information","color":"ebf442"},"Support":{"name":"Support","description":"Issues created by the A-force team to address user queries","color":"1740f3"},"Button Group widget":{"name":"Button Group widget","description":"Issue and enhancements related to the button group widget","color":"f17025"},"GraphQL Plugin":{"name":"GraphQL Plugin","description":"Issues related to GraphQL plugin","color":"8078b0"},"DevOps Pod":{"name":"DevOps Pod","description":"Issues related to devops","color":"d956c7"},"medium":{"name":"medium","description":"Issues that frustrate users due to poor UX","color":"23dfd9"},"ArangoDB":{"name":"ArangoDB","description":"Issues related to arangoDB","color":"8078b0"},"Code Refactoring":{"name":"Code Refactoring","description":"Issues related to code refactoring","color":"76310e"},"Progress bar widget":{"name":"Progress bar widget","description":"To track issues related to progress bar","color":"2d7abf"},"Audio Recorder Widget":{"name":"Audio Recorder Widget","description":"Issues related to Audio Recorder Widget","color":"9accef"},"Airtable":{"name":"Airtable","description":"Issues for Airtable","color":"60885f"},"RBAC":{"name":"RBAC","description":"Issues, requests and enhancements around RBAC.","color":"9211c3"},"Canvas / Grid":{"name":"Canvas / Grid","description":"Issues related to the canvas","color":"16b092"},"Email Config":{"name":"Email Config","description":"Issues related to configuring the email service","color":"2a21d1"},"CURL":{"name":"CURL","description":"Issues related to CURL impor","color":"60885f"},"Canvas Zooms":{"name":"Canvas Zooms","description":"Issues related to zooming the canvas","color":"e6038e"},"business":{"name":"business","description":"Features that will be a part of our business edition","color":"cd59eb"},"Action Pod":{"name":"Action Pod","description":"","color":"ee2e36"},"AutomationGap1":{"color":"a5e07c","name":"AutomationGap1","description":"Issues that needs automated tests"},"A-Force11":{"name":"A-Force11","description":"Issues raised by A-Force team","color":"d667b6"},"Business Edition":{"name":"Business Edition","description":"Features that will be a part of our business edition","color":"89bb6c"},"storeValue":{"name":"storeValue","description":"Issues related to the store value function","color":"5d3e66"},"Tests":{"name":"Tests","description":"test item","color":"1c6990"},"DynamoDB":{"name":"DynamoDB","description":"Issues that are related to DynamoDB should have this label","color":"60885f"},"Design System Pod":{"name":"Design System Pod","description":"Appsmith design system related issues","color":"706f03"},"ABAC":{"color":"e009a5","name":"ABAC","description":"User permissions and access controls"},"Backup & Restore":{"name":"Backup & Restore","description":"Issues related to backup and restore","color":"86874d"},"Billing":{"name":"Billing","description":"Billing infrastructure and flows for Business Edition and Trial users","color":"d2bc40"},"Datatype issue":{"name":"Datatype issue","description":"Issues that have risen because data types weren't handled","color":"60885f"},"OAuth":{"name":"OAuth","description":"OAuth related bugs or features","color":"60885f"},"Table Widget V2":{"name":"Table Widget V2","description":"Issues related to Table Widget V2","color":"3a7192"},"IDE Navigation":{"name":"IDE Navigation","description":"Issues/feature requests related to IDE navigation, and context switching","color":"bc0cba"},"Query performance":{"name":"Query performance","description":"Issues that have to do with lack in performance of query execution","color":"e4d966"},"SAAS Manager App":{"name":"SAAS Manager App","description":"Issues with the SAAS manager app","color":"d427db"},"Twilio":{"name":"Twilio","description":"Issues related to Twilio integration","color":"23ba8d"},"Hubspot":{"name":"Hubspot","description":"Issues related to Hubspot integration","color":"60885f"},"Zendesk":{"name":"Zendesk","description":"Issues related to Zendesk integration","color":"60885f"},"Entity Refactor":{"name":"Entity Refactor","description":"Issues related to refactor logic","color":"418fa4"},"Branding":{"name":"Branding","description":"All issues under branding and whitelabelling appsmith ecosystem","color":"7aaaf1"},"Map Chart Widget":{"name":"Map Chart Widget","description":"Issues related to Map Chart Widgets","color":"c8397f"},"Product Catchup":{"name":"Product Catchup","description":"Issues created in the product catchup","color":"29cd2c"},"Framework Functions":{"name":"Framework Functions","description":"Issues related to internal functions like showAlert(), navigateTo() etc...","color":"c25a09"},"Frontend Libraries Upgrade":{"name":"Frontend Libraries Upgrade","description":"Issues related to frontend libraries upgrade","color":"ede1fc"},"Audit Logs":{"name":"Audit Logs","description":"Audit trails to ensure data security","color":"f3fd62"},"MsSQL":{"name":"MsSQL","description":"Issues related to MsSQL plugin","color":"8078b0"},"Data Platform Pod":{"name":"Data Platform Pod","description":"Issues related to the underlying data platform","color":"3f8c3a"},"Integrations Pod":{"name":"Integrations Pod","description":"Issues related to a specific integration","color":"5dbbb1"},"Datasource Environments":{"name":"Datasource Environments","description":"Issues related to datasource environments","color":"bb7a14"},"Elastic Search":{"name":"Elastic Search","description":"Issues related to the elastic search datasource","color":"8078b0"},"Core Query Execution":{"color":"418fa4","name":"Core Query Execution","description":"Issues related to the execution of all queries"},"Query Management":{"name":"Query Management","description":"Issues related to the CRUD of actions or queries","color":"6a5b42"},"Query Settings":{"name":"Query Settings","description":"Issues related to the settings of all queries","color":"c7da7a"},"Code Editor":{"name":"Code Editor","description":"Issues related to the code editor","color":"4ca16e"},"Query Forms":{"color":"12b253","name":"Query Forms","description":"Isuses related to the query forms"},"JS Objects":{"color":"22962c","name":"JS Objects","description":"Issues related to JS Objects"},"JS Evaluation":{"color":"22962c","name":"JS Evaluation","description":"Issues related to JS evaluation on the platform"},"SmartSubstitution":{"name":"SmartSubstitution","description":"Issues related to Smart substitution of mustache bindings in queries","color":"e4d966"},"Query Generation":{"name":"Query Generation","description":"Issues related to query generation","color":"e4d966"},"Suggested Widgets":{"name":"Suggested Widgets","description":"Issues related to suggesting widgets based on query response","color":"e4d966"},"Page load executions":{"name":"Page load executions","description":"Issues related to page load execution","color":"5696b2"},"Code Scanner Widget":{"name":"Code Scanner Widget","description":"Issues related to code scanner widget","color":"9bc1a0"},"Clean URLs":{"name":"Clean URLs","description":"Issues related to clean URLs epic","color":"112623"},"Widget keyboard accessibility":{"name":"Widget keyboard accessibility","description":"All issues related to keyboard accessibility in widgets","color":"b626fd"},"Connection pool":{"name":"Connection pool","description":"issues to do with connection pooling of various plugins","color":"94fe36"},"List Widget V2":{"name":"List Widget V2","description":"Issues related to the list widget v2","color":"adaaf7"},"Auto Height":{"name":"Auto Height","description":"Issues related to dynamic height of widgets","color":"5149cf"},"cypress_failed_test":{"name":"cypress_failed_test","description":"Cypress failed tests","color":"4745d5"},"Needs validation":{"name":"Needs validation","description":"Needs problem validation before being picked up","color":"66673d"},"Slider Widget":{"name":"Slider Widget","description":"Issues raised for slider widgets.","color":"2eef5f"},"Multitenancy":{"name":"Multitenancy","description":"Support multitenancy within single appsmith instance","color":"8c49a9"},"Git Pod":{"name":"Git Pod","description":"Anything related to git sync","color":"2e5ba4"},"Mobile Pod":{"name":"Mobile Pod","description":"All issues related to mobile responsiveness","color":"6c97fd"},"Responsive Widget":{"name":"Responsive Widget","description":"All issues related to widget responsiveness","color":"d12d2e"},"Responsive Canvas":{"name":"Responsive Canvas","description":"All issues related to canvas responsiveness","color":"45a0a8"},"Conversion Algorithm":{"name":"Conversion Algorithm","description":"All issue related to converting app from fixed to flex mode & vice versa","color":"d12d2e"},"Spacing":{"name":"Spacing","description":"All issue related to spacing between widgets in auto layout","color":"d12d2e"},"Browser specific":{"name":"Browser specific","description":"All issue related to browser","color":"d12d2e"},"Error Handling":{"name":"Error Handling","description":"Issues related to error handling","color":"4e1872"},"Performance infra":{"name":"Performance infra","description":"all issue related to the performance infra","color":"8a60f6"},"DSL Update":{"name":"DSL Update","description":"Issues related to storing and updating the DSL","color":"e16cf3"},"AST-frontend":{"name":"AST-frontend","description":"Issues related to maintaining AST logic","color":"434a3a"},"AST-backend":{"name":"AST-backend","description":"Backend issues related to AST parsing","color":"c476eb"},"MariaDB":{"name":"MariaDB","description":"MariaDB datasource","color":"8428c3"},"Billing & Usage Pod":{"name":"Billing & Usage Pod","description":"Issues pertaining to licensing, billing, usage across self serve and enterprise customers","color":"256808"},"ADS Component Issue":{"name":"ADS Component Issue","description":"Issues which are caused due to ADS components","color":"d89119"},"Regressed":{"color":"723fd0","name":"Regressed","description":"Scenarios that were working before but have now regressed"},"Needs RCA":{"name":"Needs RCA","description":"a critical or high priority issue that needs an RCA","color":"2cc68f"},"Custom JS Libraries":{"name":"Custom JS Libraries","description":"Issues related to adding custom JS library","color":"bacb6d"},"Integrations Pod General":{"name":"Integrations Pod General","description":"Issues related to the Integrations Pod that don't fit into other tags.","color":"287823"},"Performance Pod":{"name":"Performance Pod","description":"All things related to Appsmith performance","color":"b5a25d"},"Performance":{"name":"Performance","description":"Issues related to performance","color":"9a18d7"},"File upload issues":{"name":"File upload issues","description":"Issues related to uploading any type of files from within Appsmith","color":"8154df"},"Action Selector":{"name":"Action Selector","description":"Issues related to action selector on the property pane","color":"2f9e20"},"Widget design system":{"name":"Widget design system","description":"","color":"cb6188"},"Deploy App":{"name":"Deploy App","description":"Issues related to app deployment","color":"6f6152"},"Community Reported":{"name":"Community Reported","description":"issues reported by community members","color":"1402e5"},"JS Function execution":{"name":"JS Function execution","description":"JS function execution","color":"7c2de1"},"Self Serve":{"name":"Self Serve","description":"For all issues related to self-serve flow for business edition","color":"4dacfc"},"Self Serve 1.0":{"name":"Self Serve 1.0","description":"For all issues related to v1 of the self serve project","color":"ae839e"},"CE Instance":{"name":"CE Instance","description":"For all issues relating to usage, licensing or billing on the CE instance","color":"d2bc40"},"Customer Portal":{"name":"Customer Portal","description":"For all tasks/issues pertaining to customer.appsmith.com","color":"d2bc40"},"Cloud Services":{"name":"Cloud Services","description":"For all tasks/issues on Appsmith cloud-services relating to licensing, usage and billing","color":"d2bc40"},"Billing Integrations":{"name":"Billing Integrations","description":"For all issues relating to 3P integrations Appsmith is using for billing & usage","color":"d2bc40"},"One-click Binding":{"name":"One-click Binding","description":"Issues related to the One click binding epic","color":"f1661c"},"Airgap":{"name":"Airgap","description":"Tickets related to supporting air-gapped Appsmith instances","color":"1cb294"},"SMTP plugin":{"name":"SMTP plugin","description":"Issues related to SMTP plugin","color":"541457"},"AWS AMI":{"name":"AWS AMI","description":"Issues Related to AWS AMI","color":"b44680"},"Old widget version":{"name":"Old widget version","description":"Use this label to raise issue specific only to an older version of a widget","color":"ff3814"},"Enterprise Billing":{"name":"Enterprise Billing","description":"To track all tasks/issues related to licensing & billing for enterprise customers","color":"14c156"},"Appsmith Business Cloud":{"name":"Appsmith Business Cloud","description":"Issues related to our business cloud offering","color":"89bb6c"},"Oracle SQL DB":{"name":"Oracle SQL DB","description":"Issues related to the Oracle plugin","color":"cbabcb"},"Community Contributor":{"name":"Community Contributor","description":"Meant to track issues that are assigned to external contributors","color":"149ab6"},"widget vertical alignment":{"name":"widget vertical alignment","description":"All issue related widget vertical alignment on the auto layout canvas","color":"d12d2e"},"Observability":{"name":"Observability","description":"Issues related to observability on the Appsmith instance","color":"dff913"},"Checkbox Component":{"name":"Checkbox Component","description":"This labels deals with checkbox component in wds package","color":"75a401"},"In-app ramps":{"name":"In-app ramps","description":"For all tasks/issues relating to adding in-app ramps in the community edition of the product","color":"8abae0"},"Analytics Improvements":{"name":"Analytics Improvements","description":"For all tasks focused on improving our overall analytics and fixing any issues ","color":"29b8ed"},"WDS team":{"name":"WDS team","description":"","color":"8d675a"},"Enterprise Edition":{"name":"Enterprise Edition","description":"Features that will be supported in Enterprise Edition only","color":"984f5e"},"Query filter":{"name":"Query filter","description":"Issues related to query filtering, e.g., WHERE clause","color":"a15134"},"Keyboard accessibility ":{"name":"Keyboard accessibility ","description":"All issue related to ADS component keyboard accessibility","color":"2ba696"},"Toggle button":{"name":"Toggle button","description":"All issue related to ADS toggle button","color":"edc47f"},"1-click upgrade":{"name":"1-click upgrade","description":"For all issues/tasks related to 1-click upgrade & downgrade project","color":"129082"},"Feature Flagging":{"name":"Feature Flagging","description":"Anything related feature flagging","color":"8d8a09"},"SCIM":{"name":"SCIM","description":"Label to collate our SCIM issues","color":"61a852"},"ADS Category Token":{"name":"ADS Category Token","description":"All issues related appsmith design system category tokens","color":"920961"},"ADS Component Documentation":{"name":"ADS Component Documentation","description":"All issues Appsmith design system component documentation","color":"64c46a"},"ADS Migration":{"name":"ADS Migration","description":"All issues related to Appsmith design system migration","color":"b082d6"},"ADS Deduplication ":{"name":"ADS Deduplication ","description":"Replacing component with ADS components","color":"b082d6"},"ADS Revamp":{"name":"ADS Revamp","description":"All issues related to ads revamp. ","color":"b082d6"},"ADS Deduplication":{"name":"ADS Deduplication","description":"Replacing component with ADS components","color":"b082d6"},"ADS Grayscale":{"name":"ADS Grayscale","description":"Support grayscale color changes","color":"b03577"},"ADS Unit Test":{"name":"ADS Unit Test","description":"All issue related ads unit cases ","color":"b082d6"},"ADS Components":{"name":"ADS Components","description":"All issues related ADS components","color":"b082d6"},"Widget Discoverability":{"name":"Widget Discoverability","description":"Issues related to Widget Discoverability","color":"7b55ce"},"Widget setter method":{"name":"Widget setter method","description":"Issues with widget property setters","color":"8dce87"},"License":{"name":"License","description":"For all issues/tasks related to licensing of appsmith-ee edition","color":"90ee98"},"Templates pod":{"name":"Templates pod","description":"Issues related to Templates","color":"b7e568"},"Community template":{"name":"Community template","description":"Label for development of community templates and its integration to platform","color":"8a0510"},"DocumentDB":{"name":"DocumentDB","description":"Issues related to support DocumentDB in Appsmith Data layer","color":"2c8b56"},"Multiple Environments":{"name":"Multiple Environments","description":"Issues or tasks related to multiple environments","color":"4e972b"},"Platformization":{"name":"Platformization","description":"Issues or tasks related to platformization of Appsmith codebase","color":"4e972b"},"Activation - datasources":{"name":"Activation - datasources","description":"issues related to activation projects","color":"7c7ace"},"Partial-import-export":{"name":"Partial-import-export","description":"Label for granular reusability.","color":"1e439c"},"AI":{"name":"AI","description":"All tasks related to AI","color":"75c4ce"},"Custom environments":{"name":"Custom environments","description":"Issues with creating or working with custom environments","color":"2137d6"},"ADS Typography":{"name":"ADS Typography","description":"All issue related typographical changes","color":"2dbe8d"},"Auto Layout":{"name":"Auto Layout","description":"Issues relates to auto layout","color":"92cf8c"},"Heroku":{"name":"Heroku","description":"Issues related to Heroku","color":"a81b69"},"ADS Visual Styles":{"name":"ADS Visual Styles","description":"All issues related to ADS visual styles","color":"d3da89"},"ADS Component Design":{"name":"ADS Component Design","description":"All issue related to component design","color":"5cc91e"},"Modal Component":{"name":"Modal Component","description":"All issue related to ads modal component","color":"ee63f3"},"App setting":{"name":"App setting","description":"Related to app settings panel within the app","color":"144206"},"BE instance":{"name":"BE instance","description":"For all issues related to license, billing on BE instance","color":"ae8f98"},"Schema":{"name":"Schema","description":"Issues related to database schema","color":"c470c2"},"Fixed layout":{"name":"Fixed layout","description":"issues related to fixed layout","color":"b66681"},"Anvil layout":{"name":"Anvil layout","description":"issues related to the new layout system anvil","color":"722bf0"},"New Deployment Mode":{"name":"New Deployment Mode","description":"Support a new mode of deployment","color":"108033"},"Custom widgets":{"name":"Custom widgets","description":"For all issues related to the custom widget project","color":"c9db9c"},"IDE Pod":{"name":"IDE Pod","description":"https://app.zenhub.com/workspaces/new-developers-pod-60507ad1d4b98d00150a2858/board","color":"d3d248"},"TM_BU":{"name":"TM_BU","description":"The issues on Team Manager which needs to be taken up by Billing & Usage","color":"198cdf"},"Homepage Experience V2":{"name":"Homepage Experience V2","description":"Label for reporting new tasks and bug fixes related to revamped homepage experience","color":"c55d54"},"Appsmith Labs":{"name":"Appsmith Labs","description":"All things related to AI and other new initiatives ","color":"712d51"},"Customer Success":{"name":"Customer Success","description":"Issues that the success team cares about","color":"6ccabd"},"Invite flow":{"name":"Invite flow","description":"Invite users flow and any associated actions","color":"881b35"},"Invite users":{"name":"Invite users","description":"Invite users flow and any associated actions","color":"23e6d6"},"Workflows Pod":{"name":"Workflows Pod","description":"For all issues related to the Workflows feature","color":"2c1f93"},"AI Assistant":{"name":"AI Assistant","description":"Tickets related to Appsmith AI Assistant","color":"2fd627"}},"success":true} \ No newline at end of file From 97bcd6c192819155f55a2ed7e0df9dedc335c04f Mon Sep 17 00:00:00 2001 From: Nikhil Nandagopal Date: Thu, 18 Jan 2024 11:50:38 +0530 Subject: [PATCH 08/16] Updated Label Config --- .github/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/config.json b/.github/config.json index 0f323c185a..ec4ac3ad70 100644 --- a/.github/config.json +++ b/.github/config.json @@ -1 +1 @@ -{"runners":[{"versioning":{"source":"milestones","type":"SemVer"},"prereleaseName":"alpha","issue":{"labels":{"Error Handling":{"conditions":[],"requires":1},"Templates pod":{"conditions":[{"label":"Templates","type":"hasLabel","value":true},{"label":"Community template","type":"hasLabel","value":true},{"label":"Partial-import-export","type":"hasLabel","value":true}],"requires":1},"Team Managers Pod":{"conditions":[{"label":"Settings","type":"hasLabel","value":true},{"label":"Home Page","type":"hasLabel","value":true},{"label":"Realtime Commenting","type":"hasLabel","value":true},{"label":"SSO","type":"hasLabel","value":true},{"label":"Multi User Realtime","type":"hasLabel","value":true},{"label":"RBAC","type":"hasLabel","value":true},{"label":"ABAC","type":"hasLabel","value":true},{"label":"Audit Logs","type":"hasLabel","value":true},{"label":"Multitenancy","type":"hasLabel","value":true},{"label":"Airgap","type":"hasLabel","value":true},{"label":"Enterprise Edition","type":"hasLabel","value":true},{"label":"SCIM","type":"hasLabel","value":true},{"label":"Invite flow","type":"hasLabel","value":true}],"requires":1},"New Developers Pod":{"conditions":[{"label":"Omnibar","type":"hasLabel","value":true},{"label":"Telemetry","type":"hasLabel","value":true},{"label":"Entity Explorer","type":"hasLabel","value":true},{"label":"IDE","type":"hasLabel","value":true},{"label":"Example Apps","type":"hasLabel","value":true},{"label":"i18n","type":"hasLabel","value":true},{"label":"IDE Navigation","type":"hasLabel","value":true},{"label":"Clean URLs","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"App setting","type":"hasLabel","value":true}],"requires":1},"BE Coders Pod":{"conditions":[{"label":"SAAS Plugins","type":"hasLabel","value":true},{"label":"SAAS Manager App","type":"hasLabel","value":true},{"label":"Data Platform Pod","type":"hasLabel","value":true},{"label":"Integrations Pod","type":"hasLabel","value":true}],"requires":1},"FE Coders Pod":{"conditions":[{"label":"JS Linting & Errors","type":"hasLabel","value":true},{"label":"Debugger","type":"hasLabel","value":true},{"label":"JS Snippets","type":"hasLabel","value":true},{"label":"Autocomplete","type":"hasLabel","value":true},{"label":"Evaluated Value","type":"hasLabel","value":true},{"label":"Slash Command","type":"hasLabel","value":true},{"label":"New JS Function","type":"hasLabel","value":true},{"label":"JS Promises","type":"hasLabel","value":true},{"label":"JS Usability","type":"hasLabel","value":true},{"label":"Code Refactoring","type":"hasLabel","value":true},{"label":"storeValue","type":"hasLabel","value":true},{"label":"OnPageLoad","type":"hasLabel","value":true},{"label":"Framework Functions","type":"hasLabel","value":true},{"label":"Code Editor","type":"hasLabel","value":true},{"label":"JS Objects","type":"hasLabel","value":true},{"label":"JS Evaluation","type":"hasLabel","value":true},{"label":"AST-frontend","type":"hasLabel","value":true},{"label":"Custom JS Libraries","type":"hasLabel","value":true},{"label":"Action Selector","type":"hasLabel","value":true},{"label":"JS Function execution","type":"hasLabel","value":true},{"label":"Widget setter method","type":"hasLabel","value":true},{"label":"Error Handling","type":"hasLabel","value":true}],"requires":1},"App Viewers Pod":{"conditions":[{"label":"Button Widget","type":"hasLabel","value":true},{"label":"Chart Widget","type":"hasLabel","value":true},{"label":"Container Widget","type":"hasLabel","value":true},{"label":"Date Picker Widget","type":"hasLabel","value":true},{"label":"Select Widget","type":"hasLabel","value":true},{"label":"File Picker Widget","type":"hasLabel","value":true},{"label":"Form Widget","type":"hasLabel","value":true},{"label":"Image Widget","type":"hasLabel","value":true},{"label":"Input Widget","type":"hasLabel","value":true},{"label":"List Widget","type":"hasLabel","value":true},{"label":"MultiSelect Widget","type":"hasLabel","value":true},{"label":"Map Widget","type":"hasLabel","value":true},{"label":"Modal Widget","type":"hasLabel","value":true},{"label":"Radio Widget","type":"hasLabel","value":true},{"label":"Rich Text Editor Widget","type":"hasLabel","value":true},{"label":"Tab Widget","type":"hasLabel","value":true},{"label":"Table Widget","type":"hasLabel","value":true},{"label":"Text Widget","type":"hasLabel","value":true},{"label":"Video Widget","type":"hasLabel","value":true},{"label":"iFrame","type":"hasLabel","value":true},{"label":"Menu Button","type":"hasLabel","value":true},{"label":"Rating","type":"hasLabel","value":true},{"label":"Widget Validation","type":"hasLabel","value":true},{"label":"reallabel","type":"hasLabel","value":true},{"label":"New Widget","type":"hasLabel","value":true},{"label":"Switch widget","type":"hasLabel","value":true},{"label":"Audio Widget","type":"hasLabel","value":true},{"label":"Icon Button Widget","type":"hasLabel","value":true},{"label":"Stat Box Widget","type":"hasLabel","value":true},{"label":"Voice Recorder Widget","type":"hasLabel","value":true},{"label":"Calendar Widget","type":"hasLabel","value":true},{"label":"Menu Button Widget","type":"hasLabel","value":true},{"label":"Divider Widget","type":"hasLabel","value":true},{"label":"Rating Widget","type":"hasLabel","value":true},{"label":"App Navigation","type":"hasLabel","value":true},{"label":"View Mode","type":"hasLabel","value":true},{"label":"Widget Property","type":"hasLabel","value":true},{"label":"Document Viewer Widget","type":"hasLabel","value":true},{"label":"Radio Group Widget","type":"hasLabel","value":true},{"label":"Currency Input Widget","type":"hasLabel","value":true},{"label":"TreeSelect","type":"hasLabel","value":true},{"label":"MultiTree Select Widget","type":"hasLabel","value":true},{"label":"Phone Input Widget","type":"hasLabel","value":true},{"label":"JSON Form","type":"hasLabel","value":true},{"label":"All Widgets","type":"hasLabel","value":true},{"label":"Button Group widget","type":"hasLabel","value":true},{"label":"Progress bar widget","type":"hasLabel","value":true},{"label":"Audio Recorder Widget","type":"hasLabel","value":true},{"label":"Camera Widget","type":"hasLabel","value":true},{"label":"Table Widget V2","type":"hasLabel","value":true},{"label":"Branding","type":"hasLabel","value":true},{"label":"Map Chart Widget","type":"hasLabel","value":true},{"label":"Code Scanner Widget","type":"hasLabel","value":true},{"label":"Widget keyboard accessibility","type":"hasLabel","value":true},{"label":"List Widget V2","type":"hasLabel","value":true},{"label":"Slider Widget","type":"hasLabel","value":true},{"label":"One-click Binding","type":"hasLabel","value":true},{"label":"Old widget version","type":"hasLabel","value":true},{"label":"Widget Discoverability","type":"hasLabel","value":true},{"label":"Custom widgets","type":"hasLabel","value":true}],"requires":1},"UI Builders Pod":{"conditions":[{"label":"Property Pane","type":"hasLabel","value":true},{"label":"Pages","type":"hasLabel","value":true},{"label":"Copy Paste","type":"hasLabel","value":true},{"label":"Drag & Drop","type":"hasLabel","value":true},{"label":"Undo/Redo","type":"hasLabel","value":true},{"label":"Widgets Pane","type":"hasLabel","value":true},{"label":"UI Performance","type":"hasLabel","value":true},{"label":"Widget Grouping","type":"hasLabel","value":true},{"label":"Reflow & Resize","type":"hasLabel","value":true},{"label":"Canvas / Grid","type":"hasLabel","value":true},{"label":"Canvas Zooms","type":"hasLabel","value":true},{"label":"Frontend Libraries Upgrade","type":"hasLabel","value":true},{"label":"Auto Height","type":"hasLabel","value":true},{"label":"Responsive Canvas","type":"hasLabel","value":true},{"label":"Responsive Widget","type":"hasLabel","value":true},{"label":"Responsive Viewport","type":"hasLabel","value":true},{"label":"Conversion Algorithm","type":"hasLabel","value":true},{"label":"Spacing","type":"hasLabel","value":true},{"label":"Browser specific","type":"hasLabel","value":true},{"label":"widget vertical alignment","type":"hasLabel","value":true},{"label":"Auto Layout","type":"hasLabel","value":true},{"label":"Fixed layout","type":"hasLabel","value":true},{"label":"Anvil layout","type":"hasLabel","value":true}],"requires":1},"User Education Pod":{"conditions":[{"label":"Content","type":"hasLabel","value":true},{"label":"Documentation","type":"hasLabel","value":true}],"requires":1},"DevOps Pod":{"conditions":[{"label":"Docker","type":"hasLabel","value":true},{"label":"Super Admin","type":"hasLabel","value":true},{"label":"Deployment","type":"hasLabel","value":true},{"label":"K8s","type":"hasLabel","value":true},{"label":"Email Config","type":"hasLabel","value":true},{"label":"Backup & Restore","type":"hasLabel","value":true},{"label":"AWS AMI","type":"hasLabel","value":true},{"label":"Observability","type":"hasLabel","value":true},{"label":"Heroku","type":"hasLabel","value":true},{"label":"New Deployment Mode","type":"hasLabel","value":true}],"requires":1},"Design System Pod":{"conditions":[{"label":"Design System Pod","type":"hasLabel","value":true},{"label":"ADS Component Issue","type":"hasLabel","value":true},{"label":"Keyboard accessibility ","type":"hasLabel","value":true},{"label":"Toggle button","type":"hasLabel","value":true},{"label":"ADS Category Token","type":"hasLabel","value":true},{"label":"ADS Component Documentation","type":"hasLabel","value":true},{"label":"ADS Migration","type":"hasLabel","value":true},{"label":"ADS Deduplication ","type":"hasLabel","value":true},{"label":"ADS Revamp","type":"hasLabel","value":true},{"label":"ADS Deduplication","type":"hasLabel","value":true},{"label":"ADS Unit Test","type":"hasLabel","value":true},{"label":"ADS Components","type":"hasLabel","value":true},{"label":"ADS Grayscale","type":"hasLabel","value":true},{"label":"Design System","type":"hasLabel","value":true},{"label":"ADS Typography","type":"hasLabel","value":true},{"label":"ADS Visual Styles","type":"hasLabel","value":true},{"label":"ADS Component Design","type":"hasLabel","value":true},{"label":"Modal Component","type":"hasLabel","value":true}],"requires":1},"Data Platform Pod":{"conditions":[{"label":"Datasource Environments","type":"hasLabel","value":true},{"label":"Datatype issue","type":"hasLabel","value":true},{"label":"Entity Refactor","type":"hasLabel","value":true},{"label":"Core Query Execution","type":"hasLabel","value":true},{"label":"Query Management","type":"hasLabel","value":true},{"label":"Query Settings","type":"hasLabel","value":true},{"label":"SmartSubstitution","type":"hasLabel","value":true},{"label":"Query Generation","type":"hasLabel","value":true},{"label":"Query performance","type":"hasLabel","value":true},{"label":"Suggested Widgets","type":"hasLabel","value":true},{"label":"Page load executions","type":"hasLabel","value":true},{"label":"DSL Update","type":"hasLabel","value":true},{"label":"AST-backend","type":"hasLabel","value":true},{"label":"Deploy App","type":"hasLabel","value":true},{"label":"File upload issues","type":"hasLabel","value":true},{"label":"Datasources","type":"hasLabel","value":true},{"label":"DocumentDB","type":"hasLabel","value":true},{"label":"Multiple Environments","type":"hasLabel","value":true},{"label":"Platformization","type":"hasLabel","value":true},{"label":"Custom environments","type":"hasLabel","value":true},{"label":"Schema","type":"hasLabel","value":true}],"requires":1},"Integrations Pod":{"conditions":[{"label":"New Datasource","type":"hasLabel","value":true},{"label":"Firestore","type":"hasLabel","value":true},{"label":"Google Sheets","type":"hasLabel","value":true},{"label":"Mongo","type":"hasLabel","value":true},{"label":"Redshift","type":"hasLabel","value":true},{"label":"snowflake","type":"hasLabel","value":true},{"label":"S3","type":"hasLabel","value":true},{"label":"Redis","type":"hasLabel","value":true},{"label":"Postgres","type":"hasLabel","value":true},{"label":"GraphQL Plugin","type":"hasLabel","value":true},{"label":"ArangoDB","type":"hasLabel","value":true},{"label":"MsSQL","type":"hasLabel","value":true},{"label":"REST API plugin","type":"hasLabel","value":true},{"label":"Elastic Search","type":"hasLabel","value":true},{"label":"OAuth","type":"hasLabel","value":true},{"label":"Airtable","type":"hasLabel","value":true},{"label":"CURL","type":"hasLabel","value":true},{"label":"DynamoDB","type":"hasLabel","value":true},{"label":"Zendesk","type":"hasLabel","value":true},{"label":"Hubspot","type":"hasLabel","value":true},{"label":"Query Forms","type":"hasLabel","value":true},{"label":"Twilio","type":"hasLabel","value":true},{"label":"MySQL","type":"hasLabel","value":true},{"label":"Connection pool","type":"hasLabel","value":true},{"label":"MariaDB","type":"hasLabel","value":true},{"label":"Integrations Pod General","type":"hasLabel","value":true},{"label":"SMTP plugin","type":"hasLabel","value":true},{"label":"Oracle SQL DB","type":"hasLabel","value":true},{"label":"Query filter","type":"hasLabel","value":true},{"label":"Activation - datasources","type":"hasLabel","value":true},{"label":"Onboarding","type":"hasLabel","value":true},{"label":"Generate Page","type":"hasLabel","value":true},{"label":"Sniping Mode","type":"hasLabel","value":true},{"label":"Welcome Screen","type":"hasLabel","value":true},{"label":"Login / Signup","type":"hasLabel","value":true}],"requires":1},"Git Pod":{"conditions":[{"label":"Git Version Control","type":"hasLabel","value":true},{"label":"Import-Export-App","type":"hasLabel","value":true},{"label":"Fork App","type":"hasLabel","value":true}],"requires":1},"Mobile Pod":{"conditions":[],"requires":1},"Billing & Usage Pod":{"conditions":[{"label":"CE Instance","type":"hasLabel","value":true},{"label":"Customer Portal","type":"hasLabel","value":true},{"label":"Cloud Services","type":"hasLabel","value":true},{"label":"Billing Integrations","type":"hasLabel","value":true},{"label":"Billing","type":"hasLabel","value":true},{"label":"Self Serve","type":"hasLabel","value":true},{"label":"Enterprise Billing","type":"hasLabel","value":true},{"label":"In-app ramps","type":"hasLabel","value":true},{"label":"Analytics Improvements","type":"hasLabel","value":true},{"label":"Self Serve 1.0","type":"hasLabel","value":true},{"label":"License","type":"hasLabel","value":true},{"label":"1-click upgrade","type":"hasLabel","value":true},{"label":"Appsmith Business Cloud","type":"hasLabel","value":true},{"label":"BE instance","type":"hasLabel","value":true},{"label":"Embedding Apps","type":"hasLabel","value":true},{"label":"TM_BU","type":"hasLabel","value":true},{"label":"Homepage Experience V2","type":"hasLabel","value":true},{"label":"Feature Flagging","type":"hasLabel","value":true},{"label":"Invite flow","type":"hasLabel","value":true},{"label":"Invite users","type":"hasLabel","value":true}],"requires":1},"Performance Pod":{"conditions":[{"label":"Performance","type":"hasLabel","value":true},{"label":"Performance infra","type":"hasLabel","value":true}],"requires":1},"Widget design system":{"conditions":[{"label":"App Theming","type":"hasLabel","value":true},{"label":"Widget Styling","type":"hasLabel","value":true},{"label":"Checkbox Group widget","type":"hasLabel","value":true},{"label":"Checkbox Widget","type":"hasLabel","value":true},{"label":"Checkbox Component","type":"hasLabel","value":true},{"label":"WDS team","type":"hasLabel","value":true},{"label":"Widget design system","type":"hasLabel","value":true}],"requires":1},"IDE Pod":{"conditions":[],"requires":1},"Appsmith Labs":{"conditions":[{"label":"AI","type":"hasLabel","value":true},{"label":"AI Assistant","type":"hasLabel","value":true}],"requires":1},"Workflows Pod":{"conditions":[],"requires":1}}},"root":"."}],"labels":{"Tab Widget":{"color":"e2c76c","name":"Tab Widget","description":""},"Dont merge":{"color":"ADB39C","name":"Dont merge","description":""},"Epic":{"color":"3E4B9E","name":"Epic","description":"A zenhub epic that describes a project"},"Menu Button Widget":{"color":"235708","name":"Menu Button Widget","description":"Issues related to Menu Button widget"},"Checkbox Group widget":{"color":"88054d","name":"Checkbox Group widget","description":"Issues related to Checkbox Group Widget"},"Input Widget":{"color":"ae65d8","name":"Input Widget","description":""},"Security":{"color":"99139C","name":"Security","description":""},"QA":{"color":"e2ca68","name":"QA","description":""},"Verified":{"color":"9bf416","name":"Verified","description":""},"Wont Fix":{"color":"ffffff","name":"Wont Fix","description":"This will not be worked on"},"MySQL":{"color":"c9ddc6","name":"MySQL","description":"Issues related to MySQL plugin"},"Development":{"color":"9F8A02","name":"Development","description":""},"Help Wanted":{"color":"008672","name":"Help Wanted","description":"Extra attention is needed"},"Home Page":{"color":"9c0c8e","name":"Home Page","description":"Issues related to the application home page"},"Rating Widget":{"color":"235708","name":"Rating Widget","description":"Issues related to the rating widget"},"Stat Box Widget":{"color":"f1c9ce","name":"Stat Box Widget","description":"Issues related to stat box"},"Enhancement":{"color":"a2eeef","name":"Enhancement","description":"New feature or request"},"Settings":{"color":"f7ff60","name":"Settings","description":"organization, team & user settings"},"Fork App":{"color":"30c76d","name":"Fork App","description":"Issues related to forking apps"},"Container Widget":{"color":"19AD0D","name":"Container Widget","description":"Container widget"},"Papercut":{"color":"B562F6","name":"Papercut","description":""},"Needs Design":{"color":"bfd4f2","name":"Needs Design","description":"needs design or changes to design"},"i18n":{"color":"1799b0","name":"i18n","description":"Represents issues that need to be tackled to handle internationalization"},"Rich Text Editor Widget":{"color":"f72cac","name":"Rich Text Editor Widget","description":""},"Onboarding":{"color":"30c76d","name":"Onboarding","description":"Issues related to onboarding new developers"},"Pages":{"color":"d7fd80","name":"Pages","description":"Issues related to configuring pages"},"skip-changelog":{"color":"06086F","name":"skip-changelog","description":"Adding this label to a PR prevents it from being listed in the changelog"},"Low":{"color":"79e53b","name":"Low","description":"An issue that is neither critical nor breaks a user flow"},"potential-duplicate":{"color":"d3cb2e","name":"potential-duplicate","description":"This label marks issues that are potential duplicates of already open issues"},"Audio Widget":{"color":"447B9A","name":"Audio Widget","description":"Issues related to Audio Widget"},"Firestore":{"color":"8078b0","name":"Firestore","description":"Issues related to the firestore Integration"},"New Widget":{"color":"be4cf2","name":"New Widget","description":"A request for a new widget"},"Modal Widget":{"color":"03846f","name":"Modal Widget","description":""},"UX Improvement":{"color":"f4a089","name":"UX Improvement","description":""},"S3":{"color":"8078b0","name":"S3","description":"Issues related to the S3 plugin"},"Release Blocker":{"color":"5756bf","name":"Release Blocker","description":"This issue must be resolved before the release"},"safari":{"color":"51C6AA","name":"safari","description":"Bugs seen on safari browser"},"Example Apps":{"color":"1799b0","name":"Example Apps","description":"Example apps created for new signups"},"MultiSelect Widget":{"color":"AB62D4","name":"MultiSelect Widget","description":"Issues related to MultiSelect Widget"},"Widget Styling":{"color":"905420","name":"Widget Styling","description":"all about widget styling"},"Calendar Widget":{"color":"8c6644","name":"Calendar Widget","description":""},"Website":{"color":"151720","name":"Website","description":"Related to www.appsmith.com website"},"Low effort":{"color":"8B59F0","name":"Low effort","description":"Something that'll take a few days to build"},"App Viewers Pod":{"color":"cd8ef9","name":"App Viewers Pod","description":"This label assigns issues to the app viewers pod"},"Checkbox Widget":{"color":"88054d","name":"Checkbox Widget","description":""},"Spam":{"color":"620faf","name":"Spam","description":""},"Voice Recorder Widget":{"color":"85bc87","name":"Voice Recorder Widget","description":""},"Select Widget":{"color":"0c669e","name":"Select Widget","description":"Select or dropdown widget"},"Bug":{"color":"d73a4a","name":"Bug","description":"Something isn't working"},"Widget Validation":{"color":"6990BC","name":"Widget Validation","description":"Issues related to widget property validation"},"Generate Page":{"color":"30c76d","name":"Generate Page","description":"Issures related to page generation"},"File Picker Widget":{"color":"6ae4f2","name":"File Picker Widget","description":""},"snowflake":{"color":"8078b0","name":"snowflake","description":"Issues related to the snowflake Integration"},"Automation":{"color":"CCAF60","name":"Automation","description":""},"hotfix":{"color":"BA3F1D","name":"hotfix","description":""},"Team Managers Pod":{"color":"bddb81","name":"Team Managers Pod","description":"Issues that team managers care about for the security and efficiency of their teams"},"Import-Export-App":{"color":"15076d","name":"Import-Export-App","description":"Issues related to importing and exporting apps"},"High effort":{"color":"A7E87B","name":"High effort","description":"Something that'll take more than a month to build"},"Telemetry":{"color":"bc70f9","name":"Telemetry","description":"Issues related to instrumenting appsmith"},"Radio Widget":{"color":"91ef15","name":"Radio Widget","description":""},"Omnibar":{"color":"10b5ce","name":"Omnibar","description":"Issues related to the omnibar for navigation"},"Button Widget":{"color":"34efae","name":"Button Widget","description":""},"Switch widget":{"color":"33A8CE","name":"Switch widget","description":"The switch widget"},"Map Widget":{"color":"7eef7a","name":"Map Widget","description":""},"Task":{"color":"085630","name":"Task","description":"A simple Todo"},"Design System":{"color":"2958a4","name":"Design System","description":"Design system"},"opera":{"color":"C63F5B","name":"opera","description":"Any issues identified on the opera browser"},"Login / Signup":{"color":"30c76d","name":"Login / Signup","description":"Authentication flows"},"Image Widget":{"color":"8de8ad","name":"Image Widget","description":""},"firefox":{"color":"6d56e2","name":"firefox","description":""},"Property Pane":{"color":"b356ff","name":"Property Pane","description":"Issues related to the behaviour of the property pane"},"Deployment":{"color":"93491f","name":"Deployment","description":"Installation process of appsmith"},"Critical":{"color":"9b1b28","name":"Critical","description":"This issue needs immediate attention. Drop everything else"},"IDE":{"color":"61b2ee","name":"IDE","description":"Issues related to the IDE"},"Production":{"color":"b60205","name":"Production","description":""},"Dependencies":{"color":"0366d6","name":"Dependencies","description":"Pull requests that update a dependency file"},"Google Sheets":{"color":"8078b0","name":"Google Sheets","description":"Issues related to Google Sheets"},"Icon Button Widget":{"color":"D319CE","name":"Icon Button Widget","description":"Issues related to the icon button widget"},"Mongo":{"color":"8078b0","name":"Mongo","description":"Issues related to Mongo DB plugin"},"Documentation":{"color":"a8dff7","name":"Documentation","description":"Improvements or additions to documentation"},"TestGap":{"color":"f28253","name":"TestGap","description":"Issues identified for test plan improvement"},"keyboard shortcut":{"color":"0688B6","name":"keyboard shortcut","description":""},"Git Version Control":{"color":"858172","name":"Git Version Control","description":"Issues related to version control"},"Reopen":{"color":"897548","name":"Reopen","description":""},"Redshift":{"color":"8078b0","name":"Redshift","description":"Issues related to the redshift integration"},"Date Picker Widget":{"color":"ef1ce1","name":"Date Picker Widget","description":""},"Entity Explorer":{"color":"a2e2f9","name":"Entity Explorer","description":"Issues related to navigation using the entity explorer"},"JS Linting & Errors":{"color":"E56AA5","name":"JS Linting & Errors","description":"Issues related to JS Linting and errors"},"iFrame":{"color":"3CD1DB","name":"iFrame","description":"Issues related to iFrame"},"Stale":{"color":"ededed","name":"Stale","description":null},"Debugger":{"color":"e79062","name":"Debugger","description":"Issues related to the debugger"},"Quick effort":{"color":"95ED65","name":"Quick effort","description":"Something that'll take a few hours to build"},"Text Widget":{"color":"d130d1","name":"Text Widget","description":""},"Video Widget":{"color":"23dd4b","name":"Video Widget","description":""},"Datasources":{"color":"5052f6","name":"Datasources","description":"Issues related to configuring datasource on appsmith"},"error":{"color":"B66773","name":"error","description":"All issues connected to error messages"},"Form Widget":{"color":"09ed77","name":"Form Widget","description":""},"Needs Triaging":{"color":"e8b851","name":"Needs Triaging","description":"Needs attention from maintainers to triage"},"Autocomplete":{"color":"235708","name":"Autocomplete","description":"Issues related to the autocomplete"},"hacktoberfest":{"color":"0052cc","name":"hacktoberfest","description":"All issues that can be solved by the community during Hacktoberfest"},"Medium effort":{"color":"D31156","name":"Medium effort","description":"Something that'll take more than a week but less than a month to build"},"Release":{"color":"57e5e0","name":"Release","description":""},"High":{"color":"c94d14","name":"High","description":"This issue blocks a user from building or impacts a lot of users"},"UI Performance":{"color":"1799b0","name":"UI Performance","description":"Issues related to UI performance"},"UI Builders Pod":{"color":"517fba","name":"UI Builders Pod","description":"Issues that UI Builders face using appsmith"},"Deploy Preview":{"color":"bfdadc","name":"Deploy Preview","description":"Issues found in Deploy Preview"},"Needs Tests":{"color":"8ee263","name":"Needs Tests","description":"Needs automated tests to assert a feature/bug fix"},"Refactor":{"color":"B96662","name":"Refactor","description":"needs refactoring of code"},"Divider Widget":{"color":"235708","name":"Divider Widget","description":"Issues related to the divider widget"},"Table Widget":{"color":"2eead1","name":"Table Widget","description":""},"Needs More Info":{"color":"e54c10","name":"Needs More Info","description":"Needs additional information"},"Good First Issue":{"color":"7057ff","name":"Good First Issue","description":"Good for newcomers"},"UI Improvement":{"color":"9aeef4","name":"UI Improvement","description":""},"Backend":{"color":"d4c5f9","name":"Backend","description":"This marks the issue or pull request to reference server code"},"Frontend":{"color":"87c7f2","name":"Frontend","description":"This label marks the issue or pull request to reference client code"},"In App Comms":{"name":"In App Comms","description":"Issues around communication with appsmith instances","color":"463cca"},"Chart Widget":{"color":"616ecc","name":"Chart Widget","description":""},"List Widget":{"color":"8508A0","name":"List Widget","description":"Issues related to the list widget"},"Duplicate":{"color":"cfd3d7","name":"Duplicate","description":"This issue or pull request already exists"},"JS Snippets":{"color":"8d62d2","name":"JS Snippets","description":"issues related to JS Snippets"},"Copy Paste":{"name":"Copy Paste","description":"Issues related to copy paste","color":"b4f0a9"},"Drag & Drop":{"name":"Drag & Drop","description":"Issues related to the drag & drop experience","color":"92115a"},"BE Coders Pod":{"color":"5d9848","name":"BE Coders Pod","description":"Issues related to users writing code to fetch and update data"},"FE Coders Pod":{"color":"a7effc","name":"FE Coders Pod","description":"Issues related to users writing javascript in appsmith"},"New Developers Pod":{"color":"6310da","name":"New Developers Pod","description":"Issues that new developers face while exploring the IDE"},"Sniping Mode":{"name":"Sniping Mode","description":"Issues related to sniping mode","color":"30c76d"},"Redis":{"name":"Redis","description":"Issues related to Redis","color":"8078b0"},"New Datasource":{"color":"60b14c","name":"New Datasource","description":"Requests for new datasources"},"Evaluated Value":{"name":"Evaluated Value","description":"Issues related to evaluated values","color":"39f6e7"},"Undo/Redo":{"name":"Undo/Redo","description":"Issues related to undo/redo","color":"f25880"},"App Navigation":{"name":"App Navigation","description":"Issues related to the topbar navigation and configuring it","color":"12b715"},"Responsive Viewport":{"color":"d12d2e","name":"Responsive Viewport","description":"Issues seen on different viewports like mobile"},"Widgets Pane":{"name":"Widgets Pane","description":"Issues related to the discovery and organisation of widgets","color":"ad5d78"},"View Mode":{"color":"1799b0","name":"View Mode","description":"Issues related to the view mode"},"User Education Pod":{"name":"User Education Pod","description":"Issues related to user education","color":"1799b0"},"Content":{"name":"Content","description":"For content related topics i.e blogs, templates, videos","color":"a8dff7"},"Embedding Apps":{"name":"Embedding Apps","description":"Issues related to embedding","color":"30c76d"},"Slash Command":{"name":"Slash Command","description":"Issues related to the slash command","color":"a0608e"},"Widget Property":{"name":"Widget Property","description":"Issues related to adding / modifying widget properties across widgets","color":"5e92cb"},"Windows":{"name":"Windows","description":"Issues related exclusively to Windows systems","color":"b4cb8a"},"Old App Issues":{"name":"Old App Issues","description":"Issues related to apps old apps a few weeks old and app issues in stale browser session","color":"87ab18"},"Document Viewer Widget":{"name":"Document Viewer Widget","description":"Issues related to Document Viewer Widget","color":"899d4b"},"Radio Group Widget":{"name":"Radio Group Widget","description":"Issues related to radio group widget","color":"b68495"},"Super Admin":{"name":"Super Admin","description":"Issues related to the super admin page","color":"aa95cf"},"Postgres":{"name":"Postgres","description":"Postgres related issues","color":"8078b0"},"REST API plugin":{"name":"REST API plugin","description":"REST API plugin related issues","color":"8078b0"},"New JS Function":{"name":"New JS Function","description":"Issues related to adding a JS Function","color":"8e8aa4"},"Cannot Reproduce Issue":{"color":"93c9cc","name":"Cannot Reproduce Issue","description":"Issues that cannot be reproduced"},"Widget Grouping":{"name":"Widget Grouping","description":"Issues related to Widget Grouping","color":"a49951"},"K8s":{"name":"K8s","description":"Kubernetes related issues","color":"5f318a"},"Docker":{"name":"Docker","description":"Issues related to docker","color":"89b808"},"Camera Widget":{"name":"Camera Widget","description":"Issues and enhancements related to camera widget","color":"e6038e"},"SAAS Plugins":{"name":"SAAS Plugins","description":"Issues related to SAAS Plugins","color":"ef9c9d"},"JS Promises":{"name":"JS Promises","description":"Issues related to promises","color":"d7771f"},"OnPageLoad":{"name":"OnPageLoad","description":"OnPageLoad issues on functions and queries","color":"50559d"},"JS Usability":{"name":"JS Usability","description":"usability issues with JS editor and JS elsewhere","color":"a302b0"},"Currency Input Widget":{"name":"Currency Input Widget","description":"Issues related to currency input widget","color":"b2164f"},"TreeSelect":{"name":"TreeSelect","description":"Issues related to TreeSelect Widget","color":"a1633e"},"MultiTree Select Widget":{"name":"MultiTree Select Widget","description":"Issues related to MultiTree Select Widget","color":"a1633e"},"Welcome Screen":{"name":"Welcome Screen","description":"Issues related to the welcome screen","color":"30c76d"},"Realtime Commenting":{"color":"a70b86","name":"Realtime Commenting","description":"In-app communication between teams"},"Phone Input Widget":{"name":"Phone Input Widget","description":"Issues related to the Phone Input widget","color":"a70b86"},"JSON Form":{"name":"JSON Form","description":"Issue / features related to the JSON form wiget","color":"46b209"},"All Widgets":{"name":"All Widgets","description":"Issues related to all widgets","color":"972b36"},"V1":{"name":"V1","description":"V1","color":"67ab2e"},"Reflow & Resize":{"name":"Reflow & Resize","description":"All issues related to reflow and resize experience","color":"748a13"},"App Theming":{"name":"App Theming","description":"Items that are related to the App level theming controls epic","color":"905420"},"SSO":{"name":"SSO","description":"Issues, requests and enhancements around Single sign-on.","color":"bf019b"},"Multi User Realtime":{"name":"Multi User Realtime","description":"Issues related to multiple users using or editing an application","color":"e7b6ce"},"Templates":{"name":"Templates","description":"Issues related to templates","color":"b7e568"},"Ready for design":{"name":"Ready for design","description":"this issue is ready for design: it contains clear problem statements and other required information","color":"ebf442"},"Support":{"name":"Support","description":"Issues created by the A-force team to address user queries","color":"1740f3"},"Button Group widget":{"name":"Button Group widget","description":"Issue and enhancements related to the button group widget","color":"f17025"},"GraphQL Plugin":{"name":"GraphQL Plugin","description":"Issues related to GraphQL plugin","color":"8078b0"},"DevOps Pod":{"name":"DevOps Pod","description":"Issues related to devops","color":"d956c7"},"medium":{"name":"medium","description":"Issues that frustrate users due to poor UX","color":"23dfd9"},"ArangoDB":{"name":"ArangoDB","description":"Issues related to arangoDB","color":"8078b0"},"Code Refactoring":{"name":"Code Refactoring","description":"Issues related to code refactoring","color":"76310e"},"Progress bar widget":{"name":"Progress bar widget","description":"To track issues related to progress bar","color":"2d7abf"},"Audio Recorder Widget":{"name":"Audio Recorder Widget","description":"Issues related to Audio Recorder Widget","color":"9accef"},"Airtable":{"name":"Airtable","description":"Issues for Airtable","color":"60885f"},"RBAC":{"name":"RBAC","description":"Issues, requests and enhancements around RBAC.","color":"9211c3"},"Canvas / Grid":{"name":"Canvas / Grid","description":"Issues related to the canvas","color":"16b092"},"Email Config":{"name":"Email Config","description":"Issues related to configuring the email service","color":"2a21d1"},"CURL":{"name":"CURL","description":"Issues related to CURL impor","color":"60885f"},"Canvas Zooms":{"name":"Canvas Zooms","description":"Issues related to zooming the canvas","color":"e6038e"},"business":{"name":"business","description":"Features that will be a part of our business edition","color":"cd59eb"},"Action Pod":{"name":"Action Pod","description":"","color":"ee2e36"},"AutomationGap1":{"color":"a5e07c","name":"AutomationGap1","description":"Issues that needs automated tests"},"A-Force11":{"name":"A-Force11","description":"Issues raised by A-Force team","color":"d667b6"},"Business Edition":{"name":"Business Edition","description":"Features that will be a part of our business edition","color":"89bb6c"},"storeValue":{"name":"storeValue","description":"Issues related to the store value function","color":"5d3e66"},"Tests":{"name":"Tests","description":"test item","color":"1c6990"},"DynamoDB":{"name":"DynamoDB","description":"Issues that are related to DynamoDB should have this label","color":"60885f"},"Design System Pod":{"name":"Design System Pod","description":"Appsmith design system related issues","color":"706f03"},"ABAC":{"color":"e009a5","name":"ABAC","description":"User permissions and access controls"},"Backup & Restore":{"name":"Backup & Restore","description":"Issues related to backup and restore","color":"86874d"},"Billing":{"name":"Billing","description":"Billing infrastructure and flows for Business Edition and Trial users","color":"d2bc40"},"Datatype issue":{"name":"Datatype issue","description":"Issues that have risen because data types weren't handled","color":"60885f"},"OAuth":{"name":"OAuth","description":"OAuth related bugs or features","color":"60885f"},"Table Widget V2":{"name":"Table Widget V2","description":"Issues related to Table Widget V2","color":"3a7192"},"IDE Navigation":{"name":"IDE Navigation","description":"Issues/feature requests related to IDE navigation, and context switching","color":"bc0cba"},"Query performance":{"name":"Query performance","description":"Issues that have to do with lack in performance of query execution","color":"e4d966"},"SAAS Manager App":{"name":"SAAS Manager App","description":"Issues with the SAAS manager app","color":"d427db"},"Twilio":{"name":"Twilio","description":"Issues related to Twilio integration","color":"23ba8d"},"Hubspot":{"name":"Hubspot","description":"Issues related to Hubspot integration","color":"60885f"},"Zendesk":{"name":"Zendesk","description":"Issues related to Zendesk integration","color":"60885f"},"Entity Refactor":{"name":"Entity Refactor","description":"Issues related to refactor logic","color":"418fa4"},"Branding":{"name":"Branding","description":"All issues under branding and whitelabelling appsmith ecosystem","color":"7aaaf1"},"Map Chart Widget":{"name":"Map Chart Widget","description":"Issues related to Map Chart Widgets","color":"c8397f"},"Product Catchup":{"name":"Product Catchup","description":"Issues created in the product catchup","color":"29cd2c"},"Framework Functions":{"name":"Framework Functions","description":"Issues related to internal functions like showAlert(), navigateTo() etc...","color":"c25a09"},"Frontend Libraries Upgrade":{"name":"Frontend Libraries Upgrade","description":"Issues related to frontend libraries upgrade","color":"ede1fc"},"Audit Logs":{"name":"Audit Logs","description":"Audit trails to ensure data security","color":"f3fd62"},"MsSQL":{"name":"MsSQL","description":"Issues related to MsSQL plugin","color":"8078b0"},"Data Platform Pod":{"name":"Data Platform Pod","description":"Issues related to the underlying data platform","color":"3f8c3a"},"Integrations Pod":{"name":"Integrations Pod","description":"Issues related to a specific integration","color":"5dbbb1"},"Datasource Environments":{"name":"Datasource Environments","description":"Issues related to datasource environments","color":"bb7a14"},"Elastic Search":{"name":"Elastic Search","description":"Issues related to the elastic search datasource","color":"8078b0"},"Core Query Execution":{"color":"418fa4","name":"Core Query Execution","description":"Issues related to the execution of all queries"},"Query Management":{"name":"Query Management","description":"Issues related to the CRUD of actions or queries","color":"6a5b42"},"Query Settings":{"name":"Query Settings","description":"Issues related to the settings of all queries","color":"c7da7a"},"Code Editor":{"name":"Code Editor","description":"Issues related to the code editor","color":"4ca16e"},"Query Forms":{"color":"12b253","name":"Query Forms","description":"Isuses related to the query forms"},"JS Objects":{"color":"22962c","name":"JS Objects","description":"Issues related to JS Objects"},"JS Evaluation":{"color":"22962c","name":"JS Evaluation","description":"Issues related to JS evaluation on the platform"},"SmartSubstitution":{"name":"SmartSubstitution","description":"Issues related to Smart substitution of mustache bindings in queries","color":"e4d966"},"Query Generation":{"name":"Query Generation","description":"Issues related to query generation","color":"e4d966"},"Suggested Widgets":{"name":"Suggested Widgets","description":"Issues related to suggesting widgets based on query response","color":"e4d966"},"Page load executions":{"name":"Page load executions","description":"Issues related to page load execution","color":"5696b2"},"Code Scanner Widget":{"name":"Code Scanner Widget","description":"Issues related to code scanner widget","color":"9bc1a0"},"Clean URLs":{"name":"Clean URLs","description":"Issues related to clean URLs epic","color":"112623"},"Widget keyboard accessibility":{"name":"Widget keyboard accessibility","description":"All issues related to keyboard accessibility in widgets","color":"b626fd"},"Connection pool":{"name":"Connection pool","description":"issues to do with connection pooling of various plugins","color":"94fe36"},"List Widget V2":{"name":"List Widget V2","description":"Issues related to the list widget v2","color":"adaaf7"},"Auto Height":{"name":"Auto Height","description":"Issues related to dynamic height of widgets","color":"5149cf"},"cypress_failed_test":{"name":"cypress_failed_test","description":"Cypress failed tests","color":"4745d5"},"Needs validation":{"name":"Needs validation","description":"Needs problem validation before being picked up","color":"66673d"},"Slider Widget":{"name":"Slider Widget","description":"Issues raised for slider widgets.","color":"2eef5f"},"Multitenancy":{"name":"Multitenancy","description":"Support multitenancy within single appsmith instance","color":"8c49a9"},"Git Pod":{"name":"Git Pod","description":"Anything related to git sync","color":"2e5ba4"},"Mobile Pod":{"name":"Mobile Pod","description":"All issues related to mobile responsiveness","color":"6c97fd"},"Responsive Widget":{"name":"Responsive Widget","description":"All issues related to widget responsiveness","color":"d12d2e"},"Responsive Canvas":{"name":"Responsive Canvas","description":"All issues related to canvas responsiveness","color":"45a0a8"},"Conversion Algorithm":{"name":"Conversion Algorithm","description":"All issue related to converting app from fixed to flex mode & vice versa","color":"d12d2e"},"Spacing":{"name":"Spacing","description":"All issue related to spacing between widgets in auto layout","color":"d12d2e"},"Browser specific":{"name":"Browser specific","description":"All issue related to browser","color":"d12d2e"},"Error Handling":{"name":"Error Handling","description":"Issues related to error handling","color":"4e1872"},"Performance infra":{"name":"Performance infra","description":"all issue related to the performance infra","color":"8a60f6"},"DSL Update":{"name":"DSL Update","description":"Issues related to storing and updating the DSL","color":"e16cf3"},"AST-frontend":{"name":"AST-frontend","description":"Issues related to maintaining AST logic","color":"434a3a"},"AST-backend":{"name":"AST-backend","description":"Backend issues related to AST parsing","color":"c476eb"},"MariaDB":{"name":"MariaDB","description":"MariaDB datasource","color":"8428c3"},"Billing & Usage Pod":{"name":"Billing & Usage Pod","description":"Issues pertaining to licensing, billing, usage across self serve and enterprise customers","color":"256808"},"ADS Component Issue":{"name":"ADS Component Issue","description":"Issues which are caused due to ADS components","color":"d89119"},"Regressed":{"color":"723fd0","name":"Regressed","description":"Scenarios that were working before but have now regressed"},"Needs RCA":{"name":"Needs RCA","description":"a critical or high priority issue that needs an RCA","color":"2cc68f"},"Custom JS Libraries":{"name":"Custom JS Libraries","description":"Issues related to adding custom JS library","color":"bacb6d"},"Integrations Pod General":{"name":"Integrations Pod General","description":"Issues related to the Integrations Pod that don't fit into other tags.","color":"287823"},"Performance Pod":{"name":"Performance Pod","description":"All things related to Appsmith performance","color":"b5a25d"},"Performance":{"name":"Performance","description":"Issues related to performance","color":"9a18d7"},"File upload issues":{"name":"File upload issues","description":"Issues related to uploading any type of files from within Appsmith","color":"8154df"},"Action Selector":{"name":"Action Selector","description":"Issues related to action selector on the property pane","color":"2f9e20"},"Widget design system":{"name":"Widget design system","description":"","color":"cb6188"},"Deploy App":{"name":"Deploy App","description":"Issues related to app deployment","color":"6f6152"},"Community Reported":{"name":"Community Reported","description":"issues reported by community members","color":"1402e5"},"JS Function execution":{"name":"JS Function execution","description":"JS function execution","color":"7c2de1"},"Self Serve":{"name":"Self Serve","description":"For all issues related to self-serve flow for business edition","color":"4dacfc"},"Self Serve 1.0":{"name":"Self Serve 1.0","description":"For all issues related to v1 of the self serve project","color":"ae839e"},"CE Instance":{"name":"CE Instance","description":"For all issues relating to usage, licensing or billing on the CE instance","color":"d2bc40"},"Customer Portal":{"name":"Customer Portal","description":"For all tasks/issues pertaining to customer.appsmith.com","color":"d2bc40"},"Cloud Services":{"name":"Cloud Services","description":"For all tasks/issues on Appsmith cloud-services relating to licensing, usage and billing","color":"d2bc40"},"Billing Integrations":{"name":"Billing Integrations","description":"For all issues relating to 3P integrations Appsmith is using for billing & usage","color":"d2bc40"},"One-click Binding":{"name":"One-click Binding","description":"Issues related to the One click binding epic","color":"f1661c"},"Airgap":{"name":"Airgap","description":"Tickets related to supporting air-gapped Appsmith instances","color":"1cb294"},"SMTP plugin":{"name":"SMTP plugin","description":"Issues related to SMTP plugin","color":"541457"},"AWS AMI":{"name":"AWS AMI","description":"Issues Related to AWS AMI","color":"b44680"},"Old widget version":{"name":"Old widget version","description":"Use this label to raise issue specific only to an older version of a widget","color":"ff3814"},"Enterprise Billing":{"name":"Enterprise Billing","description":"To track all tasks/issues related to licensing & billing for enterprise customers","color":"14c156"},"Appsmith Business Cloud":{"name":"Appsmith Business Cloud","description":"Issues related to our business cloud offering","color":"89bb6c"},"Oracle SQL DB":{"name":"Oracle SQL DB","description":"Issues related to the Oracle plugin","color":"cbabcb"},"Community Contributor":{"name":"Community Contributor","description":"Meant to track issues that are assigned to external contributors","color":"149ab6"},"widget vertical alignment":{"name":"widget vertical alignment","description":"All issue related widget vertical alignment on the auto layout canvas","color":"d12d2e"},"Observability":{"name":"Observability","description":"Issues related to observability on the Appsmith instance","color":"dff913"},"Checkbox Component":{"name":"Checkbox Component","description":"This labels deals with checkbox component in wds package","color":"75a401"},"In-app ramps":{"name":"In-app ramps","description":"For all tasks/issues relating to adding in-app ramps in the community edition of the product","color":"8abae0"},"Analytics Improvements":{"name":"Analytics Improvements","description":"For all tasks focused on improving our overall analytics and fixing any issues ","color":"29b8ed"},"WDS team":{"name":"WDS team","description":"","color":"8d675a"},"Enterprise Edition":{"name":"Enterprise Edition","description":"Features that will be supported in Enterprise Edition only","color":"984f5e"},"Query filter":{"name":"Query filter","description":"Issues related to query filtering, e.g., WHERE clause","color":"a15134"},"Keyboard accessibility ":{"name":"Keyboard accessibility ","description":"All issue related to ADS component keyboard accessibility","color":"2ba696"},"Toggle button":{"name":"Toggle button","description":"All issue related to ADS toggle button","color":"edc47f"},"1-click upgrade":{"name":"1-click upgrade","description":"For all issues/tasks related to 1-click upgrade & downgrade project","color":"129082"},"Feature Flagging":{"name":"Feature Flagging","description":"Anything related feature flagging","color":"8d8a09"},"SCIM":{"name":"SCIM","description":"Label to collate our SCIM issues","color":"61a852"},"ADS Category Token":{"name":"ADS Category Token","description":"All issues related appsmith design system category tokens","color":"920961"},"ADS Component Documentation":{"name":"ADS Component Documentation","description":"All issues Appsmith design system component documentation","color":"64c46a"},"ADS Migration":{"name":"ADS Migration","description":"All issues related to Appsmith design system migration","color":"b082d6"},"ADS Deduplication ":{"name":"ADS Deduplication ","description":"Replacing component with ADS components","color":"b082d6"},"ADS Revamp":{"name":"ADS Revamp","description":"All issues related to ads revamp. ","color":"b082d6"},"ADS Deduplication":{"name":"ADS Deduplication","description":"Replacing component with ADS components","color":"b082d6"},"ADS Grayscale":{"name":"ADS Grayscale","description":"Support grayscale color changes","color":"b03577"},"ADS Unit Test":{"name":"ADS Unit Test","description":"All issue related ads unit cases ","color":"b082d6"},"ADS Components":{"name":"ADS Components","description":"All issues related ADS components","color":"b082d6"},"Widget Discoverability":{"name":"Widget Discoverability","description":"Issues related to Widget Discoverability","color":"7b55ce"},"Widget setter method":{"name":"Widget setter method","description":"Issues with widget property setters","color":"8dce87"},"License":{"name":"License","description":"For all issues/tasks related to licensing of appsmith-ee edition","color":"90ee98"},"Templates pod":{"name":"Templates pod","description":"Issues related to Templates","color":"b7e568"},"Community template":{"name":"Community template","description":"Label for development of community templates and its integration to platform","color":"8a0510"},"DocumentDB":{"name":"DocumentDB","description":"Issues related to support DocumentDB in Appsmith Data layer","color":"2c8b56"},"Multiple Environments":{"name":"Multiple Environments","description":"Issues or tasks related to multiple environments","color":"4e972b"},"Platformization":{"name":"Platformization","description":"Issues or tasks related to platformization of Appsmith codebase","color":"4e972b"},"Activation - datasources":{"name":"Activation - datasources","description":"issues related to activation projects","color":"7c7ace"},"Partial-import-export":{"name":"Partial-import-export","description":"Label for granular reusability.","color":"1e439c"},"AI":{"name":"AI","description":"All tasks related to AI","color":"75c4ce"},"Custom environments":{"name":"Custom environments","description":"Issues with creating or working with custom environments","color":"2137d6"},"ADS Typography":{"name":"ADS Typography","description":"All issue related typographical changes","color":"2dbe8d"},"Auto Layout":{"name":"Auto Layout","description":"Issues relates to auto layout","color":"92cf8c"},"Heroku":{"name":"Heroku","description":"Issues related to Heroku","color":"a81b69"},"ADS Visual Styles":{"name":"ADS Visual Styles","description":"All issues related to ADS visual styles","color":"d3da89"},"ADS Component Design":{"name":"ADS Component Design","description":"All issue related to component design","color":"5cc91e"},"Modal Component":{"name":"Modal Component","description":"All issue related to ads modal component","color":"ee63f3"},"App setting":{"name":"App setting","description":"Related to app settings panel within the app","color":"144206"},"BE instance":{"name":"BE instance","description":"For all issues related to license, billing on BE instance","color":"ae8f98"},"Schema":{"name":"Schema","description":"Issues related to database schema","color":"c470c2"},"Fixed layout":{"name":"Fixed layout","description":"issues related to fixed layout","color":"b66681"},"Anvil layout":{"name":"Anvil layout","description":"issues related to the new layout system anvil","color":"722bf0"},"New Deployment Mode":{"name":"New Deployment Mode","description":"Support a new mode of deployment","color":"108033"},"Custom widgets":{"name":"Custom widgets","description":"For all issues related to the custom widget project","color":"c9db9c"},"IDE Pod":{"name":"IDE Pod","description":"https://app.zenhub.com/workspaces/new-developers-pod-60507ad1d4b98d00150a2858/board","color":"d3d248"},"TM_BU":{"name":"TM_BU","description":"The issues on Team Manager which needs to be taken up by Billing & Usage","color":"198cdf"},"Homepage Experience V2":{"name":"Homepage Experience V2","description":"Label for reporting new tasks and bug fixes related to revamped homepage experience","color":"c55d54"},"Appsmith Labs":{"name":"Appsmith Labs","description":"All things related to AI and other new initiatives ","color":"712d51"},"Customer Success":{"name":"Customer Success","description":"Issues that the success team cares about","color":"6ccabd"},"Invite flow":{"name":"Invite flow","description":"Invite users flow and any associated actions","color":"881b35"},"Invite users":{"name":"Invite users","description":"Invite users flow and any associated actions","color":"23e6d6"},"Workflows Pod":{"name":"Workflows Pod","description":"For all issues related to the Workflows feature","color":"2c1f93"},"AI Assistant":{"name":"AI Assistant","description":"Tickets related to Appsmith AI Assistant","color":"2fd627"}},"success":true} \ No newline at end of file +{"runners":[{"versioning":{"source":"milestones","type":"SemVer"},"prereleaseName":"alpha","issue":{"labels":{"Error Handling":{"conditions":[],"requires":1},"Templates pod":{"conditions":[{"label":"Templates","type":"hasLabel","value":true},{"label":"Community template","type":"hasLabel","value":true},{"label":"Partial-import-export","type":"hasLabel","value":true}],"requires":1},"Team Managers Pod":{"conditions":[{"label":"Settings","type":"hasLabel","value":true},{"label":"Home Page","type":"hasLabel","value":true},{"label":"Realtime Commenting","type":"hasLabel","value":true},{"label":"SSO","type":"hasLabel","value":true},{"label":"Multi User Realtime","type":"hasLabel","value":true},{"label":"RBAC","type":"hasLabel","value":true},{"label":"ABAC","type":"hasLabel","value":true},{"label":"Audit Logs","type":"hasLabel","value":true},{"label":"Multitenancy","type":"hasLabel","value":true},{"label":"Airgap","type":"hasLabel","value":true},{"label":"Enterprise Edition","type":"hasLabel","value":true},{"label":"SCIM","type":"hasLabel","value":true},{"label":"Invite flow","type":"hasLabel","value":true}],"requires":1},"New Developers Pod":{"conditions":[{"label":"Omnibar","type":"hasLabel","value":true},{"label":"Telemetry","type":"hasLabel","value":true},{"label":"Entity Explorer","type":"hasLabel","value":true},{"label":"IDE","type":"hasLabel","value":true},{"label":"Example Apps","type":"hasLabel","value":true},{"label":"i18n","type":"hasLabel","value":true},{"label":"IDE Navigation","type":"hasLabel","value":true},{"label":"Clean URLs","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"App setting","type":"hasLabel","value":true}],"requires":1},"BE Coders Pod":{"conditions":[{"label":"SAAS Plugins","type":"hasLabel","value":true},{"label":"SAAS Manager App","type":"hasLabel","value":true},{"label":"Data Platform Pod","type":"hasLabel","value":true},{"label":"Integrations Pod","type":"hasLabel","value":true}],"requires":1},"FE Coders Pod":{"conditions":[{"label":"JS Linting & Errors","type":"hasLabel","value":true},{"label":"Debugger","type":"hasLabel","value":true},{"label":"JS Snippets","type":"hasLabel","value":true},{"label":"Autocomplete","type":"hasLabel","value":true},{"label":"Evaluated Value","type":"hasLabel","value":true},{"label":"Slash Command","type":"hasLabel","value":true},{"label":"New JS Function","type":"hasLabel","value":true},{"label":"JS Promises","type":"hasLabel","value":true},{"label":"JS Usability","type":"hasLabel","value":true},{"label":"Code Refactoring","type":"hasLabel","value":true},{"label":"storeValue","type":"hasLabel","value":true},{"label":"OnPageLoad","type":"hasLabel","value":true},{"label":"Framework Functions","type":"hasLabel","value":true},{"label":"Code Editor","type":"hasLabel","value":true},{"label":"JS Objects","type":"hasLabel","value":true},{"label":"JS Evaluation","type":"hasLabel","value":true},{"label":"AST-frontend","type":"hasLabel","value":true},{"label":"Custom JS Libraries","type":"hasLabel","value":true},{"label":"Action Selector","type":"hasLabel","value":true},{"label":"JS Function execution","type":"hasLabel","value":true},{"label":"Widget setter method","type":"hasLabel","value":true},{"label":"Error Handling","type":"hasLabel","value":true}],"requires":1},"App Viewers Pod":{"conditions":[{"label":"Button Widget","type":"hasLabel","value":true},{"label":"Chart Widget","type":"hasLabel","value":true},{"label":"Container Widget","type":"hasLabel","value":true},{"label":"Date Picker Widget","type":"hasLabel","value":true},{"label":"Select Widget","type":"hasLabel","value":true},{"label":"File Picker Widget","type":"hasLabel","value":true},{"label":"Form Widget","type":"hasLabel","value":true},{"label":"Image Widget","type":"hasLabel","value":true},{"label":"Input Widget","type":"hasLabel","value":true},{"label":"List Widget","type":"hasLabel","value":true},{"label":"MultiSelect Widget","type":"hasLabel","value":true},{"label":"Map Widget","type":"hasLabel","value":true},{"label":"Modal Widget","type":"hasLabel","value":true},{"label":"Radio Widget","type":"hasLabel","value":true},{"label":"Rich Text Editor Widget","type":"hasLabel","value":true},{"label":"Tab Widget","type":"hasLabel","value":true},{"label":"Table Widget","type":"hasLabel","value":true},{"label":"Text Widget","type":"hasLabel","value":true},{"label":"Video Widget","type":"hasLabel","value":true},{"label":"iFrame","type":"hasLabel","value":true},{"label":"Menu Button","type":"hasLabel","value":true},{"label":"Rating","type":"hasLabel","value":true},{"label":"Widget Validation","type":"hasLabel","value":true},{"label":"reallabel","type":"hasLabel","value":true},{"label":"New Widget","type":"hasLabel","value":true},{"label":"Switch widget","type":"hasLabel","value":true},{"label":"Audio Widget","type":"hasLabel","value":true},{"label":"Icon Button Widget","type":"hasLabel","value":true},{"label":"Stat Box Widget","type":"hasLabel","value":true},{"label":"Voice Recorder Widget","type":"hasLabel","value":true},{"label":"Calendar Widget","type":"hasLabel","value":true},{"label":"Menu Button Widget","type":"hasLabel","value":true},{"label":"Divider Widget","type":"hasLabel","value":true},{"label":"Rating Widget","type":"hasLabel","value":true},{"label":"App Navigation","type":"hasLabel","value":true},{"label":"View Mode","type":"hasLabel","value":true},{"label":"Widget Property","type":"hasLabel","value":true},{"label":"Document Viewer Widget","type":"hasLabel","value":true},{"label":"Radio Group Widget","type":"hasLabel","value":true},{"label":"Currency Input Widget","type":"hasLabel","value":true},{"label":"TreeSelect","type":"hasLabel","value":true},{"label":"MultiTree Select Widget","type":"hasLabel","value":true},{"label":"Phone Input Widget","type":"hasLabel","value":true},{"label":"JSON Form","type":"hasLabel","value":true},{"label":"All Widgets","type":"hasLabel","value":true},{"label":"Button Group widget","type":"hasLabel","value":true},{"label":"Progress bar widget","type":"hasLabel","value":true},{"label":"Audio Recorder Widget","type":"hasLabel","value":true},{"label":"Camera Widget","type":"hasLabel","value":true},{"label":"Table Widget V2","type":"hasLabel","value":true},{"label":"Branding","type":"hasLabel","value":true},{"label":"Map Chart Widget","type":"hasLabel","value":true},{"label":"Code Scanner Widget","type":"hasLabel","value":true},{"label":"Widget keyboard accessibility","type":"hasLabel","value":true},{"label":"List Widget V2","type":"hasLabel","value":true},{"label":"Slider Widget","type":"hasLabel","value":true},{"label":"One-click Binding","type":"hasLabel","value":true},{"label":"Old widget version","type":"hasLabel","value":true},{"label":"Widget Discoverability","type":"hasLabel","value":true},{"label":"Custom widgets","type":"hasLabel","value":true}],"requires":1},"UI Builders Pod":{"conditions":[{"label":"Property Pane","type":"hasLabel","value":true},{"label":"Pages","type":"hasLabel","value":true},{"label":"Copy Paste","type":"hasLabel","value":true},{"label":"Drag & Drop","type":"hasLabel","value":true},{"label":"Undo/Redo","type":"hasLabel","value":true},{"label":"Widgets Pane","type":"hasLabel","value":true},{"label":"UI Performance","type":"hasLabel","value":true},{"label":"Widget Grouping","type":"hasLabel","value":true},{"label":"Reflow & Resize","type":"hasLabel","value":true},{"label":"Canvas / Grid","type":"hasLabel","value":true},{"label":"Canvas Zooms","type":"hasLabel","value":true},{"label":"Frontend Libraries Upgrade","type":"hasLabel","value":true},{"label":"Auto Height","type":"hasLabel","value":true},{"label":"Responsive Canvas","type":"hasLabel","value":true},{"label":"Responsive Widget","type":"hasLabel","value":true},{"label":"Responsive Viewport","type":"hasLabel","value":true},{"label":"Conversion Algorithm","type":"hasLabel","value":true},{"label":"Spacing","type":"hasLabel","value":true},{"label":"Browser specific","type":"hasLabel","value":true},{"label":"widget vertical alignment","type":"hasLabel","value":true},{"label":"Auto Layout","type":"hasLabel","value":true},{"label":"Fixed layout","type":"hasLabel","value":true},{"label":"Anvil layout","type":"hasLabel","value":true}],"requires":1},"User Education Pod":{"conditions":[{"label":"Content","type":"hasLabel","value":true},{"label":"Documentation","type":"hasLabel","value":true}],"requires":1},"DevOps Pod":{"conditions":[{"label":"Docker","type":"hasLabel","value":true},{"label":"Super Admin","type":"hasLabel","value":true},{"label":"Deployment","type":"hasLabel","value":true},{"label":"K8s","type":"hasLabel","value":true},{"label":"Email Config","type":"hasLabel","value":true},{"label":"Backup & Restore","type":"hasLabel","value":true},{"label":"AWS AMI","type":"hasLabel","value":true},{"label":"Observability","type":"hasLabel","value":true},{"label":"Heroku","type":"hasLabel","value":true},{"label":"New Deployment Mode","type":"hasLabel","value":true}],"requires":1},"Design System Pod":{"conditions":[{"label":"Design System Pod","type":"hasLabel","value":true},{"label":"ADS Component Issue","type":"hasLabel","value":true},{"label":"Keyboard accessibility ","type":"hasLabel","value":true},{"label":"Toggle button","type":"hasLabel","value":true},{"label":"ADS Category Token","type":"hasLabel","value":true},{"label":"ADS Component Documentation","type":"hasLabel","value":true},{"label":"ADS Migration","type":"hasLabel","value":true},{"label":"ADS Deduplication ","type":"hasLabel","value":true},{"label":"ADS Revamp","type":"hasLabel","value":true},{"label":"ADS Deduplication","type":"hasLabel","value":true},{"label":"ADS Unit Test","type":"hasLabel","value":true},{"label":"ADS Components","type":"hasLabel","value":true},{"label":"ADS Grayscale","type":"hasLabel","value":true},{"label":"Design System","type":"hasLabel","value":true},{"label":"ADS Typography","type":"hasLabel","value":true},{"label":"ADS Visual Styles","type":"hasLabel","value":true},{"label":"ADS Component Design","type":"hasLabel","value":true},{"label":"Modal Component","type":"hasLabel","value":true}],"requires":1},"Data Platform Pod":{"conditions":[{"label":"Datasource Environments","type":"hasLabel","value":true},{"label":"Datatype issue","type":"hasLabel","value":true},{"label":"Entity Refactor","type":"hasLabel","value":true},{"label":"Core Query Execution","type":"hasLabel","value":true},{"label":"Query Management","type":"hasLabel","value":true},{"label":"Query Settings","type":"hasLabel","value":true},{"label":"SmartSubstitution","type":"hasLabel","value":true},{"label":"Query Generation","type":"hasLabel","value":true},{"label":"Query performance","type":"hasLabel","value":true},{"label":"Suggested Widgets","type":"hasLabel","value":true},{"label":"Page load executions","type":"hasLabel","value":true},{"label":"DSL Update","type":"hasLabel","value":true},{"label":"AST-backend","type":"hasLabel","value":true},{"label":"Deploy App","type":"hasLabel","value":true},{"label":"File upload issues","type":"hasLabel","value":true},{"label":"Datasources","type":"hasLabel","value":true},{"label":"DocumentDB","type":"hasLabel","value":true},{"label":"Multiple Environments","type":"hasLabel","value":true},{"label":"Platformization","type":"hasLabel","value":true},{"label":"Custom environments","type":"hasLabel","value":true},{"label":"Schema","type":"hasLabel","value":true}],"requires":1},"Integrations Pod":{"conditions":[{"label":"New Datasource","type":"hasLabel","value":true},{"label":"Firestore","type":"hasLabel","value":true},{"label":"Google Sheets","type":"hasLabel","value":true},{"label":"Mongo","type":"hasLabel","value":true},{"label":"Redshift","type":"hasLabel","value":true},{"label":"snowflake","type":"hasLabel","value":true},{"label":"S3","type":"hasLabel","value":true},{"label":"Redis","type":"hasLabel","value":true},{"label":"Postgres","type":"hasLabel","value":true},{"label":"GraphQL Plugin","type":"hasLabel","value":true},{"label":"ArangoDB","type":"hasLabel","value":true},{"label":"MsSQL","type":"hasLabel","value":true},{"label":"REST API plugin","type":"hasLabel","value":true},{"label":"Elastic Search","type":"hasLabel","value":true},{"label":"OAuth","type":"hasLabel","value":true},{"label":"Airtable","type":"hasLabel","value":true},{"label":"CURL","type":"hasLabel","value":true},{"label":"DynamoDB","type":"hasLabel","value":true},{"label":"Zendesk","type":"hasLabel","value":true},{"label":"Hubspot","type":"hasLabel","value":true},{"label":"Query Forms","type":"hasLabel","value":true},{"label":"Twilio","type":"hasLabel","value":true},{"label":"MySQL","type":"hasLabel","value":true},{"label":"Connection pool","type":"hasLabel","value":true},{"label":"MariaDB","type":"hasLabel","value":true},{"label":"Integrations Pod General","type":"hasLabel","value":true},{"label":"SMTP plugin","type":"hasLabel","value":true},{"label":"Oracle SQL DB","type":"hasLabel","value":true},{"label":"Query filter","type":"hasLabel","value":true},{"label":"Activation - datasources","type":"hasLabel","value":true},{"label":"Onboarding","type":"hasLabel","value":true},{"label":"Generate Page","type":"hasLabel","value":true},{"label":"Sniping Mode","type":"hasLabel","value":true},{"label":"Welcome Screen","type":"hasLabel","value":true},{"label":"Login / Signup","type":"hasLabel","value":true}],"requires":1},"Git Pod":{"conditions":[{"label":"Git Version Control","type":"hasLabel","value":true},{"label":"Import-Export-App","type":"hasLabel","value":true},{"label":"Fork App","type":"hasLabel","value":true}],"requires":1},"Mobile Pod":{"conditions":[],"requires":1},"Billing & Usage Pod":{"conditions":[{"label":"CE Instance","type":"hasLabel","value":true},{"label":"Customer Portal","type":"hasLabel","value":true},{"label":"Cloud Services","type":"hasLabel","value":true},{"label":"Billing Integrations","type":"hasLabel","value":true},{"label":"Billing","type":"hasLabel","value":true},{"label":"Self Serve","type":"hasLabel","value":true},{"label":"Enterprise Billing","type":"hasLabel","value":true},{"label":"In-app ramps","type":"hasLabel","value":true},{"label":"Analytics Improvements","type":"hasLabel","value":true},{"label":"Self Serve 1.0","type":"hasLabel","value":true},{"label":"License","type":"hasLabel","value":true},{"label":"Appsmith Business Cloud","type":"hasLabel","value":true},{"label":"BE instance","type":"hasLabel","value":true},{"label":"Embedding Apps","type":"hasLabel","value":true},{"label":"TM_BU","type":"hasLabel","value":true},{"label":"Homepage Experience V2","type":"hasLabel","value":true},{"label":"Feature Flagging","type":"hasLabel","value":true},{"label":"Invite flow","type":"hasLabel","value":true},{"label":"Invite users","type":"hasLabel","value":true}],"requires":1},"Performance Pod":{"conditions":[{"label":"Performance","type":"hasLabel","value":true},{"label":"Performance infra","type":"hasLabel","value":true}],"requires":1},"Widget design system":{"conditions":[{"label":"App Theming","type":"hasLabel","value":true},{"label":"Widget Styling","type":"hasLabel","value":true},{"label":"Checkbox Group widget","type":"hasLabel","value":true},{"label":"Checkbox Widget","type":"hasLabel","value":true},{"label":"Checkbox Component","type":"hasLabel","value":true},{"label":"WDS team","type":"hasLabel","value":true},{"label":"Widget design system","type":"hasLabel","value":true}],"requires":1},"IDE Pod":{"conditions":[],"requires":1},"Appsmith Labs":{"conditions":[{"label":"AI","type":"hasLabel","value":true}],"requires":1},"Workflows Pod":{"conditions":[],"requires":1}}},"root":"."}],"labels":{"Tab Widget":{"color":"e2c76c","name":"Tab Widget","description":""},"Dont merge":{"color":"ADB39C","name":"Dont merge","description":""},"Epic":{"color":"3E4B9E","name":"Epic","description":"A zenhub epic that describes a project"},"Menu Button Widget":{"color":"235708","name":"Menu Button Widget","description":"Issues related to Menu Button widget"},"Checkbox Group widget":{"color":"88054d","name":"Checkbox Group widget","description":"Issues related to Checkbox Group Widget"},"Input Widget":{"color":"ae65d8","name":"Input Widget","description":""},"Security":{"color":"99139C","name":"Security","description":""},"QA":{"color":"e2ca68","name":"QA","description":""},"Verified":{"color":"9bf416","name":"Verified","description":""},"Wont Fix":{"color":"ffffff","name":"Wont Fix","description":"This will not be worked on"},"MySQL":{"color":"c9ddc6","name":"MySQL","description":"Issues related to MySQL plugin"},"Development":{"color":"9F8A02","name":"Development","description":""},"Help Wanted":{"color":"008672","name":"Help Wanted","description":"Extra attention is needed"},"Home Page":{"color":"9c0c8e","name":"Home Page","description":"Issues related to the application home page"},"Rating Widget":{"color":"235708","name":"Rating Widget","description":"Issues related to the rating widget"},"Stat Box Widget":{"color":"f1c9ce","name":"Stat Box Widget","description":"Issues related to stat box"},"Enhancement":{"color":"a2eeef","name":"Enhancement","description":"New feature or request"},"Settings":{"color":"f7ff60","name":"Settings","description":"organization, team & user settings"},"Fork App":{"color":"30c76d","name":"Fork App","description":"Issues related to forking apps"},"Container Widget":{"color":"19AD0D","name":"Container Widget","description":"Container widget"},"Papercut":{"color":"B562F6","name":"Papercut","description":""},"Needs Design":{"color":"bfd4f2","name":"Needs Design","description":"needs design or changes to design"},"i18n":{"color":"1799b0","name":"i18n","description":"Represents issues that need to be tackled to handle internationalization"},"Rich Text Editor Widget":{"color":"f72cac","name":"Rich Text Editor Widget","description":""},"Onboarding":{"color":"30c76d","name":"Onboarding","description":"Issues related to onboarding new developers"},"Pages":{"color":"d7fd80","name":"Pages","description":"Issues related to configuring pages"},"skip-changelog":{"color":"06086F","name":"skip-changelog","description":"Adding this label to a PR prevents it from being listed in the changelog"},"Low":{"color":"79e53b","name":"Low","description":"An issue that is neither critical nor breaks a user flow"},"potential-duplicate":{"color":"d3cb2e","name":"potential-duplicate","description":"This label marks issues that are potential duplicates of already open issues"},"Audio Widget":{"color":"447B9A","name":"Audio Widget","description":"Issues related to Audio Widget"},"Firestore":{"color":"8078b0","name":"Firestore","description":"Issues related to the firestore Integration"},"New Widget":{"color":"be4cf2","name":"New Widget","description":"A request for a new widget"},"Modal Widget":{"color":"03846f","name":"Modal Widget","description":""},"UX Improvement":{"color":"f4a089","name":"UX Improvement","description":""},"S3":{"color":"8078b0","name":"S3","description":"Issues related to the S3 plugin"},"Release Blocker":{"color":"5756bf","name":"Release Blocker","description":"This issue must be resolved before the release"},"safari":{"color":"51C6AA","name":"safari","description":"Bugs seen on safari browser"},"Example Apps":{"color":"1799b0","name":"Example Apps","description":"Example apps created for new signups"},"MultiSelect Widget":{"color":"AB62D4","name":"MultiSelect Widget","description":"Issues related to MultiSelect Widget"},"Widget Styling":{"color":"905420","name":"Widget Styling","description":"all about widget styling"},"Calendar Widget":{"color":"8c6644","name":"Calendar Widget","description":""},"Website":{"color":"151720","name":"Website","description":"Related to www.appsmith.com website"},"Low effort":{"color":"8B59F0","name":"Low effort","description":"Something that'll take a few days to build"},"App Viewers Pod":{"color":"cd8ef9","name":"App Viewers Pod","description":"This label assigns issues to the app viewers pod"},"Checkbox Widget":{"color":"88054d","name":"Checkbox Widget","description":""},"Spam":{"color":"620faf","name":"Spam","description":""},"Voice Recorder Widget":{"color":"85bc87","name":"Voice Recorder Widget","description":""},"Select Widget":{"color":"0c669e","name":"Select Widget","description":"Select or dropdown widget"},"Bug":{"color":"d73a4a","name":"Bug","description":"Something isn't working"},"Widget Validation":{"color":"6990BC","name":"Widget Validation","description":"Issues related to widget property validation"},"Generate Page":{"color":"30c76d","name":"Generate Page","description":"Issures related to page generation"},"File Picker Widget":{"color":"6ae4f2","name":"File Picker Widget","description":""},"snowflake":{"color":"8078b0","name":"snowflake","description":"Issues related to the snowflake Integration"},"Automation":{"color":"CCAF60","name":"Automation","description":""},"hotfix":{"color":"BA3F1D","name":"hotfix","description":""},"Team Managers Pod":{"color":"bddb81","name":"Team Managers Pod","description":"Issues that team managers care about for the security and efficiency of their teams"},"Import-Export-App":{"color":"15076d","name":"Import-Export-App","description":"Issues related to importing and exporting apps"},"High effort":{"color":"A7E87B","name":"High effort","description":"Something that'll take more than a month to build"},"Telemetry":{"color":"bc70f9","name":"Telemetry","description":"Issues related to instrumenting appsmith"},"Radio Widget":{"color":"91ef15","name":"Radio Widget","description":""},"Omnibar":{"color":"10b5ce","name":"Omnibar","description":"Issues related to the omnibar for navigation"},"Button Widget":{"color":"34efae","name":"Button Widget","description":""},"Switch widget":{"color":"33A8CE","name":"Switch widget","description":"The switch widget"},"Map Widget":{"color":"7eef7a","name":"Map Widget","description":""},"Task":{"color":"085630","name":"Task","description":"A simple Todo"},"Design System":{"color":"2958a4","name":"Design System","description":"Design system"},"opera":{"color":"C63F5B","name":"opera","description":"Any issues identified on the opera browser"},"Login / Signup":{"color":"30c76d","name":"Login / Signup","description":"Authentication flows"},"Image Widget":{"color":"8de8ad","name":"Image Widget","description":""},"firefox":{"color":"6d56e2","name":"firefox","description":""},"Property Pane":{"color":"b356ff","name":"Property Pane","description":"Issues related to the behaviour of the property pane"},"Deployment":{"color":"93491f","name":"Deployment","description":"Installation process of appsmith"},"Critical":{"color":"9b1b28","name":"Critical","description":"This issue needs immediate attention. Drop everything else"},"IDE":{"color":"61b2ee","name":"IDE","description":"Issues related to the IDE"},"Production":{"color":"b60205","name":"Production","description":""},"Dependencies":{"color":"0366d6","name":"Dependencies","description":"Pull requests that update a dependency file"},"Google Sheets":{"color":"8078b0","name":"Google Sheets","description":"Issues related to Google Sheets"},"Icon Button Widget":{"color":"D319CE","name":"Icon Button Widget","description":"Issues related to the icon button widget"},"Mongo":{"color":"8078b0","name":"Mongo","description":"Issues related to Mongo DB plugin"},"Documentation":{"color":"a8dff7","name":"Documentation","description":"Improvements or additions to documentation"},"TestGap":{"color":"f28253","name":"TestGap","description":"Issues identified for test plan improvement"},"keyboard shortcut":{"color":"0688B6","name":"keyboard shortcut","description":""},"Git Version Control":{"color":"858172","name":"Git Version Control","description":"Issues related to version control"},"Reopen":{"color":"897548","name":"Reopen","description":""},"Redshift":{"color":"8078b0","name":"Redshift","description":"Issues related to the redshift integration"},"Date Picker Widget":{"color":"ef1ce1","name":"Date Picker Widget","description":""},"Entity Explorer":{"color":"a2e2f9","name":"Entity Explorer","description":"Issues related to navigation using the entity explorer"},"JS Linting & Errors":{"color":"E56AA5","name":"JS Linting & Errors","description":"Issues related to JS Linting and errors"},"iFrame":{"color":"3CD1DB","name":"iFrame","description":"Issues related to iFrame"},"Stale":{"color":"ededed","name":"Stale","description":null},"Debugger":{"color":"e79062","name":"Debugger","description":"Issues related to the debugger"},"Quick effort":{"color":"95ED65","name":"Quick effort","description":"Something that'll take a few hours to build"},"Text Widget":{"color":"d130d1","name":"Text Widget","description":""},"Video Widget":{"color":"23dd4b","name":"Video Widget","description":""},"Datasources":{"color":"5052f6","name":"Datasources","description":"Issues related to configuring datasource on appsmith"},"error":{"color":"B66773","name":"error","description":"All issues connected to error messages"},"Form Widget":{"color":"09ed77","name":"Form Widget","description":""},"Needs Triaging":{"color":"e8b851","name":"Needs Triaging","description":"Needs attention from maintainers to triage"},"Autocomplete":{"color":"235708","name":"Autocomplete","description":"Issues related to the autocomplete"},"hacktoberfest":{"color":"0052cc","name":"hacktoberfest","description":"All issues that can be solved by the community during Hacktoberfest"},"Medium effort":{"color":"D31156","name":"Medium effort","description":"Something that'll take more than a week but less than a month to build"},"Release":{"color":"57e5e0","name":"Release","description":""},"High":{"color":"c94d14","name":"High","description":"This issue blocks a user from building or impacts a lot of users"},"UI Performance":{"color":"1799b0","name":"UI Performance","description":"Issues related to UI performance"},"UI Builders Pod":{"color":"517fba","name":"UI Builders Pod","description":"Issues that UI Builders face using appsmith"},"Deploy Preview":{"color":"bfdadc","name":"Deploy Preview","description":"Issues found in Deploy Preview"},"Needs Tests":{"color":"8ee263","name":"Needs Tests","description":"Needs automated tests to assert a feature/bug fix"},"Refactor":{"color":"B96662","name":"Refactor","description":"needs refactoring of code"},"Divider Widget":{"color":"235708","name":"Divider Widget","description":"Issues related to the divider widget"},"Table Widget":{"color":"2eead1","name":"Table Widget","description":""},"Needs More Info":{"color":"e54c10","name":"Needs More Info","description":"Needs additional information"},"Good First Issue":{"color":"7057ff","name":"Good First Issue","description":"Good for newcomers"},"UI Improvement":{"color":"9aeef4","name":"UI Improvement","description":""},"Backend":{"color":"d4c5f9","name":"Backend","description":"This marks the issue or pull request to reference server code"},"Frontend":{"color":"87c7f2","name":"Frontend","description":"This label marks the issue or pull request to reference client code"},"In App Comms":{"name":"In App Comms","description":"Issues around communication with appsmith instances","color":"463cca"},"Chart Widget":{"color":"616ecc","name":"Chart Widget","description":""},"List Widget":{"color":"8508A0","name":"List Widget","description":"Issues related to the list widget"},"Duplicate":{"color":"cfd3d7","name":"Duplicate","description":"This issue or pull request already exists"},"JS Snippets":{"color":"8d62d2","name":"JS Snippets","description":"issues related to JS Snippets"},"Copy Paste":{"name":"Copy Paste","description":"Issues related to copy paste","color":"b4f0a9"},"Drag & Drop":{"name":"Drag & Drop","description":"Issues related to the drag & drop experience","color":"92115a"},"BE Coders Pod":{"color":"5d9848","name":"BE Coders Pod","description":"Issues related to users writing code to fetch and update data"},"FE Coders Pod":{"color":"a7effc","name":"FE Coders Pod","description":"Issues related to users writing javascript in appsmith"},"New Developers Pod":{"color":"6310da","name":"New Developers Pod","description":"Issues that new developers face while exploring the IDE"},"Sniping Mode":{"name":"Sniping Mode","description":"Issues related to sniping mode","color":"30c76d"},"Redis":{"name":"Redis","description":"Issues related to Redis","color":"8078b0"},"New Datasource":{"color":"60b14c","name":"New Datasource","description":"Requests for new datasources"},"Evaluated Value":{"name":"Evaluated Value","description":"Issues related to evaluated values","color":"39f6e7"},"Undo/Redo":{"name":"Undo/Redo","description":"Issues related to undo/redo","color":"f25880"},"App Navigation":{"name":"App Navigation","description":"Issues related to the topbar navigation and configuring it","color":"12b715"},"Responsive Viewport":{"color":"d12d2e","name":"Responsive Viewport","description":"Issues seen on different viewports like mobile"},"Widgets Pane":{"name":"Widgets Pane","description":"Issues related to the discovery and organisation of widgets","color":"ad5d78"},"View Mode":{"color":"1799b0","name":"View Mode","description":"Issues related to the view mode"},"User Education Pod":{"name":"User Education Pod","description":"Issues related to user education","color":"1799b0"},"Content":{"name":"Content","description":"For content related topics i.e blogs, templates, videos","color":"a8dff7"},"Embedding Apps":{"name":"Embedding Apps","description":"Issues related to embedding","color":"30c76d"},"Slash Command":{"name":"Slash Command","description":"Issues related to the slash command","color":"a0608e"},"Widget Property":{"name":"Widget Property","description":"Issues related to adding / modifying widget properties across widgets","color":"5e92cb"},"Windows":{"name":"Windows","description":"Issues related exclusively to Windows systems","color":"b4cb8a"},"Old App Issues":{"name":"Old App Issues","description":"Issues related to apps old apps a few weeks old and app issues in stale browser session","color":"87ab18"},"Document Viewer Widget":{"name":"Document Viewer Widget","description":"Issues related to Document Viewer Widget","color":"899d4b"},"Radio Group Widget":{"name":"Radio Group Widget","description":"Issues related to radio group widget","color":"b68495"},"Super Admin":{"name":"Super Admin","description":"Issues related to the super admin page","color":"aa95cf"},"Postgres":{"name":"Postgres","description":"Postgres related issues","color":"8078b0"},"REST API plugin":{"name":"REST API plugin","description":"REST API plugin related issues","color":"8078b0"},"New JS Function":{"name":"New JS Function","description":"Issues related to adding a JS Function","color":"8e8aa4"},"Cannot Reproduce Issue":{"color":"93c9cc","name":"Cannot Reproduce Issue","description":"Issues that cannot be reproduced"},"Widget Grouping":{"name":"Widget Grouping","description":"Issues related to Widget Grouping","color":"a49951"},"K8s":{"name":"K8s","description":"Kubernetes related issues","color":"5f318a"},"Docker":{"name":"Docker","description":"Issues related to docker","color":"89b808"},"Camera Widget":{"name":"Camera Widget","description":"Issues and enhancements related to camera widget","color":"e6038e"},"SAAS Plugins":{"name":"SAAS Plugins","description":"Issues related to SAAS Plugins","color":"ef9c9d"},"JS Promises":{"name":"JS Promises","description":"Issues related to promises","color":"d7771f"},"OnPageLoad":{"name":"OnPageLoad","description":"OnPageLoad issues on functions and queries","color":"50559d"},"JS Usability":{"name":"JS Usability","description":"usability issues with JS editor and JS elsewhere","color":"a302b0"},"Currency Input Widget":{"name":"Currency Input Widget","description":"Issues related to currency input widget","color":"b2164f"},"TreeSelect":{"name":"TreeSelect","description":"Issues related to TreeSelect Widget","color":"a1633e"},"MultiTree Select Widget":{"name":"MultiTree Select Widget","description":"Issues related to MultiTree Select Widget","color":"a1633e"},"Welcome Screen":{"name":"Welcome Screen","description":"Issues related to the welcome screen","color":"30c76d"},"Realtime Commenting":{"color":"a70b86","name":"Realtime Commenting","description":"In-app communication between teams"},"Phone Input Widget":{"name":"Phone Input Widget","description":"Issues related to the Phone Input widget","color":"a70b86"},"JSON Form":{"name":"JSON Form","description":"Issue / features related to the JSON form wiget","color":"46b209"},"All Widgets":{"name":"All Widgets","description":"Issues related to all widgets","color":"972b36"},"V1":{"name":"V1","description":"V1","color":"67ab2e"},"Reflow & Resize":{"name":"Reflow & Resize","description":"All issues related to reflow and resize experience","color":"748a13"},"App Theming":{"name":"App Theming","description":"Items that are related to the App level theming controls epic","color":"905420"},"SSO":{"name":"SSO","description":"Issues, requests and enhancements around Single sign-on.","color":"bf019b"},"Multi User Realtime":{"name":"Multi User Realtime","description":"Issues related to multiple users using or editing an application","color":"e7b6ce"},"Templates":{"name":"Templates","description":"Issues related to templates","color":"b7e568"},"Ready for design":{"name":"Ready for design","description":"this issue is ready for design: it contains clear problem statements and other required information","color":"ebf442"},"Support":{"name":"Support","description":"Issues created by the A-force team to address user queries","color":"1740f3"},"Button Group widget":{"name":"Button Group widget","description":"Issue and enhancements related to the button group widget","color":"f17025"},"GraphQL Plugin":{"name":"GraphQL Plugin","description":"Issues related to GraphQL plugin","color":"8078b0"},"DevOps Pod":{"name":"DevOps Pod","description":"Issues related to devops","color":"d956c7"},"medium":{"name":"medium","description":"Issues that frustrate users due to poor UX","color":"23dfd9"},"ArangoDB":{"name":"ArangoDB","description":"Issues related to arangoDB","color":"8078b0"},"Code Refactoring":{"name":"Code Refactoring","description":"Issues related to code refactoring","color":"76310e"},"Progress bar widget":{"name":"Progress bar widget","description":"To track issues related to progress bar","color":"2d7abf"},"Audio Recorder Widget":{"name":"Audio Recorder Widget","description":"Issues related to Audio Recorder Widget","color":"9accef"},"Airtable":{"name":"Airtable","description":"Issues for Airtable","color":"60885f"},"RBAC":{"name":"RBAC","description":"Issues, requests and enhancements around RBAC.","color":"9211c3"},"Canvas / Grid":{"name":"Canvas / Grid","description":"Issues related to the canvas","color":"16b092"},"Email Config":{"name":"Email Config","description":"Issues related to configuring the email service","color":"2a21d1"},"CURL":{"name":"CURL","description":"Issues related to CURL impor","color":"60885f"},"Canvas Zooms":{"name":"Canvas Zooms","description":"Issues related to zooming the canvas","color":"e6038e"},"business":{"name":"business","description":"Features that will be a part of our business edition","color":"cd59eb"},"Action Pod":{"name":"Action Pod","description":"","color":"ee2e36"},"AutomationGap1":{"color":"a5e07c","name":"AutomationGap1","description":"Issues that needs automated tests"},"A-Force11":{"name":"A-Force11","description":"Issues raised by A-Force team","color":"d667b6"},"Business Edition":{"name":"Business Edition","description":"Features that will be a part of our business edition","color":"89bb6c"},"storeValue":{"name":"storeValue","description":"Issues related to the store value function","color":"5d3e66"},"Tests":{"name":"Tests","description":"test item","color":"1c6990"},"DynamoDB":{"name":"DynamoDB","description":"Issues that are related to DynamoDB should have this label","color":"60885f"},"Design System Pod":{"name":"Design System Pod","description":"Appsmith design system related issues","color":"706f03"},"ABAC":{"color":"e009a5","name":"ABAC","description":"User permissions and access controls"},"Backup & Restore":{"name":"Backup & Restore","description":"Issues related to backup and restore","color":"86874d"},"Billing":{"name":"Billing","description":"Billing infrastructure and flows for Business Edition and Trial users","color":"d2bc40"},"Datatype issue":{"name":"Datatype issue","description":"Issues that have risen because data types weren't handled","color":"60885f"},"OAuth":{"name":"OAuth","description":"OAuth related bugs or features","color":"60885f"},"Table Widget V2":{"name":"Table Widget V2","description":"Issues related to Table Widget V2","color":"3a7192"},"IDE Navigation":{"name":"IDE Navigation","description":"Issues/feature requests related to IDE navigation, and context switching","color":"bc0cba"},"Query performance":{"name":"Query performance","description":"Issues that have to do with lack in performance of query execution","color":"e4d966"},"SAAS Manager App":{"name":"SAAS Manager App","description":"Issues with the SAAS manager app","color":"d427db"},"Twilio":{"name":"Twilio","description":"Issues related to Twilio integration","color":"23ba8d"},"Hubspot":{"name":"Hubspot","description":"Issues related to Hubspot integration","color":"60885f"},"Zendesk":{"name":"Zendesk","description":"Issues related to Zendesk integration","color":"60885f"},"Entity Refactor":{"name":"Entity Refactor","description":"Issues related to refactor logic","color":"418fa4"},"Branding":{"name":"Branding","description":"All issues under branding and whitelabelling appsmith ecosystem","color":"7aaaf1"},"Map Chart Widget":{"name":"Map Chart Widget","description":"Issues related to Map Chart Widgets","color":"c8397f"},"Product Catchup":{"name":"Product Catchup","description":"Issues created in the product catchup","color":"29cd2c"},"Framework Functions":{"name":"Framework Functions","description":"Issues related to internal functions like showAlert(), navigateTo() etc...","color":"c25a09"},"Frontend Libraries Upgrade":{"name":"Frontend Libraries Upgrade","description":"Issues related to frontend libraries upgrade","color":"ede1fc"},"Audit Logs":{"name":"Audit Logs","description":"Audit trails to ensure data security","color":"f3fd62"},"MsSQL":{"name":"MsSQL","description":"Issues related to MsSQL plugin","color":"8078b0"},"Data Platform Pod":{"name":"Data Platform Pod","description":"Issues related to the underlying data platform","color":"3f8c3a"},"Integrations Pod":{"name":"Integrations Pod","description":"Issues related to a specific integration","color":"5dbbb1"},"Datasource Environments":{"name":"Datasource Environments","description":"Issues related to datasource environments","color":"bb7a14"},"Elastic Search":{"name":"Elastic Search","description":"Issues related to the elastic search datasource","color":"8078b0"},"Core Query Execution":{"color":"418fa4","name":"Core Query Execution","description":"Issues related to the execution of all queries"},"Query Management":{"name":"Query Management","description":"Issues related to the CRUD of actions or queries","color":"6a5b42"},"Query Settings":{"name":"Query Settings","description":"Issues related to the settings of all queries","color":"c7da7a"},"Code Editor":{"name":"Code Editor","description":"Issues related to the code editor","color":"4ca16e"},"Query Forms":{"color":"12b253","name":"Query Forms","description":"Isuses related to the query forms"},"JS Objects":{"color":"22962c","name":"JS Objects","description":"Issues related to JS Objects"},"JS Evaluation":{"color":"22962c","name":"JS Evaluation","description":"Issues related to JS evaluation on the platform"},"SmartSubstitution":{"name":"SmartSubstitution","description":"Issues related to Smart substitution of mustache bindings in queries","color":"e4d966"},"Query Generation":{"name":"Query Generation","description":"Issues related to query generation","color":"e4d966"},"Suggested Widgets":{"name":"Suggested Widgets","description":"Issues related to suggesting widgets based on query response","color":"e4d966"},"Page load executions":{"name":"Page load executions","description":"Issues related to page load execution","color":"5696b2"},"Code Scanner Widget":{"name":"Code Scanner Widget","description":"Issues related to code scanner widget","color":"9bc1a0"},"Clean URLs":{"name":"Clean URLs","description":"Issues related to clean URLs epic","color":"112623"},"Widget keyboard accessibility":{"name":"Widget keyboard accessibility","description":"All issues related to keyboard accessibility in widgets","color":"b626fd"},"Connection pool":{"name":"Connection pool","description":"issues to do with connection pooling of various plugins","color":"94fe36"},"List Widget V2":{"name":"List Widget V2","description":"Issues related to the list widget v2","color":"adaaf7"},"Auto Height":{"name":"Auto Height","description":"Issues related to dynamic height of widgets","color":"5149cf"},"cypress_failed_test":{"name":"cypress_failed_test","description":"Cypress failed tests","color":"4745d5"},"Needs validation":{"name":"Needs validation","description":"Needs problem validation before being picked up","color":"66673d"},"Slider Widget":{"name":"Slider Widget","description":"Issues raised for slider widgets.","color":"2eef5f"},"Multitenancy":{"name":"Multitenancy","description":"Support multitenancy within single appsmith instance","color":"8c49a9"},"Git Pod":{"name":"Git Pod","description":"Anything related to git sync","color":"2e5ba4"},"Mobile Pod":{"name":"Mobile Pod","description":"All issues related to mobile responsiveness","color":"6c97fd"},"Responsive Widget":{"name":"Responsive Widget","description":"All issues related to widget responsiveness","color":"d12d2e"},"Responsive Canvas":{"name":"Responsive Canvas","description":"All issues related to canvas responsiveness","color":"45a0a8"},"Conversion Algorithm":{"name":"Conversion Algorithm","description":"All issue related to converting app from fixed to flex mode & vice versa","color":"d12d2e"},"Spacing":{"name":"Spacing","description":"All issue related to spacing between widgets in auto layout","color":"d12d2e"},"Browser specific":{"name":"Browser specific","description":"All issue related to browser","color":"d12d2e"},"Error Handling":{"name":"Error Handling","description":"Issues related to error handling","color":"4e1872"},"Performance infra":{"name":"Performance infra","description":"all issue related to the performance infra","color":"8a60f6"},"DSL Update":{"name":"DSL Update","description":"Issues related to storing and updating the DSL","color":"e16cf3"},"AST-frontend":{"name":"AST-frontend","description":"Issues related to maintaining AST logic","color":"434a3a"},"AST-backend":{"name":"AST-backend","description":"Backend issues related to AST parsing","color":"c476eb"},"MariaDB":{"name":"MariaDB","description":"MariaDB datasource","color":"8428c3"},"Billing & Usage Pod":{"name":"Billing & Usage Pod","description":"Issues pertaining to licensing, billing, usage across self serve and enterprise customers","color":"256808"},"ADS Component Issue":{"name":"ADS Component Issue","description":"Issues which are caused due to ADS components","color":"d89119"},"Regressed":{"color":"723fd0","name":"Regressed","description":"Scenarios that were working before but have now regressed"},"Needs RCA":{"name":"Needs RCA","description":"a critical or high priority issue that needs an RCA","color":"2cc68f"},"Custom JS Libraries":{"name":"Custom JS Libraries","description":"Issues related to adding custom JS library","color":"bacb6d"},"Integrations Pod General":{"name":"Integrations Pod General","description":"Issues related to the Integrations Pod that don't fit into other tags.","color":"287823"},"Performance Pod":{"name":"Performance Pod","description":"All things related to Appsmith performance","color":"b5a25d"},"Performance":{"name":"Performance","description":"Issues related to performance","color":"9a18d7"},"File upload issues":{"name":"File upload issues","description":"Issues related to uploading any type of files from within Appsmith","color":"8154df"},"Action Selector":{"name":"Action Selector","description":"Issues related to action selector on the property pane","color":"2f9e20"},"Widget design system":{"name":"Widget design system","description":"","color":"cb6188"},"Deploy App":{"name":"Deploy App","description":"Issues related to app deployment","color":"6f6152"},"Community Reported":{"name":"Community Reported","description":"issues reported by community members","color":"1402e5"},"JS Function execution":{"name":"JS Function execution","description":"JS function execution","color":"7c2de1"},"Self Serve":{"name":"Self Serve","description":"For all issues related to self-serve flow for business edition","color":"4dacfc"},"Self Serve 1.0":{"name":"Self Serve 1.0","description":"For all issues related to v1 of the self serve project","color":"ae839e"},"CE Instance":{"name":"CE Instance","description":"For all issues relating to usage, licensing or billing on the CE instance","color":"d2bc40"},"Customer Portal":{"name":"Customer Portal","description":"For all tasks/issues pertaining to customer.appsmith.com","color":"d2bc40"},"Cloud Services":{"name":"Cloud Services","description":"For all tasks/issues on Appsmith cloud-services relating to licensing, usage and billing","color":"d2bc40"},"Billing Integrations":{"name":"Billing Integrations","description":"For all issues relating to 3P integrations Appsmith is using for billing & usage","color":"d2bc40"},"One-click Binding":{"name":"One-click Binding","description":"Issues related to the One click binding epic","color":"f1661c"},"Airgap":{"name":"Airgap","description":"Tickets related to supporting air-gapped Appsmith instances","color":"1cb294"},"SMTP plugin":{"name":"SMTP plugin","description":"Issues related to SMTP plugin","color":"541457"},"AWS AMI":{"name":"AWS AMI","description":"Issues Related to AWS AMI","color":"b44680"},"Old widget version":{"name":"Old widget version","description":"Use this label to raise issue specific only to an older version of a widget","color":"ff3814"},"Enterprise Billing":{"name":"Enterprise Billing","description":"To track all tasks/issues related to licensing & billing for enterprise customers","color":"14c156"},"Appsmith Business Cloud":{"name":"Appsmith Business Cloud","description":"Issues related to our business cloud offering","color":"89bb6c"},"Oracle SQL DB":{"name":"Oracle SQL DB","description":"Issues related to the Oracle plugin","color":"cbabcb"},"Community Contributor":{"name":"Community Contributor","description":"Meant to track issues that are assigned to external contributors","color":"149ab6"},"widget vertical alignment":{"name":"widget vertical alignment","description":"All issue related widget vertical alignment on the auto layout canvas","color":"d12d2e"},"Observability":{"name":"Observability","description":"Issues related to observability on the Appsmith instance","color":"dff913"},"Checkbox Component":{"name":"Checkbox Component","description":"This labels deals with checkbox component in wds package","color":"75a401"},"In-app ramps":{"name":"In-app ramps","description":"For all tasks/issues relating to adding in-app ramps in the community edition of the product","color":"8abae0"},"Analytics Improvements":{"name":"Analytics Improvements","description":"For all tasks focused on improving our overall analytics and fixing any issues ","color":"29b8ed"},"WDS team":{"name":"WDS team","description":"","color":"8d675a"},"Enterprise Edition":{"name":"Enterprise Edition","description":"Features that will be supported in Enterprise Edition only","color":"984f5e"},"Query filter":{"name":"Query filter","description":"Issues related to query filtering, e.g., WHERE clause","color":"a15134"},"Keyboard accessibility ":{"name":"Keyboard accessibility ","description":"All issue related to ADS component keyboard accessibility","color":"2ba696"},"Toggle button":{"name":"Toggle button","description":"All issue related to ADS toggle button","color":"edc47f"},"Feature Flagging":{"name":"Feature Flagging","description":"Anything related feature flagging","color":"8d8a09"},"SCIM":{"name":"SCIM","description":"Label to collate our SCIM issues","color":"61a852"},"ADS Category Token":{"name":"ADS Category Token","description":"All issues related appsmith design system category tokens","color":"920961"},"ADS Component Documentation":{"name":"ADS Component Documentation","description":"All issues Appsmith design system component documentation","color":"64c46a"},"ADS Migration":{"name":"ADS Migration","description":"All issues related to Appsmith design system migration","color":"b082d6"},"ADS Deduplication ":{"name":"ADS Deduplication ","description":"Replacing component with ADS components","color":"b082d6"},"ADS Revamp":{"name":"ADS Revamp","description":"All issues related to ads revamp. ","color":"b082d6"},"ADS Deduplication":{"name":"ADS Deduplication","description":"Replacing component with ADS components","color":"b082d6"},"ADS Grayscale":{"name":"ADS Grayscale","description":"Support grayscale color changes","color":"b03577"},"ADS Unit Test":{"name":"ADS Unit Test","description":"All issue related ads unit cases ","color":"b082d6"},"ADS Components":{"name":"ADS Components","description":"All issues related ADS components","color":"b082d6"},"Widget Discoverability":{"name":"Widget Discoverability","description":"Issues related to Widget Discoverability","color":"7b55ce"},"Widget setter method":{"name":"Widget setter method","description":"Issues with widget property setters","color":"8dce87"},"License":{"name":"License","description":"For all issues/tasks related to licensing of appsmith-ee edition","color":"90ee98"},"Templates pod":{"name":"Templates pod","description":"Issues related to Templates","color":"b7e568"},"Community template":{"name":"Community template","description":"Label for development of community templates and its integration to platform","color":"8a0510"},"DocumentDB":{"name":"DocumentDB","description":"Issues related to support DocumentDB in Appsmith Data layer","color":"2c8b56"},"Multiple Environments":{"name":"Multiple Environments","description":"Issues or tasks related to multiple environments","color":"4e972b"},"Platformization":{"name":"Platformization","description":"Issues or tasks related to platformization of Appsmith codebase","color":"4e972b"},"Activation - datasources":{"name":"Activation - datasources","description":"issues related to activation projects","color":"7c7ace"},"Partial-import-export":{"name":"Partial-import-export","description":"Label for granular reusability.","color":"1e439c"},"AI":{"name":"AI","description":"All tasks related to AI","color":"75c4ce"},"Custom environments":{"name":"Custom environments","description":"Issues with creating or working with custom environments","color":"2137d6"},"ADS Typography":{"name":"ADS Typography","description":"All issue related typographical changes","color":"2dbe8d"},"Auto Layout":{"name":"Auto Layout","description":"Issues relates to auto layout","color":"92cf8c"},"Heroku":{"name":"Heroku","description":"Issues related to Heroku","color":"a81b69"},"ADS Visual Styles":{"name":"ADS Visual Styles","description":"All issues related to ADS visual styles","color":"d3da89"},"ADS Component Design":{"name":"ADS Component Design","description":"All issue related to component design","color":"5cc91e"},"Modal Component":{"name":"Modal Component","description":"All issue related to ads modal component","color":"ee63f3"},"App setting":{"name":"App setting","description":"Related to app settings panel within the app","color":"144206"},"BE instance":{"name":"BE instance","description":"For all issues related to license, billing on BE instance","color":"ae8f98"},"Schema":{"name":"Schema","description":"Issues related to database schema","color":"c470c2"},"Fixed layout":{"name":"Fixed layout","description":"issues related to fixed layout","color":"b66681"},"Anvil layout":{"name":"Anvil layout","description":"issues related to the new layout system anvil","color":"722bf0"},"New Deployment Mode":{"name":"New Deployment Mode","description":"Support a new mode of deployment","color":"108033"},"Custom widgets":{"name":"Custom widgets","description":"For all issues related to the custom widget project","color":"c9db9c"},"IDE Pod":{"name":"IDE Pod","description":"https://app.zenhub.com/workspaces/new-developers-pod-60507ad1d4b98d00150a2858/board","color":"d3d248"},"TM_BU":{"name":"TM_BU","description":"The issues on Team Manager which needs to be taken up by Billing & Usage","color":"198cdf"},"Homepage Experience V2":{"name":"Homepage Experience V2","description":"Label for reporting new tasks and bug fixes related to revamped homepage experience","color":"c55d54"},"Appsmith Labs":{"name":"Appsmith Labs","description":"All things related to AI and other new initiatives ","color":"712d51"},"Customer Success":{"name":"Customer Success","description":"Issues that the success team cares about","color":"6ccabd"},"Invite flow":{"name":"Invite flow","description":"Invite users flow and any associated actions","color":"881b35"},"Invite users":{"name":"Invite users","description":"Invite users flow and any associated actions","color":"23e6d6"},"Workflows Pod":{"name":"Workflows Pod","description":"For all issues related to the Workflows feature","color":"2c1f93"},"DailyPromotionBlocker":{"name":"DailyPromotionBlocker","description":"Blocker for daily promotion","color":"9b2280"}},"success":true} \ No newline at end of file From 455741e08905ec52250344640c53911c0a1970ca Mon Sep 17 00:00:00 2001 From: Nikhil Nandagopal Date: Thu, 18 Jan 2024 11:51:53 +0530 Subject: [PATCH 09/16] Updated Label Config --- .github/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/config.json b/.github/config.json index ec4ac3ad70..dfcec61400 100644 --- a/.github/config.json +++ b/.github/config.json @@ -1 +1 @@ -{"runners":[{"versioning":{"source":"milestones","type":"SemVer"},"prereleaseName":"alpha","issue":{"labels":{"Error Handling":{"conditions":[],"requires":1},"Templates pod":{"conditions":[{"label":"Templates","type":"hasLabel","value":true},{"label":"Community template","type":"hasLabel","value":true},{"label":"Partial-import-export","type":"hasLabel","value":true}],"requires":1},"Team Managers Pod":{"conditions":[{"label":"Settings","type":"hasLabel","value":true},{"label":"Home Page","type":"hasLabel","value":true},{"label":"Realtime Commenting","type":"hasLabel","value":true},{"label":"SSO","type":"hasLabel","value":true},{"label":"Multi User Realtime","type":"hasLabel","value":true},{"label":"RBAC","type":"hasLabel","value":true},{"label":"ABAC","type":"hasLabel","value":true},{"label":"Audit Logs","type":"hasLabel","value":true},{"label":"Multitenancy","type":"hasLabel","value":true},{"label":"Airgap","type":"hasLabel","value":true},{"label":"Enterprise Edition","type":"hasLabel","value":true},{"label":"SCIM","type":"hasLabel","value":true},{"label":"Invite flow","type":"hasLabel","value":true}],"requires":1},"New Developers Pod":{"conditions":[{"label":"Omnibar","type":"hasLabel","value":true},{"label":"Telemetry","type":"hasLabel","value":true},{"label":"Entity Explorer","type":"hasLabel","value":true},{"label":"IDE","type":"hasLabel","value":true},{"label":"Example Apps","type":"hasLabel","value":true},{"label":"i18n","type":"hasLabel","value":true},{"label":"IDE Navigation","type":"hasLabel","value":true},{"label":"Clean URLs","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"App setting","type":"hasLabel","value":true}],"requires":1},"BE Coders Pod":{"conditions":[{"label":"SAAS Plugins","type":"hasLabel","value":true},{"label":"SAAS Manager App","type":"hasLabel","value":true},{"label":"Data Platform Pod","type":"hasLabel","value":true},{"label":"Integrations Pod","type":"hasLabel","value":true}],"requires":1},"FE Coders Pod":{"conditions":[{"label":"JS Linting & Errors","type":"hasLabel","value":true},{"label":"Debugger","type":"hasLabel","value":true},{"label":"JS Snippets","type":"hasLabel","value":true},{"label":"Autocomplete","type":"hasLabel","value":true},{"label":"Evaluated Value","type":"hasLabel","value":true},{"label":"Slash Command","type":"hasLabel","value":true},{"label":"New JS Function","type":"hasLabel","value":true},{"label":"JS Promises","type":"hasLabel","value":true},{"label":"JS Usability","type":"hasLabel","value":true},{"label":"Code Refactoring","type":"hasLabel","value":true},{"label":"storeValue","type":"hasLabel","value":true},{"label":"OnPageLoad","type":"hasLabel","value":true},{"label":"Framework Functions","type":"hasLabel","value":true},{"label":"Code Editor","type":"hasLabel","value":true},{"label":"JS Objects","type":"hasLabel","value":true},{"label":"JS Evaluation","type":"hasLabel","value":true},{"label":"AST-frontend","type":"hasLabel","value":true},{"label":"Custom JS Libraries","type":"hasLabel","value":true},{"label":"Action Selector","type":"hasLabel","value":true},{"label":"JS Function execution","type":"hasLabel","value":true},{"label":"Widget setter method","type":"hasLabel","value":true},{"label":"Error Handling","type":"hasLabel","value":true}],"requires":1},"App Viewers Pod":{"conditions":[{"label":"Button Widget","type":"hasLabel","value":true},{"label":"Chart Widget","type":"hasLabel","value":true},{"label":"Container Widget","type":"hasLabel","value":true},{"label":"Date Picker Widget","type":"hasLabel","value":true},{"label":"Select Widget","type":"hasLabel","value":true},{"label":"File Picker Widget","type":"hasLabel","value":true},{"label":"Form Widget","type":"hasLabel","value":true},{"label":"Image Widget","type":"hasLabel","value":true},{"label":"Input Widget","type":"hasLabel","value":true},{"label":"List Widget","type":"hasLabel","value":true},{"label":"MultiSelect Widget","type":"hasLabel","value":true},{"label":"Map Widget","type":"hasLabel","value":true},{"label":"Modal Widget","type":"hasLabel","value":true},{"label":"Radio Widget","type":"hasLabel","value":true},{"label":"Rich Text Editor Widget","type":"hasLabel","value":true},{"label":"Tab Widget","type":"hasLabel","value":true},{"label":"Table Widget","type":"hasLabel","value":true},{"label":"Text Widget","type":"hasLabel","value":true},{"label":"Video Widget","type":"hasLabel","value":true},{"label":"iFrame","type":"hasLabel","value":true},{"label":"Menu Button","type":"hasLabel","value":true},{"label":"Rating","type":"hasLabel","value":true},{"label":"Widget Validation","type":"hasLabel","value":true},{"label":"reallabel","type":"hasLabel","value":true},{"label":"New Widget","type":"hasLabel","value":true},{"label":"Switch widget","type":"hasLabel","value":true},{"label":"Audio Widget","type":"hasLabel","value":true},{"label":"Icon Button Widget","type":"hasLabel","value":true},{"label":"Stat Box Widget","type":"hasLabel","value":true},{"label":"Voice Recorder Widget","type":"hasLabel","value":true},{"label":"Calendar Widget","type":"hasLabel","value":true},{"label":"Menu Button Widget","type":"hasLabel","value":true},{"label":"Divider Widget","type":"hasLabel","value":true},{"label":"Rating Widget","type":"hasLabel","value":true},{"label":"App Navigation","type":"hasLabel","value":true},{"label":"View Mode","type":"hasLabel","value":true},{"label":"Widget Property","type":"hasLabel","value":true},{"label":"Document Viewer Widget","type":"hasLabel","value":true},{"label":"Radio Group Widget","type":"hasLabel","value":true},{"label":"Currency Input Widget","type":"hasLabel","value":true},{"label":"TreeSelect","type":"hasLabel","value":true},{"label":"MultiTree Select Widget","type":"hasLabel","value":true},{"label":"Phone Input Widget","type":"hasLabel","value":true},{"label":"JSON Form","type":"hasLabel","value":true},{"label":"All Widgets","type":"hasLabel","value":true},{"label":"Button Group widget","type":"hasLabel","value":true},{"label":"Progress bar widget","type":"hasLabel","value":true},{"label":"Audio Recorder Widget","type":"hasLabel","value":true},{"label":"Camera Widget","type":"hasLabel","value":true},{"label":"Table Widget V2","type":"hasLabel","value":true},{"label":"Branding","type":"hasLabel","value":true},{"label":"Map Chart Widget","type":"hasLabel","value":true},{"label":"Code Scanner Widget","type":"hasLabel","value":true},{"label":"Widget keyboard accessibility","type":"hasLabel","value":true},{"label":"List Widget V2","type":"hasLabel","value":true},{"label":"Slider Widget","type":"hasLabel","value":true},{"label":"One-click Binding","type":"hasLabel","value":true},{"label":"Old widget version","type":"hasLabel","value":true},{"label":"Widget Discoverability","type":"hasLabel","value":true},{"label":"Custom widgets","type":"hasLabel","value":true}],"requires":1},"UI Builders Pod":{"conditions":[{"label":"Property Pane","type":"hasLabel","value":true},{"label":"Pages","type":"hasLabel","value":true},{"label":"Copy Paste","type":"hasLabel","value":true},{"label":"Drag & Drop","type":"hasLabel","value":true},{"label":"Undo/Redo","type":"hasLabel","value":true},{"label":"Widgets Pane","type":"hasLabel","value":true},{"label":"UI Performance","type":"hasLabel","value":true},{"label":"Widget Grouping","type":"hasLabel","value":true},{"label":"Reflow & Resize","type":"hasLabel","value":true},{"label":"Canvas / Grid","type":"hasLabel","value":true},{"label":"Canvas Zooms","type":"hasLabel","value":true},{"label":"Frontend Libraries Upgrade","type":"hasLabel","value":true},{"label":"Auto Height","type":"hasLabel","value":true},{"label":"Responsive Canvas","type":"hasLabel","value":true},{"label":"Responsive Widget","type":"hasLabel","value":true},{"label":"Responsive Viewport","type":"hasLabel","value":true},{"label":"Conversion Algorithm","type":"hasLabel","value":true},{"label":"Spacing","type":"hasLabel","value":true},{"label":"Browser specific","type":"hasLabel","value":true},{"label":"widget vertical alignment","type":"hasLabel","value":true},{"label":"Auto Layout","type":"hasLabel","value":true},{"label":"Fixed layout","type":"hasLabel","value":true},{"label":"Anvil layout","type":"hasLabel","value":true}],"requires":1},"User Education Pod":{"conditions":[{"label":"Content","type":"hasLabel","value":true},{"label":"Documentation","type":"hasLabel","value":true}],"requires":1},"DevOps Pod":{"conditions":[{"label":"Docker","type":"hasLabel","value":true},{"label":"Super Admin","type":"hasLabel","value":true},{"label":"Deployment","type":"hasLabel","value":true},{"label":"K8s","type":"hasLabel","value":true},{"label":"Email Config","type":"hasLabel","value":true},{"label":"Backup & Restore","type":"hasLabel","value":true},{"label":"AWS AMI","type":"hasLabel","value":true},{"label":"Observability","type":"hasLabel","value":true},{"label":"Heroku","type":"hasLabel","value":true},{"label":"New Deployment Mode","type":"hasLabel","value":true}],"requires":1},"Design System Pod":{"conditions":[{"label":"Design System Pod","type":"hasLabel","value":true},{"label":"ADS Component Issue","type":"hasLabel","value":true},{"label":"Keyboard accessibility ","type":"hasLabel","value":true},{"label":"Toggle button","type":"hasLabel","value":true},{"label":"ADS Category Token","type":"hasLabel","value":true},{"label":"ADS Component Documentation","type":"hasLabel","value":true},{"label":"ADS Migration","type":"hasLabel","value":true},{"label":"ADS Deduplication ","type":"hasLabel","value":true},{"label":"ADS Revamp","type":"hasLabel","value":true},{"label":"ADS Deduplication","type":"hasLabel","value":true},{"label":"ADS Unit Test","type":"hasLabel","value":true},{"label":"ADS Components","type":"hasLabel","value":true},{"label":"ADS Grayscale","type":"hasLabel","value":true},{"label":"Design System","type":"hasLabel","value":true},{"label":"ADS Typography","type":"hasLabel","value":true},{"label":"ADS Visual Styles","type":"hasLabel","value":true},{"label":"ADS Component Design","type":"hasLabel","value":true},{"label":"Modal Component","type":"hasLabel","value":true}],"requires":1},"Data Platform Pod":{"conditions":[{"label":"Datasource Environments","type":"hasLabel","value":true},{"label":"Datatype issue","type":"hasLabel","value":true},{"label":"Entity Refactor","type":"hasLabel","value":true},{"label":"Core Query Execution","type":"hasLabel","value":true},{"label":"Query Management","type":"hasLabel","value":true},{"label":"Query Settings","type":"hasLabel","value":true},{"label":"SmartSubstitution","type":"hasLabel","value":true},{"label":"Query Generation","type":"hasLabel","value":true},{"label":"Query performance","type":"hasLabel","value":true},{"label":"Suggested Widgets","type":"hasLabel","value":true},{"label":"Page load executions","type":"hasLabel","value":true},{"label":"DSL Update","type":"hasLabel","value":true},{"label":"AST-backend","type":"hasLabel","value":true},{"label":"Deploy App","type":"hasLabel","value":true},{"label":"File upload issues","type":"hasLabel","value":true},{"label":"Datasources","type":"hasLabel","value":true},{"label":"DocumentDB","type":"hasLabel","value":true},{"label":"Multiple Environments","type":"hasLabel","value":true},{"label":"Platformization","type":"hasLabel","value":true},{"label":"Custom environments","type":"hasLabel","value":true},{"label":"Schema","type":"hasLabel","value":true}],"requires":1},"Integrations Pod":{"conditions":[{"label":"New Datasource","type":"hasLabel","value":true},{"label":"Firestore","type":"hasLabel","value":true},{"label":"Google Sheets","type":"hasLabel","value":true},{"label":"Mongo","type":"hasLabel","value":true},{"label":"Redshift","type":"hasLabel","value":true},{"label":"snowflake","type":"hasLabel","value":true},{"label":"S3","type":"hasLabel","value":true},{"label":"Redis","type":"hasLabel","value":true},{"label":"Postgres","type":"hasLabel","value":true},{"label":"GraphQL Plugin","type":"hasLabel","value":true},{"label":"ArangoDB","type":"hasLabel","value":true},{"label":"MsSQL","type":"hasLabel","value":true},{"label":"REST API plugin","type":"hasLabel","value":true},{"label":"Elastic Search","type":"hasLabel","value":true},{"label":"OAuth","type":"hasLabel","value":true},{"label":"Airtable","type":"hasLabel","value":true},{"label":"CURL","type":"hasLabel","value":true},{"label":"DynamoDB","type":"hasLabel","value":true},{"label":"Zendesk","type":"hasLabel","value":true},{"label":"Hubspot","type":"hasLabel","value":true},{"label":"Query Forms","type":"hasLabel","value":true},{"label":"Twilio","type":"hasLabel","value":true},{"label":"MySQL","type":"hasLabel","value":true},{"label":"Connection pool","type":"hasLabel","value":true},{"label":"MariaDB","type":"hasLabel","value":true},{"label":"Integrations Pod General","type":"hasLabel","value":true},{"label":"SMTP plugin","type":"hasLabel","value":true},{"label":"Oracle SQL DB","type":"hasLabel","value":true},{"label":"Query filter","type":"hasLabel","value":true},{"label":"Activation - datasources","type":"hasLabel","value":true},{"label":"Onboarding","type":"hasLabel","value":true},{"label":"Generate Page","type":"hasLabel","value":true},{"label":"Sniping Mode","type":"hasLabel","value":true},{"label":"Welcome Screen","type":"hasLabel","value":true},{"label":"Login / Signup","type":"hasLabel","value":true}],"requires":1},"Git Pod":{"conditions":[{"label":"Git Version Control","type":"hasLabel","value":true},{"label":"Import-Export-App","type":"hasLabel","value":true},{"label":"Fork App","type":"hasLabel","value":true}],"requires":1},"Mobile Pod":{"conditions":[],"requires":1},"Billing & Usage Pod":{"conditions":[{"label":"CE Instance","type":"hasLabel","value":true},{"label":"Customer Portal","type":"hasLabel","value":true},{"label":"Cloud Services","type":"hasLabel","value":true},{"label":"Billing Integrations","type":"hasLabel","value":true},{"label":"Billing","type":"hasLabel","value":true},{"label":"Self Serve","type":"hasLabel","value":true},{"label":"Enterprise Billing","type":"hasLabel","value":true},{"label":"In-app ramps","type":"hasLabel","value":true},{"label":"Analytics Improvements","type":"hasLabel","value":true},{"label":"Self Serve 1.0","type":"hasLabel","value":true},{"label":"License","type":"hasLabel","value":true},{"label":"Appsmith Business Cloud","type":"hasLabel","value":true},{"label":"BE instance","type":"hasLabel","value":true},{"label":"Embedding Apps","type":"hasLabel","value":true},{"label":"TM_BU","type":"hasLabel","value":true},{"label":"Homepage Experience V2","type":"hasLabel","value":true},{"label":"Feature Flagging","type":"hasLabel","value":true},{"label":"Invite flow","type":"hasLabel","value":true},{"label":"Invite users","type":"hasLabel","value":true}],"requires":1},"Performance Pod":{"conditions":[{"label":"Performance","type":"hasLabel","value":true},{"label":"Performance infra","type":"hasLabel","value":true}],"requires":1},"Widget design system":{"conditions":[{"label":"App Theming","type":"hasLabel","value":true},{"label":"Widget Styling","type":"hasLabel","value":true},{"label":"Checkbox Group widget","type":"hasLabel","value":true},{"label":"Checkbox Widget","type":"hasLabel","value":true},{"label":"Checkbox Component","type":"hasLabel","value":true},{"label":"WDS team","type":"hasLabel","value":true},{"label":"Widget design system","type":"hasLabel","value":true}],"requires":1},"IDE Pod":{"conditions":[],"requires":1},"Appsmith Labs":{"conditions":[{"label":"AI","type":"hasLabel","value":true}],"requires":1},"Workflows Pod":{"conditions":[],"requires":1}}},"root":"."}],"labels":{"Tab Widget":{"color":"e2c76c","name":"Tab Widget","description":""},"Dont merge":{"color":"ADB39C","name":"Dont merge","description":""},"Epic":{"color":"3E4B9E","name":"Epic","description":"A zenhub epic that describes a project"},"Menu Button Widget":{"color":"235708","name":"Menu Button Widget","description":"Issues related to Menu Button widget"},"Checkbox Group widget":{"color":"88054d","name":"Checkbox Group widget","description":"Issues related to Checkbox Group Widget"},"Input Widget":{"color":"ae65d8","name":"Input Widget","description":""},"Security":{"color":"99139C","name":"Security","description":""},"QA":{"color":"e2ca68","name":"QA","description":""},"Verified":{"color":"9bf416","name":"Verified","description":""},"Wont Fix":{"color":"ffffff","name":"Wont Fix","description":"This will not be worked on"},"MySQL":{"color":"c9ddc6","name":"MySQL","description":"Issues related to MySQL plugin"},"Development":{"color":"9F8A02","name":"Development","description":""},"Help Wanted":{"color":"008672","name":"Help Wanted","description":"Extra attention is needed"},"Home Page":{"color":"9c0c8e","name":"Home Page","description":"Issues related to the application home page"},"Rating Widget":{"color":"235708","name":"Rating Widget","description":"Issues related to the rating widget"},"Stat Box Widget":{"color":"f1c9ce","name":"Stat Box Widget","description":"Issues related to stat box"},"Enhancement":{"color":"a2eeef","name":"Enhancement","description":"New feature or request"},"Settings":{"color":"f7ff60","name":"Settings","description":"organization, team & user settings"},"Fork App":{"color":"30c76d","name":"Fork App","description":"Issues related to forking apps"},"Container Widget":{"color":"19AD0D","name":"Container Widget","description":"Container widget"},"Papercut":{"color":"B562F6","name":"Papercut","description":""},"Needs Design":{"color":"bfd4f2","name":"Needs Design","description":"needs design or changes to design"},"i18n":{"color":"1799b0","name":"i18n","description":"Represents issues that need to be tackled to handle internationalization"},"Rich Text Editor Widget":{"color":"f72cac","name":"Rich Text Editor Widget","description":""},"Onboarding":{"color":"30c76d","name":"Onboarding","description":"Issues related to onboarding new developers"},"Pages":{"color":"d7fd80","name":"Pages","description":"Issues related to configuring pages"},"skip-changelog":{"color":"06086F","name":"skip-changelog","description":"Adding this label to a PR prevents it from being listed in the changelog"},"Low":{"color":"79e53b","name":"Low","description":"An issue that is neither critical nor breaks a user flow"},"potential-duplicate":{"color":"d3cb2e","name":"potential-duplicate","description":"This label marks issues that are potential duplicates of already open issues"},"Audio Widget":{"color":"447B9A","name":"Audio Widget","description":"Issues related to Audio Widget"},"Firestore":{"color":"8078b0","name":"Firestore","description":"Issues related to the firestore Integration"},"New Widget":{"color":"be4cf2","name":"New Widget","description":"A request for a new widget"},"Modal Widget":{"color":"03846f","name":"Modal Widget","description":""},"UX Improvement":{"color":"f4a089","name":"UX Improvement","description":""},"S3":{"color":"8078b0","name":"S3","description":"Issues related to the S3 plugin"},"Release Blocker":{"color":"5756bf","name":"Release Blocker","description":"This issue must be resolved before the release"},"safari":{"color":"51C6AA","name":"safari","description":"Bugs seen on safari browser"},"Example Apps":{"color":"1799b0","name":"Example Apps","description":"Example apps created for new signups"},"MultiSelect Widget":{"color":"AB62D4","name":"MultiSelect Widget","description":"Issues related to MultiSelect Widget"},"Widget Styling":{"color":"905420","name":"Widget Styling","description":"all about widget styling"},"Calendar Widget":{"color":"8c6644","name":"Calendar Widget","description":""},"Website":{"color":"151720","name":"Website","description":"Related to www.appsmith.com website"},"Low effort":{"color":"8B59F0","name":"Low effort","description":"Something that'll take a few days to build"},"App Viewers Pod":{"color":"cd8ef9","name":"App Viewers Pod","description":"This label assigns issues to the app viewers pod"},"Checkbox Widget":{"color":"88054d","name":"Checkbox Widget","description":""},"Spam":{"color":"620faf","name":"Spam","description":""},"Voice Recorder Widget":{"color":"85bc87","name":"Voice Recorder Widget","description":""},"Select Widget":{"color":"0c669e","name":"Select Widget","description":"Select or dropdown widget"},"Bug":{"color":"d73a4a","name":"Bug","description":"Something isn't working"},"Widget Validation":{"color":"6990BC","name":"Widget Validation","description":"Issues related to widget property validation"},"Generate Page":{"color":"30c76d","name":"Generate Page","description":"Issures related to page generation"},"File Picker Widget":{"color":"6ae4f2","name":"File Picker Widget","description":""},"snowflake":{"color":"8078b0","name":"snowflake","description":"Issues related to the snowflake Integration"},"Automation":{"color":"CCAF60","name":"Automation","description":""},"hotfix":{"color":"BA3F1D","name":"hotfix","description":""},"Team Managers Pod":{"color":"bddb81","name":"Team Managers Pod","description":"Issues that team managers care about for the security and efficiency of their teams"},"Import-Export-App":{"color":"15076d","name":"Import-Export-App","description":"Issues related to importing and exporting apps"},"High effort":{"color":"A7E87B","name":"High effort","description":"Something that'll take more than a month to build"},"Telemetry":{"color":"bc70f9","name":"Telemetry","description":"Issues related to instrumenting appsmith"},"Radio Widget":{"color":"91ef15","name":"Radio Widget","description":""},"Omnibar":{"color":"10b5ce","name":"Omnibar","description":"Issues related to the omnibar for navigation"},"Button Widget":{"color":"34efae","name":"Button Widget","description":""},"Switch widget":{"color":"33A8CE","name":"Switch widget","description":"The switch widget"},"Map Widget":{"color":"7eef7a","name":"Map Widget","description":""},"Task":{"color":"085630","name":"Task","description":"A simple Todo"},"Design System":{"color":"2958a4","name":"Design System","description":"Design system"},"opera":{"color":"C63F5B","name":"opera","description":"Any issues identified on the opera browser"},"Login / Signup":{"color":"30c76d","name":"Login / Signup","description":"Authentication flows"},"Image Widget":{"color":"8de8ad","name":"Image Widget","description":""},"firefox":{"color":"6d56e2","name":"firefox","description":""},"Property Pane":{"color":"b356ff","name":"Property Pane","description":"Issues related to the behaviour of the property pane"},"Deployment":{"color":"93491f","name":"Deployment","description":"Installation process of appsmith"},"Critical":{"color":"9b1b28","name":"Critical","description":"This issue needs immediate attention. Drop everything else"},"IDE":{"color":"61b2ee","name":"IDE","description":"Issues related to the IDE"},"Production":{"color":"b60205","name":"Production","description":""},"Dependencies":{"color":"0366d6","name":"Dependencies","description":"Pull requests that update a dependency file"},"Google Sheets":{"color":"8078b0","name":"Google Sheets","description":"Issues related to Google Sheets"},"Icon Button Widget":{"color":"D319CE","name":"Icon Button Widget","description":"Issues related to the icon button widget"},"Mongo":{"color":"8078b0","name":"Mongo","description":"Issues related to Mongo DB plugin"},"Documentation":{"color":"a8dff7","name":"Documentation","description":"Improvements or additions to documentation"},"TestGap":{"color":"f28253","name":"TestGap","description":"Issues identified for test plan improvement"},"keyboard shortcut":{"color":"0688B6","name":"keyboard shortcut","description":""},"Git Version Control":{"color":"858172","name":"Git Version Control","description":"Issues related to version control"},"Reopen":{"color":"897548","name":"Reopen","description":""},"Redshift":{"color":"8078b0","name":"Redshift","description":"Issues related to the redshift integration"},"Date Picker Widget":{"color":"ef1ce1","name":"Date Picker Widget","description":""},"Entity Explorer":{"color":"a2e2f9","name":"Entity Explorer","description":"Issues related to navigation using the entity explorer"},"JS Linting & Errors":{"color":"E56AA5","name":"JS Linting & Errors","description":"Issues related to JS Linting and errors"},"iFrame":{"color":"3CD1DB","name":"iFrame","description":"Issues related to iFrame"},"Stale":{"color":"ededed","name":"Stale","description":null},"Debugger":{"color":"e79062","name":"Debugger","description":"Issues related to the debugger"},"Quick effort":{"color":"95ED65","name":"Quick effort","description":"Something that'll take a few hours to build"},"Text Widget":{"color":"d130d1","name":"Text Widget","description":""},"Video Widget":{"color":"23dd4b","name":"Video Widget","description":""},"Datasources":{"color":"5052f6","name":"Datasources","description":"Issues related to configuring datasource on appsmith"},"error":{"color":"B66773","name":"error","description":"All issues connected to error messages"},"Form Widget":{"color":"09ed77","name":"Form Widget","description":""},"Needs Triaging":{"color":"e8b851","name":"Needs Triaging","description":"Needs attention from maintainers to triage"},"Autocomplete":{"color":"235708","name":"Autocomplete","description":"Issues related to the autocomplete"},"hacktoberfest":{"color":"0052cc","name":"hacktoberfest","description":"All issues that can be solved by the community during Hacktoberfest"},"Medium effort":{"color":"D31156","name":"Medium effort","description":"Something that'll take more than a week but less than a month to build"},"Release":{"color":"57e5e0","name":"Release","description":""},"High":{"color":"c94d14","name":"High","description":"This issue blocks a user from building or impacts a lot of users"},"UI Performance":{"color":"1799b0","name":"UI Performance","description":"Issues related to UI performance"},"UI Builders Pod":{"color":"517fba","name":"UI Builders Pod","description":"Issues that UI Builders face using appsmith"},"Deploy Preview":{"color":"bfdadc","name":"Deploy Preview","description":"Issues found in Deploy Preview"},"Needs Tests":{"color":"8ee263","name":"Needs Tests","description":"Needs automated tests to assert a feature/bug fix"},"Refactor":{"color":"B96662","name":"Refactor","description":"needs refactoring of code"},"Divider Widget":{"color":"235708","name":"Divider Widget","description":"Issues related to the divider widget"},"Table Widget":{"color":"2eead1","name":"Table Widget","description":""},"Needs More Info":{"color":"e54c10","name":"Needs More Info","description":"Needs additional information"},"Good First Issue":{"color":"7057ff","name":"Good First Issue","description":"Good for newcomers"},"UI Improvement":{"color":"9aeef4","name":"UI Improvement","description":""},"Backend":{"color":"d4c5f9","name":"Backend","description":"This marks the issue or pull request to reference server code"},"Frontend":{"color":"87c7f2","name":"Frontend","description":"This label marks the issue or pull request to reference client code"},"In App Comms":{"name":"In App Comms","description":"Issues around communication with appsmith instances","color":"463cca"},"Chart Widget":{"color":"616ecc","name":"Chart Widget","description":""},"List Widget":{"color":"8508A0","name":"List Widget","description":"Issues related to the list widget"},"Duplicate":{"color":"cfd3d7","name":"Duplicate","description":"This issue or pull request already exists"},"JS Snippets":{"color":"8d62d2","name":"JS Snippets","description":"issues related to JS Snippets"},"Copy Paste":{"name":"Copy Paste","description":"Issues related to copy paste","color":"b4f0a9"},"Drag & Drop":{"name":"Drag & Drop","description":"Issues related to the drag & drop experience","color":"92115a"},"BE Coders Pod":{"color":"5d9848","name":"BE Coders Pod","description":"Issues related to users writing code to fetch and update data"},"FE Coders Pod":{"color":"a7effc","name":"FE Coders Pod","description":"Issues related to users writing javascript in appsmith"},"New Developers Pod":{"color":"6310da","name":"New Developers Pod","description":"Issues that new developers face while exploring the IDE"},"Sniping Mode":{"name":"Sniping Mode","description":"Issues related to sniping mode","color":"30c76d"},"Redis":{"name":"Redis","description":"Issues related to Redis","color":"8078b0"},"New Datasource":{"color":"60b14c","name":"New Datasource","description":"Requests for new datasources"},"Evaluated Value":{"name":"Evaluated Value","description":"Issues related to evaluated values","color":"39f6e7"},"Undo/Redo":{"name":"Undo/Redo","description":"Issues related to undo/redo","color":"f25880"},"App Navigation":{"name":"App Navigation","description":"Issues related to the topbar navigation and configuring it","color":"12b715"},"Responsive Viewport":{"color":"d12d2e","name":"Responsive Viewport","description":"Issues seen on different viewports like mobile"},"Widgets Pane":{"name":"Widgets Pane","description":"Issues related to the discovery and organisation of widgets","color":"ad5d78"},"View Mode":{"color":"1799b0","name":"View Mode","description":"Issues related to the view mode"},"User Education Pod":{"name":"User Education Pod","description":"Issues related to user education","color":"1799b0"},"Content":{"name":"Content","description":"For content related topics i.e blogs, templates, videos","color":"a8dff7"},"Embedding Apps":{"name":"Embedding Apps","description":"Issues related to embedding","color":"30c76d"},"Slash Command":{"name":"Slash Command","description":"Issues related to the slash command","color":"a0608e"},"Widget Property":{"name":"Widget Property","description":"Issues related to adding / modifying widget properties across widgets","color":"5e92cb"},"Windows":{"name":"Windows","description":"Issues related exclusively to Windows systems","color":"b4cb8a"},"Old App Issues":{"name":"Old App Issues","description":"Issues related to apps old apps a few weeks old and app issues in stale browser session","color":"87ab18"},"Document Viewer Widget":{"name":"Document Viewer Widget","description":"Issues related to Document Viewer Widget","color":"899d4b"},"Radio Group Widget":{"name":"Radio Group Widget","description":"Issues related to radio group widget","color":"b68495"},"Super Admin":{"name":"Super Admin","description":"Issues related to the super admin page","color":"aa95cf"},"Postgres":{"name":"Postgres","description":"Postgres related issues","color":"8078b0"},"REST API plugin":{"name":"REST API plugin","description":"REST API plugin related issues","color":"8078b0"},"New JS Function":{"name":"New JS Function","description":"Issues related to adding a JS Function","color":"8e8aa4"},"Cannot Reproduce Issue":{"color":"93c9cc","name":"Cannot Reproduce Issue","description":"Issues that cannot be reproduced"},"Widget Grouping":{"name":"Widget Grouping","description":"Issues related to Widget Grouping","color":"a49951"},"K8s":{"name":"K8s","description":"Kubernetes related issues","color":"5f318a"},"Docker":{"name":"Docker","description":"Issues related to docker","color":"89b808"},"Camera Widget":{"name":"Camera Widget","description":"Issues and enhancements related to camera widget","color":"e6038e"},"SAAS Plugins":{"name":"SAAS Plugins","description":"Issues related to SAAS Plugins","color":"ef9c9d"},"JS Promises":{"name":"JS Promises","description":"Issues related to promises","color":"d7771f"},"OnPageLoad":{"name":"OnPageLoad","description":"OnPageLoad issues on functions and queries","color":"50559d"},"JS Usability":{"name":"JS Usability","description":"usability issues with JS editor and JS elsewhere","color":"a302b0"},"Currency Input Widget":{"name":"Currency Input Widget","description":"Issues related to currency input widget","color":"b2164f"},"TreeSelect":{"name":"TreeSelect","description":"Issues related to TreeSelect Widget","color":"a1633e"},"MultiTree Select Widget":{"name":"MultiTree Select Widget","description":"Issues related to MultiTree Select Widget","color":"a1633e"},"Welcome Screen":{"name":"Welcome Screen","description":"Issues related to the welcome screen","color":"30c76d"},"Realtime Commenting":{"color":"a70b86","name":"Realtime Commenting","description":"In-app communication between teams"},"Phone Input Widget":{"name":"Phone Input Widget","description":"Issues related to the Phone Input widget","color":"a70b86"},"JSON Form":{"name":"JSON Form","description":"Issue / features related to the JSON form wiget","color":"46b209"},"All Widgets":{"name":"All Widgets","description":"Issues related to all widgets","color":"972b36"},"V1":{"name":"V1","description":"V1","color":"67ab2e"},"Reflow & Resize":{"name":"Reflow & Resize","description":"All issues related to reflow and resize experience","color":"748a13"},"App Theming":{"name":"App Theming","description":"Items that are related to the App level theming controls epic","color":"905420"},"SSO":{"name":"SSO","description":"Issues, requests and enhancements around Single sign-on.","color":"bf019b"},"Multi User Realtime":{"name":"Multi User Realtime","description":"Issues related to multiple users using or editing an application","color":"e7b6ce"},"Templates":{"name":"Templates","description":"Issues related to templates","color":"b7e568"},"Ready for design":{"name":"Ready for design","description":"this issue is ready for design: it contains clear problem statements and other required information","color":"ebf442"},"Support":{"name":"Support","description":"Issues created by the A-force team to address user queries","color":"1740f3"},"Button Group widget":{"name":"Button Group widget","description":"Issue and enhancements related to the button group widget","color":"f17025"},"GraphQL Plugin":{"name":"GraphQL Plugin","description":"Issues related to GraphQL plugin","color":"8078b0"},"DevOps Pod":{"name":"DevOps Pod","description":"Issues related to devops","color":"d956c7"},"medium":{"name":"medium","description":"Issues that frustrate users due to poor UX","color":"23dfd9"},"ArangoDB":{"name":"ArangoDB","description":"Issues related to arangoDB","color":"8078b0"},"Code Refactoring":{"name":"Code Refactoring","description":"Issues related to code refactoring","color":"76310e"},"Progress bar widget":{"name":"Progress bar widget","description":"To track issues related to progress bar","color":"2d7abf"},"Audio Recorder Widget":{"name":"Audio Recorder Widget","description":"Issues related to Audio Recorder Widget","color":"9accef"},"Airtable":{"name":"Airtable","description":"Issues for Airtable","color":"60885f"},"RBAC":{"name":"RBAC","description":"Issues, requests and enhancements around RBAC.","color":"9211c3"},"Canvas / Grid":{"name":"Canvas / Grid","description":"Issues related to the canvas","color":"16b092"},"Email Config":{"name":"Email Config","description":"Issues related to configuring the email service","color":"2a21d1"},"CURL":{"name":"CURL","description":"Issues related to CURL impor","color":"60885f"},"Canvas Zooms":{"name":"Canvas Zooms","description":"Issues related to zooming the canvas","color":"e6038e"},"business":{"name":"business","description":"Features that will be a part of our business edition","color":"cd59eb"},"Action Pod":{"name":"Action Pod","description":"","color":"ee2e36"},"AutomationGap1":{"color":"a5e07c","name":"AutomationGap1","description":"Issues that needs automated tests"},"A-Force11":{"name":"A-Force11","description":"Issues raised by A-Force team","color":"d667b6"},"Business Edition":{"name":"Business Edition","description":"Features that will be a part of our business edition","color":"89bb6c"},"storeValue":{"name":"storeValue","description":"Issues related to the store value function","color":"5d3e66"},"Tests":{"name":"Tests","description":"test item","color":"1c6990"},"DynamoDB":{"name":"DynamoDB","description":"Issues that are related to DynamoDB should have this label","color":"60885f"},"Design System Pod":{"name":"Design System Pod","description":"Appsmith design system related issues","color":"706f03"},"ABAC":{"color":"e009a5","name":"ABAC","description":"User permissions and access controls"},"Backup & Restore":{"name":"Backup & Restore","description":"Issues related to backup and restore","color":"86874d"},"Billing":{"name":"Billing","description":"Billing infrastructure and flows for Business Edition and Trial users","color":"d2bc40"},"Datatype issue":{"name":"Datatype issue","description":"Issues that have risen because data types weren't handled","color":"60885f"},"OAuth":{"name":"OAuth","description":"OAuth related bugs or features","color":"60885f"},"Table Widget V2":{"name":"Table Widget V2","description":"Issues related to Table Widget V2","color":"3a7192"},"IDE Navigation":{"name":"IDE Navigation","description":"Issues/feature requests related to IDE navigation, and context switching","color":"bc0cba"},"Query performance":{"name":"Query performance","description":"Issues that have to do with lack in performance of query execution","color":"e4d966"},"SAAS Manager App":{"name":"SAAS Manager App","description":"Issues with the SAAS manager app","color":"d427db"},"Twilio":{"name":"Twilio","description":"Issues related to Twilio integration","color":"23ba8d"},"Hubspot":{"name":"Hubspot","description":"Issues related to Hubspot integration","color":"60885f"},"Zendesk":{"name":"Zendesk","description":"Issues related to Zendesk integration","color":"60885f"},"Entity Refactor":{"name":"Entity Refactor","description":"Issues related to refactor logic","color":"418fa4"},"Branding":{"name":"Branding","description":"All issues under branding and whitelabelling appsmith ecosystem","color":"7aaaf1"},"Map Chart Widget":{"name":"Map Chart Widget","description":"Issues related to Map Chart Widgets","color":"c8397f"},"Product Catchup":{"name":"Product Catchup","description":"Issues created in the product catchup","color":"29cd2c"},"Framework Functions":{"name":"Framework Functions","description":"Issues related to internal functions like showAlert(), navigateTo() etc...","color":"c25a09"},"Frontend Libraries Upgrade":{"name":"Frontend Libraries Upgrade","description":"Issues related to frontend libraries upgrade","color":"ede1fc"},"Audit Logs":{"name":"Audit Logs","description":"Audit trails to ensure data security","color":"f3fd62"},"MsSQL":{"name":"MsSQL","description":"Issues related to MsSQL plugin","color":"8078b0"},"Data Platform Pod":{"name":"Data Platform Pod","description":"Issues related to the underlying data platform","color":"3f8c3a"},"Integrations Pod":{"name":"Integrations Pod","description":"Issues related to a specific integration","color":"5dbbb1"},"Datasource Environments":{"name":"Datasource Environments","description":"Issues related to datasource environments","color":"bb7a14"},"Elastic Search":{"name":"Elastic Search","description":"Issues related to the elastic search datasource","color":"8078b0"},"Core Query Execution":{"color":"418fa4","name":"Core Query Execution","description":"Issues related to the execution of all queries"},"Query Management":{"name":"Query Management","description":"Issues related to the CRUD of actions or queries","color":"6a5b42"},"Query Settings":{"name":"Query Settings","description":"Issues related to the settings of all queries","color":"c7da7a"},"Code Editor":{"name":"Code Editor","description":"Issues related to the code editor","color":"4ca16e"},"Query Forms":{"color":"12b253","name":"Query Forms","description":"Isuses related to the query forms"},"JS Objects":{"color":"22962c","name":"JS Objects","description":"Issues related to JS Objects"},"JS Evaluation":{"color":"22962c","name":"JS Evaluation","description":"Issues related to JS evaluation on the platform"},"SmartSubstitution":{"name":"SmartSubstitution","description":"Issues related to Smart substitution of mustache bindings in queries","color":"e4d966"},"Query Generation":{"name":"Query Generation","description":"Issues related to query generation","color":"e4d966"},"Suggested Widgets":{"name":"Suggested Widgets","description":"Issues related to suggesting widgets based on query response","color":"e4d966"},"Page load executions":{"name":"Page load executions","description":"Issues related to page load execution","color":"5696b2"},"Code Scanner Widget":{"name":"Code Scanner Widget","description":"Issues related to code scanner widget","color":"9bc1a0"},"Clean URLs":{"name":"Clean URLs","description":"Issues related to clean URLs epic","color":"112623"},"Widget keyboard accessibility":{"name":"Widget keyboard accessibility","description":"All issues related to keyboard accessibility in widgets","color":"b626fd"},"Connection pool":{"name":"Connection pool","description":"issues to do with connection pooling of various plugins","color":"94fe36"},"List Widget V2":{"name":"List Widget V2","description":"Issues related to the list widget v2","color":"adaaf7"},"Auto Height":{"name":"Auto Height","description":"Issues related to dynamic height of widgets","color":"5149cf"},"cypress_failed_test":{"name":"cypress_failed_test","description":"Cypress failed tests","color":"4745d5"},"Needs validation":{"name":"Needs validation","description":"Needs problem validation before being picked up","color":"66673d"},"Slider Widget":{"name":"Slider Widget","description":"Issues raised for slider widgets.","color":"2eef5f"},"Multitenancy":{"name":"Multitenancy","description":"Support multitenancy within single appsmith instance","color":"8c49a9"},"Git Pod":{"name":"Git Pod","description":"Anything related to git sync","color":"2e5ba4"},"Mobile Pod":{"name":"Mobile Pod","description":"All issues related to mobile responsiveness","color":"6c97fd"},"Responsive Widget":{"name":"Responsive Widget","description":"All issues related to widget responsiveness","color":"d12d2e"},"Responsive Canvas":{"name":"Responsive Canvas","description":"All issues related to canvas responsiveness","color":"45a0a8"},"Conversion Algorithm":{"name":"Conversion Algorithm","description":"All issue related to converting app from fixed to flex mode & vice versa","color":"d12d2e"},"Spacing":{"name":"Spacing","description":"All issue related to spacing between widgets in auto layout","color":"d12d2e"},"Browser specific":{"name":"Browser specific","description":"All issue related to browser","color":"d12d2e"},"Error Handling":{"name":"Error Handling","description":"Issues related to error handling","color":"4e1872"},"Performance infra":{"name":"Performance infra","description":"all issue related to the performance infra","color":"8a60f6"},"DSL Update":{"name":"DSL Update","description":"Issues related to storing and updating the DSL","color":"e16cf3"},"AST-frontend":{"name":"AST-frontend","description":"Issues related to maintaining AST logic","color":"434a3a"},"AST-backend":{"name":"AST-backend","description":"Backend issues related to AST parsing","color":"c476eb"},"MariaDB":{"name":"MariaDB","description":"MariaDB datasource","color":"8428c3"},"Billing & Usage Pod":{"name":"Billing & Usage Pod","description":"Issues pertaining to licensing, billing, usage across self serve and enterprise customers","color":"256808"},"ADS Component Issue":{"name":"ADS Component Issue","description":"Issues which are caused due to ADS components","color":"d89119"},"Regressed":{"color":"723fd0","name":"Regressed","description":"Scenarios that were working before but have now regressed"},"Needs RCA":{"name":"Needs RCA","description":"a critical or high priority issue that needs an RCA","color":"2cc68f"},"Custom JS Libraries":{"name":"Custom JS Libraries","description":"Issues related to adding custom JS library","color":"bacb6d"},"Integrations Pod General":{"name":"Integrations Pod General","description":"Issues related to the Integrations Pod that don't fit into other tags.","color":"287823"},"Performance Pod":{"name":"Performance Pod","description":"All things related to Appsmith performance","color":"b5a25d"},"Performance":{"name":"Performance","description":"Issues related to performance","color":"9a18d7"},"File upload issues":{"name":"File upload issues","description":"Issues related to uploading any type of files from within Appsmith","color":"8154df"},"Action Selector":{"name":"Action Selector","description":"Issues related to action selector on the property pane","color":"2f9e20"},"Widget design system":{"name":"Widget design system","description":"","color":"cb6188"},"Deploy App":{"name":"Deploy App","description":"Issues related to app deployment","color":"6f6152"},"Community Reported":{"name":"Community Reported","description":"issues reported by community members","color":"1402e5"},"JS Function execution":{"name":"JS Function execution","description":"JS function execution","color":"7c2de1"},"Self Serve":{"name":"Self Serve","description":"For all issues related to self-serve flow for business edition","color":"4dacfc"},"Self Serve 1.0":{"name":"Self Serve 1.0","description":"For all issues related to v1 of the self serve project","color":"ae839e"},"CE Instance":{"name":"CE Instance","description":"For all issues relating to usage, licensing or billing on the CE instance","color":"d2bc40"},"Customer Portal":{"name":"Customer Portal","description":"For all tasks/issues pertaining to customer.appsmith.com","color":"d2bc40"},"Cloud Services":{"name":"Cloud Services","description":"For all tasks/issues on Appsmith cloud-services relating to licensing, usage and billing","color":"d2bc40"},"Billing Integrations":{"name":"Billing Integrations","description":"For all issues relating to 3P integrations Appsmith is using for billing & usage","color":"d2bc40"},"One-click Binding":{"name":"One-click Binding","description":"Issues related to the One click binding epic","color":"f1661c"},"Airgap":{"name":"Airgap","description":"Tickets related to supporting air-gapped Appsmith instances","color":"1cb294"},"SMTP plugin":{"name":"SMTP plugin","description":"Issues related to SMTP plugin","color":"541457"},"AWS AMI":{"name":"AWS AMI","description":"Issues Related to AWS AMI","color":"b44680"},"Old widget version":{"name":"Old widget version","description":"Use this label to raise issue specific only to an older version of a widget","color":"ff3814"},"Enterprise Billing":{"name":"Enterprise Billing","description":"To track all tasks/issues related to licensing & billing for enterprise customers","color":"14c156"},"Appsmith Business Cloud":{"name":"Appsmith Business Cloud","description":"Issues related to our business cloud offering","color":"89bb6c"},"Oracle SQL DB":{"name":"Oracle SQL DB","description":"Issues related to the Oracle plugin","color":"cbabcb"},"Community Contributor":{"name":"Community Contributor","description":"Meant to track issues that are assigned to external contributors","color":"149ab6"},"widget vertical alignment":{"name":"widget vertical alignment","description":"All issue related widget vertical alignment on the auto layout canvas","color":"d12d2e"},"Observability":{"name":"Observability","description":"Issues related to observability on the Appsmith instance","color":"dff913"},"Checkbox Component":{"name":"Checkbox Component","description":"This labels deals with checkbox component in wds package","color":"75a401"},"In-app ramps":{"name":"In-app ramps","description":"For all tasks/issues relating to adding in-app ramps in the community edition of the product","color":"8abae0"},"Analytics Improvements":{"name":"Analytics Improvements","description":"For all tasks focused on improving our overall analytics and fixing any issues ","color":"29b8ed"},"WDS team":{"name":"WDS team","description":"","color":"8d675a"},"Enterprise Edition":{"name":"Enterprise Edition","description":"Features that will be supported in Enterprise Edition only","color":"984f5e"},"Query filter":{"name":"Query filter","description":"Issues related to query filtering, e.g., WHERE clause","color":"a15134"},"Keyboard accessibility ":{"name":"Keyboard accessibility ","description":"All issue related to ADS component keyboard accessibility","color":"2ba696"},"Toggle button":{"name":"Toggle button","description":"All issue related to ADS toggle button","color":"edc47f"},"Feature Flagging":{"name":"Feature Flagging","description":"Anything related feature flagging","color":"8d8a09"},"SCIM":{"name":"SCIM","description":"Label to collate our SCIM issues","color":"61a852"},"ADS Category Token":{"name":"ADS Category Token","description":"All issues related appsmith design system category tokens","color":"920961"},"ADS Component Documentation":{"name":"ADS Component Documentation","description":"All issues Appsmith design system component documentation","color":"64c46a"},"ADS Migration":{"name":"ADS Migration","description":"All issues related to Appsmith design system migration","color":"b082d6"},"ADS Deduplication ":{"name":"ADS Deduplication ","description":"Replacing component with ADS components","color":"b082d6"},"ADS Revamp":{"name":"ADS Revamp","description":"All issues related to ads revamp. ","color":"b082d6"},"ADS Deduplication":{"name":"ADS Deduplication","description":"Replacing component with ADS components","color":"b082d6"},"ADS Grayscale":{"name":"ADS Grayscale","description":"Support grayscale color changes","color":"b03577"},"ADS Unit Test":{"name":"ADS Unit Test","description":"All issue related ads unit cases ","color":"b082d6"},"ADS Components":{"name":"ADS Components","description":"All issues related ADS components","color":"b082d6"},"Widget Discoverability":{"name":"Widget Discoverability","description":"Issues related to Widget Discoverability","color":"7b55ce"},"Widget setter method":{"name":"Widget setter method","description":"Issues with widget property setters","color":"8dce87"},"License":{"name":"License","description":"For all issues/tasks related to licensing of appsmith-ee edition","color":"90ee98"},"Templates pod":{"name":"Templates pod","description":"Issues related to Templates","color":"b7e568"},"Community template":{"name":"Community template","description":"Label for development of community templates and its integration to platform","color":"8a0510"},"DocumentDB":{"name":"DocumentDB","description":"Issues related to support DocumentDB in Appsmith Data layer","color":"2c8b56"},"Multiple Environments":{"name":"Multiple Environments","description":"Issues or tasks related to multiple environments","color":"4e972b"},"Platformization":{"name":"Platformization","description":"Issues or tasks related to platformization of Appsmith codebase","color":"4e972b"},"Activation - datasources":{"name":"Activation - datasources","description":"issues related to activation projects","color":"7c7ace"},"Partial-import-export":{"name":"Partial-import-export","description":"Label for granular reusability.","color":"1e439c"},"AI":{"name":"AI","description":"All tasks related to AI","color":"75c4ce"},"Custom environments":{"name":"Custom environments","description":"Issues with creating or working with custom environments","color":"2137d6"},"ADS Typography":{"name":"ADS Typography","description":"All issue related typographical changes","color":"2dbe8d"},"Auto Layout":{"name":"Auto Layout","description":"Issues relates to auto layout","color":"92cf8c"},"Heroku":{"name":"Heroku","description":"Issues related to Heroku","color":"a81b69"},"ADS Visual Styles":{"name":"ADS Visual Styles","description":"All issues related to ADS visual styles","color":"d3da89"},"ADS Component Design":{"name":"ADS Component Design","description":"All issue related to component design","color":"5cc91e"},"Modal Component":{"name":"Modal Component","description":"All issue related to ads modal component","color":"ee63f3"},"App setting":{"name":"App setting","description":"Related to app settings panel within the app","color":"144206"},"BE instance":{"name":"BE instance","description":"For all issues related to license, billing on BE instance","color":"ae8f98"},"Schema":{"name":"Schema","description":"Issues related to database schema","color":"c470c2"},"Fixed layout":{"name":"Fixed layout","description":"issues related to fixed layout","color":"b66681"},"Anvil layout":{"name":"Anvil layout","description":"issues related to the new layout system anvil","color":"722bf0"},"New Deployment Mode":{"name":"New Deployment Mode","description":"Support a new mode of deployment","color":"108033"},"Custom widgets":{"name":"Custom widgets","description":"For all issues related to the custom widget project","color":"c9db9c"},"IDE Pod":{"name":"IDE Pod","description":"https://app.zenhub.com/workspaces/new-developers-pod-60507ad1d4b98d00150a2858/board","color":"d3d248"},"TM_BU":{"name":"TM_BU","description":"The issues on Team Manager which needs to be taken up by Billing & Usage","color":"198cdf"},"Homepage Experience V2":{"name":"Homepage Experience V2","description":"Label for reporting new tasks and bug fixes related to revamped homepage experience","color":"c55d54"},"Appsmith Labs":{"name":"Appsmith Labs","description":"All things related to AI and other new initiatives ","color":"712d51"},"Customer Success":{"name":"Customer Success","description":"Issues that the success team cares about","color":"6ccabd"},"Invite flow":{"name":"Invite flow","description":"Invite users flow and any associated actions","color":"881b35"},"Invite users":{"name":"Invite users","description":"Invite users flow and any associated actions","color":"23e6d6"},"Workflows Pod":{"name":"Workflows Pod","description":"For all issues related to the Workflows feature","color":"2c1f93"},"DailyPromotionBlocker":{"name":"DailyPromotionBlocker","description":"Blocker for daily promotion","color":"9b2280"}},"success":true} \ No newline at end of file +{"runners":[{"versioning":{"source":"milestones","type":"SemVer"},"prereleaseName":"alpha","issue":{"labels":{"Error Handling":{"conditions":[],"requires":1},"Templates pod":{"conditions":[{"label":"Templates","type":"hasLabel","value":true},{"label":"Community template","type":"hasLabel","value":true},{"label":"Partial-import-export","type":"hasLabel","value":true}],"requires":1},"Team Managers Pod":{"conditions":[{"label":"Settings","type":"hasLabel","value":true},{"label":"Home Page","type":"hasLabel","value":true},{"label":"Realtime Commenting","type":"hasLabel","value":true},{"label":"SSO","type":"hasLabel","value":true},{"label":"Multi User Realtime","type":"hasLabel","value":true},{"label":"RBAC","type":"hasLabel","value":true},{"label":"ABAC","type":"hasLabel","value":true},{"label":"Audit Logs","type":"hasLabel","value":true},{"label":"Multitenancy","type":"hasLabel","value":true},{"label":"Airgap","type":"hasLabel","value":true},{"label":"Enterprise Edition","type":"hasLabel","value":true},{"label":"SCIM","type":"hasLabel","value":true},{"label":"Invite flow","type":"hasLabel","value":true}],"requires":1},"New Developers Pod":{"conditions":[{"label":"Omnibar","type":"hasLabel","value":true},{"label":"Telemetry","type":"hasLabel","value":true},{"label":"Entity Explorer","type":"hasLabel","value":true},{"label":"IDE","type":"hasLabel","value":true},{"label":"Example Apps","type":"hasLabel","value":true},{"label":"i18n","type":"hasLabel","value":true},{"label":"IDE Navigation","type":"hasLabel","value":true},{"label":"Clean URLs","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"App setting","type":"hasLabel","value":true}],"requires":1},"BE Coders Pod":{"conditions":[{"label":"SAAS Plugins","type":"hasLabel","value":true},{"label":"SAAS Manager App","type":"hasLabel","value":true},{"label":"Data Platform Pod","type":"hasLabel","value":true},{"label":"Integrations Pod","type":"hasLabel","value":true}],"requires":1},"FE Coders Pod":{"conditions":[{"label":"JS Linting & Errors","type":"hasLabel","value":true},{"label":"Debugger","type":"hasLabel","value":true},{"label":"JS Snippets","type":"hasLabel","value":true},{"label":"Autocomplete","type":"hasLabel","value":true},{"label":"Evaluated Value","type":"hasLabel","value":true},{"label":"Slash Command","type":"hasLabel","value":true},{"label":"New JS Function","type":"hasLabel","value":true},{"label":"JS Promises","type":"hasLabel","value":true},{"label":"JS Usability","type":"hasLabel","value":true},{"label":"Code Refactoring","type":"hasLabel","value":true},{"label":"storeValue","type":"hasLabel","value":true},{"label":"OnPageLoad","type":"hasLabel","value":true},{"label":"Framework Functions","type":"hasLabel","value":true},{"label":"Code Editor","type":"hasLabel","value":true},{"label":"JS Objects","type":"hasLabel","value":true},{"label":"JS Evaluation","type":"hasLabel","value":true},{"label":"AST-frontend","type":"hasLabel","value":true},{"label":"Custom JS Libraries","type":"hasLabel","value":true},{"label":"Action Selector","type":"hasLabel","value":true},{"label":"JS Function execution","type":"hasLabel","value":true},{"label":"Widget setter method","type":"hasLabel","value":true},{"label":"Error Handling","type":"hasLabel","value":true}],"requires":1},"App Viewers Pod":{"conditions":[{"label":"Button Widget","type":"hasLabel","value":true},{"label":"Chart Widget","type":"hasLabel","value":true},{"label":"Container Widget","type":"hasLabel","value":true},{"label":"Date Picker Widget","type":"hasLabel","value":true},{"label":"Select Widget","type":"hasLabel","value":true},{"label":"File Picker Widget","type":"hasLabel","value":true},{"label":"Form Widget","type":"hasLabel","value":true},{"label":"Image Widget","type":"hasLabel","value":true},{"label":"Input Widget","type":"hasLabel","value":true},{"label":"List Widget","type":"hasLabel","value":true},{"label":"MultiSelect Widget","type":"hasLabel","value":true},{"label":"Map Widget","type":"hasLabel","value":true},{"label":"Modal Widget","type":"hasLabel","value":true},{"label":"Radio Widget","type":"hasLabel","value":true},{"label":"Rich Text Editor Widget","type":"hasLabel","value":true},{"label":"Tab Widget","type":"hasLabel","value":true},{"label":"Table Widget","type":"hasLabel","value":true},{"label":"Text Widget","type":"hasLabel","value":true},{"label":"Video Widget","type":"hasLabel","value":true},{"label":"iFrame","type":"hasLabel","value":true},{"label":"Menu Button","type":"hasLabel","value":true},{"label":"Rating","type":"hasLabel","value":true},{"label":"Widget Validation","type":"hasLabel","value":true},{"label":"reallabel","type":"hasLabel","value":true},{"label":"New Widget","type":"hasLabel","value":true},{"label":"Switch widget","type":"hasLabel","value":true},{"label":"Audio Widget","type":"hasLabel","value":true},{"label":"Icon Button Widget","type":"hasLabel","value":true},{"label":"Stat Box Widget","type":"hasLabel","value":true},{"label":"Voice Recorder Widget","type":"hasLabel","value":true},{"label":"Calendar Widget","type":"hasLabel","value":true},{"label":"Menu Button Widget","type":"hasLabel","value":true},{"label":"Divider Widget","type":"hasLabel","value":true},{"label":"Rating Widget","type":"hasLabel","value":true},{"label":"App Navigation","type":"hasLabel","value":true},{"label":"View Mode","type":"hasLabel","value":true},{"label":"Widget Property","type":"hasLabel","value":true},{"label":"Document Viewer Widget","type":"hasLabel","value":true},{"label":"Radio Group Widget","type":"hasLabel","value":true},{"label":"Currency Input Widget","type":"hasLabel","value":true},{"label":"TreeSelect","type":"hasLabel","value":true},{"label":"MultiTree Select Widget","type":"hasLabel","value":true},{"label":"Phone Input Widget","type":"hasLabel","value":true},{"label":"JSON Form","type":"hasLabel","value":true},{"label":"All Widgets","type":"hasLabel","value":true},{"label":"Button Group widget","type":"hasLabel","value":true},{"label":"Progress bar widget","type":"hasLabel","value":true},{"label":"Audio Recorder Widget","type":"hasLabel","value":true},{"label":"Camera Widget","type":"hasLabel","value":true},{"label":"Table Widget V2","type":"hasLabel","value":true},{"label":"Branding","type":"hasLabel","value":true},{"label":"Map Chart Widget","type":"hasLabel","value":true},{"label":"Code Scanner Widget","type":"hasLabel","value":true},{"label":"Widget keyboard accessibility","type":"hasLabel","value":true},{"label":"List Widget V2","type":"hasLabel","value":true},{"label":"Slider Widget","type":"hasLabel","value":true},{"label":"One-click Binding","type":"hasLabel","value":true},{"label":"Old widget version","type":"hasLabel","value":true},{"label":"Widget Discoverability","type":"hasLabel","value":true},{"label":"Custom widgets","type":"hasLabel","value":true}],"requires":1},"UI Builders Pod":{"conditions":[{"label":"Property Pane","type":"hasLabel","value":true},{"label":"Pages","type":"hasLabel","value":true},{"label":"Copy Paste","type":"hasLabel","value":true},{"label":"Drag & Drop","type":"hasLabel","value":true},{"label":"Undo/Redo","type":"hasLabel","value":true},{"label":"Widgets Pane","type":"hasLabel","value":true},{"label":"UI Performance","type":"hasLabel","value":true},{"label":"Widget Grouping","type":"hasLabel","value":true},{"label":"Reflow & Resize","type":"hasLabel","value":true},{"label":"Canvas / Grid","type":"hasLabel","value":true},{"label":"Canvas Zooms","type":"hasLabel","value":true},{"label":"Frontend Libraries Upgrade","type":"hasLabel","value":true},{"label":"Auto Height","type":"hasLabel","value":true},{"label":"Responsive Canvas","type":"hasLabel","value":true},{"label":"Responsive Widget","type":"hasLabel","value":true},{"label":"Responsive Viewport","type":"hasLabel","value":true},{"label":"Conversion Algorithm","type":"hasLabel","value":true},{"label":"Spacing","type":"hasLabel","value":true},{"label":"Browser specific","type":"hasLabel","value":true},{"label":"widget vertical alignment","type":"hasLabel","value":true},{"label":"Auto Layout","type":"hasLabel","value":true},{"label":"Fixed layout","type":"hasLabel","value":true},{"label":"Anvil layout","type":"hasLabel","value":true}],"requires":1},"User Education Pod":{"conditions":[{"label":"Content","type":"hasLabel","value":true},{"label":"Documentation","type":"hasLabel","value":true}],"requires":1},"DevOps Pod":{"conditions":[{"label":"Docker","type":"hasLabel","value":true},{"label":"Super Admin","type":"hasLabel","value":true},{"label":"Deployment","type":"hasLabel","value":true},{"label":"K8s","type":"hasLabel","value":true},{"label":"Email Config","type":"hasLabel","value":true},{"label":"Backup & Restore","type":"hasLabel","value":true},{"label":"AWS AMI","type":"hasLabel","value":true},{"label":"Observability","type":"hasLabel","value":true},{"label":"Heroku","type":"hasLabel","value":true},{"label":"New Deployment Mode","type":"hasLabel","value":true}],"requires":1},"Design System Pod":{"conditions":[{"label":"Design System Pod","type":"hasLabel","value":true},{"label":"ADS Component Issue","type":"hasLabel","value":true},{"label":"Keyboard accessibility ","type":"hasLabel","value":true},{"label":"Toggle button","type":"hasLabel","value":true},{"label":"ADS Category Token","type":"hasLabel","value":true},{"label":"ADS Component Documentation","type":"hasLabel","value":true},{"label":"ADS Migration","type":"hasLabel","value":true},{"label":"ADS Deduplication ","type":"hasLabel","value":true},{"label":"ADS Revamp","type":"hasLabel","value":true},{"label":"ADS Deduplication","type":"hasLabel","value":true},{"label":"ADS Unit Test","type":"hasLabel","value":true},{"label":"ADS Components","type":"hasLabel","value":true},{"label":"ADS Grayscale","type":"hasLabel","value":true},{"label":"Design System","type":"hasLabel","value":true},{"label":"ADS Typography","type":"hasLabel","value":true},{"label":"ADS Visual Styles","type":"hasLabel","value":true},{"label":"ADS Component Design","type":"hasLabel","value":true},{"label":"Modal Component","type":"hasLabel","value":true}],"requires":1},"Data Platform Pod":{"conditions":[{"label":"Datasource Environments","type":"hasLabel","value":true},{"label":"Datatype issue","type":"hasLabel","value":true},{"label":"Entity Refactor","type":"hasLabel","value":true},{"label":"Core Query Execution","type":"hasLabel","value":true},{"label":"Query Management","type":"hasLabel","value":true},{"label":"Query Settings","type":"hasLabel","value":true},{"label":"SmartSubstitution","type":"hasLabel","value":true},{"label":"Query Generation","type":"hasLabel","value":true},{"label":"Query performance","type":"hasLabel","value":true},{"label":"Suggested Widgets","type":"hasLabel","value":true},{"label":"Page load executions","type":"hasLabel","value":true},{"label":"DSL Update","type":"hasLabel","value":true},{"label":"AST-backend","type":"hasLabel","value":true},{"label":"Deploy App","type":"hasLabel","value":true},{"label":"File upload issues","type":"hasLabel","value":true},{"label":"Datasources","type":"hasLabel","value":true},{"label":"DocumentDB","type":"hasLabel","value":true},{"label":"Multiple Environments","type":"hasLabel","value":true},{"label":"Platformization","type":"hasLabel","value":true},{"label":"Custom environments","type":"hasLabel","value":true},{"label":"Schema","type":"hasLabel","value":true}],"requires":1},"Integrations Pod":{"conditions":[{"label":"New Datasource","type":"hasLabel","value":true},{"label":"Firestore","type":"hasLabel","value":true},{"label":"Google Sheets","type":"hasLabel","value":true},{"label":"Mongo","type":"hasLabel","value":true},{"label":"Redshift","type":"hasLabel","value":true},{"label":"snowflake","type":"hasLabel","value":true},{"label":"S3","type":"hasLabel","value":true},{"label":"Redis","type":"hasLabel","value":true},{"label":"Postgres","type":"hasLabel","value":true},{"label":"GraphQL Plugin","type":"hasLabel","value":true},{"label":"ArangoDB","type":"hasLabel","value":true},{"label":"MsSQL","type":"hasLabel","value":true},{"label":"REST API plugin","type":"hasLabel","value":true},{"label":"Elastic Search","type":"hasLabel","value":true},{"label":"OAuth","type":"hasLabel","value":true},{"label":"Airtable","type":"hasLabel","value":true},{"label":"CURL","type":"hasLabel","value":true},{"label":"DynamoDB","type":"hasLabel","value":true},{"label":"Zendesk","type":"hasLabel","value":true},{"label":"Hubspot","type":"hasLabel","value":true},{"label":"Query Forms","type":"hasLabel","value":true},{"label":"Twilio","type":"hasLabel","value":true},{"label":"MySQL","type":"hasLabel","value":true},{"label":"Connection pool","type":"hasLabel","value":true},{"label":"MariaDB","type":"hasLabel","value":true},{"label":"Integrations Pod General","type":"hasLabel","value":true},{"label":"SMTP plugin","type":"hasLabel","value":true},{"label":"Oracle SQL DB","type":"hasLabel","value":true},{"label":"Query filter","type":"hasLabel","value":true},{"label":"Activation - datasources","type":"hasLabel","value":true},{"label":"Onboarding","type":"hasLabel","value":true},{"label":"Generate Page","type":"hasLabel","value":true},{"label":"Sniping Mode","type":"hasLabel","value":true},{"label":"Welcome Screen","type":"hasLabel","value":true},{"label":"Login / Signup","type":"hasLabel","value":true}],"requires":1},"Git Pod":{"conditions":[{"label":"Git Version Control","type":"hasLabel","value":true},{"label":"Import-Export-App","type":"hasLabel","value":true},{"label":"Fork App","type":"hasLabel","value":true}],"requires":1},"Mobile Pod":{"conditions":[],"requires":1},"Billing & Usage Pod":{"conditions":[{"label":"CE Instance","type":"hasLabel","value":true},{"label":"Customer Portal","type":"hasLabel","value":true},{"label":"Cloud Services","type":"hasLabel","value":true},{"label":"Billing Integrations","type":"hasLabel","value":true},{"label":"Billing","type":"hasLabel","value":true},{"label":"Self Serve","type":"hasLabel","value":true},{"label":"Enterprise Billing","type":"hasLabel","value":true},{"label":"In-app ramps","type":"hasLabel","value":true},{"label":"Analytics Improvements","type":"hasLabel","value":true},{"label":"Self Serve 1.0","type":"hasLabel","value":true},{"label":"License","type":"hasLabel","value":true},{"label":"Appsmith Business Cloud","type":"hasLabel","value":true},{"label":"BE instance","type":"hasLabel","value":true},{"label":"Embedding Apps","type":"hasLabel","value":true},{"label":"TM_BU","type":"hasLabel","value":true},{"label":"Homepage Experience V2","type":"hasLabel","value":true},{"label":"Feature Flagging","type":"hasLabel","value":true},{"label":"Invite flow","type":"hasLabel","value":true},{"label":"Invite users","type":"hasLabel","value":true}],"requires":1},"Performance Pod":{"conditions":[{"label":"Performance","type":"hasLabel","value":true},{"label":"Performance infra","type":"hasLabel","value":true}],"requires":1},"Widget design system":{"conditions":[{"label":"App Theming","type":"hasLabel","value":true},{"label":"Widget Styling","type":"hasLabel","value":true},{"label":"Checkbox Group widget","type":"hasLabel","value":true},{"label":"Checkbox Widget","type":"hasLabel","value":true},{"label":"Checkbox Component","type":"hasLabel","value":true},{"label":"WDS team","type":"hasLabel","value":true},{"label":"Widget design system","type":"hasLabel","value":true}],"requires":1},"IDE Pod":{"conditions":[],"requires":1},"Appsmith Labs":{"conditions":[{"label":"AI","type":"hasLabel","value":true}],"requires":1},"Workflows Pod":{"conditions":[],"requires":1}}},"root":"."}],"labels":{"Tab Widget":{"color":"e2c76c","name":"Tab Widget","description":""},"Dont merge":{"color":"ADB39C","name":"Dont merge","description":""},"Epic":{"color":"3E4B9E","name":"Epic","description":"A zenhub epic that describes a project"},"Menu Button Widget":{"color":"235708","name":"Menu Button Widget","description":"Issues related to Menu Button widget"},"Checkbox Group widget":{"color":"88054d","name":"Checkbox Group widget","description":"Issues related to Checkbox Group Widget"},"Input Widget":{"color":"ae65d8","name":"Input Widget","description":""},"Security":{"color":"99139C","name":"Security","description":""},"QA":{"color":"e2ca68","name":"QA","description":""},"Verified":{"color":"9bf416","name":"Verified","description":""},"Wont Fix":{"color":"ffffff","name":"Wont Fix","description":"This will not be worked on"},"MySQL":{"color":"c9ddc6","name":"MySQL","description":"Issues related to MySQL plugin"},"Development":{"color":"9F8A02","name":"Development","description":""},"Help Wanted":{"color":"008672","name":"Help Wanted","description":"Extra attention is needed"},"Home Page":{"color":"9c0c8e","name":"Home Page","description":"Issues related to the application home page"},"Rating Widget":{"color":"235708","name":"Rating Widget","description":"Issues related to the rating widget"},"Stat Box Widget":{"color":"f1c9ce","name":"Stat Box Widget","description":"Issues related to stat box"},"Enhancement":{"color":"a2eeef","name":"Enhancement","description":"New feature or request"},"Settings":{"color":"f7ff60","name":"Settings","description":"organization, team & user settings"},"Fork App":{"color":"30c76d","name":"Fork App","description":"Issues related to forking apps"},"Container Widget":{"color":"19AD0D","name":"Container Widget","description":"Container widget"},"Papercut":{"color":"B562F6","name":"Papercut","description":""},"Needs Design":{"color":"bfd4f2","name":"Needs Design","description":"needs design or changes to design"},"i18n":{"color":"1799b0","name":"i18n","description":"Represents issues that need to be tackled to handle internationalization"},"Rich Text Editor Widget":{"color":"f72cac","name":"Rich Text Editor Widget","description":""},"Onboarding":{"color":"30c76d","name":"Onboarding","description":"Issues related to onboarding new developers"},"Pages":{"color":"d7fd80","name":"Pages","description":"Issues related to configuring pages"},"skip-changelog":{"color":"06086F","name":"skip-changelog","description":"Adding this label to a PR prevents it from being listed in the changelog"},"Low":{"color":"79e53b","name":"Low","description":"An issue that is neither critical nor breaks a user flow"},"potential-duplicate":{"color":"d3cb2e","name":"potential-duplicate","description":"This label marks issues that are potential duplicates of already open issues"},"Audio Widget":{"color":"447B9A","name":"Audio Widget","description":"Issues related to Audio Widget"},"Firestore":{"color":"8078b0","name":"Firestore","description":"Issues related to the firestore Integration"},"New Widget":{"color":"be4cf2","name":"New Widget","description":"A request for a new widget"},"Modal Widget":{"color":"03846f","name":"Modal Widget","description":""},"UX Improvement":{"color":"f4a089","name":"UX Improvement","description":""},"S3":{"color":"8078b0","name":"S3","description":"Issues related to the S3 plugin"},"Release Blocker":{"color":"5756bf","name":"Release Blocker","description":"This issue must be resolved before the release"},"safari":{"color":"51C6AA","name":"safari","description":"Bugs seen on safari browser"},"Example Apps":{"color":"1799b0","name":"Example Apps","description":"Example apps created for new signups"},"MultiSelect Widget":{"color":"AB62D4","name":"MultiSelect Widget","description":"Issues related to MultiSelect Widget"},"Widget Styling":{"color":"905420","name":"Widget Styling","description":"all about widget styling"},"Calendar Widget":{"color":"8c6644","name":"Calendar Widget","description":""},"Website":{"color":"151720","name":"Website","description":"Related to www.appsmith.com website"},"Low effort":{"color":"8B59F0","name":"Low effort","description":"Something that'll take a few days to build"},"App Viewers Pod":{"color":"cd8ef9","name":"App Viewers Pod","description":"This label assigns issues to the app viewers pod"},"Checkbox Widget":{"color":"88054d","name":"Checkbox Widget","description":""},"Spam":{"color":"620faf","name":"Spam","description":""},"Voice Recorder Widget":{"color":"85bc87","name":"Voice Recorder Widget","description":""},"Select Widget":{"color":"0c669e","name":"Select Widget","description":"Select or dropdown widget"},"Bug":{"color":"d73a4a","name":"Bug","description":"Something isn't working"},"Widget Validation":{"color":"6990BC","name":"Widget Validation","description":"Issues related to widget property validation"},"Generate Page":{"color":"30c76d","name":"Generate Page","description":"Issures related to page generation"},"File Picker Widget":{"color":"6ae4f2","name":"File Picker Widget","description":""},"snowflake":{"color":"8078b0","name":"snowflake","description":"Issues related to the snowflake Integration"},"Automation":{"color":"CCAF60","name":"Automation","description":""},"hotfix":{"color":"BA3F1D","name":"hotfix","description":""},"Team Managers Pod":{"color":"bddb81","name":"Team Managers Pod","description":"Issues that team managers care about for the security and efficiency of their teams"},"Import-Export-App":{"color":"15076d","name":"Import-Export-App","description":"Issues related to importing and exporting apps"},"High effort":{"color":"A7E87B","name":"High effort","description":"Something that'll take more than a month to build"},"Telemetry":{"color":"bc70f9","name":"Telemetry","description":"Issues related to instrumenting appsmith"},"Radio Widget":{"color":"91ef15","name":"Radio Widget","description":""},"Omnibar":{"color":"10b5ce","name":"Omnibar","description":"Issues related to the omnibar for navigation"},"Button Widget":{"color":"34efae","name":"Button Widget","description":""},"Switch widget":{"color":"33A8CE","name":"Switch widget","description":"The switch widget"},"Map Widget":{"color":"7eef7a","name":"Map Widget","description":""},"Task":{"color":"085630","name":"Task","description":"A simple Todo"},"Design System":{"color":"2958a4","name":"Design System","description":"Design system"},"opera":{"color":"C63F5B","name":"opera","description":"Any issues identified on the opera browser"},"Login / Signup":{"color":"30c76d","name":"Login / Signup","description":"Authentication flows"},"Image Widget":{"color":"8de8ad","name":"Image Widget","description":""},"firefox":{"color":"6d56e2","name":"firefox","description":""},"Property Pane":{"color":"b356ff","name":"Property Pane","description":"Issues related to the behaviour of the property pane"},"Deployment":{"color":"93491f","name":"Deployment","description":"Installation process of appsmith"},"Critical":{"color":"9b1b28","name":"Critical","description":"This issue needs immediate attention. Drop everything else"},"IDE":{"color":"61b2ee","name":"IDE","description":"Issues related to the IDE"},"Production":{"color":"b60205","name":"Production","description":""},"Dependencies":{"color":"0366d6","name":"Dependencies","description":"Pull requests that update a dependency file"},"Google Sheets":{"color":"8078b0","name":"Google Sheets","description":"Issues related to Google Sheets"},"Icon Button Widget":{"color":"D319CE","name":"Icon Button Widget","description":"Issues related to the icon button widget"},"Mongo":{"color":"8078b0","name":"Mongo","description":"Issues related to Mongo DB plugin"},"Documentation":{"color":"a8dff7","name":"Documentation","description":"Improvements or additions to documentation"},"TestGap":{"color":"f28253","name":"TestGap","description":"Issues identified for test plan improvement"},"keyboard shortcut":{"color":"0688B6","name":"keyboard shortcut","description":""},"Git Version Control":{"color":"858172","name":"Git Version Control","description":"Issues related to version control"},"Reopen":{"color":"897548","name":"Reopen","description":""},"Redshift":{"color":"8078b0","name":"Redshift","description":"Issues related to the redshift integration"},"Date Picker Widget":{"color":"ef1ce1","name":"Date Picker Widget","description":""},"Entity Explorer":{"color":"a2e2f9","name":"Entity Explorer","description":"Issues related to navigation using the entity explorer"},"JS Linting & Errors":{"color":"E56AA5","name":"JS Linting & Errors","description":"Issues related to JS Linting and errors"},"iFrame":{"color":"3CD1DB","name":"iFrame","description":"Issues related to iFrame"},"Stale":{"color":"ededed","name":"Stale","description":null},"Debugger":{"color":"e79062","name":"Debugger","description":"Issues related to the debugger"},"Quick effort":{"color":"95ED65","name":"Quick effort","description":"Something that'll take a few hours to build"},"Text Widget":{"color":"d130d1","name":"Text Widget","description":""},"Video Widget":{"color":"23dd4b","name":"Video Widget","description":""},"Datasources":{"color":"5052f6","name":"Datasources","description":"Issues related to configuring datasource on appsmith"},"error":{"color":"B66773","name":"error","description":"All issues connected to error messages"},"Form Widget":{"color":"09ed77","name":"Form Widget","description":""},"Needs Triaging":{"color":"e8b851","name":"Needs Triaging","description":"Needs attention from maintainers to triage"},"Autocomplete":{"color":"235708","name":"Autocomplete","description":"Issues related to the autocomplete"},"hacktoberfest":{"color":"0052cc","name":"hacktoberfest","description":"All issues that can be solved by the community during Hacktoberfest"},"Medium effort":{"color":"D31156","name":"Medium effort","description":"Something that'll take more than a week but less than a month to build"},"Release":{"color":"57e5e0","name":"Release","description":""},"High":{"color":"c94d14","name":"High","description":"This issue blocks a user from building or impacts a lot of users"},"UI Performance":{"color":"1799b0","name":"UI Performance","description":"Issues related to UI performance"},"UI Builders Pod":{"color":"517fba","name":"UI Builders Pod","description":"Issues that UI Builders face using appsmith"},"Deploy Preview":{"color":"bfdadc","name":"Deploy Preview","description":"Issues found in Deploy Preview"},"Needs Tests":{"color":"8ee263","name":"Needs Tests","description":"Needs automated tests to assert a feature/bug fix"},"Refactor":{"color":"B96662","name":"Refactor","description":"needs refactoring of code"},"Divider Widget":{"color":"235708","name":"Divider Widget","description":"Issues related to the divider widget"},"Table Widget":{"color":"2eead1","name":"Table Widget","description":""},"Needs More Info":{"color":"e54c10","name":"Needs More Info","description":"Needs additional information"},"Good First Issue":{"color":"7057ff","name":"Good First Issue","description":"Good for newcomers"},"UI Improvement":{"color":"9aeef4","name":"UI Improvement","description":""},"Backend":{"color":"d4c5f9","name":"Backend","description":"This marks the issue or pull request to reference server code"},"Frontend":{"color":"87c7f2","name":"Frontend","description":"This label marks the issue or pull request to reference client code"},"In App Comms":{"name":"In App Comms","description":"Issues around communication with appsmith instances","color":"463cca"},"Chart Widget":{"color":"616ecc","name":"Chart Widget","description":""},"List Widget":{"color":"8508A0","name":"List Widget","description":"Issues related to the list widget"},"Duplicate":{"color":"cfd3d7","name":"Duplicate","description":"This issue or pull request already exists"},"JS Snippets":{"color":"8d62d2","name":"JS Snippets","description":"issues related to JS Snippets"},"Copy Paste":{"name":"Copy Paste","description":"Issues related to copy paste","color":"b4f0a9"},"Drag & Drop":{"name":"Drag & Drop","description":"Issues related to the drag & drop experience","color":"92115a"},"BE Coders Pod":{"color":"5d9848","name":"BE Coders Pod","description":"Issues related to users writing code to fetch and update data"},"FE Coders Pod":{"color":"a7effc","name":"FE Coders Pod","description":"Issues related to users writing javascript in appsmith"},"New Developers Pod":{"color":"6310da","name":"New Developers Pod","description":"Issues that new developers face while exploring the IDE"},"Sniping Mode":{"name":"Sniping Mode","description":"Issues related to sniping mode","color":"30c76d"},"Redis":{"name":"Redis","description":"Issues related to Redis","color":"8078b0"},"New Datasource":{"color":"60b14c","name":"New Datasource","description":"Requests for new datasources"},"Evaluated Value":{"name":"Evaluated Value","description":"Issues related to evaluated values","color":"39f6e7"},"Undo/Redo":{"name":"Undo/Redo","description":"Issues related to undo/redo","color":"f25880"},"App Navigation":{"name":"App Navigation","description":"Issues related to the topbar navigation and configuring it","color":"12b715"},"Responsive Viewport":{"color":"d12d2e","name":"Responsive Viewport","description":"Issues seen on different viewports like mobile"},"Widgets Pane":{"name":"Widgets Pane","description":"Issues related to the discovery and organisation of widgets","color":"ad5d78"},"View Mode":{"color":"1799b0","name":"View Mode","description":"Issues related to the view mode"},"User Education Pod":{"name":"User Education Pod","description":"Issues related to user education","color":"1799b0"},"Content":{"name":"Content","description":"For content related topics i.e blogs, templates, videos","color":"a8dff7"},"Embedding Apps":{"name":"Embedding Apps","description":"Issues related to embedding","color":"30c76d"},"Slash Command":{"name":"Slash Command","description":"Issues related to the slash command","color":"a0608e"},"Widget Property":{"name":"Widget Property","description":"Issues related to adding / modifying widget properties across widgets","color":"5e92cb"},"Windows":{"name":"Windows","description":"Issues related exclusively to Windows systems","color":"b4cb8a"},"Old App Issues":{"name":"Old App Issues","description":"Issues related to apps old apps a few weeks old and app issues in stale browser session","color":"87ab18"},"Document Viewer Widget":{"name":"Document Viewer Widget","description":"Issues related to Document Viewer Widget","color":"899d4b"},"Radio Group Widget":{"name":"Radio Group Widget","description":"Issues related to radio group widget","color":"b68495"},"Super Admin":{"name":"Super Admin","description":"Issues related to the super admin page","color":"aa95cf"},"Postgres":{"name":"Postgres","description":"Postgres related issues","color":"8078b0"},"REST API plugin":{"name":"REST API plugin","description":"REST API plugin related issues","color":"8078b0"},"New JS Function":{"name":"New JS Function","description":"Issues related to adding a JS Function","color":"8e8aa4"},"Cannot Reproduce Issue":{"color":"93c9cc","name":"Cannot Reproduce Issue","description":"Issues that cannot be reproduced"},"Widget Grouping":{"name":"Widget Grouping","description":"Issues related to Widget Grouping","color":"a49951"},"K8s":{"name":"K8s","description":"Kubernetes related issues","color":"5f318a"},"Docker":{"name":"Docker","description":"Issues related to docker","color":"89b808"},"Camera Widget":{"name":"Camera Widget","description":"Issues and enhancements related to camera widget","color":"e6038e"},"SAAS Plugins":{"name":"SAAS Plugins","description":"Issues related to SAAS Plugins","color":"ef9c9d"},"JS Promises":{"name":"JS Promises","description":"Issues related to promises","color":"d7771f"},"OnPageLoad":{"name":"OnPageLoad","description":"OnPageLoad issues on functions and queries","color":"50559d"},"JS Usability":{"name":"JS Usability","description":"usability issues with JS editor and JS elsewhere","color":"a302b0"},"Currency Input Widget":{"name":"Currency Input Widget","description":"Issues related to currency input widget","color":"b2164f"},"TreeSelect":{"name":"TreeSelect","description":"Issues related to TreeSelect Widget","color":"a1633e"},"MultiTree Select Widget":{"name":"MultiTree Select Widget","description":"Issues related to MultiTree Select Widget","color":"a1633e"},"Welcome Screen":{"name":"Welcome Screen","description":"Issues related to the welcome screen","color":"30c76d"},"Realtime Commenting":{"color":"a70b86","name":"Realtime Commenting","description":"In-app communication between teams"},"Phone Input Widget":{"name":"Phone Input Widget","description":"Issues related to the Phone Input widget","color":"a70b86"},"JSON Form":{"name":"JSON Form","description":"Issue / features related to the JSON form wiget","color":"46b209"},"All Widgets":{"name":"All Widgets","description":"Issues related to all widgets","color":"972b36"},"V1":{"name":"V1","description":"V1","color":"67ab2e"},"Reflow & Resize":{"name":"Reflow & Resize","description":"All issues related to reflow and resize experience","color":"748a13"},"App Theming":{"name":"App Theming","description":"Items that are related to the App level theming controls epic","color":"905420"},"SSO":{"name":"SSO","description":"Issues, requests and enhancements around Single sign-on.","color":"bf019b"},"Multi User Realtime":{"name":"Multi User Realtime","description":"Issues related to multiple users using or editing an application","color":"e7b6ce"},"Templates":{"name":"Templates","description":"Issues related to templates","color":"b7e568"},"Ready for design":{"name":"Ready for design","description":"this issue is ready for design: it contains clear problem statements and other required information","color":"ebf442"},"Support":{"name":"Support","description":"Issues created by the A-force team to address user queries","color":"1740f3"},"Button Group widget":{"name":"Button Group widget","description":"Issue and enhancements related to the button group widget","color":"f17025"},"GraphQL Plugin":{"name":"GraphQL Plugin","description":"Issues related to GraphQL plugin","color":"8078b0"},"DevOps Pod":{"name":"DevOps Pod","description":"Issues related to devops","color":"d956c7"},"medium":{"name":"medium","description":"Issues that frustrate users due to poor UX","color":"23dfd9"},"ArangoDB":{"name":"ArangoDB","description":"Issues related to arangoDB","color":"8078b0"},"Code Refactoring":{"name":"Code Refactoring","description":"Issues related to code refactoring","color":"76310e"},"Progress bar widget":{"name":"Progress bar widget","description":"To track issues related to progress bar","color":"2d7abf"},"Audio Recorder Widget":{"name":"Audio Recorder Widget","description":"Issues related to Audio Recorder Widget","color":"9accef"},"Airtable":{"name":"Airtable","description":"Issues for Airtable","color":"60885f"},"RBAC":{"name":"RBAC","description":"Issues, requests and enhancements around RBAC.","color":"9211c3"},"Canvas / Grid":{"name":"Canvas / Grid","description":"Issues related to the canvas","color":"16b092"},"Email Config":{"name":"Email Config","description":"Issues related to configuring the email service","color":"2a21d1"},"CURL":{"name":"CURL","description":"Issues related to CURL impor","color":"60885f"},"Canvas Zooms":{"name":"Canvas Zooms","description":"Issues related to zooming the canvas","color":"e6038e"},"business":{"name":"business","description":"Features that will be a part of our business edition","color":"cd59eb"},"Action Pod":{"name":"Action Pod","description":"","color":"ee2e36"},"AutomationGap1":{"color":"a5e07c","name":"AutomationGap1","description":"Issues that needs automated tests"},"A-Force11":{"name":"A-Force11","description":"Issues raised by A-Force team","color":"d667b6"},"Business Edition":{"name":"Business Edition","description":"Features that will be a part of our business edition","color":"89bb6c"},"storeValue":{"name":"storeValue","description":"Issues related to the store value function","color":"5d3e66"},"Tests":{"name":"Tests","description":"test item","color":"1c6990"},"DynamoDB":{"name":"DynamoDB","description":"Issues that are related to DynamoDB should have this label","color":"60885f"},"Design System Pod":{"name":"Design System Pod","description":"Appsmith design system related issues","color":"706f03"},"ABAC":{"color":"e009a5","name":"ABAC","description":"User permissions and access controls"},"Backup & Restore":{"name":"Backup & Restore","description":"Issues related to backup and restore","color":"86874d"},"Billing":{"name":"Billing","description":"Billing infrastructure and flows for Business Edition and Trial users","color":"d2bc40"},"Datatype issue":{"name":"Datatype issue","description":"Issues that have risen because data types weren't handled","color":"60885f"},"OAuth":{"name":"OAuth","description":"OAuth related bugs or features","color":"60885f"},"Table Widget V2":{"name":"Table Widget V2","description":"Issues related to Table Widget V2","color":"3a7192"},"IDE Navigation":{"name":"IDE Navigation","description":"Issues/feature requests related to IDE navigation, and context switching","color":"bc0cba"},"Query performance":{"name":"Query performance","description":"Issues that have to do with lack in performance of query execution","color":"e4d966"},"SAAS Manager App":{"name":"SAAS Manager App","description":"Issues with the SAAS manager app","color":"d427db"},"Twilio":{"name":"Twilio","description":"Issues related to Twilio integration","color":"23ba8d"},"Hubspot":{"name":"Hubspot","description":"Issues related to Hubspot integration","color":"60885f"},"Zendesk":{"name":"Zendesk","description":"Issues related to Zendesk integration","color":"60885f"},"Entity Refactor":{"name":"Entity Refactor","description":"Issues related to refactor logic","color":"418fa4"},"Branding":{"name":"Branding","description":"All issues under branding and whitelabelling appsmith ecosystem","color":"7aaaf1"},"Map Chart Widget":{"name":"Map Chart Widget","description":"Issues related to Map Chart Widgets","color":"c8397f"},"Product Catchup":{"name":"Product Catchup","description":"Issues created in the product catchup","color":"29cd2c"},"Framework Functions":{"name":"Framework Functions","description":"Issues related to internal functions like showAlert(), navigateTo() etc...","color":"c25a09"},"Frontend Libraries Upgrade":{"name":"Frontend Libraries Upgrade","description":"Issues related to frontend libraries upgrade","color":"ede1fc"},"Audit Logs":{"name":"Audit Logs","description":"Audit trails to ensure data security","color":"f3fd62"},"MsSQL":{"name":"MsSQL","description":"Issues related to MsSQL plugin","color":"8078b0"},"Data Platform Pod":{"name":"Data Platform Pod","description":"Issues related to the underlying data platform","color":"3f8c3a"},"Integrations Pod":{"name":"Integrations Pod","description":"Issues related to a specific integration","color":"5dbbb1"},"Datasource Environments":{"name":"Datasource Environments","description":"Issues related to datasource environments","color":"bb7a14"},"Elastic Search":{"name":"Elastic Search","description":"Issues related to the elastic search datasource","color":"8078b0"},"Core Query Execution":{"color":"418fa4","name":"Core Query Execution","description":"Issues related to the execution of all queries"},"Query Management":{"name":"Query Management","description":"Issues related to the CRUD of actions or queries","color":"6a5b42"},"Query Settings":{"name":"Query Settings","description":"Issues related to the settings of all queries","color":"c7da7a"},"Code Editor":{"name":"Code Editor","description":"Issues related to the code editor","color":"4ca16e"},"Query Forms":{"color":"12b253","name":"Query Forms","description":"Isuses related to the query forms"},"JS Objects":{"color":"22962c","name":"JS Objects","description":"Issues related to JS Objects"},"JS Evaluation":{"color":"22962c","name":"JS Evaluation","description":"Issues related to JS evaluation on the platform"},"SmartSubstitution":{"name":"SmartSubstitution","description":"Issues related to Smart substitution of mustache bindings in queries","color":"e4d966"},"Query Generation":{"name":"Query Generation","description":"Issues related to query generation","color":"e4d966"},"Suggested Widgets":{"name":"Suggested Widgets","description":"Issues related to suggesting widgets based on query response","color":"e4d966"},"Page load executions":{"name":"Page load executions","description":"Issues related to page load execution","color":"5696b2"},"Code Scanner Widget":{"name":"Code Scanner Widget","description":"Issues related to code scanner widget","color":"9bc1a0"},"Clean URLs":{"name":"Clean URLs","description":"Issues related to clean URLs epic","color":"112623"},"Widget keyboard accessibility":{"name":"Widget keyboard accessibility","description":"All issues related to keyboard accessibility in widgets","color":"b626fd"},"Connection pool":{"name":"Connection pool","description":"issues to do with connection pooling of various plugins","color":"94fe36"},"List Widget V2":{"name":"List Widget V2","description":"Issues related to the list widget v2","color":"adaaf7"},"Auto Height":{"name":"Auto Height","description":"Issues related to dynamic height of widgets","color":"5149cf"},"cypress_failed_test":{"name":"cypress_failed_test","description":"Cypress failed tests","color":"4745d5"},"Needs validation":{"name":"Needs validation","description":"Needs problem validation before being picked up","color":"66673d"},"Slider Widget":{"name":"Slider Widget","description":"Issues raised for slider widgets.","color":"2eef5f"},"Multitenancy":{"name":"Multitenancy","description":"Support multitenancy within single appsmith instance","color":"8c49a9"},"Git Pod":{"name":"Git Pod","description":"Anything related to git sync","color":"2e5ba4"},"Mobile Pod":{"name":"Mobile Pod","description":"All issues related to mobile responsiveness","color":"6c97fd"},"Responsive Widget":{"name":"Responsive Widget","description":"All issues related to widget responsiveness","color":"d12d2e"},"Responsive Canvas":{"name":"Responsive Canvas","description":"All issues related to canvas responsiveness","color":"45a0a8"},"Conversion Algorithm":{"name":"Conversion Algorithm","description":"All issue related to converting app from fixed to flex mode & vice versa","color":"d12d2e"},"Spacing":{"name":"Spacing","description":"All issue related to spacing between widgets in auto layout","color":"d12d2e"},"Browser specific":{"name":"Browser specific","description":"All issue related to browser","color":"d12d2e"},"Error Handling":{"name":"Error Handling","description":"Issues related to error handling","color":"4e1872"},"Performance infra":{"name":"Performance infra","description":"all issue related to the performance infra","color":"8a60f6"},"DSL Update":{"name":"DSL Update","description":"Issues related to storing and updating the DSL","color":"e16cf3"},"AST-frontend":{"name":"AST-frontend","description":"Issues related to maintaining AST logic","color":"434a3a"},"AST-backend":{"name":"AST-backend","description":"Backend issues related to AST parsing","color":"c476eb"},"MariaDB":{"name":"MariaDB","description":"MariaDB datasource","color":"8428c3"},"Billing & Usage Pod":{"name":"Billing & Usage Pod","description":"Issues pertaining to licensing, billing, usage across self serve and enterprise customers","color":"256808"},"ADS Component Issue":{"name":"ADS Component Issue","description":"Issues which are caused due to ADS components","color":"d89119"},"Regressed":{"color":"723fd0","name":"Regressed","description":"Scenarios that were working before but have now regressed"},"Needs RCA":{"name":"Needs RCA","description":"a critical or high priority issue that needs an RCA","color":"2cc68f"},"Custom JS Libraries":{"name":"Custom JS Libraries","description":"Issues related to adding custom JS library","color":"bacb6d"},"Integrations Pod General":{"name":"Integrations Pod General","description":"Issues related to the Integrations Pod that don't fit into other tags.","color":"287823"},"Performance Pod":{"name":"Performance Pod","description":"All things related to Appsmith performance","color":"b5a25d"},"Performance":{"name":"Performance","description":"Issues related to performance","color":"9a18d7"},"File upload issues":{"name":"File upload issues","description":"Issues related to uploading any type of files from within Appsmith","color":"8154df"},"Action Selector":{"name":"Action Selector","description":"Issues related to action selector on the property pane","color":"2f9e20"},"Widget design system":{"name":"Widget design system","description":"","color":"cb6188"},"Deploy App":{"name":"Deploy App","description":"Issues related to app deployment","color":"6f6152"},"Community Reported":{"name":"Community Reported","description":"issues reported by community members","color":"1402e5"},"JS Function execution":{"name":"JS Function execution","description":"JS function execution","color":"7c2de1"},"Self Serve":{"name":"Self Serve","description":"For all issues related to self-serve flow for business edition","color":"4dacfc"},"Self Serve 1.0":{"name":"Self Serve 1.0","description":"For all issues related to v1 of the self serve project","color":"ae839e"},"CE Instance":{"name":"CE Instance","description":"For all issues relating to usage, licensing or billing on the CE instance","color":"d2bc40"},"Customer Portal":{"name":"Customer Portal","description":"For all tasks/issues pertaining to customer.appsmith.com","color":"d2bc40"},"Cloud Services":{"name":"Cloud Services","description":"For all tasks/issues on Appsmith cloud-services relating to licensing, usage and billing","color":"d2bc40"},"Billing Integrations":{"name":"Billing Integrations","description":"For all issues relating to 3P integrations Appsmith is using for billing & usage","color":"d2bc40"},"One-click Binding":{"name":"One-click Binding","description":"Issues related to the One click binding epic","color":"f1661c"},"Airgap":{"name":"Airgap","description":"Tickets related to supporting air-gapped Appsmith instances","color":"1cb294"},"SMTP plugin":{"name":"SMTP plugin","description":"Issues related to SMTP plugin","color":"541457"},"AWS AMI":{"name":"AWS AMI","description":"Issues Related to AWS AMI","color":"b44680"},"Old widget version":{"name":"Old widget version","description":"Use this label to raise issue specific only to an older version of a widget","color":"ff3814"},"Enterprise Billing":{"name":"Enterprise Billing","description":"To track all tasks/issues related to licensing & billing for enterprise customers","color":"14c156"},"Appsmith Business Cloud":{"name":"Appsmith Business Cloud","description":"Issues related to our business cloud offering","color":"89bb6c"},"Oracle SQL DB":{"name":"Oracle SQL DB","description":"Issues related to the Oracle plugin","color":"cbabcb"},"Community Contributor":{"name":"Community Contributor","description":"Meant to track issues that are assigned to external contributors","color":"149ab6"},"widget vertical alignment":{"name":"widget vertical alignment","description":"All issue related widget vertical alignment on the auto layout canvas","color":"d12d2e"},"Observability":{"name":"Observability","description":"Issues related to observability on the Appsmith instance","color":"dff913"},"Checkbox Component":{"name":"Checkbox Component","description":"This labels deals with checkbox component in wds package","color":"75a401"},"In-app ramps":{"name":"In-app ramps","description":"For all tasks/issues relating to adding in-app ramps in the community edition of the product","color":"8abae0"},"Analytics Improvements":{"name":"Analytics Improvements","description":"For all tasks focused on improving our overall analytics and fixing any issues ","color":"29b8ed"},"WDS team":{"name":"WDS team","description":"","color":"8d675a"},"Enterprise Edition":{"name":"Enterprise Edition","description":"Features that will be supported in Enterprise Edition only","color":"984f5e"},"Query filter":{"name":"Query filter","description":"Issues related to query filtering, e.g., WHERE clause","color":"a15134"},"Keyboard accessibility ":{"name":"Keyboard accessibility ","description":"All issue related to ADS component keyboard accessibility","color":"2ba696"},"Toggle button":{"name":"Toggle button","description":"All issue related to ADS toggle button","color":"edc47f"},"Feature Flagging":{"name":"Feature Flagging","description":"Anything related feature flagging","color":"8d8a09"},"SCIM":{"name":"SCIM","description":"Label to collate our SCIM issues","color":"61a852"},"ADS Category Token":{"name":"ADS Category Token","description":"All issues related appsmith design system category tokens","color":"920961"},"ADS Component Documentation":{"name":"ADS Component Documentation","description":"All issues Appsmith design system component documentation","color":"64c46a"},"ADS Migration":{"name":"ADS Migration","description":"All issues related to Appsmith design system migration","color":"b082d6"},"ADS Deduplication ":{"name":"ADS Deduplication ","description":"Replacing component with ADS components","color":"b082d6"},"ADS Revamp":{"name":"ADS Revamp","description":"All issues related to ads revamp. ","color":"b082d6"},"ADS Deduplication":{"name":"ADS Deduplication","description":"Replacing component with ADS components","color":"b082d6"},"ADS Grayscale":{"name":"ADS Grayscale","description":"Support grayscale color changes","color":"b03577"},"ADS Unit Test":{"name":"ADS Unit Test","description":"All issue related ads unit cases ","color":"b082d6"},"ADS Components":{"name":"ADS Components","description":"All issues related ADS components","color":"b082d6"},"Widget Discoverability":{"name":"Widget Discoverability","description":"Issues related to Widget Discoverability","color":"7b55ce"},"Widget setter method":{"name":"Widget setter method","description":"Issues with widget property setters","color":"8dce87"},"License":{"name":"License","description":"For all issues/tasks related to licensing of appsmith-ee edition","color":"90ee98"},"Templates pod":{"name":"Templates pod","description":"Issues related to Templates","color":"b7e568"},"Community template":{"name":"Community template","description":"Label for development of community templates and its integration to platform","color":"8a0510"},"DocumentDB":{"name":"DocumentDB","description":"Issues related to support DocumentDB in Appsmith Data layer","color":"2c8b56"},"Multiple Environments":{"name":"Multiple Environments","description":"Issues or tasks related to multiple environments","color":"4e972b"},"Platformization":{"name":"Platformization","description":"Issues or tasks related to platformization of Appsmith codebase","color":"4e972b"},"Activation - datasources":{"name":"Activation - datasources","description":"issues related to activation projects","color":"7c7ace"},"Partial-import-export":{"name":"Partial-import-export","description":"Label for granular reusability.","color":"1e439c"},"AI":{"name":"AI","description":"All tasks related to AI","color":"75c4ce"},"Custom environments":{"name":"Custom environments","description":"Issues with creating or working with custom environments","color":"2137d6"},"ADS Typography":{"name":"ADS Typography","description":"All issue related typographical changes","color":"2dbe8d"},"Auto Layout":{"name":"Auto Layout","description":"Issues relates to auto layout","color":"92cf8c"},"Heroku":{"name":"Heroku","description":"Issues related to Heroku","color":"a81b69"},"ADS Visual Styles":{"name":"ADS Visual Styles","description":"All issues related to ADS visual styles","color":"d3da89"},"ADS Component Design":{"name":"ADS Component Design","description":"All issue related to component design","color":"5cc91e"},"Modal Component":{"name":"Modal Component","description":"All issue related to ads modal component","color":"ee63f3"},"App setting":{"name":"App setting","description":"Related to app settings panel within the app","color":"144206"},"BE instance":{"name":"BE instance","description":"For all issues related to license, billing on BE instance","color":"ae8f98"},"Schema":{"name":"Schema","description":"Issues related to database schema","color":"c470c2"},"Fixed layout":{"name":"Fixed layout","description":"issues related to fixed layout","color":"b66681"},"Anvil layout":{"name":"Anvil layout","description":"issues related to the new layout system anvil","color":"722bf0"},"New Deployment Mode":{"name":"New Deployment Mode","description":"Support a new mode of deployment","color":"108033"},"Custom widgets":{"name":"Custom widgets","description":"For all issues related to the custom widget project","color":"c9db9c"},"IDE Pod":{"name":"IDE Pod","description":"https://app.zenhub.com/workspaces/new-developers-pod-60507ad1d4b98d00150a2858/board","color":"d3d248"},"TM_BU":{"name":"TM_BU","description":"The issues on Team Manager which needs to be taken up by Billing & Usage","color":"198cdf"},"Homepage Experience V2":{"name":"Homepage Experience V2","description":"Label for reporting new tasks and bug fixes related to revamped homepage experience","color":"c55d54"},"Appsmith Labs":{"name":"Appsmith Labs","description":"All things related to AI and other new initiatives ","color":"712d51"},"Customer Success":{"name":"Customer Success","description":"Issues that the success team cares about","color":"6ccabd"},"Invite flow":{"name":"Invite flow","description":"Invite users flow and any associated actions","color":"881b35"},"Invite users":{"name":"Invite users","description":"Invite users flow and any associated actions","color":"23e6d6"},"Workflows Pod":{"name":"Workflows Pod","description":"For all issues related to the Workflows feature","color":"2c1f93"},"DailyPromotionBlocker":{"name":"DailyPromotionBlocker","description":"DailyPromotion Blocker","color":"9b2280"}},"success":true} \ No newline at end of file From 4b3d19b238d6b82e88256f5da79713ca7a77732a Mon Sep 17 00:00:00 2001 From: Arpit Mohan Date: Thu, 18 Jan 2024 12:45:23 +0530 Subject: [PATCH 10/16] ci: Fixing Slack notification in test-build-docker-image (#30407) Fixing minor syntax issues when notifying on Slack on failure of the Test build Docker workflow. ## Summary by CodeRabbit - **Chores** - Improved Slack notification messages by adjusting link formatting. --- .github/workflows/test-build-docker-image.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-build-docker-image.yml b/.github/workflows/test-build-docker-image.yml index c93b10b3d8..0ca0c30b85 100644 --- a/.github/workflows/test-build-docker-image.yml +++ b/.github/workflows/test-build-docker-image.yml @@ -403,10 +403,10 @@ jobs: merged_upto_sha='${{ steps.merge.outputs.merged_upto_sha }}' merged_upto_sha_short="$(echo "$merged_upto_sha" | grep -o '^.\{8\}' || true)" - details="🚨 TBP workflow failed in <$run_url|${{ github.run_id }}/attempts/${{ github.run_attempt }}>. + details="🚨 TBP workflow failed in <$run_url|${{ github.run_id }}/attempts/${{ github.run_attempt }}>." - # This unweildy horror of a sed command, converts standard Markdown links to Slack's unweildy link syntax. - slack_message="$(echo "$details" | sed -E 's/\[([^]]+)\]\(([^)]+)\)/<\2|\1>/g')" + # This unwieldy horror of a sed command, converts standard Markdown links to Slack's unwieldy link syntax. + slack_message=$(echo "$details" | sed -E 's/\[([^]]+)\]\(([^)]+)\)/<\2|\1>/g') # This is the ChannelId of the tech channel. body="$(jq -nc \ From 6b9294e6a85e8335c0814ac41c04d0450e63c887 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Thu, 18 Jan 2024 14:02:42 +0530 Subject: [PATCH 11/16] chore: Re-arrange IDE entity selectors for split (#30420) ## Description Small re arrangement so that we update these implementations in EE ## Summary by CodeRabbit - **Refactor** - Improved internal selector structure for better maintainability. - **Documentation** - Updated import statements across multiple files for consistency. --- .../src/ce/selectors/appIDESelectors.ts | 58 ++----------------- .../src/ce/selectors/entitiesSelector.ts | 46 +++++++++++++++ .../pages/Editor/IDE/EditorTabs/FileTabs.tsx | 2 +- .../src/pages/Editor/IDE/EditorTabs/JSTab.tsx | 2 +- .../pages/Editor/IDE/EditorTabs/QueryTab.tsx | 10 ++-- app/client/src/sagas/ActionSagas.ts | 6 +- 6 files changed, 60 insertions(+), 64 deletions(-) diff --git a/app/client/src/ce/selectors/appIDESelectors.ts b/app/client/src/ce/selectors/appIDESelectors.ts index 5a9ebca107..edfcfe5434 100644 --- a/app/client/src/ce/selectors/appIDESelectors.ts +++ b/app/client/src/ce/selectors/appIDESelectors.ts @@ -1,28 +1,16 @@ import { groupBy, sortBy } from "lodash"; import { createSelector } from "reselect"; -import { PluginType } from "entities/Action"; +import type { EntityItem } from "@appsmith/selectors/entitiesSelector"; import { - isEmbeddedAIDataSource, - isEmbeddedRestDatasource, -} from "entities/Datasource"; -import { - getCurrentActions, - getCurrentJSCollections, - selectDatasourceIdToNameMap, -} from "./entitiesSelector"; + getJSSegmentItems, + getQuerySegmentItems, +} from "@appsmith/selectors/entitiesSelector"; export type EditorSegmentList = Array<{ group: string | "NA"; items: EntityItem[]; }>; -export interface EntityItem { - title: string; - type: PluginType; - key: string; - group?: string; -} - const groupAndSortEntitySegmentList = ( items: EntityItem[], ): EditorSegmentList => { @@ -48,50 +36,12 @@ function recentSortEntitySegmentTabs(items: EntityItem[]) { return sortBy(items, "title"); } -export const getQuerySegmentItems = createSelector( - getCurrentActions, - selectDatasourceIdToNameMap, - (actions, datasourceIdToNameMap) => { - const items: EntityItem[] = actions.map((action) => { - let group; - if (action.config.pluginType === PluginType.API) { - group = isEmbeddedRestDatasource(action.config.datasource) - ? "APIs" - : datasourceIdToNameMap[action.config.datasource.id] ?? "APIs"; - } else if (action.config.pluginType === PluginType.AI) { - group = isEmbeddedAIDataSource(action.config.datasource) - ? "AI Queries" - : datasourceIdToNameMap[action.config.datasource.id] ?? "AI Queries"; - } else { - group = datasourceIdToNameMap[action.config.datasource.id]; - } - return { - title: action.config.name, - key: action.config.id, - type: action.config.pluginType, - group, - }; - }); - return items; - }, -); export const selectQuerySegmentEditorList = createSelector( getQuerySegmentItems, (items) => { return groupAndSortEntitySegmentList(items); }, ); -export const getJSSegmentItems = createSelector( - getCurrentJSCollections, - (jsActions) => { - const items: EntityItem[] = jsActions.map((js) => ({ - title: js.config.name, - key: js.config.id, - type: PluginType.JS, - })); - return items; - }, -); export const selectJSSegmentEditorList = createSelector( getJSSegmentItems, (items) => { diff --git a/app/client/src/ce/selectors/entitiesSelector.ts b/app/client/src/ce/selectors/entitiesSelector.ts index 7f6bdcaad8..68ab7551f0 100644 --- a/app/client/src/ce/selectors/entitiesSelector.ts +++ b/app/client/src/ce/selectors/entitiesSelector.ts @@ -1458,3 +1458,49 @@ export const getNewEntityName = createSelector( return getNextEntityName(prefix, actionNames.concat(jsActionNames)); }, ); + +export interface EntityItem { + title: string; + type: PluginType; + key: string; + group?: string; +} + +export const getQuerySegmentItems = createSelector( + getCurrentActions, + selectDatasourceIdToNameMap, + (actions, datasourceIdToNameMap) => { + const items: EntityItem[] = actions.map((action) => { + let group; + if (action.config.pluginType === PluginType.API) { + group = isEmbeddedRestDatasource(action.config.datasource) + ? "APIs" + : datasourceIdToNameMap[action.config.datasource.id] ?? "APIs"; + } else if (action.config.pluginType === PluginType.AI) { + group = isEmbeddedAIDataSource(action.config.datasource) + ? "AI Queries" + : datasourceIdToNameMap[action.config.datasource.id] ?? "AI Queries"; + } else { + group = datasourceIdToNameMap[action.config.datasource.id]; + } + return { + title: action.config.name, + key: action.config.id, + type: action.config.pluginType, + group, + }; + }); + return items; + }, +); +export const getJSSegmentItems = createSelector( + getCurrentJSCollections, + (jsActions) => { + const items: EntityItem[] = jsActions.map((js) => ({ + title: js.config.name, + key: js.config.id, + type: PluginType.JS, + })); + return items; + }, +); diff --git a/app/client/src/pages/Editor/IDE/EditorTabs/FileTabs.tsx b/app/client/src/pages/Editor/IDE/EditorTabs/FileTabs.tsx index b1622209bd..2d6f6b4e9a 100644 --- a/app/client/src/pages/Editor/IDE/EditorTabs/FileTabs.tsx +++ b/app/client/src/pages/Editor/IDE/EditorTabs/FileTabs.tsx @@ -3,7 +3,7 @@ import { Flex } from "design-system"; import { useCurrentEditorState } from "../hooks"; import { EditorEntityTab } from "@appsmith/entities/IDE/constants"; import { useSelector } from "react-redux"; -import type { EntityItem } from "@appsmith/selectors/appIDESelectors"; +import type { EntityItem } from "@appsmith/selectors/entitiesSelector"; import { selectJSSegmentEditorTabs, selectQuerySegmentEditorTabs, diff --git a/app/client/src/pages/Editor/IDE/EditorTabs/JSTab.tsx b/app/client/src/pages/Editor/IDE/EditorTabs/JSTab.tsx index aae38276a4..a96ef3d3b4 100644 --- a/app/client/src/pages/Editor/IDE/EditorTabs/JSTab.tsx +++ b/app/client/src/pages/Editor/IDE/EditorTabs/JSTab.tsx @@ -2,7 +2,7 @@ import React from "react"; import clsx from "classnames"; import { useSelector } from "react-redux"; -import type { EntityItem } from "@appsmith/selectors/appIDESelectors"; +import type { EntityItem } from "@appsmith/selectors/entitiesSelector"; import { getCurrentPageId } from "@appsmith/selectors/entitiesSelector"; import { useActiveAction } from "@appsmith/pages/Editor/Explorer/hooks"; import { jsCollectionIdURL } from "@appsmith/RouteBuilder"; diff --git a/app/client/src/pages/Editor/IDE/EditorTabs/QueryTab.tsx b/app/client/src/pages/Editor/IDE/EditorTabs/QueryTab.tsx index d9d1956b69..9012d537d2 100644 --- a/app/client/src/pages/Editor/IDE/EditorTabs/QueryTab.tsx +++ b/app/client/src/pages/Editor/IDE/EditorTabs/QueryTab.tsx @@ -2,10 +2,12 @@ import React, { useMemo } from "react"; import clsx from "classnames"; import { useSelector } from "react-redux"; -import type { EntityItem } from "@appsmith/selectors/appIDESelectors"; -import { getAction } from "@appsmith/selectors/entitiesSelector"; -import { getPlugins } from "@appsmith/selectors/entitiesSelector"; -import { getCurrentPageId } from "@appsmith/selectors/entitiesSelector"; +import type { EntityItem } from "@appsmith/selectors/entitiesSelector"; +import { + getAction, + getPlugins, + getCurrentPageId, +} from "@appsmith/selectors/entitiesSelector"; import { useActiveAction } from "@appsmith/pages/Editor/Explorer/hooks"; import history, { NavigationMethod } from "utils/history"; import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers"; diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index 363026151c..d062d4b92d 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -64,10 +64,8 @@ import { SlashCommand, } from "entities/Action"; import type { ActionData } from "@appsmith/reducers/entityReducers/actionsReducer"; -import type { - EditorSegmentList, - EntityItem, -} from "@appsmith/selectors/appIDESelectors"; +import type { EditorSegmentList } from "@appsmith/selectors/appIDESelectors"; +import type { EntityItem } from "@appsmith/selectors/entitiesSelector"; import { selectQuerySegmentEditorList } from "@appsmith/selectors/appIDESelectors"; import { getAction, From 0a4b72fb2ab44f982110045e8329e19fc75d4670 Mon Sep 17 00:00:00 2001 From: albinAppsmith <87797149+albinAppsmith@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:34:07 +0530 Subject: [PATCH 12/16] fix: Admin setting radio button issue (#30419) ## Description Issue: Admin settings embedded setting tag was rendering a radio button inside. Cause: In design system, while styling the radio button, it was only looking for child span. This means any span comes inside the Radio button children, will be considered as the radio part. #### PR fixes following issue(s) Fixes https://github.com/appsmithorg/appsmith/issues/28889 #### Type of change - Bug fix (non-breaking change which fixes an issue) ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [ ] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed --- app/client/package.json | 2 +- app/client/yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/client/package.json b/app/client/package.json index a7a1a62ce1..19ae67cb28 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -108,7 +108,7 @@ "cypress-log-to-output": "^1.1.2", "dayjs": "^1.10.6", "deep-diff": "^1.0.2", - "design-system": "npm:@appsmithorg/design-system@2.1.30", + "design-system": "npm:@appsmithorg/design-system@2.1.31", "design-system-old": "npm:@appsmithorg/design-system-old@1.1.14", "downloadjs": "^1.4.7", "echarts": "^5.4.2", diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 6b8bd23d71..155fe79eab 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -13359,7 +13359,7 @@ __metadata: cypress-xpath: ^1.6.0 dayjs: ^1.10.6 deep-diff: ^1.0.2 - design-system: "npm:@appsmithorg/design-system@2.1.30" + design-system: "npm:@appsmithorg/design-system@2.1.31" design-system-old: "npm:@appsmithorg/design-system-old@1.1.14" diff: ^5.0.0 dotenv: ^8.1.0 @@ -17407,9 +17407,9 @@ __metadata: languageName: node linkType: hard -"design-system@npm:@appsmithorg/design-system@2.1.30": - version: 2.1.30 - resolution: "@appsmithorg/design-system@npm:2.1.30" +"design-system@npm:@appsmithorg/design-system@2.1.31": + version: 2.1.31 + resolution: "@appsmithorg/design-system@npm:2.1.31" dependencies: "@radix-ui/react-dialog": ^1.0.2 "@radix-ui/react-dropdown-menu": ^2.0.4 @@ -17439,7 +17439,7 @@ __metadata: react-dom: ^17.0.2 react-router-dom: ^5.0.0 styled-components: ^5.3.6 - checksum: b41c9a2f85db6c7ed59c74cabfb874a27d9df9fdaa11dd2e33a687be5df653c3d8f6547e332af625f7061b168ae1375068255535d8e31642b69ea8c0ca8e415e + checksum: 4fc89bbb7f4403a9583960dd410c722ed3469e22c3e3d3bc256b3024ce6a7288f46308ef89386d931c264e9179cf22141f673f1c0ed9675dc60e8401c3845be8 languageName: node linkType: hard From 20d6dfb591feff513ee671ad942d34cd16b4c0bf Mon Sep 17 00:00:00 2001 From: Manish Kumar <107841575+sondermanish@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:16:40 +0530 Subject: [PATCH 13/16] chore: Import export application refactor (#29691) ## Description First draft for Import export flow refactor Steps for refactoring the application import flow to context agnostic flow /** * Step 1: Schema Migration * Step 2: Validation of context Json * Step 3: create placeholder objects for internal stuffs * Step 4: set schema version and other stuffs common misc * Step 5: get workspace and user with right set of permission * Step 6: get application specific import entities * Step 7: get allImportEntities like plugins, datasource, action and other stuffs * Step 8: get update page and new action with already created references. */ fixes: https://github.com/appsmithorg/appsmith/issues/29748, #29819 , #29820, #29821, #29822 ## Summary by CodeRabbit - **New Features** - Introduced comprehensive application import capabilities, allowing users to import and manage application artifacts easily. - **Improvements** - Enhanced artifact management with new functionalities for syncing client and schema versions, updating artifacts, and permission handling. - **Documentation** - Added new interfaces and classes to support the import process, ensuring clarity in the import service's operations. - **Refactor** - Streamlined import services by renaming and updating method signatures for consistency and improved artifact-centric logic. - **Bug Fixes** - Addressed issues in the import logic to cater to different artifact types, ensuring a smoother import experience. --- ...tionCollectionImportableServiceCEImpl.java | 15 +- .../base/ApplicationImportServiceCEImpl.java | 711 +++ .../base/ApplicationImportServiceImpl.java | 63 + .../imports/ApplicationImportService.java | 3 + .../imports/ApplicationImportServiceCE.java | 16 + .../server/constants/ArtifactJsonType.java | 11 + .../server/constants/ce/FieldNameCE.java | 3 + .../controllers/ApplicationController.java | 7 +- .../ce/ApplicationControllerCE.java | 9 +- .../DatasourceImportableServiceCEImpl.java | 30 +- .../appsmith/server/domains/Application.java | 2 +- .../server/domains/ImportableArtifact.java | 5 + .../domains/ce/ImportableArtifactCE.java | 8 + .../server/dtos/ApplicationImportDTO.java | 3 +- .../server/dtos/ArtifactExchangeJson.java | 5 + .../server/dtos/ImportableArtifactDTO.java | 3 + .../server/dtos/ImportingMetaDTO.java | 18 +- .../server/dtos/ce/ApplicationJsonCE.java | 23 +- .../dtos/ce/ArtifactExchangeJsonCE.java | 24 + .../ce/MappedImportableResourcesCE_DTO.java | 20 +- ... => ImportArtifactPermissionProvider.java} | 23 +- .../imports/importable/ImportService.java | 3 + .../imports/importable/ImportServiceCE.java | 96 + .../importable/ImportableServiceCE.java | 13 + .../internal/ContextBasedImportService.java | 9 + .../internal/ContextBasedImportServiceCE.java | 153 + .../ImportApplicationServiceCEImpl.java | 28 +- .../imports/internal/ImportServiceCEImpl.java | 741 +++ .../imports/internal/ImportServiceImpl.java | 43 + .../internal/PartialImportServiceCEImpl.java | 10 +- .../migrations/ArtifactSchemaMigration.java | 3 + .../migrations/ArtifactSchemaMigrationCE.java | 105 + .../NewActionImportableServiceCEImpl.java | 25 +- .../NewPageImportableServiceCEImpl.java | 39 +- .../PluginImportableServiceCEImpl.java | 24 + .../server/solutions/ArtifactPermission.java | 5 + .../solutions/ce/ApplicationPermissionCE.java | 8 +- .../solutions/ce/ArtifactPermissionCE.java | 12 + .../imports/ThemeImportableServiceCEImpl.java | 26 +- .../ApplicationControllerTest.java | 4 + ...ImportArtifactPermissionProviderTest.java} | 45 +- .../imports/internal/ImportServiceTests.java | 5181 +++++++++++++++++ 42 files changed, 7467 insertions(+), 108 deletions(-) create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationImportServiceCEImpl.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationImportServiceImpl.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/applications/imports/ApplicationImportService.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/applications/imports/ApplicationImportServiceCE.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ArtifactJsonType.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ImportableArtifact.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/ImportableArtifactCE.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ArtifactExchangeJson.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ImportableArtifactDTO.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ArtifactExchangeJsonCE.java rename app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/{ImportApplicationPermissionProvider.java => ImportArtifactPermissionProvider.java} (91%) create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/imports/importable/ImportService.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/imports/importable/ImportServiceCE.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ContextBasedImportService.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ContextBasedImportServiceCE.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportServiceCEImpl.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportServiceImpl.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/ArtifactSchemaMigration.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/ArtifactSchemaMigrationCE.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ArtifactPermission.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ArtifactPermissionCE.java rename app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/{ImportApplicationPermissionProviderTest.java => ImportArtifactPermissionProviderTest.java} (85%) create mode 100644 app/server/appsmith-server/src/test/java/com/appsmith/server/imports/internal/ImportServiceTests.java diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/imports/ActionCollectionImportableServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/imports/ActionCollectionImportableServiceCEImpl.java index e0dd9efc89..f0c541c5e1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/imports/ActionCollectionImportableServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/imports/ActionCollectionImportableServiceCEImpl.java @@ -75,12 +75,13 @@ public class ActionCollectionImportableServiceCEImpl implements ImportableServic MappedImportableResourcesDTO mappedImportableResourcesDTO) { Mono> importedActionCollectionMono = Mono.just(importedActionCollectionList); - if (importingMetaDTO.getAppendToApp()) { + if (importingMetaDTO.getAppendToArtifact()) { importedActionCollectionMono = importedActionCollectionMono.map(importedActionCollectionList1 -> { - List importedNewPages = mappedImportableResourcesDTO.getPageNameMap().values().stream() + List importedNewPages = mappedImportableResourcesDTO.getPageOrModuleMap().values().stream() .distinct() + .map(branchAwareDomain -> (NewPage) branchAwareDomain) .toList(); - Map newToOldNameMap = mappedImportableResourcesDTO.getNewPageNameToOldPageNameMap(); + Map newToOldNameMap = mappedImportableResourcesDTO.getPageOrModuleNewNameToOldName(); for (NewPage newPage : importedNewPages) { String newPageName = newPage.getUnpublishedPage().getName(); @@ -194,7 +195,8 @@ public class ActionCollectionImportableServiceCEImpl implements ImportableServic .getPluginMap() .get(unpublishedCollection.getPluginId())); parentPage = updatePageInActionCollection( - unpublishedCollection, mappedImportableResourcesDTO.getPageNameMap()); + unpublishedCollection, (Map) + mappedImportableResourcesDTO.getPageOrModuleMap()); } if (publishedCollection != null && publishedCollection.getName() != null) { @@ -209,8 +211,9 @@ public class ActionCollectionImportableServiceCEImpl implements ImportableServic if (StringUtils.isEmpty(publishedCollection.getPageId())) { publishedCollection.setPageId(fallbackParentPageId); } - NewPage publishedCollectionPage = updatePageInActionCollection( - publishedCollection, mappedImportableResourcesDTO.getPageNameMap()); + NewPage publishedCollectionPage = + updatePageInActionCollection(publishedCollection, (Map) + mappedImportableResourcesDTO.getPageOrModuleMap()); parentPage = parentPage == null ? publishedCollectionPage : parentPage; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationImportServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationImportServiceCEImpl.java new file mode 100644 index 0000000000..0cec2011f0 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationImportServiceCEImpl.java @@ -0,0 +1,711 @@ +package com.appsmith.server.applications.base; + +import com.appsmith.external.constants.AnalyticsEvents; +import com.appsmith.external.models.Datasource; +import com.appsmith.external.models.DatasourceStorageDTO; +import com.appsmith.server.applications.imports.ApplicationImportServiceCE; +import com.appsmith.server.constants.FieldName; +import com.appsmith.server.datasources.base.DatasourceService; +import com.appsmith.server.domains.ActionCollection; +import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ApplicationPage; +import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.ImportableArtifact; +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.domains.Workspace; +import com.appsmith.server.dtos.ApplicationImportDTO; +import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.dtos.ArtifactExchangeJson; +import com.appsmith.server.dtos.ImportingMetaDTO; +import com.appsmith.server.dtos.MappedImportableResourcesDTO; +import com.appsmith.server.exceptions.AppsmithError; +import com.appsmith.server.exceptions.AppsmithException; +import com.appsmith.server.helpers.ce.ImportArtifactPermissionProvider; +import com.appsmith.server.imports.importable.ImportableService; +import com.appsmith.server.migrations.ApplicationVersion; +import com.appsmith.server.newactions.base.NewActionService; +import com.appsmith.server.services.AnalyticsService; +import com.appsmith.server.services.ApplicationPageService; +import com.appsmith.server.services.WorkspaceService; +import com.appsmith.server.solutions.ActionPermission; +import com.appsmith.server.solutions.ApplicationPermission; +import com.appsmith.server.solutions.DatasourcePermission; +import com.appsmith.server.solutions.PagePermission; +import com.appsmith.server.solutions.WorkspacePermission; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.dao.DuplicateKeyException; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.appsmith.server.helpers.ImportExportUtils.setPropertiesToExistingApplication; +import static com.appsmith.server.helpers.ImportExportUtils.setPublishedApplicationProperties; + +/** + * This service is currently not in use, however this service will replace ImportApplicationService + */ +@Slf4j +public class ApplicationImportServiceCEImpl implements ApplicationImportServiceCE { + + private final DatasourceService datasourceService; + private final WorkspaceService workspaceService; + private final ApplicationService applicationService; + private final ApplicationPageService applicationPageService; + private final NewActionService newActionService; + private final AnalyticsService analyticsService; + private final DatasourcePermission datasourcePermission; + private final WorkspacePermission workspacePermission; + private final ApplicationPermission applicationPermission; + private final PagePermission pagePermission; + private final ActionPermission actionPermission; + private final Gson gson; + private final ImportableService themeImportableService; + private final ImportableService newPageImportableService; + private final ImportableService customJSLibImportableService; + private final ImportableService newActionImportableService; + private final ImportableService actionCollectionImportableService; + + /** + * This map keeps constants which are specific to context of Application, parallel to other Artifacts. + * i.e. Artifact --> Application + * i.e. ID --> applicationId + */ + protected final Map applicationConstantsMap = new HashMap<>(); + + public ApplicationImportServiceCEImpl( + DatasourceService datasourceService, + WorkspaceService workspaceService, + ApplicationService applicationService, + ApplicationPageService applicationPageService, + NewActionService newActionService, + AnalyticsService analyticsService, + DatasourcePermission datasourcePermission, + WorkspacePermission workspacePermission, + ApplicationPermission applicationPermission, + PagePermission pagePermission, + ActionPermission actionPermission, + Gson gson, + ImportableService themeImportableService, + ImportableService newPageImportableService, + ImportableService customJSLibImportableService, + ImportableService newActionImportableService, + ImportableService actionCollectionImportableService) { + this.datasourceService = datasourceService; + this.workspaceService = workspaceService; + this.applicationService = applicationService; + this.applicationPageService = applicationPageService; + this.newActionService = newActionService; + this.analyticsService = analyticsService; + this.datasourcePermission = datasourcePermission; + this.workspacePermission = workspacePermission; + this.applicationPermission = applicationPermission; + this.pagePermission = pagePermission; + this.actionPermission = actionPermission; + this.gson = gson; + this.themeImportableService = themeImportableService; + this.newPageImportableService = newPageImportableService; + this.customJSLibImportableService = customJSLibImportableService; + this.newActionImportableService = newActionImportableService; + this.actionCollectionImportableService = actionCollectionImportableService; + applicationConstantsMap.putAll( + Map.of(FieldName.ARTIFACT_CONTEXT, FieldName.APPLICATION, FieldName.ID, FieldName.APPLICATION_ID)); + } + + @Override + public ApplicationJson extractArtifactExchangeJson(String jsonString) { + Type fileType = new TypeToken() {}.getType(); + return gson.fromJson(jsonString, fileType); + } + + @Override + public ImportArtifactPermissionProvider getImportArtifactPermissionProviderForImportingArtifact( + Set userPermissionGroups) { + return ImportArtifactPermissionProvider.builder( + applicationPermission, + pagePermission, + actionPermission, + datasourcePermission, + workspacePermission) + .requiredPermissionOnTargetWorkspace(workspacePermission.getApplicationCreatePermission()) + .permissionRequiredToCreateDatasource(true) + .permissionRequiredToEditDatasource(true) + .currentUserPermissionGroups(userPermissionGroups) + .build(); + } + + @Override + public ImportArtifactPermissionProvider getImportArtifactPermissionProviderForUpdatingArtifact( + Set userPermissions) { + return ImportArtifactPermissionProvider.builder( + applicationPermission, + pagePermission, + actionPermission, + datasourcePermission, + workspacePermission) + .requiredPermissionOnTargetWorkspace(workspacePermission.getReadPermission()) + .requiredPermissionOnTargetApplication(applicationPermission.getEditPermission()) + .allPermissionsRequired() + .currentUserPermissionGroups(userPermissions) + .build(); + } + + /** + * If the application is connected to git, then the user must have edit permission on the application. + * If user is importing application from Git, create application permission is already checked by the + * caller method, so it's not required here. + * Other permissions are not required because Git is the source of truth for the application and Git + * Sync is a system level operation to get the latest code from Git. If the user does not have some + * permissions on the Application e.g. create page, that'll be checked when the user tries to create a page. + */ + @Override + public ImportArtifactPermissionProvider getImportArtifactPermissionProviderForConnectingToGit( + Set userPermissions) { + return ImportArtifactPermissionProvider.builder( + applicationPermission, + pagePermission, + actionPermission, + datasourcePermission, + workspacePermission) + .requiredPermissionOnTargetApplication(applicationPermission.getEditPermission()) + .currentUserPermissionGroups(userPermissions) + .build(); + } + + @Override + public ImportArtifactPermissionProvider getImportArtifactPermissionProviderForRestoringSnapshot( + Set userPermissions) { + return ImportArtifactPermissionProvider.builder( + applicationPermission, + pagePermission, + actionPermission, + datasourcePermission, + workspacePermission) + .requiredPermissionOnTargetWorkspace(workspacePermission.getReadPermission()) + .requiredPermissionOnTargetApplication(applicationPermission.getEditPermission()) + .currentUserPermissionGroups(userPermissions) + .build(); + } + + @Override + public ImportArtifactPermissionProvider getImportArtifactPermissionProviderForMergingJsonWithArtifact( + Set userPermissions) { + return ImportArtifactPermissionProvider.builder( + applicationPermission, + pagePermission, + actionPermission, + datasourcePermission, + workspacePermission) + .requiredPermissionOnTargetWorkspace(workspacePermission.getReadPermission()) + .requiredPermissionOnTargetApplication(applicationPermission.getEditPermission()) + .allPermissionsRequired() + .currentUserPermissionGroups(userPermissions) + .build(); + } + + /** + * this method removes the application name from Json file as updating the app-name is not supported via import + * this avoids name conflict during import flow within workspace + * + * @param applicationId : ID of the application which has been saved. + * @param artifactExchangeJson : the ArtifactExchangeJSON which is getting imported + */ + @Override + public void setJsonArtifactNameToNullBeforeUpdate(String applicationId, ArtifactExchangeJson artifactExchangeJson) { + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + if (!StringUtils.isEmpty(applicationId) && (applicationJson).getExportedApplication() != null) { + applicationJson.getExportedApplication().setName(null); + applicationJson.getExportedApplication().setSlug(null); + } + } + + protected List> getPageDependentImportables( + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + Mono workspaceMono, + Mono importedApplicationMono, + ApplicationJson applicationJson) { + + // Requires pageNameMap, pageNameToOldNameMap, pluginMap and datasourceNameToIdMap to be present in importable + // resources. + // Updates actionResultDTO in importable resources. + // Also, directly updates required information in DB + Mono importedNewActionsMono = newActionImportableService.importEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + importedApplicationMono, + applicationJson, + false); + + // Requires pageNameMap, pageNameToOldNameMap, pluginMap and actionResultDTO to be present in importable + // resources. + // Updates actionCollectionResultDTO in importable resources. + // Also, directly updates required information in DB + Mono importedActionCollectionsMono = actionCollectionImportableService.importEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + importedApplicationMono, + applicationJson, + false); + + Mono combinedActionImportablesMono = importedNewActionsMono.then(importedActionCollectionsMono); + return List.of(combinedActionImportablesMono); + } + + @Override + public Mono getImportableArtifactDTO( + String workspaceId, String applicationId, ImportableArtifact importableArtifact) { + Application application = (Application) importableArtifact; + return findDatasourceByApplicationId(applicationId, workspaceId) + .zipWith(workspaceService.getDefaultEnvironmentId(workspaceId, null)) + .map(tuple2 -> { + List datasources = tuple2.getT1(); + String environmentId = tuple2.getT2(); + ApplicationImportDTO applicationImportDTO = new ApplicationImportDTO(); + applicationImportDTO.setApplication(application); + Boolean isUnConfiguredDatasource = datasources.stream().anyMatch(datasource -> { + DatasourceStorageDTO datasourceStorageDTO = + datasource.getDatasourceStorages().get(environmentId); + if (datasourceStorageDTO == null) { + // If this environment has not been configured, + // We do not expect to find a storage, user will have to reconfigure + return Boolean.FALSE; + } + return Boolean.FALSE.equals(datasourceStorageDTO.getIsConfigured()); + }); + if (Boolean.TRUE.equals(isUnConfiguredDatasource)) { + applicationImportDTO.setIsPartialImport(true); + applicationImportDTO.setUnConfiguredDatasourceList(datasources); + } else { + applicationImportDTO.setIsPartialImport(false); + } + return applicationImportDTO; + }); + } + + @Override + public Mono> findDatasourceByApplicationId(String applicationId, String workspaceId) { + // TODO: Investigate further why datasourcePermission.getReadPermission() is not being used. + Mono> listMono = datasourceService + .getAllByWorkspaceIdWithStorages(workspaceId, Optional.empty()) + .collectList(); + return newActionService + .findAllByApplicationIdAndViewMode(applicationId, false, Optional.empty(), Optional.empty()) + .collectList() + .zipWith(listMono) + .flatMap(objects -> { + List datasourceList = objects.getT2(); + List actionList = objects.getT1(); + List usedDatasource = actionList.stream() + .map(newAction -> newAction + .getUnpublishedAction() + .getDatasource() + .getId()) + .toList(); + + datasourceList.removeIf(datasource -> !usedDatasource.contains(datasource.getId())); + + return Mono.just(datasourceList); + }); + } + + @Override + public void updateArtifactExchangeJsonWithEntitiesToBeConsumed( + ArtifactExchangeJson artifactExchangeJson, List entitiesToImport) { + + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + + // Update the application JSON to prepare it for merging inside an existing application + if (applicationJson.getExportedApplication() != null) { + // setting some properties to null so that target application is not updated by these properties + applicationJson.getExportedApplication().setName(null); + applicationJson.getExportedApplication().setSlug(null); + applicationJson.getExportedApplication().setForkingEnabled(null); + applicationJson.getExportedApplication().setForkWithConfiguration(null); + applicationJson.getExportedApplication().setClonedFromApplicationId(null); + applicationJson.getExportedApplication().setExportWithConfiguration(null); + } + + // need to remove git sync id. Also filter pages if pageToImport is not empty + if (applicationJson.getPageList() != null) { + List applicationPageList = + new ArrayList<>(applicationJson.getPageList().size()); + List pageNames = + new ArrayList<>(applicationJson.getPageList().size()); + List importedNewPageList = applicationJson.getPageList().stream() + .filter(newPage -> newPage.getUnpublishedPage() != null + && (CollectionUtils.isEmpty(entitiesToImport) + || entitiesToImport.contains( + newPage.getUnpublishedPage().getName()))) + .peek(newPage -> { + ApplicationPage applicationPage = new ApplicationPage(); + applicationPage.setId(newPage.getUnpublishedPage().getName()); + applicationPage.setIsDefault(false); + applicationPageList.add(applicationPage); + pageNames.add(applicationPage.getId()); + }) + .peek(newPage -> newPage.setGitSyncId(null)) + .collect(Collectors.toList()); + applicationJson.setPageList(importedNewPageList); + // Remove the pages from the exported Application inside the json based on the pagesToImport + applicationJson.getExportedApplication().setPages(applicationPageList); + applicationJson.getExportedApplication().setPublishedPages(applicationPageList); + } + if (applicationJson.getActionList() != null) { + List importedNewActionList = applicationJson.getActionList().stream() + .filter(newAction -> newAction.getUnpublishedAction() != null + && (CollectionUtils.isEmpty(entitiesToImport) + || entitiesToImport.contains( + newAction.getUnpublishedAction().getPageId()))) + .peek(newAction -> + newAction.setGitSyncId(null)) // setting this null so that this action can be imported again + .collect(Collectors.toList()); + applicationJson.setActionList(importedNewActionList); + } + if (applicationJson.getActionCollectionList() != null) { + List importedActionCollectionList = applicationJson.getActionCollectionList().stream() + .filter(actionCollection -> (CollectionUtils.isEmpty(entitiesToImport) + || entitiesToImport.contains( + actionCollection.getUnpublishedCollection().getPageId()))) + .peek(actionCollection -> actionCollection.setGitSyncId( + null)) // setting this null so that this action collection can be imported again + .collect(Collectors.toList()); + applicationJson.setActionCollectionList(importedActionCollectionList); + } + } + + /** + * To send analytics event for import and export of application + * + * @param application Application object imported or exported + * @param event AnalyticsEvents event + * @return The application which is imported or exported + */ + private Mono sendImportExportApplicationAnalyticsEvent( + Application application, AnalyticsEvents event) { + return workspaceService.getById(application.getWorkspaceId()).flatMap(workspace -> { + final Map eventData = Map.of( + FieldName.APPLICATION, application, + FieldName.WORKSPACE, workspace); + + final Map data = Map.of( + FieldName.APPLICATION_ID, application.getId(), + FieldName.WORKSPACE_ID, workspace.getId(), + FieldName.EVENT_DATA, eventData); + + return analyticsService.sendObjectEvent(event, application, data); + }); + } + + /** + * To send analytics event for import and export of application + * + * @param applicationId ID of application being imported or exported + * @param event AnalyticsEvents event + * @return The application which is imported or exported + */ + private Mono sendImportExportApplicationAnalyticsEvent(String applicationId, AnalyticsEvents event) { + return applicationService + .findById(applicationId, Optional.empty()) + .flatMap(application -> sendImportExportApplicationAnalyticsEvent(application, event)); + } + + @Override + public void syncClientAndSchemaVersion(ArtifactExchangeJson artifactExchangeJson) { + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + Application importedApplication = applicationJson.getExportedApplication(); + importedApplication.setServerSchemaVersion(applicationJson.getServerSchemaVersion()); + importedApplication.setClientSchemaVersion(applicationJson.getClientSchemaVersion()); + } + + @Override + public Mono generateArtifactSpecificImportableEntities( + ArtifactExchangeJson artifactExchangeJson, + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO) { + + // Persists relevant information and updates mapped resources + return customJSLibImportableService.importEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + null, + null, + (ApplicationJson) artifactExchangeJson, + false); + } + + @Override + public Mono isArtifactConnectedToGit(String artifactId) { + return applicationService.isApplicationConnectedToGit(artifactId); + } + + @Override + public Mono updateAndSaveArtifactInContext( + ImportableArtifact importableArtifact, + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + Mono currentUserMono) { + Mono importApplicationMono = Mono.just((Application) importableArtifact) + .map(application -> { + if (application.getApplicationVersion() == null) { + application.setApplicationVersion(ApplicationVersion.EARLIEST_VERSION); + } + application.setViewMode(false); + application.setForkWithConfiguration(null); + application.setExportWithConfiguration(null); + application.setWorkspaceId(importingMetaDTO.getWorkspaceId()); + application.setIsPublic(null); + application.setPolicies(null); + Map> mapOfApplicationPageList = Map.of( + FieldName.PUBLISHED, + application.getPublishedPages(), + FieldName.UNPUBLISHED, + application.getPages()); + mappedImportableResourcesDTO + .getResourceStoreFromArtifactExchangeJson() + .putAll(mapOfApplicationPageList); + application.setPages(null); + application.setPublishedPages(null); + return application; + }) + .map(application -> { + application.setUnpublishedCustomJSLibs( + new HashSet<>(mappedImportableResourcesDTO.getInstalledJsLibsList())); + return application; + }); + + importApplicationMono = importApplicationMono.zipWith(currentUserMono).map(objects -> { + Application application = objects.getT1(); + application.setModifiedBy(objects.getT2().getUsername()); + return application; + }); + + if (StringUtils.isEmpty(importingMetaDTO.getArtifactId())) { + importApplicationMono = importApplicationMono.flatMap(application -> { + return applicationPageService.createOrUpdateSuffixedApplication(application, application.getName(), 0); + }); + } else { + Mono existingApplicationMono = applicationService + .findById( + importingMetaDTO.getArtifactId(), + importingMetaDTO.getPermissionProvider().getRequiredPermissionOnTargetApplication()) + .switchIfEmpty(Mono.defer(() -> { + log.error( + "No application found with id: {} and permission: {}", + importingMetaDTO.getArtifactId(), + importingMetaDTO.getPermissionProvider().getRequiredPermissionOnTargetApplication()); + return Mono.error(new AppsmithException( + AppsmithError.ACL_NO_RESOURCE_FOUND, + FieldName.APPLICATION, + importingMetaDTO.getArtifactId())); + })) + .cache(); + + // this can be a git sync, import page from template, update app with json, restore snapshot + if (importingMetaDTO.getAppendToArtifact()) { // we don't need to do anything with the imported application + importApplicationMono = existingApplicationMono; + } else { + importApplicationMono = importApplicationMono + .zipWith(existingApplicationMono) + .map(objects -> { + Application newApplication = objects.getT1(); + Application existingApplication = objects.getT2(); + // This method sets the published mode properties in the imported + // application.When a user imports an application from the git repo, + // since the git only stores the unpublished version, the current + // deployed version in the newly imported app is not updated. + // This function sets the initial deployed version to the same as the + // edit mode one. + setPublishedApplicationProperties(newApplication); + setPropertiesToExistingApplication(newApplication, existingApplication); + return existingApplication; + }) + .flatMap(application -> { + Mono parentApplicationMono; + if (application.getGitApplicationMetadata() != null) { + parentApplicationMono = applicationService.findById( + application.getGitApplicationMetadata().getDefaultApplicationId()); + } else { + parentApplicationMono = Mono.just(application); + } + return Mono.zip(Mono.just(application), parentApplicationMono); + }) + .flatMap(objects -> { + Application application = objects.getT1(); + Application parentApplication = objects.getT2(); + application.setPolicies(parentApplication.getPolicies()); + return applicationService + .save(application) + .onErrorResume(DuplicateKeyException.class, error -> { + if (error.getMessage() != null) { + return applicationPageService.createOrUpdateSuffixedApplication( + application, application.getName(), 0); + } + throw error; + }); + }); + } + } + return importApplicationMono + .elapsed() + .map(tuples -> { + log.debug("time to create or update application object: {}", tuples.getT1()); + return tuples.getT2(); + }) + .onErrorResume(error -> { + log.error("Error while creating or updating application object", error); + return Mono.error(error); + }); + } + + @Override + public Mono updateImportableArtifact(ImportableArtifact importableArtifact) { + return Mono.just((Application) importableArtifact).flatMap(application -> { + log.info("Imported application with id {}", application.getId()); + // Need to update the application object with updated pages and publishedPages + Application updateApplication = new Application(); + updateApplication.setPages(application.getPages()); + updateApplication.setPublishedPages(application.getPublishedPages()); + + return applicationService.update(application.getId(), updateApplication); + }); + } + + @Override + public Mono updateImportableEntities( + ImportableArtifact importableContext, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + ImportingMetaDTO importingMetaDTO) { + return Mono.just((Application) importableContext).flatMap(application -> { + return newActionImportableService + .updateImportedEntities(application, importingMetaDTO, mappedImportableResourcesDTO, false) + .then(newPageImportableService.updateImportedEntities( + application, importingMetaDTO, mappedImportableResourcesDTO, false)) + .thenReturn(application); + }); + } + + @Override + public Map createImportAnalyticsData( + ArtifactExchangeJson artifactExchangeJson, ImportableArtifact importableArtifact) { + + Application application = (Application) importableArtifact; + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + + int jsObjectCount = CollectionUtils.isEmpty(applicationJson.getActionCollectionList()) + ? 0 + : applicationJson.getActionCollectionList().size(); + int actionCount = CollectionUtils.isEmpty(applicationJson.getActionList()) + ? 0 + : applicationJson.getActionList().size(); + + final Map data = Map.of( + FieldName.APPLICATION_ID, + application.getId(), + FieldName.WORKSPACE_ID, + application.getWorkspaceId(), + "pageCount", + applicationJson.getPageList().size(), + "actionCount", + actionCount, + "JSObjectCount", + jsObjectCount); + + return data; + } + + @Override + public Flux generateArtifactContextIndependentImportableEntities( + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + Mono workspaceMono, + Mono importableArtifactMono, + ArtifactExchangeJson artifactExchangeJson) { + return importableArtifactMono.flatMapMany(importableContext -> { + Application application = (Application) importableContext; + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + + // Updates pageNametoIdMap and pageNameMap in importable resources. + // Also, directly updates required information in DB + Mono importedPagesMono = newPageImportableService.importEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + Mono.just(application), + applicationJson, + false); + + // Directly updates required theme information in DB + Mono importedThemesMono = themeImportableService.importEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + Mono.just(application), + applicationJson, + false, + true); + + return Flux.merge(List.of(importedPagesMono, importedThemesMono)); + }); + } + + @Override + public Flux generateArtifactContextDependentImportableEntities( + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + Mono workspaceMono, + Mono importableArtifactMono, + ArtifactExchangeJson artifactExchangeJson) { + + return importableArtifactMono.flatMapMany(importableContext -> { + Application application = (Application) importableContext; + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + + List> pageDependentImportables = getPageDependentImportables( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + Mono.just(application), + applicationJson); + + return Flux.merge(pageDependentImportables); + }); + } + + @Override + public String validateArtifactSpecificFields(ArtifactExchangeJson artifactExchangeJson) { + ApplicationJson importedDoc = (ApplicationJson) artifactExchangeJson; + String errorField = ""; + if (CollectionUtils.isEmpty(importedDoc.getPageList())) { + errorField = FieldName.PAGE_LIST; + } else if (importedDoc.getActionList() == null) { + errorField = FieldName.ACTIONS; + } else if (importedDoc.getDatasourceList() == null) { + errorField = FieldName.DATASOURCE; + } + + return errorField; + } + + @Override + public Map getArtifactSpecificConstantsMap() { + return applicationConstantsMap; + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationImportServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationImportServiceImpl.java new file mode 100644 index 0000000000..04dead67bf --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationImportServiceImpl.java @@ -0,0 +1,63 @@ +package com.appsmith.server.applications.base; + +import com.appsmith.server.applications.imports.ApplicationImportService; +import com.appsmith.server.datasources.base.DatasourceService; +import com.appsmith.server.domains.ActionCollection; +import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.NewAction; +import com.appsmith.server.domains.NewPage; +import com.appsmith.server.domains.Theme; +import com.appsmith.server.imports.importable.ImportableService; +import com.appsmith.server.newactions.base.NewActionService; +import com.appsmith.server.services.AnalyticsService; +import com.appsmith.server.services.ApplicationPageService; +import com.appsmith.server.services.WorkspaceService; +import com.appsmith.server.solutions.ActionPermission; +import com.appsmith.server.solutions.ApplicationPermission; +import com.appsmith.server.solutions.DatasourcePermission; +import com.appsmith.server.solutions.PagePermission; +import com.appsmith.server.solutions.WorkspacePermission; +import com.google.gson.Gson; +import org.springframework.stereotype.Component; + +@Component +public class ApplicationImportServiceImpl extends ApplicationImportServiceCEImpl implements ApplicationImportService { + + public ApplicationImportServiceImpl( + DatasourceService datasourceService, + WorkspaceService workspaceService, + ApplicationService applicationService, + ApplicationPageService applicationPageService, + NewActionService newActionService, + AnalyticsService analyticsService, + DatasourcePermission datasourcePermission, + WorkspacePermission workspacePermission, + ApplicationPermission applicationPermission, + PagePermission pagePermission, + ActionPermission actionPermission, + Gson gson, + ImportableService themeImportableService, + ImportableService newPageImportableService, + ImportableService customJSLibImportableService, + ImportableService newActionImportableService, + ImportableService actionCollectionImportableService) { + super( + datasourceService, + workspaceService, + applicationService, + applicationPageService, + newActionService, + analyticsService, + datasourcePermission, + workspacePermission, + applicationPermission, + pagePermission, + actionPermission, + gson, + themeImportableService, + newPageImportableService, + customJSLibImportableService, + newActionImportableService, + actionCollectionImportableService); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/imports/ApplicationImportService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/imports/ApplicationImportService.java new file mode 100644 index 0000000000..8804065eb9 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/imports/ApplicationImportService.java @@ -0,0 +1,3 @@ +package com.appsmith.server.applications.imports; + +public interface ApplicationImportService extends ApplicationImportServiceCE {} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/imports/ApplicationImportServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/imports/ApplicationImportServiceCE.java new file mode 100644 index 0000000000..99d17d82fc --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/imports/ApplicationImportServiceCE.java @@ -0,0 +1,16 @@ +package com.appsmith.server.applications.imports; + +import com.appsmith.external.models.Datasource; +import com.appsmith.server.domains.Application; +import com.appsmith.server.dtos.ApplicationImportDTO; +import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.imports.internal.ContextBasedImportService; +import reactor.core.publisher.Mono; + +import java.util.List; + +public interface ApplicationImportServiceCE + extends ContextBasedImportService { + + Mono> findDatasourceByApplicationId(String applicationId, String orgId); +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ArtifactJsonType.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ArtifactJsonType.java new file mode 100644 index 0000000000..b26aff45b4 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ArtifactJsonType.java @@ -0,0 +1,11 @@ +package com.appsmith.server.constants; + +/** + * The type of Json which the system deals with, it could be application, packages, or workflows. + * Collectively called Artifact + */ +public enum ArtifactJsonType { + APPLICATION, + PACKAGE, + WORKFLOW +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ce/FieldNameCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ce/FieldNameCE.java index 041365f32c..c36ec7803f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ce/FieldNameCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ce/FieldNameCE.java @@ -196,4 +196,7 @@ public class FieldNameCE { public static final String INSTANCE_ID = "instanceId"; public static final String IP_ADDRESS = "ipAddress"; public static final String VERSION = "version"; + public static final String PUBLISHED = "published"; + public static final String UNPUBLISHED = "unpublished"; + public static final String ARTIFACT_CONTEXT = "artifactContext"; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java index b699de97f8..495c0624f4 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java @@ -6,6 +6,7 @@ import com.appsmith.server.controllers.ce.ApplicationControllerCE; import com.appsmith.server.exports.internal.ExportApplicationService; import com.appsmith.server.exports.internal.PartialExportService; import com.appsmith.server.fork.internal.ApplicationForkingService; +import com.appsmith.server.imports.importable.ImportService; import com.appsmith.server.imports.internal.ImportApplicationService; import com.appsmith.server.imports.internal.PartialImportService; import com.appsmith.server.services.ApplicationPageService; @@ -29,7 +30,8 @@ public class ApplicationController extends ApplicationControllerCE { ThemeService themeService, ApplicationSnapshotService applicationSnapshotService, PartialExportService partialExportService, - PartialImportService partialImportService) { + PartialImportService partialImportService, + ImportService importService) { super( service, applicationPageService, @@ -40,6 +42,7 @@ public class ApplicationController extends ApplicationControllerCE { themeService, applicationSnapshotService, partialExportService, - partialImportService); + partialImportService, + importService); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java index a05ec6e194..ef798c9228 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java @@ -14,6 +14,7 @@ import com.appsmith.server.dtos.ApplicationImportDTO; import com.appsmith.server.dtos.ApplicationJson; import com.appsmith.server.dtos.ApplicationPagesDTO; import com.appsmith.server.dtos.GitAuthDTO; +import com.appsmith.server.dtos.ImportableArtifactDTO; import com.appsmith.server.dtos.PartialExportFileDTO; import com.appsmith.server.dtos.ReleaseItemsDTO; import com.appsmith.server.dtos.ResponseDTO; @@ -23,6 +24,7 @@ import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.exports.internal.ExportApplicationService; import com.appsmith.server.exports.internal.PartialExportService; import com.appsmith.server.fork.internal.ApplicationForkingService; +import com.appsmith.server.imports.importable.ImportService; import com.appsmith.server.imports.internal.ImportApplicationService; import com.appsmith.server.imports.internal.PartialImportService; import com.appsmith.server.services.ApplicationPageService; @@ -69,6 +71,7 @@ public class ApplicationControllerCE extends BaseController> importApplicationFromFile( + public Mono> importApplicationFromFile( @RequestPart("file") Mono fileMono, @PathVariable String workspaceId, @RequestParam(name = FieldName.APPLICATION_ID, required = false) String applicationId) { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/imports/DatasourceImportableServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/imports/DatasourceImportableServiceCEImpl.java index 24b84c7bb5..faa879a7d2 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/imports/DatasourceImportableServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/imports/DatasourceImportableServiceCEImpl.java @@ -13,13 +13,15 @@ import com.appsmith.external.models.OAuth2; import com.appsmith.server.constants.FieldName; import com.appsmith.server.datasources.base.DatasourceService; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ImportableArtifact; import com.appsmith.server.domains.Workspace; import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.dtos.ArtifactExchangeJson; import com.appsmith.server.dtos.ImportingMetaDTO; import com.appsmith.server.dtos.MappedImportableResourcesDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; -import com.appsmith.server.helpers.ce.ImportApplicationPermissionProvider; +import com.appsmith.server.helpers.ce.ImportArtifactPermissionProvider; import com.appsmith.server.imports.importable.ImportableServiceCE; import com.appsmith.server.services.SequenceService; import com.appsmith.server.services.WorkspaceService; @@ -54,6 +56,28 @@ public class DatasourceImportableServiceCEImpl implements ImportableServiceCE importEntities( + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + Mono workspaceMono, + Mono importContextMono, + ArtifactExchangeJson importableContextJson, + boolean isPartialImport, + boolean isContextAgnostic) { + return importContextMono.flatMap(importableContext -> { + Application application = (Application) importableContext; + ApplicationJson applicationJson = (ApplicationJson) importableContextJson; + return importEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + Mono.just(application), + applicationJson, + isPartialImport); + }); + } + // Requires pluginMap to be present in importable resources. // Updates datasourceNameToIdMap in importable resources. // Also directly updates required information in DB @@ -71,7 +95,7 @@ public class DatasourceImportableServiceCEImpl implements ImportableServiceCE> existingDatasourceMono = - getExistingDatasourceMono(importingMetaDTO.getApplicationId(), existingDatasourceFlux); + getExistingDatasourceMono(importingMetaDTO.getArtifactId(), existingDatasourceFlux); Mono> datasourceMapMono = importDatasources( applicationJson, existingDatasourceMono, @@ -247,7 +271,7 @@ public class DatasourceImportableServiceCEImpl implements ImportableServiceCE unConfiguredDatasourceList; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ArtifactExchangeJson.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ArtifactExchangeJson.java new file mode 100644 index 0000000000..3e5c75c2a5 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ArtifactExchangeJson.java @@ -0,0 +1,5 @@ +package com.appsmith.server.dtos; + +import com.appsmith.server.dtos.ce.ArtifactExchangeJsonCE; + +public interface ArtifactExchangeJson extends ArtifactExchangeJsonCE {} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ImportableArtifactDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ImportableArtifactDTO.java new file mode 100644 index 0000000000..32b8f2606e --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ImportableArtifactDTO.java @@ -0,0 +1,3 @@ +package com.appsmith.server.dtos; + +public abstract class ImportableArtifactDTO {} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ImportingMetaDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ImportingMetaDTO.java index 6ea04a37f8..1cd11b253d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ImportingMetaDTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ImportingMetaDTO.java @@ -1,6 +1,6 @@ package com.appsmith.server.dtos; -import com.appsmith.server.helpers.ce.ImportApplicationPermissionProvider; +import com.appsmith.server.helpers.ce.ImportArtifactPermissionProvider; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -14,9 +14,19 @@ import java.util.Set; @Builder(toBuilder = true) public class ImportingMetaDTO { String workspaceId; - String applicationId; + /** + * this represents any parent entity's id which could be imported. + * e.g. application, packages, workflows + */ + String artifactId; + String branchName; - Boolean appendToApp; - ImportApplicationPermissionProvider permissionProvider; + + /** + * this flag is for verifying whether the artifact in focus needs to be updated with the given provided json + */ + Boolean appendToArtifact; + + ImportArtifactPermissionProvider permissionProvider; Set currentUserPermissionGroups; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ApplicationJsonCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ApplicationJsonCE.java index b366c04c6a..81d380131d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ApplicationJsonCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ApplicationJsonCE.java @@ -5,16 +5,18 @@ import com.appsmith.external.models.DatasourceStorageStructure; import com.appsmith.external.models.DecryptedSensitiveFields; import com.appsmith.external.models.InvisibleActionFields; import com.appsmith.external.views.Views; +import com.appsmith.server.constants.ArtifactJsonType; import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.ImportableArtifact; import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.Theme; +import com.appsmith.server.dtos.ArtifactExchangeJson; import com.fasterxml.jackson.annotation.JsonView; import lombok.Getter; import lombok.Setter; -import org.springframework.data.annotation.Transient; import java.util.List; import java.util.Map; @@ -27,17 +29,15 @@ import java.util.Set; */ @Getter @Setter -public class ApplicationJsonCE { +public class ApplicationJsonCE implements ArtifactExchangeJson { // To convey the schema version of the client and will be used to check if the imported file is compatible with // current DSL schema - @Transient @JsonView({Views.Public.class, Views.Export.class}) Integer clientSchemaVersion; // To convey the schema version of the server and will be used to check if the imported file is compatible with // current DB schema - @Transient @JsonView({Views.Public.class, Views.Export.class}) Integer serverSchemaVersion; @@ -114,4 +114,19 @@ public class ApplicationJsonCE { @JsonView({Views.Public.class, Views.Export.class}) String widgets; + + @Override + public ArtifactJsonType getArtifactJsonType() { + return ArtifactJsonType.APPLICATION; + } + + @Override + public ImportableArtifact getImportableArtifact() { + return this.getExportedApplication(); + } + + @Override + public List getCustomJsLibFromArtifact() { + return this.getCustomJSLibList(); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ArtifactExchangeJsonCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ArtifactExchangeJsonCE.java new file mode 100644 index 0000000000..520af3f880 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ArtifactExchangeJsonCE.java @@ -0,0 +1,24 @@ +package com.appsmith.server.dtos.ce; + +import com.appsmith.server.constants.ArtifactJsonType; +import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.ImportableArtifact; + +import java.util.List; + +public interface ArtifactExchangeJsonCE { + + Integer getClientSchemaVersion(); + + void setClientSchemaVersion(Integer clientSchemaVersion); + + Integer getServerSchemaVersion(); + + void setServerSchemaVersion(Integer serverSchemaVersion); + + ArtifactJsonType getArtifactJsonType(); + + ImportableArtifact getImportableArtifact(); + + List getCustomJsLibFromArtifact(); +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/MappedImportableResourcesCE_DTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/MappedImportableResourcesCE_DTO.java index 55bec0245f..d0d8d015cf 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/MappedImportableResourcesCE_DTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/MappedImportableResourcesCE_DTO.java @@ -1,6 +1,6 @@ package com.appsmith.server.dtos.ce; -import com.appsmith.server.domains.NewPage; +import com.appsmith.external.models.BranchAwareDomain; import com.appsmith.server.dtos.CustomJSLibContextDTO; import com.appsmith.server.dtos.ImportActionCollectionResultDTO; import com.appsmith.server.dtos.ImportActionResultDTO; @@ -16,13 +16,27 @@ import java.util.Map; @Data public class MappedImportableResourcesCE_DTO { + // Artifacts independent entities Map pluginMap = new HashMap<>(); Map datasourceNameToIdMap = new HashMap<>(); + // Artifact dependent + // This attribute is re-usable across artifacts according to the needs + Map pageOrModuleNewNameToOldName; + + /** + * Attribute used to carry objects specific to the context of the Artifacts. + * In case of application it carries the NewPage entity + * In case of packages it would carry modules + */ + Map pageOrModuleMap; + + // Artifact dependent and common List installedJsLibsList; - Map newPageNameToOldPageNameMap; - Map pageNameMap; ImportActionResultDTO actionResultDTO; ImportActionCollectionResultDTO actionCollectionResultDTO; ImportedActionAndCollectionMapsDTO actionAndCollectionMapsDTO = new ImportedActionAndCollectionMapsDTO(); + + // This is being used to carry the resources from ArtifactExchangeJson + Map resourceStoreFromArtifactExchangeJson = new HashMap<>(); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/ImportApplicationPermissionProvider.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/ImportArtifactPermissionProvider.java similarity index 91% rename from app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/ImportApplicationPermissionProvider.java rename to app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/ImportArtifactPermissionProvider.java index b4ed7061d0..2295fdaf34 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/ImportApplicationPermissionProvider.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/ImportArtifactPermissionProvider.java @@ -9,6 +9,7 @@ import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.Workspace; import com.appsmith.server.solutions.ActionPermission; import com.appsmith.server.solutions.ApplicationPermission; +import com.appsmith.server.solutions.ArtifactPermission; import com.appsmith.server.solutions.DatasourcePermission; import com.appsmith.server.solutions.PagePermission; import com.appsmith.server.solutions.WorkspacePermission; @@ -39,9 +40,9 @@ import java.util.Set; */ @AllArgsConstructor @Getter -public class ImportApplicationPermissionProvider { +public class ImportArtifactPermissionProvider { @Getter(AccessLevel.NONE) - private final ApplicationPermission applicationPermission; + private final ArtifactPermission artifactPermission; @Getter(AccessLevel.NONE) private final PagePermission pagePermission; @@ -124,7 +125,7 @@ public class ImportApplicationPermissionProvider { if (!permissionRequiredToCreatePage) { return true; } - return hasPermission(applicationPermission.getPageCreatePermission(), application); + return hasPermission(((ApplicationPermission) artifactPermission).getPageCreatePermission(), application); } public boolean canCreateAction(NewPage page) { @@ -142,19 +143,19 @@ public class ImportApplicationPermissionProvider { } public static Builder builder( - ApplicationPermission applicationPermission, + ArtifactPermission artifactPermission, PagePermission pagePermission, ActionPermission actionPermission, DatasourcePermission datasourcePermission, WorkspacePermission workspacePermission) { return new Builder( - applicationPermission, pagePermission, actionPermission, datasourcePermission, workspacePermission); + artifactPermission, pagePermission, actionPermission, datasourcePermission, workspacePermission); } @Setter @Accessors(chain = true, fluent = true) public static class Builder { - private final ApplicationPermission applicationPermission; + private final ArtifactPermission artifactPermission; private final PagePermission pagePermission; private final ActionPermission actionPermission; private final DatasourcePermission datasourcePermission; @@ -173,12 +174,12 @@ public class ImportApplicationPermissionProvider { private boolean permissionRequiredToEditDatasource; private Builder( - ApplicationPermission applicationPermission, + ArtifactPermission artifactPermission, PagePermission pagePermission, ActionPermission actionPermission, DatasourcePermission datasourcePermission, WorkspacePermission workspacePermission) { - this.applicationPermission = applicationPermission; + this.artifactPermission = artifactPermission; this.pagePermission = pagePermission; this.actionPermission = actionPermission; this.datasourcePermission = datasourcePermission; @@ -195,11 +196,11 @@ public class ImportApplicationPermissionProvider { return this; } - public ImportApplicationPermissionProvider build() { + public ImportArtifactPermissionProvider build() { // IMPORTANT: make sure that we've added unit tests for all the properties. // Otherwise, we may end up passing value of one attribute of same type to another. - return new ImportApplicationPermissionProvider( - applicationPermission, + return new ImportArtifactPermissionProvider( + artifactPermission, pagePermission, actionPermission, datasourcePermission, diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/importable/ImportService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/importable/ImportService.java new file mode 100644 index 0000000000..ad0d620388 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/importable/ImportService.java @@ -0,0 +1,3 @@ +package com.appsmith.server.imports.importable; + +public interface ImportService extends ImportServiceCE {} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/importable/ImportServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/importable/ImportServiceCE.java new file mode 100644 index 0000000000..e488c603ea --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/importable/ImportServiceCE.java @@ -0,0 +1,96 @@ +package com.appsmith.server.imports.importable; + +import com.appsmith.server.constants.ArtifactJsonType; +import com.appsmith.server.domains.ImportableArtifact; +import com.appsmith.server.dtos.ArtifactExchangeJson; +import com.appsmith.server.dtos.ImportableArtifactDTO; +import com.appsmith.server.imports.internal.ContextBasedImportService; +import org.springframework.http.codec.multipart.Part; +import reactor.core.publisher.Mono; + +import java.util.List; + +public interface ImportServiceCE { + + /** + * This method provides the importService specific to the artifact based on the ArtifactJsonType. + * time complexity is O(1), as the map from which the service is being passes is pre-computed + * @param artifactExchangeJson : Entity Json which is implementing the artifactExchangeJson + * @return import-service which is implementing the ContextBasedServiceInterface + */ + ContextBasedImportService< + ? extends ImportableArtifact, ? extends ImportableArtifactDTO, ? extends ArtifactExchangeJson> + getContextBasedImportService(ArtifactExchangeJson artifactExchangeJson); + + /** + * This method provides the importService specific to the artifact based on the ArtifactJsonType. + * time complexity is O(1), as the map from which the service is being passes is pre-computed + * @param artifactJsonType : Type of Json serialisation + * @return import-service which is implementing the ContextBasedServiceInterface + */ + ContextBasedImportService< + ? extends ImportableArtifact, ? extends ImportableArtifactDTO, ? extends ArtifactExchangeJson> + getContextBasedImportService(ArtifactJsonType artifactJsonType); + + /** + * This method takes a file part and makes a Json entity which implements the ArtifactExchangeJson interface + * + * @param filePart : filePart from which the contents would be made + * @param artifactJsonType : type of the dataExchangeJson + * @return : Json entity which implements ArtifactExchangeJson + */ + Mono extractArtifactExchangeJson(Part filePart, ArtifactJsonType artifactJsonType); + + /** + * Hydrates an ImportableArtifact within the specified workspace by saving the provided JSON file. + * + * @param filePart The filePart representing the ImportableArtifact object to be saved. + * The ImportableArtifact implements the ImportableArtifact interface. + * @param workspaceId The identifier for the destination workspace. + * @param artifactId + * @param artifactJsonType + */ + Mono extractArtifactExchangeJsonAndSaveArtifact( + Part filePart, String workspaceId, String artifactId, ArtifactJsonType artifactJsonType); + + /** + * Saves the provided ArtifactExchangeJson within the specified workspace. + * + * @param workspaceId The identifier for the destination workspace. + * @param artifactExchangeJson The JSON file representing the ImportableArtifact object to be saved. + * The ImportableArtifact implements the ImportableArtifact interface. + */ + Mono importNewArtifactInWorkspaceFromJson( + String workspaceId, ArtifactExchangeJson artifactExchangeJson); + + Mono updateNonGitConnectedArtifactFromJson( + String workspaceId, String artifactId, ArtifactExchangeJson artifactExchangeJson); + + /** + * Updates an existing ImportableArtifact connected to Git within the specified workspace. + * + * @param workspaceId The identifier for the destination workspace. + * @param artifactId The ImportableArtifact id that needs to be updated with the new resources. + * @param artifactExchangeJson The ImportableArtifact JSON containing necessary information to update the ImportableArtifact. + * @param branchName The name of the Git branch. Set to null if not connected to Git. + * @return The updated ImportableArtifact stored in the database. + */ + Mono importArtifactInWorkspaceFromGit( + String workspaceId, String artifactId, ArtifactExchangeJson artifactExchangeJson, String branchName); + + Mono mergeArtifactExchangeJsonWithImportableArtifact( + String workspaceId, + String artifactId, + String branchName, + ArtifactExchangeJson artifactExchangeJson, + List entitiesToImport); + + Mono restoreSnapshot( + String workspaceId, ArtifactExchangeJson artifactExchangeJson, String artifactId, String branchName); + + Mono getArtifactImportDTO( + String workspaceId, + String artifactId, + ImportableArtifact importableArtifact, + ArtifactExchangeJson artifactExchangeJson); +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/importable/ImportableServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/importable/ImportableServiceCE.java index 05ac52dcf6..4397f5f922 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/importable/ImportableServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/importable/ImportableServiceCE.java @@ -2,8 +2,10 @@ package com.appsmith.server.imports.importable; import com.appsmith.external.models.BaseDomain; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ImportableArtifact; import com.appsmith.server.domains.Workspace; import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.dtos.ArtifactExchangeJson; import com.appsmith.server.dtos.ImportingMetaDTO; import com.appsmith.server.dtos.MappedImportableResourcesDTO; import reactor.core.publisher.Mono; @@ -25,4 +27,15 @@ public interface ImportableServiceCE { boolean isPartialImport) { return null; } + + default Mono importEntities( + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + Mono workspaceMono, + Mono importContextMono, + ArtifactExchangeJson importableContextJson, + boolean isPartialImport, + boolean isContextAgnostic) { + return null; + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ContextBasedImportService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ContextBasedImportService.java new file mode 100644 index 0000000000..f3c3fc93c2 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ContextBasedImportService.java @@ -0,0 +1,9 @@ +package com.appsmith.server.imports.internal; + +import com.appsmith.server.domains.ImportableArtifact; +import com.appsmith.server.dtos.ArtifactExchangeJson; +import com.appsmith.server.dtos.ImportableArtifactDTO; + +public interface ContextBasedImportService< + T extends ImportableArtifact, U extends ImportableArtifactDTO, V extends ArtifactExchangeJson> + extends ContextBasedImportServiceCE {} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ContextBasedImportServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ContextBasedImportServiceCE.java new file mode 100644 index 0000000000..cfea6521b5 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ContextBasedImportServiceCE.java @@ -0,0 +1,153 @@ +package com.appsmith.server.imports.internal; + +import com.appsmith.server.domains.ImportableArtifact; +import com.appsmith.server.domains.User; +import com.appsmith.server.domains.Workspace; +import com.appsmith.server.dtos.ArtifactExchangeJson; +import com.appsmith.server.dtos.ImportableArtifactDTO; +import com.appsmith.server.dtos.ImportingMetaDTO; +import com.appsmith.server.dtos.MappedImportableResourcesDTO; +import com.appsmith.server.helpers.ce.ImportArtifactPermissionProvider; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface ContextBasedImportServiceCE< + T extends ImportableArtifact, U extends ImportableArtifactDTO, V extends ArtifactExchangeJson> { + + V extractArtifactExchangeJson(String jsonString); + + ImportArtifactPermissionProvider getImportArtifactPermissionProviderForImportingArtifact( + Set userPermissions); + + ImportArtifactPermissionProvider getImportArtifactPermissionProviderForUpdatingArtifact( + Set userPermissions); + + ImportArtifactPermissionProvider getImportArtifactPermissionProviderForConnectingToGit(Set userPermissions); + + ImportArtifactPermissionProvider getImportArtifactPermissionProviderForRestoringSnapshot( + Set userPermissions); + + ImportArtifactPermissionProvider getImportArtifactPermissionProviderForMergingJsonWithArtifact( + Set userPermissions); + + /** + * this method creates updates the entities which is to be imported in context to the artifact + * + * @param artifactExchangeJson : json for the artifact which is going to be imported + * @param entitiesToImport : list of names of entities which is going to be imported + */ + default void updateArtifactExchangeJsonWithEntitiesToBeConsumed( + ArtifactExchangeJson artifactExchangeJson, List entitiesToImport) {} + + /** + * this method sets the names to null before the update to avoid conflict + * + * @param artifactId + * @param artifactExchangeJson + */ + void setJsonArtifactNameToNullBeforeUpdate(String artifactId, ArtifactExchangeJson artifactExchangeJson); + + Mono getImportableArtifactDTO(String workspaceId, String artifactId, ImportableArtifact importableArtifact); + + /** + * This method sets the client & server schema version to artifacts which is inside JSON from the clientSchemaVersion + * & serverSchemaVersion attribute from ArtifactExchangeJson + * @param artifactExchangeJson : ArtifactExchangeJson created from file part while import flow + */ + void syncClientAndSchemaVersion(ArtifactExchangeJson artifactExchangeJson); + + /** + * This method saves the context from the import json for the first time after dehydrating all the details which can cause conflicts + * + * @param importableArtifact + * @param importingMetaDTO + * @param mappedImportableResourcesDTO + * @param currentUserMono + * @return + */ + Mono updateAndSaveArtifactInContext( + ImportableArtifact importableArtifact, + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + Mono currentUserMono); + + /** + * update importable entities with the context references post creation of context in db + * @param importableContext + * @param mappedImportableResourcesDTO + * @param importingMetaDTO + * @return + */ + Mono updateImportableEntities( + ImportableArtifact importableContext, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + ImportingMetaDTO importingMetaDTO); + + /** + * Update the artifact after the entities has been created + * @param importableArtifact : the artifact which has to be updated + * @return + */ + Mono updateImportableArtifact(ImportableArtifact importableArtifact); + + Map createImportAnalyticsData( + ArtifactExchangeJson artifactExchangeJson, ImportableArtifact importableArtifact); + + /** + * @param importingMetaDTO + * @param mappedImportableResourcesDTO + * @param workspaceMono + * @param importableArtifactMono + * @param artifactExchangeJson + * @return + */ + Flux generateArtifactContextIndependentImportableEntities( + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + Mono workspaceMono, + Mono importableArtifactMono, + ArtifactExchangeJson artifactExchangeJson); + + /** + * @param importingMetaDTO + * @param mappedImportableResourcesDTO + * @param workspaceMono + * @param importableArtifactMono + * @param artifactExchangeJson + * @return + */ + Flux generateArtifactContextDependentImportableEntities( + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + Mono workspaceMono, + Mono importableArtifactMono, + ArtifactExchangeJson artifactExchangeJson); + + /** + * Add entities which are specific to the artifact. i.e. customJsLib + * @param artifactExchangeJson + * @param importingMetaDTO + * @param mappedImportableResourcesDTO + * @return + */ + Mono generateArtifactSpecificImportableEntities( + ArtifactExchangeJson artifactExchangeJson, + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO); + + Mono isArtifactConnectedToGit(String artifactId); + + String validateArtifactSpecificFields(ArtifactExchangeJson artifactExchangeJson); + + /** + * This map keeps constants which are specific to the contexts i.e. Application, packages. + * which is parallel to other Artifacts. + * i.e. Artifact --> Application, Packages + * i.e. ID --> applicationId, packageId + */ + Map getArtifactSpecificConstantsMap(); +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportApplicationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportApplicationServiceCEImpl.java index a994819a7a..c833e135a5 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportApplicationServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportApplicationServiceCEImpl.java @@ -24,7 +24,7 @@ import com.appsmith.server.dtos.MappedImportableResourcesDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.ImportExportUtils; -import com.appsmith.server.helpers.ce.ImportApplicationPermissionProvider; +import com.appsmith.server.helpers.ce.ImportArtifactPermissionProvider; import com.appsmith.server.imports.importable.ImportableService; import com.appsmith.server.layouts.UpdateLayoutService; import com.appsmith.server.migrations.ApplicationVersion; @@ -155,9 +155,9 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC }); } - private Mono getPermissionProviderForUpdateNonGitConnectedAppFromJson() { + private Mono getPermissionProviderForUpdateNonGitConnectedAppFromJson() { return permissionGroupRepository.getCurrentUserPermissionGroups().map(permissionGroups -> { - ImportApplicationPermissionProvider permissionProvider = ImportApplicationPermissionProvider.builder( + ImportArtifactPermissionProvider permissionProvider = ImportArtifactPermissionProvider.builder( applicationPermission, pagePermission, actionPermission, @@ -213,7 +213,7 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC return getPermissionProviderForUpdateNonGitConnectedAppFromJson() .zipWith(permissionGroupIdsMono) .flatMap(tuple2 -> { - ImportApplicationPermissionProvider permissionProvider = tuple2.getT1(); + ImportArtifactPermissionProvider permissionProvider = tuple2.getT1(); Set permissionGroups = tuple2.getT2(); if (!StringUtils.isEmpty(applicationId) @@ -266,7 +266,7 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC } return permissionGroupRepository.getCurrentUserPermissionGroups().flatMap(userPermissionGroups -> { - ImportApplicationPermissionProvider permissionProvider = ImportApplicationPermissionProvider.builder( + ImportArtifactPermissionProvider permissionProvider = ImportArtifactPermissionProvider.builder( applicationPermission, pagePermission, actionPermission, @@ -304,7 +304,7 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC * Sync is a system level operation to get the latest code from Git. If the user does not have some * permissions on the Application e.g. create page, that'll be checked when the user tries to create a page. */ - ImportApplicationPermissionProvider permissionProvider = ImportApplicationPermissionProvider.builder( + ImportArtifactPermissionProvider permissionProvider = ImportArtifactPermissionProvider.builder( applicationPermission, pagePermission, actionPermission, @@ -332,7 +332,7 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC * Only permission required is to edit the application. */ return permissionGroupRepository.getCurrentUserPermissionGroups().flatMap(userPermissionGroups -> { - ImportApplicationPermissionProvider permissionProvider = ImportApplicationPermissionProvider.builder( + ImportArtifactPermissionProvider permissionProvider = ImportArtifactPermissionProvider.builder( applicationPermission, pagePermission, actionPermission, @@ -406,29 +406,29 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC return application; }); - if (StringUtils.isEmpty(importingMetaDTO.getApplicationId())) { + if (StringUtils.isEmpty(importingMetaDTO.getArtifactId())) { importApplicationMono = importApplicationMono.flatMap(application -> { return applicationPageService.createOrUpdateSuffixedApplication(application, application.getName(), 0); }); } else { Mono existingApplicationMono = applicationService .findById( - importingMetaDTO.getApplicationId(), + importingMetaDTO.getArtifactId(), importingMetaDTO.getPermissionProvider().getRequiredPermissionOnTargetApplication()) .switchIfEmpty(Mono.defer(() -> { log.error( "No application found with id: {} and permission: {}", - importingMetaDTO.getApplicationId(), + importingMetaDTO.getArtifactId(), importingMetaDTO.getPermissionProvider().getRequiredPermissionOnTargetApplication()); return Mono.error(new AppsmithException( AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.APPLICATION, - importingMetaDTO.getApplicationId())); + importingMetaDTO.getArtifactId())); })) .cache(); // this can be a git sync, import page from template, update app with json, restore snapshot - if (importingMetaDTO.getAppendToApp()) { // we don't need to do anything with the imported application + if (importingMetaDTO.getAppendToArtifact()) { // we don't need to do anything with the imported application importApplicationMono = existingApplicationMono; } else { importApplicationMono = Mono.zip(importApplicationMono, existingApplicationMono) @@ -500,7 +500,7 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC String applicationId, String branchName, boolean appendToApp, - ImportApplicationPermissionProvider permissionProvider, + ImportArtifactPermissionProvider permissionProvider, Set permissionGroups) { /* 1. Migrate resource to latest schema @@ -905,7 +905,7 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC } return permissionGroupRepository.getCurrentUserPermissionGroups().flatMap(userPermissionGroups -> { - ImportApplicationPermissionProvider permissionProvider = ImportApplicationPermissionProvider.builder( + ImportArtifactPermissionProvider permissionProvider = ImportArtifactPermissionProvider.builder( applicationPermission, pagePermission, actionPermission, diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportServiceCEImpl.java new file mode 100644 index 0000000000..7665e2055e --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportServiceCEImpl.java @@ -0,0 +1,741 @@ +package com.appsmith.server.imports.internal; + +import com.appsmith.external.constants.AnalyticsEvents; +import com.appsmith.external.helpers.Stopwatch; +import com.appsmith.external.models.Datasource; +import com.appsmith.server.applications.imports.ApplicationImportService; +import com.appsmith.server.constants.ArtifactJsonType; +import com.appsmith.server.constants.FieldName; +import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.ImportableArtifact; +import com.appsmith.server.domains.Plugin; +import com.appsmith.server.domains.Theme; +import com.appsmith.server.domains.User; +import com.appsmith.server.domains.Workspace; +import com.appsmith.server.dtos.ArtifactExchangeJson; +import com.appsmith.server.dtos.ImportableArtifactDTO; +import com.appsmith.server.dtos.ImportingMetaDTO; +import com.appsmith.server.dtos.MappedImportableResourcesDTO; +import com.appsmith.server.exceptions.AppsmithError; +import com.appsmith.server.exceptions.AppsmithException; +import com.appsmith.server.helpers.ImportExportUtils; +import com.appsmith.server.helpers.ce.ImportArtifactPermissionProvider; +import com.appsmith.server.imports.importable.ImportServiceCE; +import com.appsmith.server.imports.importable.ImportableService; +import com.appsmith.server.migrations.ArtifactSchemaMigration; +import com.appsmith.server.repositories.PermissionGroupRepository; +import com.appsmith.server.services.AnalyticsService; +import com.appsmith.server.services.SessionUserService; +import com.appsmith.server.services.WorkspaceService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.MediaType; +import org.springframework.http.codec.multipart.Part; +import org.springframework.transaction.reactive.TransactionalOperator; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.appsmith.server.constants.ArtifactJsonType.APPLICATION; + +@Slf4j +public class ImportServiceCEImpl implements ImportServiceCE { + + public static final Set ALLOWED_CONTENT_TYPES = Set.of(MediaType.APPLICATION_JSON); + private static final String INVALID_JSON_FILE = "invalid json file"; + private final ApplicationImportService applicationImportService; + private final SessionUserService sessionUserService; + private final WorkspaceService workspaceService; + private final ImportableService customJSLibImportableService; + private final PermissionGroupRepository permissionGroupRepository; + private final TransactionalOperator transactionalOperator; + private final AnalyticsService analyticsService; + private final ImportableService pluginImportableService; + private final ImportableService datasourceImportableService; + private final ImportableService themeImportableService; + private final Map> serviceFactory = new HashMap<>(); + + public ImportServiceCEImpl( + ApplicationImportService applicationImportService, + SessionUserService sessionUserService, + WorkspaceService workspaceService, + ImportableService customJSLibImportableService, + PermissionGroupRepository permissionGroupRepository, + TransactionalOperator transactionalOperator, + AnalyticsService analyticsService, + ImportableService pluginImportableService, + ImportableService datasourceImportableService, + ImportableService themeImportableService) { + this.applicationImportService = applicationImportService; + this.workspaceService = workspaceService; + this.sessionUserService = sessionUserService; + this.customJSLibImportableService = customJSLibImportableService; + this.permissionGroupRepository = permissionGroupRepository; + this.transactionalOperator = transactionalOperator; + this.analyticsService = analyticsService; + this.pluginImportableService = pluginImportableService; + this.datasourceImportableService = datasourceImportableService; + this.themeImportableService = themeImportableService; + serviceFactory.put(APPLICATION, applicationImportService); + } + + /** + * This method provides the importService specific to the artifact based on the ArtifactJsonType. + * time complexity is O(1), as the map from which the service is being passes is pre-computed + * @param artifactExchangeJson : Entity Json which is implementing the artifactExchangeJson + * @return import-service which is implementing the ContextBasedServiceInterface + */ + @Override + public ContextBasedImportService< + ? extends ImportableArtifact, ? extends ImportableArtifactDTO, ? extends ArtifactExchangeJson> + getContextBasedImportService(ArtifactExchangeJson artifactExchangeJson) { + return getContextBasedImportService(artifactExchangeJson.getArtifactJsonType()); + } + + /** + * This method provides the importService specific to the artifact based on the ArtifactJsonType. + * time complexity is O(1), as the map from which the service is being passes is pre-computed + * @param artifactJsonType : Type of Json serialisation + * @return import-service which is implementing the ContextBasedServiceInterface + */ + @Override + public ContextBasedImportService< + ? extends ImportableArtifact, ? extends ImportableArtifactDTO, ? extends ArtifactExchangeJson> + getContextBasedImportService(ArtifactJsonType artifactJsonType) { + return serviceFactory.getOrDefault(artifactJsonType, applicationImportService); + } + + /** + * This method takes a file part and makes a Json entity which implements the ArtifactExchangeJson interface + * + * @param filePart : filePart from which the contents would be made + * @param artifactJsonType : type of the json which is getting imported + * @return : Json entity which implements ArtifactExchangeJson + */ + public Mono extractArtifactExchangeJson( + Part filePart, ArtifactJsonType artifactJsonType) { + + final MediaType contentType = filePart.headers().getContentType(); + if (contentType == null || !ALLOWED_CONTENT_TYPES.contains(contentType)) { + log.error("Invalid content type, {}", contentType); + return Mono.error(new AppsmithException(AppsmithError.VALIDATION_FAILURE, INVALID_JSON_FILE)); + } + + return DataBufferUtils.join(filePart.content()) + .map(dataBuffer -> { + byte[] data = new byte[dataBuffer.readableByteCount()]; + dataBuffer.read(data); + DataBufferUtils.release(dataBuffer); + return new String(data); + }) + .map(jsonString -> + getContextBasedImportService(artifactJsonType).extractArtifactExchangeJson(jsonString)); + } + + /** + * Hydrates an ImportableArtifact within the specified workspace by saving the provided JSON file. + * + * @param filePart The filePart representing the ImportableArtifact object to be saved. + * The ImportableArtifact implements the ImportableArtifact interface. + * @param workspaceId The identifier for the destination workspace. + */ + @Override + public Mono extractArtifactExchangeJsonAndSaveArtifact( + Part filePart, String workspaceId, String artifactId, ArtifactJsonType artifactJsonType) { + + if (StringUtils.isEmpty(workspaceId)) { + return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.WORKSPACE_ID)); + } + + Mono importedContextMono = extractArtifactExchangeJson(filePart, artifactJsonType) + .zipWhen(contextJson -> { + if (StringUtils.isEmpty(artifactId)) { + return importNewArtifactInWorkspaceFromJson(workspaceId, contextJson); + } else { + return updateNonGitConnectedArtifactFromJson(workspaceId, artifactId, contextJson); + } + }) + .flatMap(tuple2 -> { + ImportableArtifact context = tuple2.getT2(); + ArtifactExchangeJson artifactExchangeJson = tuple2.getT1(); + return getArtifactImportDTO( + context.getWorkspaceId(), context.getId(), context, artifactExchangeJson); + }); + + return Mono.create( + sink -> importedContextMono.subscribe(sink::success, sink::error, null, sink.currentContext())); + } + + /** + * Saves the provided ArtifactExchangeJson within the specified workspace. + * + * @param workspaceId The identifier for the destination workspace. + * @param artifactExchangeJson The JSON file representing the ImportableArtifact object to be saved. + * The ImportableArtifact implements the ImportableArtifact interface. + */ + @Override + public Mono importNewArtifactInWorkspaceFromJson( + String workspaceId, ArtifactExchangeJson artifactExchangeJson) { + + // workspace id must be present and valid + if (StringUtils.isEmpty(workspaceId)) { + return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.WORKSPACE_ID)); + } + + ContextBasedImportService contextBasedImportService = + getContextBasedImportService(artifactExchangeJson); + return permissionGroupRepository + .getCurrentUserPermissionGroups() + .zipWhen(userPermissionGroup -> { + return Mono.just(contextBasedImportService.getImportArtifactPermissionProviderForImportingArtifact( + userPermissionGroup)); + }) + .flatMap(tuple2 -> { + Set userPermissionGroup = tuple2.getT1(); + ImportArtifactPermissionProvider permissionProvider = tuple2.getT2(); + return importArtifactInWorkspace( + workspaceId, + artifactExchangeJson, + null, + null, + false, + permissionProvider, + userPermissionGroup); + }); + } + + @Override + public Mono updateNonGitConnectedArtifactFromJson( + String workspaceId, String artifactId, ArtifactExchangeJson artifactExchangeJson) { + ContextBasedImportService contextBasedImportService = + getContextBasedImportService(artifactExchangeJson); + + if (StringUtils.isEmpty(workspaceId)) { + return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.WORKSPACE_ID)); + } + + if (StringUtils.isEmpty(artifactId)) { + // error message according to the context + return Mono.error(new AppsmithException( + AppsmithError.INVALID_PARAMETER, + contextBasedImportService.getArtifactSpecificConstantsMap().get(FieldName.ID))); + } + + // Check if the application is connected to git and if it's connected throw exception asking user to update + // app via git ops like pull, merge etc. + Mono isArtifactConnectedToGitMono = Mono.just(Boolean.FALSE); + if (!StringUtils.isEmpty(artifactId)) { + isArtifactConnectedToGitMono = contextBasedImportService.isArtifactConnectedToGit(artifactId); + } + + Mono importedContextMono = isArtifactConnectedToGitMono.flatMap(isConnectedToGit -> { + if (isConnectedToGit) { + return Mono.error(new AppsmithException( + AppsmithError.UNSUPPORTED_IMPORT_OPERATION_FOR_GIT_CONNECTED_APPLICATION)); + } else { + contextBasedImportService.setJsonArtifactNameToNullBeforeUpdate(artifactId, artifactExchangeJson); + return permissionGroupRepository + .getCurrentUserPermissionGroups() + .zipWhen(userPermissionGroup -> { + return Mono.just( + contextBasedImportService.getImportArtifactPermissionProviderForUpdatingArtifact( + userPermissionGroup)); + }) + .flatMap(tuple2 -> { + Set userPermissionGroup = tuple2.getT1(); + ImportArtifactPermissionProvider permissionProvider = tuple2.getT2(); + return importArtifactInWorkspace( + workspaceId, + artifactExchangeJson, + artifactId, + null, + false, + permissionProvider, + userPermissionGroup); + }) + .onErrorResume(error -> { + if (error instanceof AppsmithException) { + return Mono.error(error); + } + return Mono.error(new AppsmithException( + AppsmithError.GENERIC_JSON_IMPORT_ERROR, workspaceId, error.getMessage())); + }); + } + }); + + return Mono.create( + sink -> importedContextMono.subscribe(sink::success, sink::error, null, sink.currentContext())); + } + + /** + * Updates an existing ImportableArtifact connected to Git within the specified workspace. + * + * @param workspaceId The identifier for the destination workspace. + * @param artifactId The ImportableArtifact id that needs to be updated with the new resources. + * @param artifactExchangeJson The ImportableArtifact JSON containing necessary information to update the ImportableArtifact. + * @param branchName The name of the Git branch. Set to null if not connected to Git. + * @return The updated ImportableArtifact stored in the database. + */ + @Override + public Mono importArtifactInWorkspaceFromGit( + String workspaceId, String artifactId, ArtifactExchangeJson artifactExchangeJson, String branchName) { + + ContextBasedImportService contextBasedImportService = + getContextBasedImportService(artifactExchangeJson); + return permissionGroupRepository + .getCurrentUserPermissionGroups() + .zipWhen(userPermissionGroups -> { + return Mono.just(contextBasedImportService.getImportArtifactPermissionProviderForConnectingToGit( + userPermissionGroups)); + }) + .flatMap(tuple2 -> { + Set userPermissionGroup = tuple2.getT1(); + ImportArtifactPermissionProvider artifactPermissionProvider = tuple2.getT2(); + return importArtifactInWorkspace( + workspaceId, + artifactExchangeJson, + artifactId, + branchName, + false, + artifactPermissionProvider, + userPermissionGroup); + }); + } + + @Override + public Mono restoreSnapshot( + String workspaceId, ArtifactExchangeJson artifactExchangeJson, String artifactId, String branchName) { + + /** + * Like Git, restore snapshot is a system level operation. So, we're not checking for any permissions here. + * Only permission required is to edit the artifact. + */ + ContextBasedImportService contextBasedImportService = + getContextBasedImportService(artifactExchangeJson); + return permissionGroupRepository + .getCurrentUserPermissionGroups() + .zipWhen(userPermissionGroups -> { + return Mono.just(contextBasedImportService.getImportArtifactPermissionProviderForRestoringSnapshot( + userPermissionGroups)); + }) + .flatMap(tuple2 -> { + Set userPermissionGroup = tuple2.getT1(); + ImportArtifactPermissionProvider importArtifactPermissionProvider = tuple2.getT2(); + return importArtifactInWorkspace( + workspaceId, + artifactExchangeJson, + artifactId, + branchName, + false, + importArtifactPermissionProvider, + userPermissionGroup); + }); + } + + /** + * This function will take the Json filePart and saves the artifact (likely an application) in workspace. + * It'll not create a new ImportableArtifact, it'll update the existing ImportableArtifact by appending the pages to the ImportableArtifact. + * The destination ImportableArtifact will be as it is, only the pages will be appended. + * This method will likely be only applicable for applications + * + * @param workspaceId ID in which the artifact is to be merged + * @param artifactId default ID of the importableArtifact where this artifactExchangeJson is going to get merged with + * @param branchName name of the branch of the importableArtifact where this artifactExchangeJson is going to get merged with + * @param artifactExchangeJson artifactExchangeJson of the importableArtifact that will be merged to + * @param entitiesToImport Name of the pages that should be merged from the artifactExchangeJson. + * If null or empty, all pages will be merged. + * @return Merged ImportableArtifact + */ + @Override + public Mono mergeArtifactExchangeJsonWithImportableArtifact( + String workspaceId, + String artifactId, + String branchName, + ArtifactExchangeJson artifactExchangeJson, + List entitiesToImport) { + ContextBasedImportService contextBasedImportService = + getContextBasedImportService(artifactExchangeJson); + contextBasedImportService.updateArtifactExchangeJsonWithEntitiesToBeConsumed( + artifactExchangeJson, entitiesToImport); + return permissionGroupRepository + .getCurrentUserPermissionGroups() + .zipWhen(userPermissionGroups -> { + return Mono.just( + contextBasedImportService.getImportArtifactPermissionProviderForMergingJsonWithArtifact( + userPermissionGroups)); + }) + .flatMap(tuple2 -> { + Set userPermissionGroup = tuple2.getT1(); + ImportArtifactPermissionProvider contextPermissionProvider = tuple2.getT2(); + return importArtifactInWorkspace( + workspaceId, + artifactExchangeJson, + artifactId, + branchName, + true, + contextPermissionProvider, + userPermissionGroup); + }); + } + + /** + * @param workspaceId ID in which the context is to be merged + * @param artifactId default ID of the artifact where this artifactExchangeJson is going to get merged with + * @param importableArtifact the context (i.e. application, packages which is imported) + * @param artifactExchangeJson the Json entity from which the import is happening + * @return ImportableArtifactDTO + */ + @Override + public Mono getArtifactImportDTO( + String workspaceId, + String artifactId, + ImportableArtifact importableArtifact, + ArtifactExchangeJson artifactExchangeJson) { + return getContextBasedImportService(artifactExchangeJson) + .getImportableArtifactDTO(workspaceId, artifactId, importableArtifact); + } + + /** + * Imports an application into MongoDB based on the provided application reference object. + * + * @param workspaceId The identifier for the destination workspace. + * @param artifactExchangeJson The application resource containing necessary information for importing the application. + * @param artifactId The context identifier of the application that needs to be saved with the updated resources. + * @param branchName The name of the branch of the artifact with the specified artifactId. + * @param appendToArtifact Indicates whether artifactExchangeJson will be appended to the existing application or not. + * @return The updated artifact stored in MongoDB. + */ + private Mono importArtifactInWorkspace( + String workspaceId, + ArtifactExchangeJson artifactExchangeJson, + String artifactId, + String branchName, + boolean appendToArtifact, + ImportArtifactPermissionProvider permissionProvider, + Set permissionGroups) { + + ContextBasedImportService contextBasedImportService = + getContextBasedImportService(artifactExchangeJson); + + String artifactContextString = + contextBasedImportService.getArtifactSpecificConstantsMap().get(FieldName.ARTIFACT_CONTEXT); + + // step 1: Schema Migration + ArtifactExchangeJson importedDoc = + ArtifactSchemaMigration.migrateArtifactExchangeJsonToLatestSchema(artifactExchangeJson); + + // Step 2: Validation of context Json + // check for validation error and raise exception if error found + String errorField = validateArtifactExchangeJson(importedDoc); + if (!errorField.isEmpty()) { + log.error("Error in importing {}. Field {} is missing", artifactContextString, errorField); + if (errorField.equals(artifactContextString)) { + return Mono.error( + new AppsmithException( + AppsmithError.VALIDATION_FAILURE, + "Field '" + artifactContextString + + "' Sorry! Seems like you've imported a page-level json instead of an application. Please use the import within the page.")); + } + return Mono.error(new AppsmithException( + AppsmithError.VALIDATION_FAILURE, "Field '" + errorField + "' is missing in the JSON.")); + } + + ImportingMetaDTO importingMetaDTO = new ImportingMetaDTO( + workspaceId, artifactId, branchName, appendToArtifact, permissionProvider, permissionGroups); + + MappedImportableResourcesDTO mappedImportableResourcesDTO = new MappedImportableResourcesDTO(); + contextBasedImportService.syncClientAndSchemaVersion(importedDoc); + + Mono workspaceMono = workspaceService + .findById(workspaceId, permissionProvider.getRequiredPermissionOnTargetWorkspace()) + .switchIfEmpty(Mono.defer(() -> { + log.error( + "No workspace found with id: {} and permission: {}", + workspaceId, + permissionProvider.getRequiredPermissionOnTargetWorkspace()); + return Mono.error(new AppsmithException( + AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.WORKSPACE, workspaceId)); + })) + .cache(); + + Mono currUserMono = sessionUserService.getCurrentUser().cache(); + + // Start the stopwatch to log the execution time + Stopwatch stopwatch = new Stopwatch(AnalyticsEvents.IMPORT.getEventName()); + + // this would import customJsLibs for all type of artifacts + Mono artifactSpecificImportableEntities = + contextBasedImportService.generateArtifactSpecificImportableEntities( + importedDoc, importingMetaDTO, mappedImportableResourcesDTO); + + /* + Calling the workspaceMono first to avoid creating multiple mongo transactions. + If the first db call inside a transaction is a Flux, then there's a chance of creating multiple mongo + transactions which will lead to NoSuchTransaction exception. + */ + final Mono importedArtifactMono = workspaceMono + .then(Mono.defer(() -> artifactSpecificImportableEntities)) + .then(Mono.defer(() -> contextBasedImportService.updateAndSaveArtifactInContext( + importedDoc.getImportableArtifact(), + importingMetaDTO, + mappedImportableResourcesDTO, + currUserMono))) + .cache(); + + Mono importMono = importedArtifactMono + .then(Mono.defer(() -> generateImportableEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + importedArtifactMono, + importedDoc))) + .then(importedArtifactMono) + .flatMap(importableArtifact -> updateImportableEntities( + contextBasedImportService, importableArtifact, mappedImportableResourcesDTO, importingMetaDTO)) + .flatMap(importableArtifact -> updateImportableArtifact(contextBasedImportService, importableArtifact)) + .onErrorResume(throwable -> { + String errorMessage = ImportExportUtils.getErrorMessage(throwable); + log.error("Error importing {}. Error: {}", artifactContextString, errorMessage, throwable); + return Mono.error( + new AppsmithException(AppsmithError.GENERIC_JSON_IMPORT_ERROR, workspaceId, errorMessage)); + }) + .as(transactionalOperator::transactional); + + final Mono resultMono = importMono + .flatMap(importableArtifact -> sendImportedContextAnalyticsEvent( + contextBasedImportService, importableArtifact, AnalyticsEvents.IMPORT)) + .zipWith(currUserMono) + .flatMap(tuple -> { + ImportableArtifact importableArtifact = tuple.getT1(); + User user = tuple.getT2(); + stopwatch.stopTimer(); + stopwatch.stopAndLogTimeInMillis(); + return sendImportRelatedAnalyticsEvent(importedDoc, importableArtifact, stopwatch, user); + }); + + // Import Context is currently a slow API because it needs to import and create context, pages, actions + // and action collection. This process may take time and the client may cancel the request. This leads to the + // flow getting stopped midway producing corrupted objects in DB. The following ensures that even though the + // client may have refreshes the page, the imported context is available and is in sane state. + // To achieve this, we use a synchronous sink which does not take subscription cancellations into account. This + // means that even if the subscriber has cancelled its subscription, the create method still generates its + // event. + return Mono.create(sink -> resultMono.subscribe(sink::success, sink::error, null, sink.currentContext())); + } + + /** + * validates whether an artifactExchangeJson contains the required fields or not. + * + * @param importedDoc artifactExchangeJson object that needs to be validated + * @return Name of the field that have error. Empty string otherwise + */ + private String validateArtifactExchangeJson(ArtifactExchangeJson importedDoc) { + // validate common schema things + ContextBasedImportService contextBasedImportService = getContextBasedImportService(importedDoc); + String errorField = ""; + if (importedDoc.getImportableArtifact() == null) { + // the error field will be either application, packages, or workflows + errorField = + contextBasedImportService.getArtifactSpecificConstantsMap().get(FieldName.ARTIFACT_CONTEXT); + } else { + // validate contextSpecific-errors + errorField = getContextBasedImportService(importedDoc).validateArtifactSpecificFields(importedDoc); + } + + return errorField; + } + + /** + * Updates importable entities with the contextDetails. + * + * @param contextBasedImportService + * @param importableArtifact + * @param mappedImportableResourcesDTO + * @param importingMetaDTO + * @return + */ + private Mono updateImportableEntities( + ContextBasedImportService contextBasedImportService, + ImportableArtifact importableArtifact, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + ImportingMetaDTO importingMetaDTO) { + return contextBasedImportService.updateImportableEntities( + importableArtifact, mappedImportableResourcesDTO, importingMetaDTO); + } + + /** + * update the importable context with contextSpecific entities after the entities has been created. + * + * @param contextBasedImportService + * @param importableArtifact + * @return + */ + private Mono updateImportableArtifact( + ContextBasedImportService contextBasedImportService, ImportableArtifact importableArtifact) { + return contextBasedImportService.updateImportableArtifact(importableArtifact); + } + + /** + * This method creates the entities which are mentioned in the contextJson, these are imported in mongodb and then + * the references are added to context + * + * @param importingMetaDTO + * @param mappedImportableResourcesDTO + * @param workspaceMono + * @param importedArtifactMono + * @param artifactExchangeJson + * @return + */ + private Mono generateImportableEntities( + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + Mono workspaceMono, + Mono importedArtifactMono, + ArtifactExchangeJson artifactExchangeJson) { + + ContextBasedImportService contextBasedImportService = + getContextBasedImportService(artifactExchangeJson); + + Flux artifactAgnosticImportables = generateArtifactIndependentImportableEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + importedArtifactMono, + artifactExchangeJson); + + Flux artifactSpecificImportables = + contextBasedImportService.generateArtifactContextIndependentImportableEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + importedArtifactMono, + artifactExchangeJson); + + Flux artifactContextDependentImportables = + contextBasedImportService.generateArtifactContextDependentImportableEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + importedArtifactMono, + artifactExchangeJson); + + return artifactAgnosticImportables + .thenMany(artifactSpecificImportables) + .thenMany(artifactContextDependentImportables) + .then(); + } + + /** + * Generate the entities which should be imported irrespective of the context (be it application or packages). + * some of these are plugin and datasource + * + * @param importingMetaDTO + * @param mappedImportableResourcesDTO + * @param workspaceMono + * @param importedArtifactMono + * @param artifactExchangeJson + * @return + */ + protected Flux generateArtifactIndependentImportableEntities( + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + Mono workspaceMono, + Mono importedArtifactMono, + ArtifactExchangeJson artifactExchangeJson) { + + // Updates plugin map in importable resources + Mono installedPluginsMono = pluginImportableService.importEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + importedArtifactMono, + artifactExchangeJson, + false, + true); + + // Requires pluginMap to be present in importable resources. + // Updates datasourceNameToIdMap in importable resources. + // Also directly updates required information in DB + Mono importedDatasourcesMono = installedPluginsMono.then(datasourceImportableService.importEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + importedArtifactMono, + artifactExchangeJson, + false, + true)); + + // Directly updates required theme information in DB + Mono importedThemesMono = themeImportableService.importEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + importedArtifactMono, + artifactExchangeJson, + false, + true); + + return Flux.merge(List.of(importedDatasourcesMono, importedThemesMono)); + } + + /** + * To send analytics event for import and export of ImportableArtifact i.e. application, packages + * + * @param importableArtifact ImportableArtifact object imported or exported + * @param event AnalyticsEvents event + * @return The ImportableArtifact which is imported or exported + */ + private Mono sendImportedContextAnalyticsEvent( + ContextBasedImportService contextBasedImportService, + ImportableArtifact importableArtifact, + AnalyticsEvents event) { + // this would result in "application", "packages", or "workflows" + String artifactContextString = + contextBasedImportService.getArtifactSpecificConstantsMap().get(FieldName.ARTIFACT_CONTEXT); + // this would result in "applicationId", "packageId", or "workflowId" + String contextIdString = + contextBasedImportService.getArtifactSpecificConstantsMap().get(FieldName.ID); + return workspaceService.getById(importableArtifact.getWorkspaceId()).flatMap(workspace -> { + final Map eventData = + Map.of(artifactContextString, importableArtifact, FieldName.WORKSPACE, workspace); + + final Map data = Map.of( + contextIdString, + importableArtifact.getId(), + FieldName.WORKSPACE_ID, + workspace.getId(), + FieldName.EVENT_DATA, + eventData); + + return analyticsService.sendObjectEvent(event, importableArtifact, data); + }); + } + + /** + * This method deals in data only pertaining to import flow i.e. time taken, entities size, e.t.c + * @param artifactExchangeJson : Json which has been used for importing the artifact + * @param importableArtifact: the artifact which is imported + * @param stopwatch : stopwatch + * @param currentUser : user which has initiated the import + */ + private Mono sendImportRelatedAnalyticsEvent( + ArtifactExchangeJson artifactExchangeJson, + ImportableArtifact importableArtifact, + Stopwatch stopwatch, + User currentUser) { + + Map analyticsData = new HashMap<>(getContextBasedImportService(artifactExchangeJson) + .createImportAnalyticsData(artifactExchangeJson, importableArtifact)); + analyticsData.put(FieldName.FLOW_NAME, stopwatch.getFlow()); + analyticsData.put("executionTime", stopwatch.getExecutionTime()); + + return analyticsService + .sendEvent(AnalyticsEvents.UNIT_EXECUTION_TIME.getEventName(), currentUser.getUsername(), analyticsData) + .thenReturn(importableArtifact); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportServiceImpl.java new file mode 100644 index 0000000000..058c871d10 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportServiceImpl.java @@ -0,0 +1,43 @@ +package com.appsmith.server.imports.internal; + +import com.appsmith.external.models.Datasource; +import com.appsmith.server.applications.imports.ApplicationImportService; +import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.Plugin; +import com.appsmith.server.domains.Theme; +import com.appsmith.server.imports.importable.ImportService; +import com.appsmith.server.imports.importable.ImportableService; +import com.appsmith.server.repositories.PermissionGroupRepository; +import com.appsmith.server.services.AnalyticsService; +import com.appsmith.server.services.SessionUserService; +import com.appsmith.server.services.WorkspaceService; +import org.springframework.stereotype.Component; +import org.springframework.transaction.reactive.TransactionalOperator; + +@Component +public class ImportServiceImpl extends ImportServiceCEImpl implements ImportService { + + public ImportServiceImpl( + ApplicationImportService applicationImportService, + SessionUserService sessionUserService, + WorkspaceService workspaceService, + ImportableService customJSLibImportableService, + PermissionGroupRepository permissionGroupRepository, + TransactionalOperator transactionalOperator, + AnalyticsService analyticsService, + ImportableService pluginImportableService, + ImportableService datasourceImportableService, + ImportableService themeImportableService) { + super( + applicationImportService, + sessionUserService, + workspaceService, + customJSLibImportableService, + permissionGroupRepository, + transactionalOperator, + analyticsService, + pluginImportableService, + datasourceImportableService, + themeImportableService); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/PartialImportServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/PartialImportServiceCEImpl.java index a5a45caa31..2b1ffb1e10 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/PartialImportServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/PartialImportServiceCEImpl.java @@ -18,7 +18,7 @@ import com.appsmith.server.dtos.ImportingMetaDTO; import com.appsmith.server.dtos.MappedImportableResourcesDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; -import com.appsmith.server.helpers.ce.ImportApplicationPermissionProvider; +import com.appsmith.server.helpers.ce.ImportArtifactPermissionProvider; import com.appsmith.server.imports.importable.ImportableService; import com.appsmith.server.newpages.base.NewPageService; import com.appsmith.server.repositories.PermissionGroupRepository; @@ -82,7 +82,7 @@ public class PartialImportServiceCEImpl implements PartialImportServiceCE { .zipWith(getImportApplicationPermissions()) .flatMap(tuple -> { ApplicationJson applicationJson = tuple.getT1(); - ImportApplicationPermissionProvider permissionProvider = tuple.getT2(); + ImportArtifactPermissionProvider permissionProvider = tuple.getT2(); // Set Application in App JSON, remove the pages other than the one to be imported in // Set the current page in the JSON to be imported // Debug and get the value from getImportApplicationMono method if any difference @@ -180,9 +180,9 @@ public class PartialImportServiceCEImpl implements PartialImportServiceCE { }); } - private Mono getImportApplicationPermissions() { + private Mono getImportApplicationPermissions() { return permissionGroupRepository.getCurrentUserPermissionGroups().flatMap(userPermissionGroups -> { - ImportApplicationPermissionProvider permissionProvider = ImportApplicationPermissionProvider.builder( + ImportArtifactPermissionProvider permissionProvider = ImportArtifactPermissionProvider.builder( applicationPermission, pagePermission, actionPermission, @@ -261,7 +261,7 @@ public class PartialImportServiceCEImpl implements PartialImportServiceCE { // update page name reference with newPage Map pageNameMap = new HashMap<>(); pageNameMap.put(pageName, newPage); - mappedImportableResourcesDTO.setPageNameMap(pageNameMap); + mappedImportableResourcesDTO.setPageOrModuleMap(pageNameMap); if (applicationJson.getActionList() == null) { return Mono.just(pageName); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/ArtifactSchemaMigration.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/ArtifactSchemaMigration.java new file mode 100644 index 0000000000..d5e9cf642e --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/ArtifactSchemaMigration.java @@ -0,0 +1,3 @@ +package com.appsmith.server.migrations; + +public class ArtifactSchemaMigration extends ArtifactSchemaMigrationCE {} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/ArtifactSchemaMigrationCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/ArtifactSchemaMigrationCE.java new file mode 100644 index 0000000000..02ab8fb0fc --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/ArtifactSchemaMigrationCE.java @@ -0,0 +1,105 @@ +package com.appsmith.server.migrations; + +import com.appsmith.server.constants.ArtifactJsonType; +import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.dtos.ArtifactExchangeJson; +import com.appsmith.server.exceptions.AppsmithError; +import com.appsmith.server.exceptions.AppsmithException; +import com.appsmith.server.helpers.CollectionUtils; + +public class ArtifactSchemaMigrationCE { + + private static boolean checkCompatibility(ArtifactExchangeJson artifactExchangeJson) { + return (artifactExchangeJson.getClientSchemaVersion() <= JsonSchemaVersions.clientVersion) + && (artifactExchangeJson.getServerSchemaVersion() <= JsonSchemaVersions.serverVersion); + } + + public static ArtifactExchangeJson migrateArtifactExchangeJsonToLatestSchema( + ArtifactExchangeJson artifactExchangeJson) { + // Check if the schema versions are available and set to initial version if not present + Integer serverSchemaVersion = artifactExchangeJson.getServerSchemaVersion() == null + ? 0 + : artifactExchangeJson.getServerSchemaVersion(); + Integer clientSchemaVersion = artifactExchangeJson.getClientSchemaVersion() == null + ? 0 + : artifactExchangeJson.getClientSchemaVersion(); + + artifactExchangeJson.setClientSchemaVersion(clientSchemaVersion); + artifactExchangeJson.setServerSchemaVersion(serverSchemaVersion); + if (!checkCompatibility(artifactExchangeJson)) { + throw new AppsmithException(AppsmithError.INCOMPATIBLE_IMPORTED_JSON); + } + + migrateClientAndServerSchemas(artifactExchangeJson); + return artifactExchangeJson; + } + + /** + * This method migrates the client & server schema of artifactExchangeJson after choosing the right method for migration + * this will likely be overridden in EE codebase for more choices + * @param artifactExchangeJson artifactExchangeJson which is imported + */ + private static void migrateClientAndServerSchemas(ArtifactExchangeJson artifactExchangeJson) { + if (ArtifactJsonType.APPLICATION.equals(artifactExchangeJson.getArtifactJsonType())) { + migrateApplicationJsonClientSchema((ApplicationJson) artifactExchangeJson); + migrateApplicationJsonServerSchema((ApplicationJson) artifactExchangeJson); + } + } + + private static ApplicationJson migrateApplicationJsonServerSchema(ApplicationJson applicationJson) { + if (JsonSchemaVersions.serverVersion.equals(applicationJson.getServerSchemaVersion())) { + // No need to run server side migration + return applicationJson; + } + // Run migration linearly + // Updating the schema version after each migration is not required as we are not exiting by breaking the switch + // cases, but this keeps the version number and the migration in sync + switch (applicationJson.getServerSchemaVersion()) { + case 0: + + case 1: + // Migration for deprecating archivedAt field in ActionDTO + if (!CollectionUtils.isNullOrEmpty(applicationJson.getActionList())) { + MigrationHelperMethods.updateArchivedAtByDeletedATForActions(applicationJson.getActionList()); + } + applicationJson.setServerSchemaVersion(2); + case 2: + // Migration for converting formData elements to one that supports viewType + MigrationHelperMethods.migrateActionFormDataToObject(applicationJson); + applicationJson.setServerSchemaVersion(3); + case 3: + // File structure migration to update git directory structure + applicationJson.setServerSchemaVersion(4); + case 4: + // Remove unwanted fields from DTO and allow serialization for JsonIgnore fields + if (!CollectionUtils.isNullOrEmpty(applicationJson.getPageList()) + && applicationJson.getExportedApplication() != null) { + MigrationHelperMethods.arrangeApplicationPagesAsPerImportedPageOrder(applicationJson); + MigrationHelperMethods.updateMongoEscapedWidget(applicationJson); + } + if (!CollectionUtils.isNullOrEmpty(applicationJson.getActionList())) { + MigrationHelperMethods.updateUserSetOnLoadAction(applicationJson); + } + applicationJson.setServerSchemaVersion(5); + case 5: + MigrationHelperMethods.migrateGoogleSheetsActionsToUqi(applicationJson); + applicationJson.setServerSchemaVersion(6); + case 6: + MigrationHelperMethods.ensureXmlParserPresenceInCustomJsLibList(applicationJson); + applicationJson.setServerSchemaVersion(7); + default: + // Unable to detect the serverSchema + } + return applicationJson; + } + + private static ApplicationJson migrateApplicationJsonClientSchema(ApplicationJson applicationJson) { + if (JsonSchemaVersions.clientVersion.equals(applicationJson.getClientSchemaVersion())) { + // No need to run client side migration + return applicationJson; + } + // Today server is not responsible to run the client side DSL migration but this can be useful if we start + // supporting this on server side + return applicationJson; + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/imports/NewActionImportableServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/imports/NewActionImportableServiceCEImpl.java index aec0187de2..1239044154 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/imports/NewActionImportableServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/imports/NewActionImportableServiceCEImpl.java @@ -18,7 +18,7 @@ import com.appsmith.server.dtos.ImportingMetaDTO; import com.appsmith.server.dtos.MappedImportableResourcesDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; -import com.appsmith.server.helpers.ce.ImportApplicationPermissionProvider; +import com.appsmith.server.helpers.ce.ImportArtifactPermissionProvider; import com.appsmith.server.imports.importable.ImportableServiceCE; import com.appsmith.server.newactions.base.NewActionService; import com.appsmith.server.repositories.NewActionRepository; @@ -69,12 +69,13 @@ public class NewActionImportableServiceCEImpl implements ImportableServiceCE importedNewActionList = applicationJson.getActionList(); Mono> importedNewActionMono = Mono.justOrEmpty(importedNewActionList); - if (TRUE.equals(importingMetaDTO.getAppendToApp())) { + if (TRUE.equals(importingMetaDTO.getAppendToArtifact())) { importedNewActionMono = importedNewActionMono.map(importedNewActionList1 -> { - List importedNewPages = mappedImportableResourcesDTO.getPageNameMap().values().stream() + List importedNewPages = mappedImportableResourcesDTO.getPageOrModuleMap().values().stream() .distinct() + .map(branchAwareDomain -> (NewPage) branchAwareDomain) .toList(); - Map newToOldNameMap = mappedImportableResourcesDTO.getNewPageNameToOldPageNameMap(); + Map newToOldNameMap = mappedImportableResourcesDTO.getPageOrModuleNewNameToOldName(); for (NewPage newPage : importedNewPages) { String newPageName = newPage.getUnpublishedPage().getName(); @@ -98,8 +99,8 @@ public class NewActionImportableServiceCEImpl implements ImportableServiceCE invalidActionIds = new HashSet<>(); @@ -159,8 +160,8 @@ public class NewActionImportableServiceCEImpl implements ImportableServiceCE invalidCollectionIds = new HashSet<>(); @@ -273,7 +274,8 @@ public class NewActionImportableServiceCEImpl implements ImportableServiceCE) + mappedImportableResourcesDTO.getPageOrModuleMap(), importActionResultDTO.getActionIdMap()); sanitizeDatasourceInActionDTO( unpublishedAction, @@ -290,7 +292,8 @@ public class NewActionImportableServiceCEImpl implements ImportableServiceCE) + mappedImportableResourcesDTO.getPageOrModuleMap(), importActionResultDTO.getActionIdMap()); parentPage = parentPage == null ? publishedActionPage : parentPage; sanitizeDatasourceInActionDTO( @@ -484,7 +487,7 @@ public class NewActionImportableServiceCEImpl implements ImportableServiceCE newPages = mappedImportableResourcesDTO.getPageNameMap().values().stream() + List newPages = mappedImportableResourcesDTO.getPageOrModuleMap().values().stream() .distinct() + .map(branchAwareDomain -> (NewPage) branchAwareDomain) .toList(); return Flux.fromIterable(newPages) .flatMap(newPage -> { @@ -170,7 +171,7 @@ public class NewPageImportableServiceCEImpl implements ImportableServiceCE importApplicationMono, boolean appendToApp, String branchName, - ImportApplicationPermissionProvider permissionProvider, + ImportArtifactPermissionProvider permissionProvider, MappedImportableResourcesDTO mappedImportableResourcesDTO) { return Mono.just(importedNewPageList) .zipWith(existingPagesMono) @@ -184,7 +185,7 @@ public class NewPageImportableServiceCEImpl implements ImportableServiceCE, Map>> importedNewPagesMono, MappedImportableResourcesDTO mappedImportableResourcesDTO) { - List editModeApplicationPages = importedApplication.getPages(); - List publishedModeApplicationPages = importedApplication.getPublishedPages(); + // The access source has been changes because the order of execution has changed. + List editModeApplicationPages = (List) mappedImportableResourcesDTO + .getResourceStoreFromArtifactExchangeJson() + .get(FieldName.UNPUBLISHED); + + // this conditional is being placed just for compatibility of the PR #29691 + if (CollectionUtils.isEmpty(editModeApplicationPages)) { + editModeApplicationPages = importedApplication.getPages(); + } + + List publishedModeApplicationPages = (List) mappedImportableResourcesDTO + .getResourceStoreFromArtifactExchangeJson() + .get(FieldName.PUBLISHED); + + // this conditional is being placed just for compatibility of the PR #29691 + if (CollectionUtils.isEmpty(publishedModeApplicationPages)) { + publishedModeApplicationPages = importedApplication.getPublishedPages(); + } Mono> unpublishedPagesMono = importUnpublishedPages(editModeApplicationPages, appendToApp, applicationMono, importedNewPagesMono); @@ -234,7 +251,7 @@ public class NewPageImportableServiceCEImpl implements ImportableServiceCE pageNameMap = objects.getT3(); Application savedApp = objects.getT4(); - mappedImportableResourcesDTO.setPageNameMap(pageNameMap); + mappedImportableResourcesDTO.setPageOrModuleMap(pageNameMap); log.debug("New pages imported for application: {}", savedApp.getId()); Map> applicationPages = new HashMap<>(); @@ -370,7 +387,7 @@ public class NewPageImportableServiceCEImpl implements ImportableServiceCE> existingPages, - ImportApplicationPermissionProvider permissionProvider) { + ImportArtifactPermissionProvider permissionProvider) { Map oldToNewLayoutIds = new HashMap<>(); pages.forEach(newPage -> { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/imports/PluginImportableServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/imports/PluginImportableServiceCEImpl.java index c824ce1df2..c7697f4bfd 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/imports/PluginImportableServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/imports/PluginImportableServiceCEImpl.java @@ -1,11 +1,13 @@ package com.appsmith.server.plugins.imports; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ImportableArtifact; import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.QPlugin; import com.appsmith.server.domains.Workspace; import com.appsmith.server.domains.WorkspacePlugin; import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.dtos.ArtifactExchangeJson; import com.appsmith.server.dtos.ImportingMetaDTO; import com.appsmith.server.dtos.MappedImportableResourcesDTO; import com.appsmith.server.imports.importable.ImportableServiceCE; @@ -55,4 +57,26 @@ public class PluginImportableServiceCEImpl implements ImportableServiceCE log.debug("time to get plugin map: {}", tuples.getT1())) .then(); } + + @Override + public Mono importEntities( + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + Mono workspaceMono, + Mono importContextMono, + ArtifactExchangeJson importableContextJson, + boolean isPartialImport, + boolean isContextAgnostic) { + return importContextMono.flatMap(importableContext -> { + Application application = (Application) importableContext; + ApplicationJson applicationJson = (ApplicationJson) importableContextJson; + return importEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + Mono.just(application), + applicationJson, + isPartialImport); + }); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ArtifactPermission.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ArtifactPermission.java new file mode 100644 index 0000000000..d6cc0b1dec --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ArtifactPermission.java @@ -0,0 +1,5 @@ +package com.appsmith.server.solutions; + +import com.appsmith.server.solutions.ce.ArtifactPermissionCE; + +public interface ArtifactPermission extends ArtifactPermissionCE {} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ApplicationPermissionCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ApplicationPermissionCE.java index 79e6328a2f..1bf23e8d27 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ApplicationPermissionCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ApplicationPermissionCE.java @@ -1,11 +1,9 @@ package com.appsmith.server.solutions.ce; import com.appsmith.server.acl.AclPermission; +import com.appsmith.server.solutions.ArtifactPermission; -public interface ApplicationPermissionCE { - AclPermission getDeletePermission(); - - AclPermission getExportPermission(); +public interface ApplicationPermissionCE extends ArtifactPermission { AclPermission getMakePublicPermission(); @@ -13,8 +11,6 @@ public interface ApplicationPermissionCE { AclPermission getPageCreatePermission(); - AclPermission getGitConnectPermission(); - AclPermission getManageProtectedBranchPermission(); AclPermission getManageDefaultBranchPermission(); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ArtifactPermissionCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ArtifactPermissionCE.java new file mode 100644 index 0000000000..f2e9b574bb --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ArtifactPermissionCE.java @@ -0,0 +1,12 @@ +package com.appsmith.server.solutions.ce; + +import com.appsmith.server.acl.AclPermission; + +public interface ArtifactPermissionCE { + + AclPermission getDeletePermission(); + + AclPermission getGitConnectPermission(); + + AclPermission getExportPermission(); +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/themes/imports/ThemeImportableServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/themes/imports/ThemeImportableServiceCEImpl.java index 40949be952..086f9bc7b7 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/themes/imports/ThemeImportableServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/themes/imports/ThemeImportableServiceCEImpl.java @@ -2,9 +2,11 @@ package com.appsmith.server.themes.imports; import com.appsmith.server.applications.base.ApplicationService; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ImportableArtifact; import com.appsmith.server.domains.Theme; import com.appsmith.server.domains.Workspace; import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.dtos.ArtifactExchangeJson; import com.appsmith.server.dtos.ImportingMetaDTO; import com.appsmith.server.dtos.MappedImportableResourcesDTO; import com.appsmith.server.imports.importable.ImportableServiceCE; @@ -55,7 +57,7 @@ public class ThemeImportableServiceCEImpl implements ImportableServiceCE Mono applicationMono, ApplicationJson applicationJson, boolean isPartialImport) { - if (Boolean.TRUE.equals(importingMetaDTO.getAppendToApp())) { + if (Boolean.TRUE.equals(importingMetaDTO.getAppendToArtifact())) { // appending to existing app, theme should not change return Mono.empty().then(); } @@ -113,4 +115,26 @@ public class ThemeImportableServiceCEImpl implements ImportableServiceCE } }); } + + @Override + public Mono importEntities( + ImportingMetaDTO importingMetaDTO, + MappedImportableResourcesDTO mappedImportableResourcesDTO, + Mono workspaceMono, + Mono importContextMono, + ArtifactExchangeJson importableContextJson, + boolean isPartialImport, + boolean isContextAgnostic) { + return importContextMono.flatMap(importableContext -> { + Application application = (Application) importableContext; + ApplicationJson applicationJson = (ApplicationJson) importableContextJson; + return importEntities( + importingMetaDTO, + mappedImportableResourcesDTO, + workspaceMono, + Mono.just(application), + applicationJson, + isPartialImport); + }); + } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/controllers/ApplicationControllerTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/controllers/ApplicationControllerTest.java index f7c318b961..b40f704293 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/controllers/ApplicationControllerTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/controllers/ApplicationControllerTest.java @@ -12,6 +12,7 @@ import com.appsmith.server.exports.internal.PartialExportService; import com.appsmith.server.fork.internal.ApplicationForkingService; import com.appsmith.server.helpers.GitFileUtils; import com.appsmith.server.helpers.RedisUtils; +import com.appsmith.server.imports.importable.ImportService; import com.appsmith.server.imports.internal.ImportApplicationService; import com.appsmith.server.imports.internal.PartialImportService; import com.appsmith.server.services.AnalyticsService; @@ -58,6 +59,9 @@ public class ApplicationControllerTest { @MockBean ImportApplicationService importApplicationService; + @MockBean + ImportService importService; + @MockBean ExportApplicationService exportApplicationService; diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/ImportApplicationPermissionProviderTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/ImportArtifactPermissionProviderTest.java similarity index 85% rename from app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/ImportApplicationPermissionProviderTest.java rename to app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/ImportArtifactPermissionProviderTest.java index 1930362aaa..3bf73e03f7 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/ImportApplicationPermissionProviderTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/ImportArtifactPermissionProviderTest.java @@ -30,7 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @SpringBootTest -class ImportApplicationPermissionProviderTest { +class ImportArtifactPermissionProviderTest { @Autowired ApplicationPermission applicationPermission; @@ -48,22 +48,21 @@ class ImportApplicationPermissionProviderTest { @Test public void testCheckPermissionMethods_WhenNoPermissionProvided_ReturnsTrue() { - ImportApplicationPermissionProvider importApplicationPermissionProvider = - ImportApplicationPermissionProvider.builder( - applicationPermission, - pagePermission, - actionPermission, - datasourcePermission, - workspacePermission) - .build(); + ImportArtifactPermissionProvider importArtifactPermissionProvider = ImportArtifactPermissionProvider.builder( + applicationPermission, + pagePermission, + actionPermission, + datasourcePermission, + workspacePermission) + .build(); - assertTrue(importApplicationPermissionProvider.hasEditPermission(new NewPage())); - assertTrue(importApplicationPermissionProvider.hasEditPermission(new NewAction())); - assertTrue(importApplicationPermissionProvider.hasEditPermission(new Datasource())); + assertTrue(importArtifactPermissionProvider.hasEditPermission(new NewPage())); + assertTrue(importArtifactPermissionProvider.hasEditPermission(new NewAction())); + assertTrue(importArtifactPermissionProvider.hasEditPermission(new Datasource())); - assertTrue(importApplicationPermissionProvider.canCreateDatasource(new Workspace())); - assertTrue(importApplicationPermissionProvider.canCreateAction(new NewPage())); - assertTrue(importApplicationPermissionProvider.canCreatePage(new Application())); + assertTrue(importArtifactPermissionProvider.canCreateDatasource(new Workspace())); + assertTrue(importArtifactPermissionProvider.canCreateAction(new NewPage())); + assertTrue(importArtifactPermissionProvider.canCreatePage(new Application())); } @Test @@ -81,7 +80,7 @@ class ImportApplicationPermissionProviderTest { for (Tuple2 domainAndPermission : domainAndPermissionList) { BaseDomain domain = domainAndPermission.getT1(); // create a permission provider that sets edit permission on the domain - ImportApplicationPermissionProvider provider = + ImportArtifactPermissionProvider provider = createPermissionProviderForDomainEditPermission(domain, domainAndPermission.getT2()); if (domain instanceof NewPage) { @@ -108,7 +107,7 @@ class ImportApplicationPermissionProviderTest { for (Tuple2 domainAndPermission : domainAndPermissionList) { BaseDomain domain = domainAndPermission.getT1(); // create a permission provider that sets edit permission on the domain - ImportApplicationPermissionProvider provider = + ImportArtifactPermissionProvider provider = createPermissionProviderForDomainCreatePermission(domain, domainAndPermission.getT2()); if (domain instanceof Application) { @@ -123,7 +122,7 @@ class ImportApplicationPermissionProviderTest { @Test public void tesBuilderIsSettingTheCorrectParametersToPermissionProvider() { - ImportApplicationPermissionProvider.Builder builder = ImportApplicationPermissionProvider.builder( + ImportArtifactPermissionProvider.Builder builder = ImportArtifactPermissionProvider.builder( applicationPermission, pagePermission, actionPermission, datasourcePermission, workspacePermission); assertThat(builder.requiredPermissionOnTargetApplication(applicationPermission.getEditPermission()) @@ -147,7 +146,7 @@ class ImportApplicationPermissionProviderTest { @Test public void testAllPermissionsRequiredIsSettingAllPermissionsAsRequired() { - ImportApplicationPermissionProvider provider = ImportApplicationPermissionProvider.builder( + ImportArtifactPermissionProvider provider = ImportArtifactPermissionProvider.builder( applicationPermission, pagePermission, actionPermission, @@ -176,11 +175,11 @@ class ImportApplicationPermissionProviderTest { * @param domainPermission * @return */ - private ImportApplicationPermissionProvider createPermissionProviderForDomainEditPermission( + private ImportArtifactPermissionProvider createPermissionProviderForDomainEditPermission( BaseDomain baseDomain, DomainPermission domainPermission) { setPoliciesToDomain(baseDomain, domainPermission.getEditPermission()); - ImportApplicationPermissionProvider.Builder builder = ImportApplicationPermissionProvider.builder( + ImportArtifactPermissionProvider.Builder builder = ImportArtifactPermissionProvider.builder( applicationPermission, pagePermission, actionPermission, @@ -210,11 +209,11 @@ class ImportApplicationPermissionProviderTest { * @param permission * @return */ - private ImportApplicationPermissionProvider createPermissionProviderForDomainCreatePermission( + private ImportArtifactPermissionProvider createPermissionProviderForDomainCreatePermission( BaseDomain baseDomain, AclPermission permission) { setPoliciesToDomain(baseDomain, permission); - ImportApplicationPermissionProvider.Builder builder = ImportApplicationPermissionProvider.builder( + ImportArtifactPermissionProvider.Builder builder = ImportArtifactPermissionProvider.builder( applicationPermission, pagePermission, actionPermission, diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/imports/internal/ImportServiceTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/imports/internal/ImportServiceTests.java new file mode 100644 index 0000000000..c2c64b193c --- /dev/null +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/imports/internal/ImportServiceTests.java @@ -0,0 +1,5181 @@ +package com.appsmith.server.imports.internal; + +import com.appsmith.external.helpers.AppsmithBeanUtils; +import com.appsmith.external.models.ActionConfiguration; +import com.appsmith.external.models.ActionDTO; +import com.appsmith.external.models.BearerTokenAuth; +import com.appsmith.external.models.Connection; +import com.appsmith.external.models.CreatorContextType; +import com.appsmith.external.models.DBAuth; +import com.appsmith.external.models.Datasource; +import com.appsmith.external.models.DatasourceConfiguration; +import com.appsmith.external.models.DatasourceStorage; +import com.appsmith.external.models.DatasourceStorageDTO; +import com.appsmith.external.models.DecryptedSensitiveFields; +import com.appsmith.external.models.InvisibleActionFields; +import com.appsmith.external.models.PluginType; +import com.appsmith.external.models.Policy; +import com.appsmith.external.models.Property; +import com.appsmith.external.models.SSLDetails; +import com.appsmith.server.actioncollections.base.ActionCollectionService; +import com.appsmith.server.applications.base.ApplicationService; +import com.appsmith.server.constants.ArtifactJsonType; +import com.appsmith.server.constants.FieldName; +import com.appsmith.server.constants.SerialiseApplicationObjective; +import com.appsmith.server.datasources.base.DatasourceService; +import com.appsmith.server.domains.ActionCollection; +import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ApplicationDetail; +import com.appsmith.server.domains.ApplicationMode; +import com.appsmith.server.domains.ApplicationPage; +import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.GitApplicationMetadata; +import com.appsmith.server.domains.Layout; +import com.appsmith.server.domains.NewAction; +import com.appsmith.server.domains.NewPage; +import com.appsmith.server.domains.PermissionGroup; +import com.appsmith.server.domains.Plugin; +import com.appsmith.server.domains.Theme; +import com.appsmith.server.domains.User; +import com.appsmith.server.domains.Workspace; +import com.appsmith.server.dtos.ActionCollectionDTO; +import com.appsmith.server.dtos.ApplicationAccessDTO; +import com.appsmith.server.dtos.ApplicationImportDTO; +import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.dtos.ApplicationPagesDTO; +import com.appsmith.server.dtos.ImportableArtifactDTO; +import com.appsmith.server.dtos.PageDTO; +import com.appsmith.server.dtos.PageNameIdDTO; +import com.appsmith.server.exceptions.AppsmithError; +import com.appsmith.server.exceptions.AppsmithException; +import com.appsmith.server.exports.internal.ExportApplicationService; +import com.appsmith.server.helpers.MockPluginExecutor; +import com.appsmith.server.helpers.PluginExecutorHelper; +import com.appsmith.server.imports.importable.ImportService; +import com.appsmith.server.jslibs.base.CustomJSLibService; +import com.appsmith.server.layouts.UpdateLayoutService; +import com.appsmith.server.migrations.ApplicationVersion; +import com.appsmith.server.migrations.JsonSchemaMigration; +import com.appsmith.server.migrations.JsonSchemaVersions; +import com.appsmith.server.newactions.base.NewActionService; +import com.appsmith.server.newpages.base.NewPageService; +import com.appsmith.server.plugins.base.PluginService; +import com.appsmith.server.repositories.ApplicationRepository; +import com.appsmith.server.repositories.CacheableRepositoryHelper; +import com.appsmith.server.repositories.PermissionGroupRepository; +import com.appsmith.server.repositories.PluginRepository; +import com.appsmith.server.repositories.ThemeRepository; +import com.appsmith.server.services.ApplicationPageService; +import com.appsmith.server.services.LayoutActionService; +import com.appsmith.server.services.LayoutCollectionService; +import com.appsmith.server.services.PermissionGroupService; +import com.appsmith.server.services.SessionUserService; +import com.appsmith.server.services.WorkspaceService; +import com.appsmith.server.solutions.ApplicationPermission; +import com.appsmith.server.solutions.EnvironmentPermission; +import com.appsmith.server.solutions.PagePermission; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import lombok.extern.slf4j.Slf4j; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.codec.multipart.FilePart; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringExtension; +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.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import static com.appsmith.external.constants.ce.GitConstantsCE.NAME_SEPARATOR; +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.READ_ACTIONS; +import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS; +import static com.appsmith.server.acl.AclPermission.READ_PAGES; +import static com.appsmith.server.acl.AclPermission.READ_WORKSPACES; +import static com.appsmith.server.constants.ce.FieldNameCE.DEFAULT_PAGE_LAYOUT; +import static com.appsmith.server.dtos.ce.CustomJSLibContextCE_DTO.getDTOFromCustomJSLib; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +@Slf4j +@ExtendWith(SpringExtension.class) +@SpringBootTest +@DirtiesContext +@TestMethodOrder(MethodOrderer.MethodName.class) +public class ImportServiceTests { + + private static final String INVALID_JSON_FILE = "invalid json file"; + private static final Map datasourceMap = new HashMap<>(); + private static Plugin installedPlugin; + private static String workspaceId; + private static String defaultEnvironmentId; + private static String testAppId; + private static Datasource jsDatasource; + private static Plugin installedJsPlugin; + private static Boolean isSetupDone = false; + private static String exportWithConfigurationAppId; + + @Autowired + ImportService importService; + + @Autowired + ExportApplicationService exportApplicationService; + + // @Autowired + // ImportApplicationService importApplicationService; + + @Autowired + Gson gson; + + @Autowired + ApplicationPageService applicationPageService; + + @Autowired + PluginRepository pluginRepository; + + @Autowired + ApplicationRepository applicationRepository; + + @Autowired + DatasourceService datasourceService; + + @Autowired + NewPageService newPageService; + + @Autowired + NewActionService newActionService; + + @Autowired + WorkspaceService workspaceService; + + @Autowired + LayoutActionService layoutActionService; + + @Autowired + UpdateLayoutService updateLayoutService; + + @Autowired + LayoutCollectionService layoutCollectionService; + + @Autowired + ActionCollectionService actionCollectionService; + + @MockBean + PluginExecutorHelper pluginExecutorHelper; + + @Autowired + ThemeRepository themeRepository; + + @Autowired + ApplicationService applicationService; + + @Autowired + PermissionGroupRepository permissionGroupRepository; + + @Autowired + PermissionGroupService permissionGroupService; + + @Autowired + CustomJSLibService customJSLibService; + + @Autowired + EnvironmentPermission environmentPermission; + + @Autowired + PagePermission pagePermission; + + @Autowired + ApplicationPermission applicationPermission; + + @SpyBean + PluginService pluginService; + + @Autowired + CacheableRepositoryHelper cacheableRepositoryHelper; + + @Autowired + SessionUserService sessionUserService; + + @BeforeEach + public void setup() { + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())) + .thenReturn(Mono.just(new MockPluginExecutor())); + + if (Boolean.TRUE.equals(isSetupDone)) { + return; + } + User currentUser = sessionUserService.getCurrentUser().block(); + Set beforeCreatingWorkspace = + cacheableRepositoryHelper.getPermissionGroupsOfUser(currentUser).block(); + log.info("Permission Groups for User before creating workspace: {}", beforeCreatingWorkspace); + installedPlugin = pluginRepository.findByPackageName("installed-plugin").block(); + Workspace workspace = new Workspace(); + workspace.setName("Import-Export-Test-Workspace"); + Workspace savedWorkspace = workspaceService.create(workspace).block(); + workspaceId = savedWorkspace.getId(); + defaultEnvironmentId = workspaceService + .getDefaultEnvironmentId(workspaceId, environmentPermission.getExecutePermission()) + .block(); + Set afterCreatingWorkspace = + cacheableRepositoryHelper.getPermissionGroupsOfUser(currentUser).block(); + log.info("Permission Groups for User after creating workspace: {}", afterCreatingWorkspace); + + log.info("Workspace ID: {}", workspaceId); + log.info("Workspace Role Ids: {}", workspace.getDefaultPermissionGroups()); + log.info("Policy for created Workspace: {}", workspace.getPolicies()); + log.info("Current User ID: {}", currentUser.getId()); + + Application testApplication = new Application(); + testApplication.setName("Export-Application-Test-Application"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + + Application.ThemeSetting themeSettings = getThemeSetting(); + testApplication.setUnpublishedApplicationDetail(new ApplicationDetail()); + testApplication.getUnpublishedApplicationDetail().setThemeSetting(themeSettings); + + Application savedApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .block(); + testAppId = savedApplication.getId(); + + Datasource ds1 = new Datasource(); + ds1.setName("DS1"); + ds1.setWorkspaceId(workspaceId); + ds1.setPluginId(installedPlugin.getId()); + final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setUrl("http://example.org/get"); + datasourceConfiguration.setHeaders(List.of(new Property("X-Answer", "42"))); + + HashMap storages1 = new HashMap<>(); + storages1.put( + defaultEnvironmentId, new DatasourceStorageDTO(null, defaultEnvironmentId, datasourceConfiguration)); + ds1.setDatasourceStorages(storages1); + + Datasource ds2 = new Datasource(); + ds2.setName("DS2"); + ds2.setPluginId(installedPlugin.getId()); + ds2.setWorkspaceId(workspaceId); + DatasourceConfiguration datasourceConfiguration2 = new DatasourceConfiguration(); + DBAuth auth = new DBAuth(); + auth.setPassword("awesome-password"); + datasourceConfiguration2.setAuthentication(auth); + HashMap storages2 = new HashMap<>(); + storages2.put( + defaultEnvironmentId, new DatasourceStorageDTO(null, defaultEnvironmentId, datasourceConfiguration2)); + ds2.setDatasourceStorages(storages2); + + jsDatasource = new Datasource(); + jsDatasource.setName("Default JS datasource"); + jsDatasource.setWorkspaceId(workspaceId); + installedJsPlugin = + pluginRepository.findByPackageName("installed-js-plugin").block(); + assert installedJsPlugin != null; + jsDatasource.setPluginId(installedJsPlugin.getId()); + + ds1 = datasourceService.create(ds1).block(); + ds2 = datasourceService.create(ds2).block(); + datasourceMap.put("DS1", ds1); + datasourceMap.put("DS2", ds2); + isSetupDone = true; + } + + private Flux getActionsInApplication(Application application) { + return newPageService + // fetch the unpublished pages + .findByApplicationId(application.getId(), READ_PAGES, false) + .flatMap(page -> newActionService.getUnpublishedActions( + new LinkedMultiValueMap<>(Map.of(FieldName.PAGE_ID, Collections.singletonList(page.getId()))), + "")); + } + + private FilePart createFilePart(String filePath) { + FilePart filepart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS); + Flux dataBufferFlux = DataBufferUtils.read( + new ClassPathResource(filePath), new DefaultDataBufferFactory(), 4096) + .cache(); + + Mockito.when(filepart.content()).thenReturn(dataBufferFlux); + Mockito.when(filepart.headers().getContentType()).thenReturn(MediaType.APPLICATION_JSON); + + return filepart; + } + + private Mono createAppJson(String filePath) { + FilePart filePart = createFilePart(filePath); + + Mono stringifiedFile = DataBufferUtils.join(filePart.content()).map(dataBuffer -> { + byte[] data = new byte[dataBuffer.readableByteCount()]; + dataBuffer.read(data); + DataBufferUtils.release(dataBuffer); + return new String(data); + }); + + return stringifiedFile + .map(data -> { + return gson.fromJson(data, ApplicationJson.class); + }) + .map(JsonSchemaMigration::migrateApplicationToLatestSchema); + } + + private Workspace createTemplateWorkspace() { + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("Template Workspace"); + return workspaceService.create(newWorkspace).block(); + } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplicationById_WhenContainsInternalFields_InternalFieldsNotExported() { + Mono resultMono = exportApplicationService.exportApplicationById(testAppId, ""); + + StepVerifier.create(resultMono) + .assertNext(applicationJson -> { + Application exportedApplication = applicationJson.getExportedApplication(); + assertThat(exportedApplication.getModifiedBy()).isNull(); + assertThat(exportedApplication.getLastUpdateTime()).isNull(); + assertThat(exportedApplication.getLastEditedAt()).isNull(); + assertThat(exportedApplication.getLastDeployedAt()).isNull(); + assertThat(exportedApplication.getGitApplicationMetadata()).isNull(); + assertThat(exportedApplication.getEditModeThemeId()).isNull(); + assertThat(exportedApplication.getPublishedModeThemeId()).isNull(); + assertThat(exportedApplication.getExportWithConfiguration()).isNull(); + assertThat(exportedApplication.getForkWithConfiguration()).isNull(); + assertThat(exportedApplication.getForkingEnabled()).isNull(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void createExportAppJsonWithDatasourceButWithoutActionsTest() { + + Application testApplication = new Application(); + testApplication.setName("Another Export Application"); + + final Mono resultMono = workspaceService + .getById(workspaceId) + .flatMap(workspace -> { + final Datasource ds1 = datasourceMap.get("DS1"); + ds1.setWorkspaceId(workspace.getId()); + + final Datasource ds2 = datasourceMap.get("DS2"); + ds2.setWorkspaceId(workspace.getId()); + + return applicationPageService.createApplication(testApplication, workspaceId); + }) + .flatMap(application -> exportApplicationService.exportApplicationById(application.getId(), "")); + + StepVerifier.create(resultMono) + .assertNext(applicationJson -> { + assertThat(applicationJson.getPageList()).hasSize(1); + assertThat(applicationJson.getActionList()).isEmpty(); + assertThat(applicationJson.getDatasourceList()).isEmpty(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void createExportAppJsonWithActionAndActionCollectionTest() { + + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("template-org-with-ds"); + + Application testApplication = new Application(); + testApplication.setName("ApplicationWithActionCollectionAndDatasource"); + testApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .block(); + + assert testApplication != null; + final String appName = testApplication.getName(); + final Mono resultMono = Mono.zip( + Mono.just(testApplication), + newPageService.findPageById( + testApplication.getPages().get(0).getId(), READ_PAGES, false)) + .flatMap(tuple -> { + Application testApp = tuple.getT1(); + PageDTO testPage = tuple.getT2(); + + Layout layout = testPage.getLayouts().get(0); + ObjectMapper objectMapper = new ObjectMapper(); + JSONObject dsl = new JSONObject(); + try { + dsl = new JSONObject(objectMapper.readValue( + DEFAULT_PAGE_LAYOUT, new TypeReference>() {})); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + + ArrayList children = (ArrayList) dsl.get("children"); + JSONObject testWidget = new JSONObject(); + testWidget.put("widgetName", "firstWidget"); + JSONArray temp = new JSONArray(); + temp.add(new JSONObject(Map.of("key", "testField"))); + testWidget.put("dynamicBindingPathList", temp); + testWidget.put("testField", "{{ validAction.data }}"); + children.add(testWidget); + + JSONObject tableWidget = new JSONObject(); + tableWidget.put("widgetName", "Table1"); + tableWidget.put("type", "TABLE_WIDGET"); + Map primaryColumns = new HashMap<>(); + JSONObject jsonObject = new JSONObject(Map.of("key", "value")); + primaryColumns.put("_id", "{{ PageAction.data }}"); + primaryColumns.put("_class", jsonObject); + tableWidget.put("primaryColumns", primaryColumns); + final ArrayList objects = new ArrayList<>(); + JSONArray temp2 = new JSONArray(); + temp2.add(new JSONObject(Map.of("key", "primaryColumns._id"))); + tableWidget.put("dynamicBindingPathList", temp2); + children.add(tableWidget); + + layout.setDsl(dsl); + layout.setPublishedDsl(dsl); + + ActionDTO action = new ActionDTO(); + action.setName("validAction"); + action.setPageId(testPage.getId()); + action.setExecuteOnLoad(true); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasourceMap.get("DS2")); + + ActionDTO action2 = new ActionDTO(); + action2.setName("validAction2"); + action2.setPageId(testPage.getId()); + action2.setExecuteOnLoad(true); + action2.setUserSetOnLoad(true); + ActionConfiguration actionConfiguration2 = new ActionConfiguration(); + actionConfiguration2.setHttpMethod(HttpMethod.GET); + action2.setActionConfiguration(actionConfiguration2); + action2.setDatasource(datasourceMap.get("DS2")); + + ActionCollectionDTO actionCollectionDTO1 = new ActionCollectionDTO(); + actionCollectionDTO1.setName("testCollection1"); + actionCollectionDTO1.setPageId(testPage.getId()); + actionCollectionDTO1.setApplicationId(testApp.getId()); + actionCollectionDTO1.setWorkspaceId(testApp.getWorkspaceId()); + actionCollectionDTO1.setPluginId(jsDatasource.getPluginId()); + ActionDTO action1 = new ActionDTO(); + action1.setName("testAction1"); + action1.setActionConfiguration(new ActionConfiguration()); + action1.getActionConfiguration().setBody("mockBody"); + actionCollectionDTO1.setActions(List.of(action1)); + actionCollectionDTO1.setPluginType(PluginType.JS); + + return layoutCollectionService + .createCollection(actionCollectionDTO1, null) + .then(layoutActionService.createSingleAction(action, Boolean.FALSE)) + .then(layoutActionService.createSingleAction(action2, Boolean.FALSE)) + .then(updateLayoutService.updateLayout( + testPage.getId(), testPage.getApplicationId(), layout.getId(), layout)) + .then(exportApplicationService.exportApplicationById(testApp.getId(), "")); + }) + .cache(); + + Mono> actionListMono = resultMono.then(newActionService + .findAllByApplicationIdAndViewMode(testApplication.getId(), false, READ_ACTIONS, null) + .collectList()); + + Mono> collectionListMono = resultMono.then(actionCollectionService + .findAllByApplicationIdAndViewMode(testApplication.getId(), false, READ_ACTIONS, null) + .collectList()); + + Mono> pageListMono = resultMono.then(newPageService + .findNewPagesByApplicationId(testApplication.getId(), READ_PAGES) + .collectList()); + + StepVerifier.create(Mono.zip(resultMono, actionListMono, collectionListMono, pageListMono)) + .assertNext(tuple -> { + ApplicationJson applicationJson = tuple.getT1(); + List DBActions = tuple.getT2(); + List DBCollections = tuple.getT3(); + List DBPages = tuple.getT4(); + + Application exportedApp = applicationJson.getExportedApplication(); + List pageList = applicationJson.getPageList(); + List actionList = applicationJson.getActionList(); + List actionCollectionList = applicationJson.getActionCollectionList(); + List datasourceList = applicationJson.getDatasourceList(); + + List exportedCollectionIds = actionCollectionList.stream() + .map(ActionCollection::getId) + .collect(Collectors.toList()); + List exportedActionIds = + actionList.stream().map(NewAction::getId).collect(Collectors.toList()); + List DBCollectionIds = + DBCollections.stream().map(ActionCollection::getId).collect(Collectors.toList()); + List DBActionIds = + DBActions.stream().map(NewAction::getId).collect(Collectors.toList()); + List DBOnLayoutLoadActionIds = new ArrayList<>(); + List exportedOnLayoutLoadActionIds = new ArrayList<>(); + + assertThat(DBPages).hasSize(1); + DBPages.forEach( + newPage -> newPage.getUnpublishedPage().getLayouts().forEach(layout -> { + if (layout.getLayoutOnLoadActions() != null) { + layout.getLayoutOnLoadActions().forEach(dslActionDTOSet -> { + dslActionDTOSet.forEach( + actionDTO -> DBOnLayoutLoadActionIds.add(actionDTO.getId())); + }); + } + })); + pageList.forEach( + newPage -> newPage.getUnpublishedPage().getLayouts().forEach(layout -> { + if (layout.getLayoutOnLoadActions() != null) { + layout.getLayoutOnLoadActions().forEach(dslActionDTOSet -> { + dslActionDTOSet.forEach( + actionDTO -> exportedOnLayoutLoadActionIds.add(actionDTO.getId())); + }); + } + })); + + NewPage defaultPage = pageList.get(0); + + // Check if the mongo escaped widget names are carried to exported file from DB + Layout pageLayout = + DBPages.get(0).getUnpublishedPage().getLayouts().get(0); + Set mongoEscapedWidgets = pageLayout.getMongoEscapedWidgetNames(); + Set expectedMongoEscapedWidgets = Set.of("Table1"); + assertThat(mongoEscapedWidgets).isEqualTo(expectedMongoEscapedWidgets); + + pageLayout = + pageList.get(0).getUnpublishedPage().getLayouts().get(0); + Set exportedMongoEscapedWidgets = pageLayout.getMongoEscapedWidgetNames(); + assertThat(exportedMongoEscapedWidgets).isEqualTo(expectedMongoEscapedWidgets); + + assertThat(exportedApp.getName()).isEqualTo(appName); + assertThat(exportedApp.getWorkspaceId()).isNull(); + assertThat(exportedApp.getPages()).hasSize(1); + assertThat(exportedApp.getPages().get(0).getId()) + .isEqualTo(defaultPage.getUnpublishedPage().getName()); + + assertThat(exportedApp.getPolicies()).isNull(); + + assertThat(pageList).hasSize(1); + assertThat(defaultPage.getApplicationId()).isNull(); + assertThat(defaultPage + .getUnpublishedPage() + .getLayouts() + .get(0) + .getDsl()) + .isNotNull(); + assertThat(defaultPage.getId()).isNull(); + assertThat(defaultPage.getPolicies()).isNull(); + + assertThat(actionList.isEmpty()).isFalse(); + assertThat(actionList).hasSize(3); + NewAction validAction = actionList.stream() + .filter(action -> action.getId().equals("Page1_validAction")) + .findFirst() + .get(); + assertThat(validAction.getApplicationId()).isNull(); + assertThat(validAction.getPluginId()).isEqualTo(installedPlugin.getPackageName()); + assertThat(validAction.getPluginType()).isEqualTo(PluginType.API); + assertThat(validAction.getWorkspaceId()).isNull(); + assertThat(validAction.getPolicies()).isNull(); + assertThat(validAction.getId()).isNotNull(); + ActionDTO unpublishedAction = validAction.getUnpublishedAction(); + assertThat(unpublishedAction.getPageId()) + .isEqualTo(defaultPage.getUnpublishedPage().getName()); + assertThat(unpublishedAction.getDatasource().getPluginId()) + .isEqualTo(installedPlugin.getPackageName()); + + NewAction testAction1 = actionList.stream() + .filter(action -> + action.getUnpublishedAction().getName().equals("testAction1")) + .findFirst() + .get(); + assertThat(testAction1.getId()).isEqualTo("Page1_testCollection1.testAction1"); + + assertThat(actionCollectionList.isEmpty()).isFalse(); + assertThat(actionCollectionList).hasSize(1); + final ActionCollection actionCollection = actionCollectionList.get(0); + assertThat(actionCollection.getApplicationId()).isNull(); + assertThat(actionCollection.getWorkspaceId()).isNull(); + assertThat(actionCollection.getPolicies()).isNull(); + assertThat(actionCollection.getId()).isNotNull(); + assertThat(actionCollection.getUnpublishedCollection().getPluginType()) + .isEqualTo(PluginType.JS); + assertThat(actionCollection.getUnpublishedCollection().getPageId()) + .isEqualTo(defaultPage.getUnpublishedPage().getName()); + assertThat(actionCollection.getUnpublishedCollection().getPluginId()) + .isEqualTo(installedJsPlugin.getPackageName()); + + assertThat(datasourceList).hasSize(1); + DatasourceStorage datasource = datasourceList.get(0); + assertThat(datasource.getWorkspaceId()).isNull(); + assertThat(datasource.getId()).isNull(); + assertThat(datasource.getPluginId()).isEqualTo(installedPlugin.getPackageName()); + assertThat(datasource.getDatasourceConfiguration()).isNotNull(); + assertThat(datasource.getDatasourceConfiguration().getAuthentication()) + .isNull(); + assertThat(datasource.getDatasourceConfiguration().getSshProxy()) + .isNull(); + assertThat(datasource.getDatasourceConfiguration().getSshProxyEnabled()) + .isNull(); + assertThat(datasource.getDatasourceConfiguration().getProperties()) + .isNull(); + + assertThat(applicationJson.getInvisibleActionFields()).isNull(); + NewAction validAction2 = actionList.stream() + .filter(action -> action.getId().equals("Page1_validAction2")) + .findFirst() + .get(); + assertEquals(true, validAction2.getUnpublishedAction().getUserSetOnLoad()); + + assertThat(applicationJson.getUnpublishedLayoutmongoEscapedWidgets()) + .isNull(); + assertThat(applicationJson.getPublishedLayoutmongoEscapedWidgets()) + .isNull(); + assertThat(applicationJson.getEditModeTheme()).isNotNull(); + assertThat(applicationJson.getEditModeTheme().isSystemTheme()) + .isTrue(); + assertThat(applicationJson.getEditModeTheme().getName()) + .isEqualToIgnoringCase(Theme.DEFAULT_THEME_NAME); + + assertThat(applicationJson.getPublishedTheme()).isNotNull(); + assertThat(applicationJson.getPublishedTheme().isSystemTheme()) + .isTrue(); + assertThat(applicationJson.getPublishedTheme().getName()) + .isEqualToIgnoringCase(Theme.DEFAULT_THEME_NAME); + + assertThat(exportedCollectionIds).isNotEmpty(); + assertThat(exportedCollectionIds).doesNotContain(String.valueOf(DBCollectionIds)); + + assertThat(exportedActionIds).isNotEmpty(); + assertThat(exportedActionIds).doesNotContain(String.valueOf(DBActionIds)); + + assertThat(exportedOnLayoutLoadActionIds).isNotEmpty(); + assertThat(exportedOnLayoutLoadActionIds).doesNotContain(String.valueOf(DBOnLayoutLoadActionIds)); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void createExportAppJsonForGitTest() { + + StringBuilder pageName = new StringBuilder(); + final Mono resultMono = applicationRepository + .findById(testAppId) + .flatMap(testApp -> { + final String pageId = testApp.getPages().get(0).getId(); + return Mono.zip(Mono.just(testApp), newPageService.findPageById(pageId, READ_PAGES, false)); + }) + .flatMap(tuple -> { + Datasource ds1 = datasourceMap.get("DS1"); + Application testApp = tuple.getT1(); + PageDTO testPage = tuple.getT2(); + pageName.append(testPage.getName()); + + Layout layout = testPage.getLayouts().get(0); + JSONObject dsl = new JSONObject(Map.of("text", "{{ query1.data }}")); + + layout.setDsl(dsl); + layout.setPublishedDsl(dsl); + + ActionDTO action = new ActionDTO(); + action.setName("validAction"); + action.setPageId(testPage.getId()); + action.setExecuteOnLoad(true); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(ds1); + + return layoutActionService + .createAction(action) + .then(exportApplicationService.exportApplicationById( + testApp.getId(), SerialiseApplicationObjective.VERSION_CONTROL)); + }); + + StepVerifier.create(resultMono) + .assertNext(applicationJson -> { + Application exportedApp = applicationJson.getExportedApplication(); + List pageList = applicationJson.getPageList(); + List actionList = applicationJson.getActionList(); + List datasourceList = applicationJson.getDatasourceList(); + + NewPage newPage = pageList.get(0); + + assertThat(applicationJson.getServerSchemaVersion()).isEqualTo(JsonSchemaVersions.serverVersion); + assertThat(applicationJson.getClientSchemaVersion()).isEqualTo(JsonSchemaVersions.clientVersion); + + assertThat(exportedApp.getName()).isNotNull(); + assertThat(exportedApp.getWorkspaceId()).isNull(); + assertThat(exportedApp.getPages()).hasSize(1); + assertThat(exportedApp.getPages().get(0).getId()).isEqualTo(pageName.toString()); + assertThat(exportedApp.getGitApplicationMetadata()).isNull(); + + assertThat(exportedApp.getApplicationDetail()).isNotNull(); + assertThat(exportedApp.getApplicationDetail().getThemeSetting()) + .isNotNull(); + assertThat(exportedApp + .getApplicationDetail() + .getThemeSetting() + .getSizing()) + .isNotNull(); + assertThat(exportedApp + .getApplicationDetail() + .getThemeSetting() + .getAccentColor()) + .isEqualTo("#FFFFFF"); + assertThat(exportedApp + .getApplicationDetail() + .getThemeSetting() + .getColorMode()) + .isEqualTo(Application.ThemeSetting.Type.LIGHT); + assertThat(exportedApp + .getApplicationDetail() + .getThemeSetting() + .getDensity()) + .isEqualTo(1); + assertThat(exportedApp + .getApplicationDetail() + .getThemeSetting() + .getFontFamily()) + .isEqualTo("#000000"); + assertThat(exportedApp + .getApplicationDetail() + .getThemeSetting() + .getSizing()) + .isEqualTo(1); + + assertThat(exportedApp.getPolicies()).isNull(); + assertThat(exportedApp.getUserPermissions()).isNull(); + + assertThat(pageList).hasSize(1); + assertThat(newPage.getApplicationId()).isNull(); + assertThat(newPage.getUnpublishedPage().getLayouts().get(0).getDsl()) + .isNotNull(); + assertThat(newPage.getId()).isNull(); + assertThat(newPage.getPolicies()).isNull(); + + assertThat(actionList.isEmpty()).isFalse(); + NewAction validAction = actionList.get(0); + assertThat(validAction.getApplicationId()).isNull(); + assertThat(validAction.getPluginId()).isEqualTo(installedPlugin.getPackageName()); + assertThat(validAction.getPluginType()).isEqualTo(PluginType.API); + assertThat(validAction.getWorkspaceId()).isNull(); + assertThat(validAction.getPolicies()).isNull(); + assertThat(validAction.getId()).isNotNull(); + assertThat(validAction.getUnpublishedAction().getPageId()) + .isEqualTo(newPage.getUnpublishedPage().getName()); + + assertThat(datasourceList).hasSize(1); + DatasourceStorage datasource = datasourceList.get(0); + assertThat(datasource.getWorkspaceId()).isNull(); + assertThat(datasource.getId()).isNull(); + assertThat(datasource.getPluginId()).isEqualTo(installedPlugin.getPackageName()); + assertThat(datasource.getDatasourceConfiguration()).isNull(); + assertThat(applicationJson.getUnpublishedLayoutmongoEscapedWidgets()) + .isNull(); + assertThat(applicationJson.getPublishedLayoutmongoEscapedWidgets()) + .isNull(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importArtifactFromInvalidFileTest() { + FilePart filepart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS); + Flux dataBufferFlux = DataBufferUtils.read( + new ClassPathResource("test_assets/WorkspaceServiceTest/my_workspace_logo.png"), + new DefaultDataBufferFactory(), + 4096) + .cache(); + + Mockito.when(filepart.content()).thenReturn(dataBufferFlux); + Mockito.when(filepart.headers().getContentType()).thenReturn(MediaType.IMAGE_PNG); + + Mono resultMono = importService.extractArtifactExchangeJsonAndSaveArtifact( + filepart, workspaceId, null, ArtifactJsonType.APPLICATION); + + StepVerifier.create(resultMono) + .expectErrorMatches(error -> error instanceof AppsmithException) + .verify(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importArtifactWithNullWorkspaceIdTest() { + FilePart filepart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS); + + Mono resultMono = importService.extractArtifactExchangeJsonAndSaveArtifact( + filepart, null, null, ArtifactJsonType.APPLICATION); + + StepVerifier.create(resultMono) + .expectErrorMatches(throwable -> throwable instanceof AppsmithException + && throwable + .getMessage() + .equals(AppsmithError.INVALID_PARAMETER.getMessage(FieldName.WORKSPACE_ID))) + .verify(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importArtifactFromInvalidJsonFileWithoutPagesTest() { + + FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/invalid-json-without-pages.json"); + + Mono resultMono = importService.extractArtifactExchangeJsonAndSaveArtifact( + filePart, workspaceId, null, ArtifactJsonType.APPLICATION); + + StepVerifier.create(resultMono) + .expectErrorMatches(throwable -> throwable instanceof AppsmithException + && throwable + .getMessage() + .equals(AppsmithError.VALIDATION_FAILURE.getMessage( + "Field '" + FieldName.PAGE_LIST + "' is missing in the JSON."))) + .verify(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importArtifactFromInvalidJsonFileWithoutArtifactTest() { + + FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/invalid-json-without-app.json"); + Mono resultMono = importService.extractArtifactExchangeJsonAndSaveArtifact( + filePart, workspaceId, null, ArtifactJsonType.APPLICATION); + + StepVerifier.create(resultMono) + .expectErrorMatches( + throwable -> throwable instanceof AppsmithException + && throwable + .getMessage() + .equals( + AppsmithError.VALIDATION_FAILURE.getMessage( + "Field '" + FieldName.APPLICATION + + "' Sorry! Seems like you've imported a page-level json instead of an application. Please use the import within the page."))) + .verify(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importArtifactFromValidJsonFileTest() { + + FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application.json"); + + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("Template Workspace"); + + Mono workspaceMono = workspaceService.create(newWorkspace).cache(); + + String environmentId = workspaceMono + .flatMap(workspace -> workspaceService.getDefaultEnvironmentId( + workspace.getId(), environmentPermission.getExecutePermission())) + .block(); + + final Mono resultMono = + workspaceMono.flatMap(workspace -> importService.extractArtifactExchangeJsonAndSaveArtifact( + filePart, workspace.getId(), null, ArtifactJsonType.APPLICATION)); + + List permissionGroups = workspaceMono + .flatMapMany(savedWorkspace -> { + Set defaultPermissionGroups = savedWorkspace.getDefaultPermissionGroups(); + return permissionGroupRepository.findAllById(defaultPermissionGroups); + }) + .collectList() + .block(); + + PermissionGroup adminPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.ADMINISTRATOR)) + .findFirst() + .get(); + + PermissionGroup developerPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.DEVELOPER)) + .findFirst() + .get(); + + PermissionGroup viewerPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.VIEWER)) + .findFirst() + .get(); + + Policy manageAppPolicy = Policy.builder() + .permission(MANAGE_APPLICATIONS.getValue()) + .permissionGroups(Set.of(adminPermissionGroup.getId(), developerPermissionGroup.getId())) + .build(); + Policy readAppPolicy = Policy.builder() + .permission(READ_APPLICATIONS.getValue()) + .permissionGroups(Set.of( + adminPermissionGroup.getId(), developerPermissionGroup.getId(), viewerPermissionGroup.getId())) + .build(); + + StepVerifier.create(resultMono.flatMap(importArtifactDTO -> { + ApplicationImportDTO applicationImportDTO = (ApplicationImportDTO) importArtifactDTO; + Application application = applicationImportDTO.getApplication(); + return Mono.zip( + Mono.just(applicationImportDTO), + datasourceService + .getAllByWorkspaceIdWithStorages( + application.getWorkspaceId(), Optional.of(MANAGE_DATASOURCES)) + .collectList(), + newActionService + .findAllByApplicationIdAndViewMode(application.getId(), false, READ_ACTIONS, null) + .collectList(), + newPageService + .findByApplicationId(application.getId(), MANAGE_PAGES, false) + .collectList(), + actionCollectionService + .findAllByApplicationIdAndViewMode(application.getId(), false, MANAGE_ACTIONS, null) + .collectList(), + customJSLibService.getAllJSLibsInContext( + application.getId(), CreatorContextType.APPLICATION, null, false)); + })) + .assertNext(tuple -> { + final Application application = tuple.getT1().getApplication(); + final List unConfiguredDatasourceList = + tuple.getT1().getUnConfiguredDatasourceList(); + final boolean isPartialImport = tuple.getT1().getIsPartialImport(); + final List datasourceList = tuple.getT2(); + final List actionList = tuple.getT3(); + final List pageList = tuple.getT4(); + final List actionCollectionList = tuple.getT5(); + final List importedJSLibList = tuple.getT6(); + + // although the imported list had only one jsLib entry, the other entry comes from ensuring an xml + // parser entry + // for backward compatibility + assertEquals(2, importedJSLibList.size()); + CustomJSLib importedJSLib = (CustomJSLib) importedJSLibList.toArray()[0]; + CustomJSLib expectedJSLib = new CustomJSLib( + "TestLib", Set.of("accessor1"), "url", "docsUrl", "1" + ".0", "defs_string"); + assertEquals(expectedJSLib.getName(), importedJSLib.getName()); + assertEquals(expectedJSLib.getAccessor(), importedJSLib.getAccessor()); + assertEquals(expectedJSLib.getUrl(), importedJSLib.getUrl()); + assertEquals(expectedJSLib.getDocsUrl(), importedJSLib.getDocsUrl()); + assertEquals(expectedJSLib.getVersion(), importedJSLib.getVersion()); + assertEquals(expectedJSLib.getDefs(), importedJSLib.getDefs()); + // although the imported list had only one jsLib entry, the other entry comes from ensuring an xml + // parser entry + // for backward compatibility + assertEquals(2, application.getUnpublishedCustomJSLibs().size()); + + assertThat(application.getName()).isEqualTo("valid_application"); + assertThat(application.getWorkspaceId()).isNotNull(); + assertThat(application.getPages()).hasSize(2); + assertThat(application.getPolicies()).containsAll(Set.of(manageAppPolicy, readAppPolicy)); + assertThat(application.getPublishedPages()).hasSize(1); + assertThat(application.getModifiedBy()).isEqualTo("api_user"); + assertThat(application.getUpdatedAt()).isNotNull(); + assertThat(application.getEditModeThemeId()).isNotNull(); + assertThat(application.getPublishedModeThemeId()).isNotNull(); + assertThat(isPartialImport).isEqualTo(Boolean.TRUE); + assertThat(unConfiguredDatasourceList).isNotNull(); + + assertThat(datasourceList).isNotEmpty(); + datasourceList.forEach(datasource -> { + assertThat(datasource.getWorkspaceId()).isEqualTo(application.getWorkspaceId()); + DatasourceStorageDTO storageDTO = + datasource.getDatasourceStorages().get(environmentId); + assertThat(storageDTO.getDatasourceConfiguration()).isNotNull(); + }); + + List collectionIdInAction = new ArrayList<>(); + assertThat(actionList).isNotEmpty(); + actionList.forEach(newAction -> { + ActionDTO actionDTO = newAction.getUnpublishedAction(); + assertThat(actionDTO.getPageId()) + .isNotEqualTo(pageList.get(0).getName()); + + if (StringUtils.equals(actionDTO.getName(), "api_wo_auth")) { + ActionDTO publishedAction = newAction.getPublishedAction(); + assertThat(publishedAction).isNotNull(); + assertThat(publishedAction.getActionConfiguration()).isNotNull(); + // Test the fallback page ID from the unpublishedAction is copied to published version when + // published version does not have pageId + assertThat(actionDTO.getPageId()).isEqualTo(publishedAction.getPageId()); + // check that createAt field is getting populated from JSON + assertThat(actionDTO.getCreatedAt()).isEqualTo("2023-12-13T12:10:02Z"); + } + + if (!StringUtils.isEmpty(actionDTO.getCollectionId())) { + collectionIdInAction.add(actionDTO.getCollectionId()); + assertThat(actionDTO.getDefaultResources().getCollectionId()) + .isEqualTo(actionDTO.getCollectionId()); + } + }); + + assertThat(actionCollectionList).isNotEmpty(); + actionCollectionList.forEach(actionCollection -> { + assertThat(actionCollection.getUnpublishedCollection().getPageId()) + .isNotEqualTo(pageList.get(0).getName()); + if (StringUtils.equals( + actionCollection.getUnpublishedCollection().getName(), "JSObject2")) { + // Check if this action collection is not attached to any action + assertThat(collectionIdInAction).doesNotContain(actionCollection.getId()); + } else { + assertThat(collectionIdInAction).contains(actionCollection.getId()); + } + }); + + assertThat(pageList).hasSize(2); + + ApplicationPage defaultAppPage = application.getPages().stream() + .filter(ApplicationPage::getIsDefault) + .findFirst() + .orElse(null); + assertThat(defaultAppPage).isNotNull(); + + PageDTO defaultPageDTO = pageList.stream() + .filter(pageDTO -> pageDTO.getId().equals(defaultAppPage.getId())) + .findFirst() + .orElse(null); + + assertThat(defaultPageDTO).isNotNull(); + assertThat(defaultPageDTO.getLayouts().get(0).getLayoutOnLoadActions()) + .isNotEmpty(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importFromValidJson_cancelledMidway_importSuccess() { + + FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application.json"); + + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("Midway cancel import app workspace"); + newWorkspace = workspaceService.create(newWorkspace).block(); + + importService + .extractArtifactExchangeJsonAndSaveArtifact( + filePart, newWorkspace.getId(), null, ArtifactJsonType.APPLICATION) + .timeout(Duration.ofMillis(10)) + .subscribe(); + + // Wait for import to complete + Mono importedAppFromDbMono = Mono.just(newWorkspace).flatMap(workspace -> { + try { + // Before fetching the imported application, sleep for 5 seconds to ensure that the import completes + Thread.sleep(5000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return applicationRepository + .findByWorkspaceId(workspace.getId(), READ_APPLICATIONS) + .next(); + }); + + StepVerifier.create(importedAppFromDbMono) + .assertNext(application -> { + assertThat(application.getId()).isNotEmpty(); + }) + .verifyComplete(); + } + + @Test + @Disabled + @WithUserDetails(value = "api_user") + public void importApplicationInWorkspace_WhenCustomizedThemes_ThemesCreated() { + FilePart filePart = + createFilePart("test_assets/ImportExportServiceTest/valid-application-with-custom-themes.json"); + + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("Import theme test org"); + + final Mono resultMono = workspaceService + .create(newWorkspace) + .flatMap(workspace -> importService.extractArtifactExchangeJsonAndSaveArtifact( + filePart, workspace.getId(), null, ArtifactJsonType.APPLICATION)) + .map(artifactImportDTO -> (ApplicationImportDTO) artifactImportDTO); + + StepVerifier.create(resultMono.flatMap(applicationImportDTO -> Mono.zip( + Mono.just(applicationImportDTO), + themeRepository.findById( + applicationImportDTO.getApplication().getEditModeThemeId()), + themeRepository.findById( + applicationImportDTO.getApplication().getPublishedModeThemeId())))) + .assertNext(tuple -> { + final Application application = tuple.getT1().getApplication(); + Theme editTheme = tuple.getT2(); + Theme publishedTheme = tuple.getT3(); + + assertThat(editTheme.isSystemTheme()).isFalse(); + assertThat(editTheme.getDisplayName()).isEqualTo("Custom edit theme"); + assertThat(editTheme.getWorkspaceId()).isNull(); + assertThat(editTheme.getApplicationId()).isNull(); + + assertThat(publishedTheme.isSystemTheme()).isFalse(); + assertThat(publishedTheme.getDisplayName()).isEqualTo("Custom published theme"); + assertThat(publishedTheme.getWorkspaceId()).isNullOrEmpty(); + assertThat(publishedTheme.getApplicationId()).isNullOrEmpty(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importArtifact_withoutActionCollection_succeedsWithoutError() { + + FilePart filePart = + createFilePart("test_assets/ImportExportServiceTest/valid-application-without-action-collection.json"); + + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("Template Workspace"); + + Mono workspaceMono = workspaceService.create(newWorkspace).cache(); + + final Mono resultMono = workspaceMono + .flatMap(workspace -> importService.extractArtifactExchangeJsonAndSaveArtifact( + filePart, workspace.getId(), null, ArtifactJsonType.APPLICATION)) + .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); + + List permissionGroups = workspaceMono + .flatMapMany(savedWorkspace -> { + Set defaultPermissionGroups = savedWorkspace.getDefaultPermissionGroups(); + return permissionGroupRepository.findAllById(defaultPermissionGroups); + }) + .collectList() + .block(); + + PermissionGroup adminPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.ADMINISTRATOR)) + .findFirst() + .get(); + + PermissionGroup developerPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.DEVELOPER)) + .findFirst() + .get(); + + PermissionGroup viewerPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.VIEWER)) + .findFirst() + .get(); + + Policy manageAppPolicy = Policy.builder() + .permission(MANAGE_APPLICATIONS.getValue()) + .permissionGroups(Set.of(adminPermissionGroup.getId(), developerPermissionGroup.getId())) + .build(); + Policy readAppPolicy = Policy.builder() + .permission(READ_APPLICATIONS.getValue()) + .permissionGroups(Set.of( + adminPermissionGroup.getId(), developerPermissionGroup.getId(), viewerPermissionGroup.getId())) + .build(); + + StepVerifier.create(resultMono.flatMap(applicationImportDTO -> Mono.zip( + Mono.just(applicationImportDTO), + datasourceService + .getAllByWorkspaceIdWithStorages( + applicationImportDTO.getApplication().getWorkspaceId(), + Optional.of(MANAGE_DATASOURCES)) + .collectList(), + getActionsInApplication(applicationImportDTO.getApplication()) + .collectList(), + newPageService + .findByApplicationId( + applicationImportDTO.getApplication().getId(), MANAGE_PAGES, false) + .collectList(), + actionCollectionService + .findAllByApplicationIdAndViewMode( + applicationImportDTO.getApplication().getId(), false, MANAGE_ACTIONS, null) + .collectList(), + workspaceMono.flatMap(workspace -> workspaceService.getDefaultEnvironmentId( + workspace.getId(), environmentPermission.getExecutePermission()))))) + .assertNext(tuple -> { + final Application application = tuple.getT1().getApplication(); + final List datasourceList = tuple.getT2(); + final List actionDTOS = tuple.getT3(); + final List pageList = tuple.getT4(); + final List actionCollectionList = tuple.getT5(); + String environmentId = tuple.getT6(); + + assertThat(application.getName()).isEqualTo("valid_application"); + assertThat(application.getWorkspaceId()).isNotNull(); + assertThat(application.getPages()).hasSize(2); + assertThat(application.getPolicies()).containsAll(Set.of(manageAppPolicy, readAppPolicy)); + assertThat(application.getPublishedPages()).hasSize(1); + assertThat(application.getModifiedBy()).isEqualTo("api_user"); + assertThat(application.getUpdatedAt()).isNotNull(); + + assertThat(datasourceList).isNotEmpty(); + datasourceList.forEach(datasource -> { + assertThat(datasource.getWorkspaceId()).isEqualTo(application.getWorkspaceId()); + DatasourceStorageDTO storageDTO = + datasource.getDatasourceStorages().get(environmentId); + assertThat(storageDTO.getDatasourceConfiguration()).isNotNull(); + }); + + assertThat(actionDTOS).isNotEmpty(); + actionDTOS.forEach(actionDTO -> { + assertThat(actionDTO.getPageId()) + .isNotEqualTo(pageList.get(0).getName()); + }); + + assertThat(actionCollectionList).isEmpty(); + + assertThat(pageList).hasSize(2); + + ApplicationPage defaultAppPage = application.getPages().stream() + .filter(ApplicationPage::getIsDefault) + .findFirst() + .orElse(null); + assertThat(defaultAppPage).isNotNull(); + + PageDTO defaultPageDTO = pageList.stream() + .filter(pageDTO -> pageDTO.getId().equals(defaultAppPage.getId())) + .findFirst() + .orElse(null); + + assertThat(defaultPageDTO).isNotNull(); + assertThat(defaultPageDTO.getLayouts().get(0).getLayoutOnLoadActions()) + .isNotEmpty(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importArtifact_WithoutThemes_LegacyThemesAssigned() { + FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application-without-theme.json"); + + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("Template Workspace"); + + final Mono resultMono = workspaceService + .create(newWorkspace) + .flatMap(workspace -> importService.extractArtifactExchangeJsonAndSaveArtifact( + filePart, workspace.getId(), null, ArtifactJsonType.APPLICATION)) + .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); + + StepVerifier.create(resultMono) + .assertNext(applicationImportDTO -> { + assertThat(applicationImportDTO.getApplication().getEditModeThemeId()) + .isNotEmpty(); + assertThat(applicationImportDTO.getApplication().getPublishedModeThemeId()) + .isNotEmpty(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importArtifact_withoutPageIdInActionCollection_succeeds() { + + FilePart filePart = createFilePart( + "test_assets/ImportExportServiceTest/invalid-application-without-pageId-action-collection.json"); + + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("Template Workspace"); + + final Mono resultMono = workspaceService + .create(newWorkspace) + .flatMap(workspace -> importService.extractArtifactExchangeJsonAndSaveArtifact( + filePart, workspace.getId(), null, ArtifactJsonType.APPLICATION)) + .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); + + StepVerifier.create(resultMono.flatMap(applicationImportDTO -> Mono.zip( + Mono.just(applicationImportDTO), + datasourceService + .getAllByWorkspaceIdWithStorages( + applicationImportDTO.getApplication().getWorkspaceId(), + Optional.of(MANAGE_DATASOURCES)) + .collectList(), + getActionsInApplication(applicationImportDTO.getApplication()) + .collectList(), + newPageService + .findByApplicationId( + applicationImportDTO.getApplication().getId(), MANAGE_PAGES, false) + .collectList(), + actionCollectionService + .findAllByApplicationIdAndViewMode( + applicationImportDTO.getApplication().getId(), false, MANAGE_ACTIONS, null) + .collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1().getApplication(); + final List datasourceList = tuple.getT2(); + final List actionDTOS = tuple.getT3(); + final List pageList = tuple.getT4(); + final List actionCollectionList = tuple.getT5(); + + assertThat(datasourceList).isNotEmpty(); + + assertThat(actionDTOS).hasSize(1); + actionDTOS.forEach(actionDTO -> { + assertThat(actionDTO.getPageId()) + .isNotEqualTo(pageList.get(0).getName()); + }); + + assertThat(actionCollectionList).isEmpty(); + }) + .verifyComplete(); + } + + // this test would be re-written post export flow is completed + @Test + @WithUserDetails(value = "api_user") + public void exportImportApplication_importWithBranchName_updateApplicationResourcesWithBranch() { + Application testApplication = new Application(); + testApplication.setName("Export-Import-Update-Branch_Test-App"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("testBranch"); + testApplication.setGitApplicationMetadata(gitData); + + Application savedApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + + Mono result = newPageService + .findNewPagesByApplicationId(savedApplication.getId(), READ_PAGES) + .collectList() + .flatMap(newPages -> { + NewPage newPage = newPages.get(0); + + ActionDTO action = new ActionDTO(); + action.setName("validAction"); + action.setPageId(newPage.getId()); + action.setExecuteOnLoad(true); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasourceMap.get("DS1")); + return layoutActionService + .createAction(action) + .flatMap(createdAction -> newActionService.findById(createdAction.getId(), READ_ACTIONS)); + }) + .then(exportApplicationService + .exportApplicationById(savedApplication.getId(), SerialiseApplicationObjective.VERSION_CONTROL) + .flatMap(applicationJson -> importService.importArtifactInWorkspaceFromGit( + workspaceId, savedApplication.getId(), applicationJson, gitData.getBranchName()))) + .map(importableArtifact -> (Application) importableArtifact) + .cache(); + + Mono> updatedPagesMono = result.then(newPageService + .findNewPagesByApplicationId(savedApplication.getId(), READ_PAGES) + .collectList()); + + Mono> updatedActionsMono = result.then(newActionService + .findAllByApplicationIdAndViewMode(savedApplication.getId(), false, READ_PAGES, null) + .collectList()); + + StepVerifier.create(Mono.zip(result, updatedPagesMono, updatedActionsMono)) + .assertNext(tuple -> { + Application application = tuple.getT1(); + List pageList = tuple.getT2(); + List actionList = tuple.getT3(); + + final String branchName = + application.getGitApplicationMetadata().getBranchName(); + pageList.forEach(page -> { + assertThat(page.getDefaultResources()).isNotNull(); + assertThat(page.getDefaultResources().getBranchName()).isEqualTo(branchName); + }); + + actionList.forEach(action -> { + assertThat(action.getDefaultResources()).isNotNull(); + assertThat(action.getDefaultResources().getBranchName()).isEqualTo(branchName); + }); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importApplication_incompatibleJsonFile_throwException() { + FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/incompatible_version.json"); + Mono resultMono = importService + .extractArtifactExchangeJsonAndSaveArtifact(filePart, workspaceId, null, ArtifactJsonType.APPLICATION) + .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); + + StepVerifier.create(resultMono) + .expectErrorMatches(throwable -> throwable instanceof AppsmithException + && throwable.getMessage().equals(AppsmithError.INCOMPATIBLE_IMPORTED_JSON.getMessage())) + .verify(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importApplication_withUnConfiguredDatasources_Success() { + FilePart filePart = createFilePart( + "test_assets/ImportExportServiceTest/valid-application-with-un-configured-datasource.json"); + + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("Template Workspace"); + + Mono workspaceMono = workspaceService.create(newWorkspace).cache(); + + final Mono resultMono = workspaceMono + .flatMap(workspace -> importService.extractArtifactExchangeJsonAndSaveArtifact( + filePart, workspace.getId(), null, ArtifactJsonType.APPLICATION)) + .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); + + List permissionGroups = workspaceMono + .flatMapMany(savedWorkspace -> { + Set defaultPermissionGroups = savedWorkspace.getDefaultPermissionGroups(); + return permissionGroupRepository.findAllById(defaultPermissionGroups); + }) + .collectList() + .block(); + + PermissionGroup adminPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.ADMINISTRATOR)) + .findFirst() + .get(); + + PermissionGroup developerPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.DEVELOPER)) + .findFirst() + .get(); + + PermissionGroup viewerPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.VIEWER)) + .findFirst() + .get(); + + Policy manageAppPolicy = Policy.builder() + .permission(MANAGE_APPLICATIONS.getValue()) + .permissionGroups(Set.of(adminPermissionGroup.getId(), developerPermissionGroup.getId())) + .build(); + Policy readAppPolicy = Policy.builder() + .permission(READ_APPLICATIONS.getValue()) + .permissionGroups(Set.of( + adminPermissionGroup.getId(), developerPermissionGroup.getId(), viewerPermissionGroup.getId())) + .build(); + + StepVerifier.create(resultMono.flatMap(applicationImportDTO -> { + Application application = applicationImportDTO.getApplication(); + return Mono.zip( + Mono.just(applicationImportDTO), + datasourceService + .getAllByWorkspaceIdWithStorages( + application.getWorkspaceId(), Optional.of(MANAGE_DATASOURCES)) + .collectList(), + newActionService + .findAllByApplicationIdAndViewMode(application.getId(), false, READ_ACTIONS, null) + .collectList(), + newPageService + .findByApplicationId(application.getId(), MANAGE_PAGES, false) + .collectList(), + actionCollectionService + .findAllByApplicationIdAndViewMode(application.getId(), false, MANAGE_ACTIONS, null) + .collectList()); + })) + .assertNext(tuple -> { + final Application application = tuple.getT1().getApplication(); + final List unConfiguredDatasourceList = + tuple.getT1().getUnConfiguredDatasourceList(); + final boolean isPartialImport = tuple.getT1().getIsPartialImport(); + final List datasourceList = tuple.getT2(); + final List actionList = tuple.getT3(); + final List pageList = tuple.getT4(); + final List actionCollectionList = tuple.getT5(); + + assertThat(application.getName()).isEqualTo("importExportTest"); + assertThat(application.getWorkspaceId()).isNotNull(); + assertThat(application.getPages()).hasSize(1); + assertThat(application.getPolicies()).containsAll(Set.of(manageAppPolicy, readAppPolicy)); + assertThat(application.getPublishedPages()).hasSize(1); + assertThat(application.getModifiedBy()).isEqualTo("api_user"); + assertThat(application.getUpdatedAt()).isNotNull(); + assertThat(application.getEditModeThemeId()).isNotNull(); + assertThat(application.getPublishedModeThemeId()).isNotNull(); + assertThat(isPartialImport).isEqualTo(Boolean.TRUE); + assertThat(unConfiguredDatasourceList.size()).isNotEqualTo(0); + + assertThat(datasourceList).isNotEmpty(); + List datasourceNames = unConfiguredDatasourceList.stream() + .map(Datasource::getName) + .collect(Collectors.toList()); + assertThat(datasourceNames).contains("mongoDatasource", "postgresTest"); + + List collectionIdInAction = new ArrayList<>(); + assertThat(actionList).isNotEmpty(); + actionList.forEach(newAction -> { + ActionDTO actionDTO = newAction.getUnpublishedAction(); + assertThat(actionDTO.getPageId()) + .isNotEqualTo(pageList.get(0).getName()); + if (!StringUtils.isEmpty(actionDTO.getCollectionId())) { + collectionIdInAction.add(actionDTO.getCollectionId()); + } + }); + + assertThat(actionCollectionList).isEmpty(); + + assertThat(pageList).hasSize(1); + + ApplicationPage defaultAppPage = application.getPages().stream() + .filter(ApplicationPage::getIsDefault) + .findFirst() + .orElse(null); + assertThat(defaultAppPage).isNotNull(); + + PageDTO defaultPageDTO = pageList.stream() + .filter(pageDTO -> pageDTO.getId().equals(defaultAppPage.getId())) + .findFirst() + .orElse(null); + + assertThat(defaultPageDTO).isNotNull(); + }) + .verifyComplete(); + } + + public void importArtifactIntoWorkspace_pageRemovedAndUpdatedDefaultPageNameInBranchApplication_Success() { + Application testApplication = new Application(); + testApplication.setName("importApplicationIntoWorkspace_pageRemovedInBranchApplication_Success"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("master"); + testApplication.setGitApplicationMetadata(gitData); + + Application application = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + String gitSyncIdBeforeImport = newPageService + .findById(application.getPages().get(0).getId(), MANAGE_PAGES) + .block() + .getGitSyncId(); + + PageDTO page = new PageDTO(); + page.setName("Page 2"); + page.setApplicationId(application.getId()); + PageDTO savedPage = applicationPageService.createPage(page).block(); + + assert application.getId() != null; + Set applicationPageIdsBeforeImport = + Objects.requireNonNull(applicationRepository + .findById(application.getId()) + .block()) + .getPages() + .stream() + .map(ApplicationPage::getId) + .collect(Collectors.toSet()); + + ApplicationJson applicationJson = createAppJson( + "test_assets/ImportExportServiceTest/valid-application-with-page-removed.json") + .block(); + applicationJson.getPageList().get(0).setGitSyncId(gitSyncIdBeforeImport); + + Application importedApplication = importService + .importArtifactInWorkspaceFromGit(workspaceId, application.getId(), applicationJson, "master") + .map(artifact -> (Application) artifact) + .block(); + + assert importedApplication != null; + Mono> pageList = Flux.fromIterable(importedApplication.getPages().stream() + .map(ApplicationPage::getId) + .collect(Collectors.toList())) + .flatMap(s -> newPageService.findById(s, MANAGE_PAGES)) + .collectList(); + + StepVerifier.create(pageList) + .assertNext(newPages -> { + // Check before import we had both the pages + assertThat(applicationPageIdsBeforeImport).hasSize(2); + assertThat(applicationPageIdsBeforeImport).contains(savedPage.getId()); + + assertThat(newPages.size()).isEqualTo(1); + assertThat(importedApplication.getPages().size()).isEqualTo(1); + assertThat(importedApplication.getPages().get(0).getId()) + .isEqualTo(newPages.get(0).getId()); + assertThat(newPages.get(0).getPublishedPage().getName()).isEqualTo("importedPage"); + assertThat(newPages.get(0).getGitSyncId()).isEqualTo(gitSyncIdBeforeImport); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importArtifactIntoWorkspace_pageAddedInBranchApplication_Success() { + Application testApplication = new Application(); + testApplication.setName("importApplicationIntoWorkspace_pageAddedInBranchApplication_Success"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("master"); + testApplication.setGitApplicationMetadata(gitData); + + Application application = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + + String gitSyncIdBeforeImport = newPageService + .findById(application.getPages().get(0).getId(), MANAGE_PAGES) + .block() + .getGitSyncId(); + + assert application.getId() != null; + Set applicationPageIdsBeforeImport = + Objects.requireNonNull(applicationRepository + .findById(application.getId()) + .block()) + .getPages() + .stream() + .map(ApplicationPage::getId) + .collect(Collectors.toSet()); + + ApplicationJson applicationJson = createAppJson( + "test_assets/ImportExportServiceTest/valid-application-with-page-added.json") + .block(); + applicationJson.getPageList().get(0).setGitSyncId(gitSyncIdBeforeImport); + + Application applicationMono = importService + .importArtifactInWorkspaceFromGit(workspaceId, application.getId(), applicationJson, "master") + .map(artifact -> (Application) artifact) + .block(); + + Mono> pageList = Flux.fromIterable(applicationMono.getPages().stream() + .map(ApplicationPage::getId) + .collect(Collectors.toList())) + .flatMap(s -> newPageService.findById(s, MANAGE_PAGES)) + .collectList(); + + StepVerifier.create(pageList) + .assertNext(newPages -> { + // Check before import we had both the pages + assertThat(applicationPageIdsBeforeImport).hasSize(1); + assertThat(newPages.size()).isEqualTo(3); + List pageNames = newPages.stream() + .map(newPage -> newPage.getUnpublishedPage().getName()) + .collect(Collectors.toList()); + assertThat(pageNames).contains("Page1"); + assertThat(pageNames).contains("Page2"); + assertThat(pageNames).contains("Page3"); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importUpdatedApplicationIntoWorkspaceFromFile_publicApplication_visibilityFlagNotReset() { + // Create a application and make it public + // Now add a page and export the same import it to the app + // Check if the policies and visibility flag are not reset + + Mono workspaceResponse = workspaceService.findById(workspaceId, READ_WORKSPACES); + + List permissionGroups = workspaceResponse + .flatMapMany(savedWorkspace -> { + Set defaultPermissionGroups = savedWorkspace.getDefaultPermissionGroups(); + return permissionGroupRepository.findAllById(defaultPermissionGroups); + }) + .collectList() + .block(); + + PermissionGroup adminPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.ADMINISTRATOR)) + .findFirst() + .get(); + + PermissionGroup developerPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.DEVELOPER)) + .findFirst() + .get(); + + PermissionGroup viewerPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.VIEWER)) + .findFirst() + .get(); + + Application testApplication = new Application(); + testApplication.setName( + "importUpdatedApplicationIntoWorkspaceFromFile_publicApplication_visibilityFlagNotReset"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("master"); + testApplication.setGitApplicationMetadata(gitData); + + Application application = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + ApplicationAccessDTO applicationAccessDTO = new ApplicationAccessDTO(); + applicationAccessDTO.setPublicAccess(true); + Application newApplication = applicationService + .changeViewAccess(application.getId(), "master", applicationAccessDTO) + .block(); + + PermissionGroup anonymousPermissionGroup = + permissionGroupService.getPublicPermissionGroup().block(); + + Policy manageAppPolicy = Policy.builder() + .permission(MANAGE_APPLICATIONS.getValue()) + .permissionGroups(Set.of(adminPermissionGroup.getId(), developerPermissionGroup.getId())) + .build(); + Policy readAppPolicy = Policy.builder() + .permission(READ_APPLICATIONS.getValue()) + .permissionGroups(Set.of( + adminPermissionGroup.getId(), + developerPermissionGroup.getId(), + viewerPermissionGroup.getId(), + anonymousPermissionGroup.getId())) + .build(); + + Mono applicationMono = exportApplicationService + .exportApplicationById(application.getId(), "master") + .flatMap(applicationJson -> importService + .importArtifactInWorkspaceFromGit(workspaceId, application.getId(), applicationJson, "master") + .map(artifact -> (Application) artifact)); + + StepVerifier.create(applicationMono) + .assertNext(application1 -> { + assertThat(application1.getIsPublic()).isEqualTo(Boolean.TRUE); + assertThat(application1.getPolicies()).containsAll(Set.of(manageAppPolicy, readAppPolicy)); + }) + .verifyComplete(); + } + + /** + * Testcase for checking the discard changes flow for following events: + * 1. Import application in org + * 2. Add new page to the imported application + * 3. User tries to import application from same application json file + * 4. Added page will be removed + */ + @Test + @WithUserDetails(value = "api_user") + public void discardChange_addNewPageAfterImport_addedPageRemoved() { + + /* + 1. Import application + 2. Add single page to imported app + 3. Import the application from same JSON with applicationId + 4. Added page should be deleted from DB + */ + Mono applicationJsonMono = + createAppJson("test_assets/ImportExportServiceTest/valid-application.json"); + String workspaceId = createTemplateWorkspace().getId(); + final Mono resultMonoWithoutDiscardOperation = applicationJsonMono + .flatMap(applicationJson -> { + applicationJson.getExportedApplication().setName("discard-change-page-added"); + return importService + .importNewArtifactInWorkspaceFromJson(workspaceId, applicationJson) + .map(importableArtifact -> (Application) importableArtifact); + }) + .flatMap(application -> { + PageDTO page = new PageDTO(); + page.setName("discard-page-test"); + page.setApplicationId(application.getId()); + return applicationPageService.createPage(page); + }) + .flatMap(page -> applicationRepository.findById(page.getApplicationId())) + .cache(); + List pageListBefore = resultMonoWithoutDiscardOperation + .flatMap(application -> newPageService + .findByApplicationId(application.getId(), MANAGE_PAGES, false) + .collectList()) + .block(); + + StepVerifier.create(resultMonoWithoutDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + newPageService + .findByApplicationId(application.getId(), MANAGE_PAGES, false) + .collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List pageList = tuple.getT2(); + + assertThat(application.getName()).isEqualTo("discard-change-page-added"); + assertThat(application.getWorkspaceId()).isNotNull(); + assertThat(application.getPages()).hasSize(3); + assertThat(application.getPublishedPages()).hasSize(1); + assertThat(application.getModifiedBy()).isEqualTo("api_user"); + assertThat(application.getUpdatedAt()).isNotNull(); + assertThat(application.getEditModeThemeId()).isNotNull(); + assertThat(application.getPublishedModeThemeId()).isNotNull(); + + assertThat(pageList).hasSize(3); + + ApplicationPage defaultAppPage = application.getPages().stream() + .filter(ApplicationPage::getIsDefault) + .findFirst() + .orElse(null); + assertThat(defaultAppPage).isNotNull(); + + PageDTO defaultPageDTO = pageList.stream() + .filter(pageDTO -> pageDTO.getId().equals(defaultAppPage.getId())) + .findFirst() + .orElse(null); + + assertThat(defaultPageDTO).isNotNull(); + assertThat(defaultPageDTO.getLayouts().get(0).getLayoutOnLoadActions()) + .isNotEmpty(); + + List pageNames = new ArrayList<>(); + pageList.forEach(page -> pageNames.add(page.getName())); + assertThat(pageNames).contains("discard-page-test"); + }) + .verifyComplete(); + + // Import the same application again to find if the added page is deleted + final Mono resultMonoWithDiscardOperation = resultMonoWithoutDiscardOperation.flatMap( + importedApplication -> applicationJsonMono.flatMap(applicationJson -> { + importedApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + importedApplication + .getGitApplicationMetadata() + .setDefaultApplicationId(importedApplication.getId()); + return applicationService + .save(importedApplication) + .then(importService.importArtifactInWorkspaceFromGit( + importedApplication.getWorkspaceId(), + importedApplication.getId(), + applicationJson, + "main")) + .map(importableArtifact -> (Application) importableArtifact); + })); + + StepVerifier.create(resultMonoWithDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + newPageService + .findByApplicationId(application.getId(), MANAGE_PAGES, false) + .collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List pageList = tuple.getT2(); + + assertThat(application.getPages()).hasSize(2); + assertThat(application.getPublishedPages()).hasSize(1); + + assertThat(pageList).hasSize(2); + for (PageDTO page : pageList) { + PageDTO curentPage = pageListBefore.stream() + .filter(pageDTO -> pageDTO.getId().equals(page.getId())) + .collect(Collectors.toList()) + .get(0); + assertThat(page.getPolicies()).isEqualTo(curentPage.getPolicies()); + } + + List pageNames = new ArrayList<>(); + pageList.forEach(page -> pageNames.add(page.getName())); + assertThat(pageNames).doesNotContain("discard-page-test"); + }) + .verifyComplete(); + } + + /** + * Testcase for checking the discard changes flow for following events: + * 1. Import application in org + * 2. Add new action to the imported application + * 3. User tries to import application from same application json file + * 4. Added action will be removed + */ + @Test + @WithUserDetails(value = "api_user") + public void discardChange_addNewActionAfterImport_addedActionRemoved() { + + Mono applicationJsonMono = + createAppJson("test_assets/ImportExportServiceTest/valid-application.json"); + String workspaceId = createTemplateWorkspace().getId(); + + final Mono resultMonoWithoutDiscardOperation = applicationJsonMono + .flatMap(applicationJson -> { + applicationJson.getExportedApplication().setName("discard-change-action-added"); + return importService + .importNewArtifactInWorkspaceFromJson(workspaceId, applicationJson) + .map(importableArtifact -> (Application) importableArtifact); + }) + .flatMap(application -> { + ActionDTO action = new ActionDTO(); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasourceMap.get("DS1")); + action.setName("discard-action-test"); + action.setPageId(application.getPages().get(0).getId()); + return layoutActionService.createAction(action); + }) + .flatMap(actionDTO -> newActionService.getById(actionDTO.getId())) + .flatMap(newAction -> applicationRepository.findById(newAction.getApplicationId())) + .cache(); + + List actionListBefore = resultMonoWithoutDiscardOperation + .flatMap(application -> getActionsInApplication(application).collectList()) + .block(); + + StepVerifier.create(resultMonoWithoutDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + getActionsInApplication(application).collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List actionList = tuple.getT2(); + + assertThat(application.getName()).isEqualTo("discard-change-action-added"); + assertThat(application.getWorkspaceId()).isNotNull(); + + List actionNames = new ArrayList<>(); + actionList.forEach(actionDTO -> actionNames.add(actionDTO.getName())); + assertThat(actionNames).contains("discard-action-test"); + }) + .verifyComplete(); + + // Import the same application again + final Mono resultMonoWithDiscardOperation = resultMonoWithoutDiscardOperation.flatMap( + importedApplication -> applicationJsonMono.flatMap(applicationJson -> { + importedApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + importedApplication + .getGitApplicationMetadata() + .setDefaultApplicationId(importedApplication.getId()); + return applicationService + .save(importedApplication) + .then(importService.importArtifactInWorkspaceFromGit( + importedApplication.getWorkspaceId(), + importedApplication.getId(), + applicationJson, + "main")) + .map(importableArtifact -> (Application) importableArtifact); + })); + + StepVerifier.create(resultMonoWithDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + getActionsInApplication(application).collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List actionList = tuple.getT2(); + + assertThat(application.getWorkspaceId()).isNotNull(); + assertThat(application.getServerSchemaVersion()).isNotNull(); + assertThat(application.getClientSchemaVersion()).isNotNull(); + + List actionNames = new ArrayList<>(); + actionList.forEach(actionDTO -> actionNames.add(actionDTO.getName())); + assertThat(actionNames).doesNotContain("discard-action-test"); + for (ActionDTO action : actionListBefore) { + ActionDTO currentAction = actionListBefore.stream() + .filter(actionDTO -> actionDTO.getId().equals(action.getId())) + .collect(Collectors.toList()) + .get(0); + assertThat(action.getPolicies()).isEqualTo(currentAction.getPolicies()); + } + }) + .verifyComplete(); + } + + /** + * Testcase for checking the discard changes flow for following events: + * 1. Import application in org + * 2. Add actionCollection to the imported application + * 3. User tries to import application from same application json file + * 4. Added actionCollection will be removed + */ + @Test + @WithUserDetails(value = "api_user") + public void discardChange_addNewActionCollectionAfterImport_addedActionCollectionRemoved() { + + Mono applicationJsonMono = + createAppJson("test_assets/ImportExportServiceTest/valid-application-without-action-collection.json"); + String workspaceId = createTemplateWorkspace().getId(); + final Mono resultMonoWithoutDiscardOperation = applicationJsonMono + .flatMap(applicationJson -> { + applicationJson.getExportedApplication().setName("discard-change-collection-added"); + return importService + .importNewArtifactInWorkspaceFromJson(workspaceId, applicationJson) + .map(importableArtifact -> (Application) importableArtifact); + }) + .flatMap(application -> { + ActionCollectionDTO actionCollectionDTO1 = new ActionCollectionDTO(); + actionCollectionDTO1.setName("discard-action-collection-test"); + actionCollectionDTO1.setPageId(application.getPages().get(0).getId()); + actionCollectionDTO1.setApplicationId(application.getId()); + actionCollectionDTO1.setWorkspaceId(application.getWorkspaceId()); + actionCollectionDTO1.setPluginId(jsDatasource.getPluginId()); + ActionDTO action1 = new ActionDTO(); + action1.setName("discard-action-collection-test-action"); + action1.setActionConfiguration(new ActionConfiguration()); + action1.getActionConfiguration().setBody("mockBody"); + actionCollectionDTO1.setActions(List.of(action1)); + actionCollectionDTO1.setPluginType(PluginType.JS); + + return layoutCollectionService.createCollection(actionCollectionDTO1, null); + }) + .flatMap(actionCollectionDTO -> actionCollectionService.getById(actionCollectionDTO.getId())) + .flatMap(actionCollection -> applicationRepository.findById(actionCollection.getApplicationId())) + .cache(); + + List actionCollectionListBefore = resultMonoWithoutDiscardOperation + .flatMap(application -> actionCollectionService + .findAllByApplicationIdAndViewMode(application.getId(), false, READ_ACTIONS, null) + .collectList()) + .block(); + List actionListBefore = resultMonoWithoutDiscardOperation + .flatMap(application -> getActionsInApplication(application).collectList()) + .block(); + + StepVerifier.create(resultMonoWithoutDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + actionCollectionService + .findAllByApplicationIdAndViewMode(application.getId(), false, READ_ACTIONS, null) + .collectList(), + getActionsInApplication(application).collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List actionCollectionList = tuple.getT2(); + final List actionList = tuple.getT3(); + + assertThat(application.getName()).isEqualTo("discard-change-collection-added"); + assertThat(application.getWorkspaceId()).isNotNull(); + + List actionCollectionNames = new ArrayList<>(); + actionCollectionList.forEach(actionCollection -> actionCollectionNames.add( + actionCollection.getUnpublishedCollection().getName())); + assertThat(actionCollectionNames).contains("discard-action-collection-test"); + + List actionNames = new ArrayList<>(); + actionList.forEach(actionDTO -> actionNames.add(actionDTO.getName())); + assertThat(actionNames).contains("discard-action-collection-test-action"); + }) + .verifyComplete(); + + // Import the same application again + final Mono resultMonoWithDiscardOperation = resultMonoWithoutDiscardOperation.flatMap( + importedApplication -> applicationJsonMono.flatMap(applicationJson -> { + importedApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + importedApplication + .getGitApplicationMetadata() + .setDefaultApplicationId(importedApplication.getId()); + return applicationService + .save(importedApplication) + .then(importService.importArtifactInWorkspaceFromGit( + importedApplication.getWorkspaceId(), + importedApplication.getId(), + applicationJson, + "main")) + .map(importableArtifact -> (Application) importableArtifact); + })); + + StepVerifier.create(resultMonoWithDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + actionCollectionService + .findAllByApplicationIdAndViewMode(application.getId(), false, READ_ACTIONS, null) + .collectList(), + getActionsInApplication(application).collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List actionCollectionList = tuple.getT2(); + final List actionList = tuple.getT3(); + + assertThat(application.getWorkspaceId()).isNotNull(); + + List actionCollectionNames = new ArrayList<>(); + actionCollectionList.forEach(actionCollection -> actionCollectionNames.add( + actionCollection.getUnpublishedCollection().getName())); + assertThat(actionCollectionNames).doesNotContain("discard-action-collection-test"); + + List actionNames = new ArrayList<>(); + actionList.forEach(actionDTO -> actionNames.add(actionDTO.getName())); + assertThat(actionNames).doesNotContain("discard-action-collection-test-action"); + for (ActionDTO action : actionListBefore) { + ActionDTO currentAction = actionListBefore.stream() + .filter(actionDTO -> actionDTO.getId().equals(action.getId())) + .collect(Collectors.toList()) + .get(0); + assertThat(action.getPolicies()).isEqualTo(currentAction.getPolicies()); + } + + for (ActionCollection actionCollection : actionCollectionListBefore) { + ActionCollection currentAction = actionCollectionListBefore.stream() + .filter(actionDTO -> actionDTO.getId().equals(actionCollection.getId())) + .collect(Collectors.toList()) + .get(0); + assertThat(actionCollection.getPolicies()).isEqualTo(currentAction.getPolicies()); + } + }) + .verifyComplete(); + } + + /** + * Testcase for checking the discard changes flow for following events: + * 1. Import application in org + * 2. Remove existing page from imported application + * 3. Import application from same application json file + * 4. Removed page will be restored + */ + @Test + @WithUserDetails(value = "api_user") + public void discardChange_removeNewPageAfterImport_removedPageRestored() { + + Mono applicationJsonMono = + createAppJson("test_assets/ImportExportServiceTest/valid-application.json"); + String workspaceId = createTemplateWorkspace().getId(); + final Mono resultMonoWithoutDiscardOperation = applicationJsonMono + .flatMap(applicationJson -> { + applicationJson.getExportedApplication().setName("discard-change-page-removed"); + return importService + .importNewArtifactInWorkspaceFromJson(workspaceId, applicationJson) + .map(importableArtifact -> (Application) importableArtifact); + }) + .flatMap(application -> { + Optional applicationPage = application.getPages().stream() + .filter(page -> !page.isDefault()) + .findFirst(); + return applicationPageService.deleteUnpublishedPage( + applicationPage.get().getId()); + }) + .flatMap(page -> applicationRepository.findById(page.getApplicationId())) + .cache(); + + StepVerifier.create(resultMonoWithoutDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + newPageService + .findByApplicationId(application.getId(), MANAGE_PAGES, false) + .collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List pageList = tuple.getT2(); + + assertThat(application.getName()).isEqualTo("discard-change-page-removed"); + assertThat(application.getWorkspaceId()).isNotNull(); + assertThat(application.getPages()).hasSize(1); + + assertThat(pageList).hasSize(1); + }) + .verifyComplete(); + + // Import the same application again + final Mono resultMonoWithDiscardOperation = resultMonoWithoutDiscardOperation.flatMap( + importedApplication -> applicationJsonMono.flatMap(applicationJson -> { + importedApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + importedApplication + .getGitApplicationMetadata() + .setDefaultApplicationId(importedApplication.getId()); + return applicationService + .save(importedApplication) + .then(importService.importArtifactInWorkspaceFromGit( + importedApplication.getWorkspaceId(), + importedApplication.getId(), + applicationJson, + "main")) + .map(importableArtifact -> (Application) importableArtifact); + })); + + StepVerifier.create(resultMonoWithDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + newPageService + .findByApplicationId(application.getId(), MANAGE_PAGES, false) + .collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List pageList = tuple.getT2(); + + assertThat(application.getPages()).hasSize(2); + assertThat(application.getPublishedPages()).hasSize(1); + + assertThat(pageList).hasSize(2); + }) + .verifyComplete(); + } + + /** + * Testcase for checking the discard changes flow for following events: + * 1. Import application in org + * 2. Remove existing action from imported application + * 3. Import application from same application json file + * 4. Removed action will be restored + */ + @Test + @WithUserDetails(value = "api_user") + public void discardChange_removeNewActionAfterImport_removedActionRestored() { + + Mono applicationJsonMono = + createAppJson("test_assets/ImportExportServiceTest/valid-application.json"); + String workspaceId = createTemplateWorkspace().getId(); + final String[] deletedActionName = new String[1]; + final Mono resultMonoWithoutDiscardOperation = applicationJsonMono + .flatMap(applicationJson -> { + applicationJson.getExportedApplication().setName("discard-change-action-removed"); + return importService + .importNewArtifactInWorkspaceFromJson(workspaceId, applicationJson) + .map(importableArtifact -> (Application) importableArtifact); + }) + .flatMap(application -> { + return getActionsInApplication(application) + .next() + .flatMap(actionDTO -> { + deletedActionName[0] = actionDTO.getName(); + return newActionService.deleteUnpublishedAction(actionDTO.getId()); + }) + .then(applicationPageService.publish(application.getId(), true)); + }) + .cache(); + + StepVerifier.create(resultMonoWithoutDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + getActionsInApplication(application).collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List actionList = tuple.getT2(); + + assertThat(application.getName()).isEqualTo("discard-change-action-removed"); + assertThat(application.getWorkspaceId()).isNotNull(); + + List actionNames = new ArrayList<>(); + actionList.forEach(actionDTO -> actionNames.add(actionDTO.getName())); + assertThat(actionNames).doesNotContain(deletedActionName[0]); + }) + .verifyComplete(); + + // Import the same application again + final Mono resultMonoWithDiscardOperation = resultMonoWithoutDiscardOperation.flatMap( + importedApplication -> applicationJsonMono.flatMap(applicationJson -> { + importedApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + importedApplication + .getGitApplicationMetadata() + .setDefaultApplicationId(importedApplication.getId()); + return applicationService + .save(importedApplication) + .then(importService.importArtifactInWorkspaceFromGit( + importedApplication.getWorkspaceId(), + importedApplication.getId(), + applicationJson, + "main")) + .map(importableArtifact -> (Application) importableArtifact); + })); + + StepVerifier.create(resultMonoWithDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + getActionsInApplication(application).collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List actionList = tuple.getT2(); + + assertThat(application.getWorkspaceId()).isNotNull(); + + List actionNames = new ArrayList<>(); + actionList.forEach(actionDTO -> actionNames.add(actionDTO.getName())); + assertThat(actionNames).contains(deletedActionName[0]); + }) + .verifyComplete(); + } + + /** + * Testcase for checking the discard changes flow for following events: + * 1. Import application in org + * 2. Remove existing actionCollection from imported application + * 3. Import application from same application json file + * 4. Removed actionCollection along-with actions will be restored + */ + @Test + @WithUserDetails(value = "api_user") + public void discardChange_removeNewActionCollection_removedActionCollectionRestored() { + + Mono applicationJsonMono = + createAppJson("test_assets/ImportExportServiceTest/valid-application.json"); + String workspaceId = createTemplateWorkspace().getId(); + final String[] deletedActionCollectionNames = new String[1]; + final Mono resultMonoWithoutDiscardOperation = applicationJsonMono + .flatMap(applicationJson -> { + applicationJson.getExportedApplication().setName("discard-change-collection-removed"); + return importService + .importNewArtifactInWorkspaceFromJson(workspaceId, applicationJson) + .map(importableArtifact -> (Application) importableArtifact); + }) + .flatMap(application -> { + return actionCollectionService + .findAllByApplicationIdAndViewMode(application.getId(), false, READ_ACTIONS, null) + .next() + .flatMap(actionCollection -> { + deletedActionCollectionNames[0] = actionCollection + .getUnpublishedCollection() + .getName(); + return actionCollectionService.deleteUnpublishedActionCollection( + actionCollection.getId()); + }) + .then(applicationPageService.publish(application.getId(), true)); + }) + .cache(); + + StepVerifier.create(resultMonoWithoutDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + actionCollectionService + .findAllByApplicationIdAndViewMode(application.getId(), false, READ_ACTIONS, null) + .collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List actionCollectionList = tuple.getT2(); + + assertThat(application.getName()).isEqualTo("discard-change-collection-removed"); + assertThat(application.getWorkspaceId()).isNotNull(); + + List actionCollectionNames = new ArrayList<>(); + actionCollectionList.forEach(actionCollection -> actionCollectionNames.add( + actionCollection.getUnpublishedCollection().getName())); + assertThat(actionCollectionNames).doesNotContain(deletedActionCollectionNames); + }) + .verifyComplete(); + + // Import the same application again + final Mono resultMonoWithDiscardOperation = resultMonoWithoutDiscardOperation.flatMap( + importedApplication -> applicationJsonMono.flatMap(applicationJson -> { + importedApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + importedApplication + .getGitApplicationMetadata() + .setDefaultApplicationId(importedApplication.getId()); + return applicationService + .save(importedApplication) + .then(importService.importArtifactInWorkspaceFromGit( + importedApplication.getWorkspaceId(), + importedApplication.getId(), + applicationJson, + "main")) + .map(importableArtifact -> (Application) importableArtifact); + })); + + StepVerifier.create(resultMonoWithDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + actionCollectionService + .findAllByApplicationIdAndViewMode(application.getId(), false, READ_ACTIONS, null) + .collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List actionCollectionList = tuple.getT2(); + + assertThat(application.getWorkspaceId()).isNotNull(); + + List actionCollectionNames = new ArrayList<>(); + actionCollectionList.forEach(actionCollection -> actionCollectionNames.add( + actionCollection.getUnpublishedCollection().getName())); + assertThat(actionCollectionNames).contains(deletedActionCollectionNames); + }) + .verifyComplete(); + } + + /** + * Testcase for checking the discard changes flow for following events: + * 1. Import application in org + * 2. Add Navigation Settings to the imported application + * 3. User tries to import application from same application json file + * 4. Added NavigationSetting will be removed + */ + @Test + @WithUserDetails(value = "api_user") + public void discardChange_addNavigationAndThemeSettingAfterImport_addedNavigationAndThemeSettingRemoved() { + Mono applicationJsonMono = createAppJson( + "test_assets/ImportExportServiceTest/valid-application-without-navigation-theme-setting.json"); + String workspaceId = createTemplateWorkspace().getId(); + final Mono resultMonoWithoutDiscardOperation = applicationJsonMono + .flatMap(applicationJson -> { + applicationJson.getExportedApplication().setName("discard-change-navsettings-added"); + return importService + .importNewArtifactInWorkspaceFromJson(workspaceId, applicationJson) + .map(importableArtifact -> (Application) importableArtifact); + }) + .flatMap(application -> { + ApplicationDetail applicationDetail = new ApplicationDetail(); + Application.NavigationSetting navigationSetting = new Application.NavigationSetting(); + navigationSetting.setOrientation("top"); + applicationDetail.setNavigationSetting(navigationSetting); + + Application.ThemeSetting themeSettings = getThemeSetting(); + applicationDetail.setThemeSetting(themeSettings); + + application.setUnpublishedApplicationDetail(applicationDetail); + application.setPublishedApplicationDetail(applicationDetail); + return applicationService.save(application); + }) + .cache(); + + StepVerifier.create(resultMonoWithoutDiscardOperation) + .assertNext(initialApplication -> { + assertThat(initialApplication.getUnpublishedApplicationDetail()) + .isNotNull(); + assertThat(initialApplication + .getUnpublishedApplicationDetail() + .getNavigationSetting()) + .isNotNull(); + assertThat(initialApplication + .getUnpublishedApplicationDetail() + .getNavigationSetting() + .getOrientation()) + .isEqualTo("top"); + assertThat(initialApplication.getPublishedApplicationDetail()) + .isNotNull(); + assertThat(initialApplication + .getPublishedApplicationDetail() + .getNavigationSetting()) + .isNotNull(); + assertThat(initialApplication + .getPublishedApplicationDetail() + .getNavigationSetting() + .getOrientation()) + .isEqualTo("top"); + + Application.ThemeSetting themes = + initialApplication.getApplicationDetail().getThemeSetting(); + assertThat(themes.getAccentColor()).isEqualTo("#FFFFFF"); + assertThat(themes.getBorderRadius()).isEqualTo("#000000"); + assertThat(themes.getColorMode()).isEqualTo(Application.ThemeSetting.Type.LIGHT); + assertThat(themes.getDensity()).isEqualTo(1); + assertThat(themes.getFontFamily()).isEqualTo("#000000"); + assertThat(themes.getSizing()).isEqualTo(1); + }) + .verifyComplete(); + // Import the same application again + final Mono resultMonoWithDiscardOperation = resultMonoWithoutDiscardOperation.flatMap( + importedApplication -> applicationJsonMono.flatMap(applicationJson -> { + importedApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + importedApplication + .getGitApplicationMetadata() + .setDefaultApplicationId(importedApplication.getId()); + return applicationService + .save(importedApplication) + .then(importService.importArtifactInWorkspaceFromGit( + importedApplication.getWorkspaceId(), + importedApplication.getId(), + applicationJson, + "main")) + .map(importableArtifact -> (Application) importableArtifact); + })); + + StepVerifier.create(resultMonoWithDiscardOperation) + .assertNext(application -> { + assertThat(application.getWorkspaceId()).isNotNull(); + assertThat(application.getUnpublishedApplicationDetail()).isNull(); + assertThat(application.getPublishedApplicationDetail()).isNull(); + }) + .verifyComplete(); + } + + @NotNull private static Application.ThemeSetting getThemeSetting() { + Application.ThemeSetting themeSettings = new Application.ThemeSetting(); + themeSettings.setSizing(1); + themeSettings.setDensity(1); + themeSettings.setBorderRadius("#000000"); + themeSettings.setAccentColor("#FFFFFF"); + themeSettings.setFontFamily("#000000"); + themeSettings.setColorMode(Application.ThemeSetting.Type.LIGHT); + return themeSettings; + } + + /** + * Testcase for checking the discard changes flow for following events: + * 1. Import application in org + * 2. Updated App Layout Settings to the imported application + * 3. User tries to import application from same application json file + * 4. Added App Layout will be removed + */ + @Test + @WithUserDetails(value = "api_user") + public void discardChange_addAppLayoutAfterImport_addedAppLayoutRemoved() { + + Mono applicationJsonMono = + createAppJson("test_assets/ImportExportServiceTest/valid-application-without-app-layout.json"); + String workspaceId = createTemplateWorkspace().getId(); + final Mono resultMonoWithoutDiscardOperation = applicationJsonMono + .flatMap(applicationJson -> { + applicationJson.getExportedApplication().setName("discard-change-applayout-added"); + return importService + .importNewArtifactInWorkspaceFromJson(workspaceId, applicationJson) + .map(importableArtifact -> (Application) importableArtifact); + }) + .flatMap(application -> { + application.setUnpublishedAppLayout(new Application.AppLayout(Application.AppLayout.Type.DESKTOP)); + application.setPublishedAppLayout(new Application.AppLayout(Application.AppLayout.Type.DESKTOP)); + return applicationService.save(application); + }) + .cache(); + + StepVerifier.create(resultMonoWithoutDiscardOperation) + .assertNext(initialApplication -> { + assertThat(initialApplication.getUnpublishedAppLayout()).isNotNull(); + assertThat(initialApplication.getUnpublishedAppLayout().getType()) + .isEqualTo(Application.AppLayout.Type.DESKTOP); + assertThat(initialApplication.getPublishedAppLayout()).isNotNull(); + assertThat(initialApplication.getPublishedAppLayout().getType()) + .isEqualTo(Application.AppLayout.Type.DESKTOP); + }) + .verifyComplete(); + + // Import the same application again + final Mono resultMonoWithDiscardOperation = resultMonoWithoutDiscardOperation.flatMap( + importedApplication -> applicationJsonMono.flatMap(applicationJson -> { + importedApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + importedApplication + .getGitApplicationMetadata() + .setDefaultApplicationId(importedApplication.getId()); + return applicationService + .save(importedApplication) + .then(importService.importArtifactInWorkspaceFromGit( + importedApplication.getWorkspaceId(), + importedApplication.getId(), + applicationJson, + "main")) + .map(importableArtifact -> (Application) importableArtifact); + })); + + StepVerifier.create(resultMonoWithDiscardOperation) + .assertNext(application -> { + assertThat(application.getWorkspaceId()).isNotNull(); + assertThat(application.getUnpublishedAppLayout()).isNull(); + assertThat(application.getPublishedAppLayout()).isNull(); + }) + .verifyComplete(); + } + + /** + * Testcase for checking the discard changes flow for following events: + * 1. Import application in org which has app positioning in applicationDetail already added + * 2. Add Navigation Settings to the imported application + * 3. User tries to import application from same application json file + * 4. Added NavigationSetting will be removed + */ + @Test + @WithUserDetails(value = "api_user") + public void + discardChange_addNavigationSettingAfterAppPositioningAlreadyPresentInImport_addedNavigationSettingRemoved() { + Mono applicationJsonMono = + createAppJson("test_assets/ImportExportServiceTest/valid-application-with-app-positioning.json"); + String workspaceId = createTemplateWorkspace().getId(); + final Mono resultMonoWithoutDiscardOperation = applicationJsonMono + .flatMap(applicationJson -> { + applicationJson + .getExportedApplication() + .setName("discard-change-navsettings-added-appPositioning-present"); + return importService + .importNewArtifactInWorkspaceFromJson(workspaceId, applicationJson) + .map(importableArtifact -> (Application) importableArtifact); + }) + .flatMap(application -> { + ApplicationDetail applicationDetail = application.getUnpublishedApplicationDetail(); + Application.NavigationSetting navigationSetting = new Application.NavigationSetting(); + navigationSetting.setOrientation("top"); + applicationDetail.setNavigationSetting(navigationSetting); + application.setUnpublishedApplicationDetail(applicationDetail); + application.setPublishedApplicationDetail(applicationDetail); + return applicationService.save(application); + }) + .cache(); + + StepVerifier.create(resultMonoWithoutDiscardOperation) + .assertNext(initialApplication -> { + assertThat(initialApplication.getUnpublishedApplicationDetail()) + .isNotNull(); + assertThat(initialApplication + .getUnpublishedApplicationDetail() + .getNavigationSetting()) + .isNotNull(); + assertThat(initialApplication + .getUnpublishedApplicationDetail() + .getNavigationSetting() + .getOrientation()) + .isEqualTo("top"); + assertThat(initialApplication.getPublishedApplicationDetail()) + .isNotNull(); + assertThat(initialApplication + .getPublishedApplicationDetail() + .getNavigationSetting()) + .isNotNull(); + assertThat(initialApplication + .getPublishedApplicationDetail() + .getNavigationSetting() + .getOrientation()) + .isEqualTo("top"); + assertThat(initialApplication + .getUnpublishedApplicationDetail() + .getAppPositioning()) + .isNotNull(); + assertThat(initialApplication + .getUnpublishedApplicationDetail() + .getAppPositioning() + .getType()) + .isEqualTo(Application.AppPositioning.Type.AUTO); + assertThat(initialApplication + .getPublishedApplicationDetail() + .getAppPositioning()) + .isNotNull(); + assertThat(initialApplication + .getPublishedApplicationDetail() + .getAppPositioning() + .getType()) + .isEqualTo(Application.AppPositioning.Type.AUTO); + }) + .verifyComplete(); + // Import the same application again + final Mono resultMonoWithDiscardOperation = resultMonoWithoutDiscardOperation.flatMap( + importedApplication -> applicationJsonMono.flatMap(applicationJson -> { + importedApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + importedApplication + .getGitApplicationMetadata() + .setDefaultApplicationId(importedApplication.getId()); + return applicationService + .save(importedApplication) + .then(importService.importArtifactInWorkspaceFromGit( + importedApplication.getWorkspaceId(), + importedApplication.getId(), + applicationJson, + "main")) + .map(importableArtifact -> (Application) importableArtifact); + })); + + StepVerifier.create(resultMonoWithDiscardOperation) + .assertNext(application -> { + assertThat(application.getWorkspaceId()).isNotNull(); + assertThat(application.getUnpublishedApplicationDetail()).isNotNull(); + assertThat(application.getPublishedApplicationDetail()).isNotNull(); + assertThat(application.getUnpublishedApplicationDetail().getAppPositioning()) + .isNotNull(); + assertThat(application + .getUnpublishedApplicationDetail() + .getAppPositioning() + .getType()) + .isEqualTo(Application.AppPositioning.Type.AUTO); + assertThat(application.getUnpublishedApplicationDetail().getNavigationSetting()) + .isNull(); + assertThat(application.getPublishedApplicationDetail().getNavigationSetting()) + .isNull(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void applySchemaMigration_jsonFileWithFirstVersion_migratedToLatestVersionSuccess() { + FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/file-with-v1.json"); + + Mono stringifiedFile = DataBufferUtils.join(filePart.content()).map(dataBuffer -> { + byte[] data = new byte[dataBuffer.readableByteCount()]; + dataBuffer.read(data); + DataBufferUtils.release(dataBuffer); + return new String(data); + }); + Mono v1ApplicationMono = stringifiedFile + .map(data -> { + return gson.fromJson(data, ApplicationJson.class); + }) + .cache(); + + Mono migratedApplicationMono = v1ApplicationMono.map(applicationJson -> { + ApplicationJson applicationJson1 = new ApplicationJson(); + AppsmithBeanUtils.copyNestedNonNullProperties(applicationJson, applicationJson1); + return JsonSchemaMigration.migrateApplicationToLatestSchema(applicationJson1); + }); + + StepVerifier.create(Mono.zip(v1ApplicationMono, migratedApplicationMono)) + .assertNext(tuple -> { + ApplicationJson v1ApplicationJson = tuple.getT1(); + ApplicationJson latestApplicationJson = tuple.getT2(); + + assertThat(v1ApplicationJson.getServerSchemaVersion()).isEqualTo(1); + assertThat(v1ApplicationJson.getClientSchemaVersion()).isEqualTo(1); + + assertThat(latestApplicationJson.getServerSchemaVersion()) + .isEqualTo(JsonSchemaVersions.serverVersion); + assertThat(latestApplicationJson.getClientSchemaVersion()) + .isEqualTo(JsonSchemaVersions.clientVersion); + }) + .verifyComplete(); + } + + /** + * Testcase to check if the application is exported with the datasource configuration object if this setting is + * enabled from application object + * This can be enabled with exportWithConfiguration: true + */ + @Test + @WithUserDetails(value = "api_user") + public void exportApplication_withDatasourceConfig_exportedWithDecryptedFields() { + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("template-org-with-ds"); + + Application testApplication = new Application(); + testApplication.setName("exportApplication_withCredentialsForSampleApps_SuccessWithDecryptFields"); + testApplication.setExportWithConfiguration(true); + testApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .block(); + assert testApplication != null; + exportWithConfigurationAppId = testApplication.getId(); + ApplicationAccessDTO accessDTO = new ApplicationAccessDTO(); + accessDTO.setPublicAccess(true); + applicationService + .changeViewAccess(exportWithConfigurationAppId, accessDTO) + .block(); + final String appName = testApplication.getName(); + final Mono resultMono = Mono.zip( + Mono.just(testApplication), + newPageService.findPageById( + testApplication.getPages().get(0).getId(), READ_PAGES, false)) + .flatMap(tuple -> { + Application testApp = tuple.getT1(); + PageDTO testPage = tuple.getT2(); + + Layout layout = testPage.getLayouts().get(0); + ObjectMapper objectMapper = new ObjectMapper(); + JSONObject dsl = new JSONObject(); + try { + dsl = new JSONObject(objectMapper.readValue( + DEFAULT_PAGE_LAYOUT, new TypeReference>() {})); + } catch (JsonProcessingException e) { + e.printStackTrace(); + fail(); + } + + ArrayList children = (ArrayList) dsl.get("children"); + JSONObject testWidget = new JSONObject(); + testWidget.put("widgetName", "firstWidget"); + JSONArray temp = new JSONArray(); + temp.add(new JSONObject(Map.of("key", "testField"))); + testWidget.put("dynamicBindingPathList", temp); + testWidget.put("testField", "{{ validAction.data }}"); + children.add(testWidget); + + layout.setDsl(dsl); + layout.setPublishedDsl(dsl); + + ActionDTO action = new ActionDTO(); + action.setName("validAction"); + action.setPageId(testPage.getId()); + action.setExecuteOnLoad(true); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasourceMap.get("DS2")); + + ActionDTO action2 = new ActionDTO(); + action2.setName("validAction2"); + action2.setPageId(testPage.getId()); + action2.setExecuteOnLoad(true); + action2.setUserSetOnLoad(true); + ActionConfiguration actionConfiguration2 = new ActionConfiguration(); + actionConfiguration2.setHttpMethod(HttpMethod.GET); + action2.setActionConfiguration(actionConfiguration2); + action2.setDatasource(datasourceMap.get("DS2")); + + ActionCollectionDTO actionCollectionDTO1 = new ActionCollectionDTO(); + actionCollectionDTO1.setName("testCollection1"); + actionCollectionDTO1.setPageId(testPage.getId()); + actionCollectionDTO1.setApplicationId(testApp.getId()); + actionCollectionDTO1.setWorkspaceId(testApp.getWorkspaceId()); + actionCollectionDTO1.setPluginId(jsDatasource.getPluginId()); + ActionDTO action1 = new ActionDTO(); + action1.setName("testAction1"); + action1.setActionConfiguration(new ActionConfiguration()); + action1.getActionConfiguration().setBody("mockBody"); + actionCollectionDTO1.setActions(List.of(action1)); + actionCollectionDTO1.setPluginType(PluginType.JS); + + return layoutCollectionService + .createCollection(actionCollectionDTO1, null) + .then(layoutActionService.createSingleAction(action, Boolean.FALSE)) + .then(layoutActionService.createSingleAction(action2, Boolean.FALSE)) + .then(updateLayoutService.updateLayout( + testPage.getId(), testPage.getApplicationId(), layout.getId(), layout)) + .then(exportApplicationService.exportApplicationById(testApp.getId(), "")); + }) + .cache(); + + Mono> actionListMono = resultMono.then(newActionService + .findAllByApplicationIdAndViewMode(testApplication.getId(), false, READ_ACTIONS, null) + .collectList()); + + Mono> collectionListMono = resultMono.then(actionCollectionService + .findAllByApplicationIdAndViewMode(testApplication.getId(), false, READ_ACTIONS, null) + .collectList()); + + Mono> pageListMono = resultMono.then(newPageService + .findNewPagesByApplicationId(testApplication.getId(), READ_PAGES) + .collectList()); + + StepVerifier.create(Mono.zip(resultMono, actionListMono, collectionListMono, pageListMono)) + .assertNext(tuple -> { + ApplicationJson applicationJson = tuple.getT1(); + List DBActions = tuple.getT2(); + List DBCollections = tuple.getT3(); + List DBPages = tuple.getT4(); + + Application exportedApp = applicationJson.getExportedApplication(); + List pageList = applicationJson.getPageList(); + List actionList = applicationJson.getActionList(); + List actionCollectionList = applicationJson.getActionCollectionList(); + List datasourceList = applicationJson.getDatasourceList(); + + List exportedCollectionIds = actionCollectionList.stream() + .map(ActionCollection::getId) + .collect(Collectors.toList()); + List exportedActionIds = + actionList.stream().map(NewAction::getId).collect(Collectors.toList()); + List DBCollectionIds = + DBCollections.stream().map(ActionCollection::getId).collect(Collectors.toList()); + List DBActionIds = + DBActions.stream().map(NewAction::getId).collect(Collectors.toList()); + List DBOnLayoutLoadActionIds = new ArrayList<>(); + List exportedOnLayoutLoadActionIds = new ArrayList<>(); + + DBPages.forEach( + newPage -> newPage.getUnpublishedPage().getLayouts().forEach(layout -> { + if (layout.getLayoutOnLoadActions() != null) { + layout.getLayoutOnLoadActions().forEach(dslActionDTOSet -> { + dslActionDTOSet.forEach( + actionDTO -> DBOnLayoutLoadActionIds.add(actionDTO.getId())); + }); + } + })); + + pageList.forEach( + newPage -> newPage.getUnpublishedPage().getLayouts().forEach(layout -> { + if (layout.getLayoutOnLoadActions() != null) { + layout.getLayoutOnLoadActions().forEach(dslActionDTOSet -> { + dslActionDTOSet.forEach( + actionDTO -> exportedOnLayoutLoadActionIds.add(actionDTO.getId())); + }); + } + })); + + NewPage defaultPage = pageList.get(0); + + assertThat(exportedApp.getName()).isEqualTo(appName); + assertThat(exportedApp.getWorkspaceId()).isNull(); + assertThat(exportedApp.getPages()).hasSize(1); + ApplicationPage page = exportedApp.getPages().get(0); + + assertThat(page.getId()) + .isEqualTo(defaultPage.getUnpublishedPage().getName()); + assertThat(page.getIsDefault()).isTrue(); + assertThat(page.getDefaultPageId()).isNull(); + + assertThat(exportedApp.getPolicies()).isNull(); + + assertThat(pageList).hasSize(1); + assertThat(defaultPage.getApplicationId()).isNull(); + assertThat(defaultPage + .getUnpublishedPage() + .getLayouts() + .get(0) + .getDsl()) + .isNotNull(); + assertThat(defaultPage.getId()).isNull(); + assertThat(defaultPage.getPolicies()).isNull(); + + assertThat(actionList.isEmpty()).isFalse(); + assertThat(actionList).hasSize(3); + NewAction validAction = actionList.stream() + .filter(action -> action.getId().equals("Page1_validAction")) + .findFirst() + .get(); + assertThat(validAction.getApplicationId()).isNull(); + assertThat(validAction.getPluginId()).isEqualTo(installedPlugin.getPackageName()); + assertThat(validAction.getPluginType()).isEqualTo(PluginType.API); + assertThat(validAction.getWorkspaceId()).isNull(); + assertThat(validAction.getPolicies()).isNull(); + assertThat(validAction.getId()).isNotNull(); + ActionDTO unpublishedAction = validAction.getUnpublishedAction(); + assertThat(unpublishedAction.getPageId()) + .isEqualTo(defaultPage.getUnpublishedPage().getName()); + assertThat(unpublishedAction.getDatasource().getPluginId()) + .isEqualTo(installedPlugin.getPackageName()); + + NewAction testAction1 = actionList.stream() + .filter(action -> + action.getUnpublishedAction().getName().equals("testAction1")) + .findFirst() + .get(); + assertThat(testAction1.getId()).isEqualTo("Page1_testCollection1.testAction1"); + + assertThat(actionCollectionList.isEmpty()).isFalse(); + assertThat(actionCollectionList).hasSize(1); + final ActionCollection actionCollection = actionCollectionList.get(0); + assertThat(actionCollection.getApplicationId()).isNull(); + assertThat(actionCollection.getWorkspaceId()).isNull(); + assertThat(actionCollection.getPolicies()).isNull(); + assertThat(actionCollection.getId()).isNotNull(); + assertThat(actionCollection.getUnpublishedCollection().getPluginType()) + .isEqualTo(PluginType.JS); + assertThat(actionCollection.getUnpublishedCollection().getPageId()) + .isEqualTo(defaultPage.getUnpublishedPage().getName()); + assertThat(actionCollection.getUnpublishedCollection().getPluginId()) + .isEqualTo(installedJsPlugin.getPackageName()); + + assertThat(datasourceList).hasSize(1); + DatasourceStorage datasource = datasourceList.get(0); + assertThat(datasource.getWorkspaceId()).isNull(); + assertThat(datasource.getId()).isNull(); + assertThat(datasource.getPluginId()).isEqualTo(installedPlugin.getPackageName()); + assertThat(datasource.getDatasourceConfiguration()).isNotNull(); + + final Map invisibleActionFields = + applicationJson.getInvisibleActionFields(); + + assertThat(invisibleActionFields).isNull(); + for (NewAction newAction : actionList) { + if (newAction.getId().equals("Page1_validAction2")) { + assertEquals(true, newAction.getUnpublishedAction().getUserSetOnLoad()); + } else { + assertEquals(false, newAction.getUnpublishedAction().getUserSetOnLoad()); + } + } + + assertThat(applicationJson.getUnpublishedLayoutmongoEscapedWidgets()) + .isNull(); + assertThat(applicationJson.getPublishedLayoutmongoEscapedWidgets()) + .isNull(); + assertThat(applicationJson.getEditModeTheme()).isNotNull(); + assertThat(applicationJson.getEditModeTheme().isSystemTheme()) + .isTrue(); + assertThat(applicationJson.getEditModeTheme().getName()) + .isEqualToIgnoringCase(Theme.DEFAULT_THEME_NAME); + + assertThat(applicationJson.getPublishedTheme()).isNotNull(); + assertThat(applicationJson.getPublishedTheme().isSystemTheme()) + .isTrue(); + assertThat(applicationJson.getPublishedTheme().getName()) + .isEqualToIgnoringCase(Theme.DEFAULT_THEME_NAME); + + assertThat(exportedCollectionIds).isNotEmpty(); + assertThat(exportedCollectionIds).doesNotContain(String.valueOf(DBCollectionIds)); + + assertThat(exportedActionIds).isNotEmpty(); + assertThat(exportedActionIds).doesNotContain(String.valueOf(DBActionIds)); + + assertThat(exportedOnLayoutLoadActionIds).isNotEmpty(); + assertThat(exportedOnLayoutLoadActionIds).doesNotContain(String.valueOf(DBOnLayoutLoadActionIds)); + + assertThat(applicationJson.getDecryptedFields()).isNotNull(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void + importApplication_datasourceWithSameNameAndDifferentPlugin_importedWithValidActionsAndSuffixedDatasource() { + + ApplicationJson applicationJson = createAppJson("test_assets/ImportExportServiceTest/valid-application.json") + .block(); + + Workspace testWorkspace = new Workspace(); + testWorkspace.setName("Duplicate datasource with different plugin org"); + testWorkspace = workspaceService.create(testWorkspace).block(); + String defaultEnvironmentId = workspaceService + .getDefaultEnvironmentId(testWorkspace.getId(), environmentPermission.getExecutePermission()) + .block(); + + Datasource testDatasource = new Datasource(); + // Chose any plugin except for mongo, as json static file has mongo plugin for datasource + Plugin postgreSQLPlugin = pluginRepository.findByName("PostgreSQL").block(); + testDatasource.setPluginId(postgreSQLPlugin.getId()); + testDatasource.setWorkspaceId(testWorkspace.getId()); + final String datasourceName = applicationJson.getDatasourceList().get(0).getName(); + testDatasource.setName(datasourceName); + + HashMap storages = new HashMap<>(); + storages.put(defaultEnvironmentId, new DatasourceStorageDTO(null, defaultEnvironmentId, null)); + testDatasource.setDatasourceStorages(storages); + + datasourceService.create(testDatasource).block(); + + final Mono resultMono = importService + .importNewArtifactInWorkspaceFromJson(testWorkspace.getId(), applicationJson) + .map(importableArtifact -> (Application) importableArtifact); + + StepVerifier.create(resultMono.flatMap(application -> Mono.zip( + Mono.just(application), + datasourceService + .getAllByWorkspaceIdWithStorages( + application.getWorkspaceId(), Optional.of(MANAGE_DATASOURCES)) + .collectList(), + newActionService + .findAllByApplicationIdAndViewMode(application.getId(), false, READ_ACTIONS, null) + .collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List datasourceList = tuple.getT2(); + final List actionList = tuple.getT3(); + + assertThat(application.getName()).isEqualTo("valid_application"); + + List datasourceNameList = new ArrayList<>(); + assertThat(datasourceList).isNotEmpty(); + datasourceList.forEach(datasource -> { + assertThat(datasource.getWorkspaceId()).isEqualTo(application.getWorkspaceId()); + datasourceNameList.add(datasource.getName()); + }); + // Check if both suffixed and newly imported datasource are present + assertThat(datasourceNameList).contains(datasourceName, datasourceName + " #1"); + + assertThat(actionList).isNotEmpty(); + actionList.forEach(newAction -> { + ActionDTO actionDTO = newAction.getUnpublishedAction(); + assertThat(actionDTO.getDatasource()).isNotNull(); + }); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importApplication_datasourceWithSameNameAndPlugin_importedWithValidActionsWithoutSuffixedDatasource() { + + ApplicationJson applicationJson = createAppJson("test_assets/ImportExportServiceTest/valid-application.json") + .block(); + + Workspace testWorkspace = new Workspace(); + testWorkspace.setName("Duplicate datasource with same plugin org"); + testWorkspace = workspaceService.create(testWorkspace).block(); + String defaultEnvironmentId = workspaceService + .getDefaultEnvironmentId(testWorkspace.getId(), environmentPermission.getExecutePermission()) + .block(); + Datasource testDatasource = new Datasource(); + // Chose plugin same as mongo, as json static file has mongo plugin for datasource + Plugin postgreSQLPlugin = pluginRepository.findByName("MongoDB").block(); + testDatasource.setPluginId(postgreSQLPlugin.getId()); + testDatasource.setWorkspaceId(testWorkspace.getId()); + final String datasourceName = applicationJson.getDatasourceList().get(0).getName(); + testDatasource.setName(datasourceName); + + HashMap storages = new HashMap<>(); + storages.put(defaultEnvironmentId, new DatasourceStorageDTO(null, defaultEnvironmentId, null)); + testDatasource.setDatasourceStorages(storages); + datasourceService.create(testDatasource).block(); + + final Mono resultMono = importService + .importNewArtifactInWorkspaceFromJson(testWorkspace.getId(), applicationJson) + .map(importableArtifact -> (Application) importableArtifact); + + StepVerifier.create(resultMono.flatMap(application -> Mono.zip( + Mono.just(application), + datasourceService + .getAllByWorkspaceIdWithStorages( + application.getWorkspaceId(), Optional.of(MANAGE_DATASOURCES)) + .collectList(), + newActionService + .findAllByApplicationIdAndViewMode(application.getId(), false, READ_ACTIONS, null) + .collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List datasourceList = tuple.getT2(); + final List actionList = tuple.getT3(); + + assertThat(application.getName()).isEqualTo("valid_application"); + + List datasourceNameList = new ArrayList<>(); + assertThat(datasourceList).isNotEmpty(); + datasourceList.forEach(datasource -> { + assertThat(datasource.getWorkspaceId()).isEqualTo(application.getWorkspaceId()); + datasourceNameList.add(datasource.getName()); + }); + // Check that there are no datasources are created with suffix names as datasource's are of same + // plugin + assertThat(datasourceNameList).contains(datasourceName); + + assertThat(actionList).isNotEmpty(); + actionList.forEach(newAction -> { + ActionDTO actionDTO = newAction.getUnpublishedAction(); + assertThat(actionDTO.getDatasource()).isNotNull(); + }); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void + exportAndImportApplication_withMultiplePagesOrderSameInDeployAndEditMode_PagesOrderIsMaintainedInEditAndViewMode() { + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("template-org-with-ds"); + + Application testApplication = new Application(); + testApplication.setName( + "exportAndImportApplication_withMultiplePagesOrderSameInDeployAndEditMode_PagesOrderIsMaintainedInEditAndViewMode"); + testApplication.setExportWithConfiguration(true); + testApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .block(); + assert testApplication != null; + + PageDTO testPage1 = new PageDTO(); + testPage1.setName("testPage1"); + testPage1.setApplicationId(testApplication.getId()); + testPage1 = applicationPageService.createPage(testPage1).block(); + + PageDTO testPage2 = new PageDTO(); + testPage2.setName("testPage2"); + testPage2.setApplicationId(testApplication.getId()); + testPage2 = applicationPageService.createPage(testPage2).block(); + + // Set order for the newly created pages + applicationPageService + .reorderPage(testApplication.getId(), testPage1.getId(), 0, null) + .block(); + applicationPageService + .reorderPage(testApplication.getId(), testPage2.getId(), 1, null) + .block(); + // Deploy the current application + applicationPageService.publish(testApplication.getId(), true).block(); + + Mono applicationJsonMono = exportApplicationService + .exportApplicationById(testApplication.getId(), "") + .cache(); + + StepVerifier.create(applicationJsonMono) + .assertNext(applicationJson -> { + assertThat(applicationJson.getPageOrder()).isNull(); + assertThat(applicationJson.getPublishedPageOrder()).isNull(); + List pageList = applicationJson.getExportedApplication().getPages().stream() + .map(ApplicationPage::getId) + .collect(Collectors.toList()); + + assertThat(pageList.get(0)).isEqualTo("testPage1"); + assertThat(pageList.get(1)).isEqualTo("testPage2"); + assertThat(pageList.get(2)).isEqualTo("Page1"); + + List publishedPageList = + applicationJson.getExportedApplication().getPublishedPages().stream() + .map(ApplicationPage::getId) + .collect(Collectors.toList()); + + assertThat(publishedPageList.get(0)).isEqualTo("testPage1"); + assertThat(publishedPageList.get(1)).isEqualTo("testPage2"); + assertThat(publishedPageList.get(2)).isEqualTo("Page1"); + }) + .verifyComplete(); + + ApplicationJson applicationJson = applicationJsonMono.block(); + Application application = importService + .importNewArtifactInWorkspaceFromJson(workspaceId, applicationJson) + .map(importableArtifact -> (Application) importableArtifact) + .block(); + + // Get the unpublished pages and verify the order + List pageDTOS = application.getPages(); + Mono newPageMono1 = newPageService.findById(pageDTOS.get(0).getId(), MANAGE_PAGES); + Mono newPageMono2 = newPageService.findById(pageDTOS.get(1).getId(), MANAGE_PAGES); + Mono newPageMono3 = newPageService.findById(pageDTOS.get(2).getId(), MANAGE_PAGES); + + StepVerifier.create(Mono.zip(newPageMono1, newPageMono2, newPageMono3)) + .assertNext(objects -> { + NewPage newPage1 = objects.getT1(); + NewPage newPage2 = objects.getT2(); + NewPage newPage3 = objects.getT3(); + assertThat(newPage1.getUnpublishedPage().getName()).isEqualTo("testPage1"); + assertThat(newPage2.getUnpublishedPage().getName()).isEqualTo("testPage2"); + assertThat(newPage3.getUnpublishedPage().getName()).isEqualTo("Page1"); + + assertThat(newPage1.getId()).isEqualTo(pageDTOS.get(0).getId()); + assertThat(newPage2.getId()).isEqualTo(pageDTOS.get(1).getId()); + assertThat(newPage3.getId()).isEqualTo(pageDTOS.get(2).getId()); + }) + .verifyComplete(); + + // Get the published pages + List publishedPageDTOs = application.getPublishedPages(); + Mono newPublishedPageMono1 = + newPageService.findById(publishedPageDTOs.get(0).getId(), MANAGE_PAGES); + Mono newPublishedPageMono2 = + newPageService.findById(publishedPageDTOs.get(1).getId(), MANAGE_PAGES); + Mono newPublishedPageMono3 = + newPageService.findById(publishedPageDTOs.get(2).getId(), MANAGE_PAGES); + + StepVerifier.create(Mono.zip(newPublishedPageMono1, newPublishedPageMono2, newPublishedPageMono3)) + .assertNext(objects -> { + NewPage newPage1 = objects.getT1(); + NewPage newPage2 = objects.getT2(); + NewPage newPage3 = objects.getT3(); + assertThat(newPage1.getPublishedPage().getName()).isEqualTo("testPage1"); + assertThat(newPage2.getPublishedPage().getName()).isEqualTo("testPage2"); + assertThat(newPage3.getPublishedPage().getName()).isEqualTo("Page1"); + + assertThat(newPage1.getId()) + .isEqualTo(publishedPageDTOs.get(0).getId()); + assertThat(newPage2.getId()) + .isEqualTo(publishedPageDTOs.get(1).getId()); + assertThat(newPage3.getId()) + .isEqualTo(publishedPageDTOs.get(2).getId()); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void + importApplicationInWorkspaceFromGit_WithNavSettingsInEditMode_ImportedAppHasNavSettingsInEditAndViewMode() { + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("import-with-navSettings-in-editMode"); + + Application testApplication = new Application(); + testApplication.setName( + "importApplicationInWorkspaceFromGit_WithNavSettingsInEditMode_ImportedAppHasNavSettingsInEditAndViewMode"); + Application.NavigationSetting appNavigationSetting = new Application.NavigationSetting(); + appNavigationSetting.setOrientation("top"); + testApplication.setUnpublishedApplicationDetail(new ApplicationDetail()); + testApplication.getUnpublishedApplicationDetail().setNavigationSetting(appNavigationSetting); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("testBranch"); + testApplication.setGitApplicationMetadata(gitData); + Application savedApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + + Mono result = exportApplicationService + .exportApplicationById(savedApplication.getId(), SerialiseApplicationObjective.VERSION_CONTROL) + .flatMap(applicationJson -> { + // setting published mode resource as null, similar to the app json exported to git repo + applicationJson.getExportedApplication().setPublishedApplicationDetail(null); + return importService + .importArtifactInWorkspaceFromGit( + workspaceId, savedApplication.getId(), applicationJson, gitData.getBranchName()) + .map(importableArtifact -> (Application) importableArtifact); + }); + + StepVerifier.create(result) + .assertNext(importedApp -> { + assertThat(importedApp.getUnpublishedApplicationDetail()).isNotNull(); + assertThat(importedApp.getPublishedApplicationDetail()).isNotNull(); + assertThat(importedApp.getUnpublishedApplicationDetail().getNavigationSetting()) + .isNotNull(); + assertEquals( + importedApp + .getUnpublishedApplicationDetail() + .getNavigationSetting() + .getOrientation(), + "top"); + assertThat(importedApp.getPublishedApplicationDetail().getNavigationSetting()) + .isNotNull(); + assertEquals( + importedApp + .getPublishedApplicationDetail() + .getNavigationSetting() + .getOrientation(), + "top"); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importApplicationInWorkspaceFromGit_WithAppLayoutInEditMode_ImportedAppHasAppLayoutInEditAndViewMode() { + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("import-with-appLayout-in-editMode"); + + Application testApplication = new Application(); + testApplication.setName( + "importApplicationInWorkspaceFromGit_WithAppLayoutInEditMode_ImportedAppHasAppLayoutInEditAndViewMode"); + testApplication.setUnpublishedAppLayout(new Application.AppLayout(Application.AppLayout.Type.DESKTOP)); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("testBranch"); + testApplication.setGitApplicationMetadata(gitData); + Application savedApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + + Mono result = exportApplicationService + .exportApplicationById(savedApplication.getId(), SerialiseApplicationObjective.VERSION_CONTROL) + .flatMap(applicationJson -> { + // setting published mode resource as null, similar to the app json exported to git repo + applicationJson.getExportedApplication().setPublishedAppLayout(null); + return importService + .importArtifactInWorkspaceFromGit( + workspaceId, savedApplication.getId(), applicationJson, gitData.getBranchName()) + .map(importableArtifact -> (Application) importableArtifact); + }); + + StepVerifier.create(result) + .assertNext(importedApp -> { + assertThat(importedApp.getUnpublishedAppLayout()).isNotNull(); + assertThat(importedApp.getPublishedAppLayout()).isNotNull(); + assertThat(importedApp.getUnpublishedAppLayout().getType()) + .isEqualTo(Application.AppLayout.Type.DESKTOP); + assertThat(importedApp.getPublishedAppLayout().getType()) + .isEqualTo(Application.AppLayout.Type.DESKTOP); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void + exportAndImportApplication_withMultiplePagesOrderDifferentInDeployAndEditMode_PagesOrderIsMaintainedInEditAndViewMode() { + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("template-org-with-ds"); + + Application testApplication = new Application(); + testApplication.setName( + "exportAndImportApplication_withMultiplePagesOrderDifferentInDeployAndEditMode_PagesOrderIsMaintainedInEditAndViewMode"); + testApplication.setExportWithConfiguration(true); + testApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .block(); + assert testApplication != null; + + PageDTO testPage1 = new PageDTO(); + testPage1.setName("testPage1"); + testPage1.setApplicationId(testApplication.getId()); + testPage1 = applicationPageService.createPage(testPage1).block(); + + PageDTO testPage2 = new PageDTO(); + testPage2.setName("testPage2"); + testPage2.setApplicationId(testApplication.getId()); + testPage2 = applicationPageService.createPage(testPage2).block(); + + // Deploy the current application so that edit and view mode will have different page order + applicationPageService.publish(testApplication.getId(), true).block(); + + // Set order for the newly created pages + applicationPageService + .reorderPage(testApplication.getId(), testPage1.getId(), 0, null) + .block(); + applicationPageService + .reorderPage(testApplication.getId(), testPage2.getId(), 1, null) + .block(); + + Mono applicationJsonMono = exportApplicationService + .exportApplicationById(testApplication.getId(), "") + .cache(); + + StepVerifier.create(applicationJsonMono) + .assertNext(applicationJson -> { + Application exportedApplication = applicationJson.getExportedApplication(); + exportedApplication.setViewMode(false); + List pageOrder = exportedApplication.getPages().stream() + .map(ApplicationPage::getId) + .collect(Collectors.toList()); + assertThat(pageOrder.get(0)).isEqualTo("testPage1"); + assertThat(pageOrder.get(1)).isEqualTo("testPage2"); + assertThat(pageOrder.get(2)).isEqualTo("Page1"); + + pageOrder.clear(); + pageOrder = exportedApplication.getPublishedPages().stream() + .map(ApplicationPage::getId) + .collect(Collectors.toList()); + assertThat(pageOrder.get(0)).isEqualTo("Page1"); + assertThat(pageOrder.get(1)).isEqualTo("testPage1"); + assertThat(pageOrder.get(2)).isEqualTo("testPage2"); + }) + .verifyComplete(); + + ApplicationJson applicationJson = applicationJsonMono.block(); + Application application = importService + .importNewArtifactInWorkspaceFromJson(workspaceId, applicationJson) + .map(importableArtifact -> (Application) importableArtifact) + .block(); + + // Get the unpublished pages and verify the order + application.setViewMode(false); + List pageDTOS = application.getPages(); + Mono newPageMono1 = newPageService.findById(pageDTOS.get(0).getId(), MANAGE_PAGES); + Mono newPageMono2 = newPageService.findById(pageDTOS.get(1).getId(), MANAGE_PAGES); + Mono newPageMono3 = newPageService.findById(pageDTOS.get(2).getId(), MANAGE_PAGES); + + StepVerifier.create(Mono.zip(newPageMono1, newPageMono2, newPageMono3)) + .assertNext(objects -> { + NewPage newPage1 = objects.getT1(); + NewPage newPage2 = objects.getT2(); + NewPage newPage3 = objects.getT3(); + assertThat(newPage1.getUnpublishedPage().getName()).isEqualTo("testPage1"); + assertThat(newPage2.getUnpublishedPage().getName()).isEqualTo("testPage2"); + assertThat(newPage3.getUnpublishedPage().getName()).isEqualTo("Page1"); + + assertThat(newPage1.getId()).isEqualTo(pageDTOS.get(0).getId()); + assertThat(newPage2.getId()).isEqualTo(pageDTOS.get(1).getId()); + assertThat(newPage3.getId()).isEqualTo(pageDTOS.get(2).getId()); + }) + .verifyComplete(); + + // Get the published pages + List publishedPageDTOs = application.getPublishedPages(); + Mono newPublishedPageMono1 = + newPageService.findById(publishedPageDTOs.get(0).getId(), MANAGE_PAGES); + Mono newPublishedPageMono2 = + newPageService.findById(publishedPageDTOs.get(1).getId(), MANAGE_PAGES); + Mono newPublishedPageMono3 = + newPageService.findById(publishedPageDTOs.get(2).getId(), MANAGE_PAGES); + + StepVerifier.create(Mono.zip(newPublishedPageMono1, newPublishedPageMono2, newPublishedPageMono3)) + .assertNext(objects -> { + NewPage newPage1 = objects.getT1(); + NewPage newPage2 = objects.getT2(); + NewPage newPage3 = objects.getT3(); + assertThat(newPage1.getPublishedPage().getName()).isEqualTo("Page1"); + assertThat(newPage2.getPublishedPage().getName()).isEqualTo("testPage1"); + assertThat(newPage3.getPublishedPage().getName()).isEqualTo("testPage2"); + + assertThat(newPage1.getId()) + .isEqualTo(publishedPageDTOs.get(0).getId()); + assertThat(newPage2.getId()) + .isEqualTo(publishedPageDTOs.get(1).getId()); + assertThat(newPage3.getId()) + .isEqualTo(publishedPageDTOs.get(2).getId()); + }) + .verifyComplete(); + } + + private ApplicationJson createApplicationJSON(List pageNames) { + ApplicationJson applicationJson = new ApplicationJson(); + + // set the application data + Application application = new Application(); + application.setName("Template Application"); + application.setSlug("template-application"); + application.setForkingEnabled(true); + application.setIsPublic(true); + application.setApplicationVersion(ApplicationVersion.LATEST_VERSION); + applicationJson.setExportedApplication(application); + + DatasourceStorage sampleDatasource = new DatasourceStorage(); + sampleDatasource.setName("SampleDS"); + sampleDatasource.setPluginId("restapi-plugin"); + + applicationJson.setDatasourceList(List.of(sampleDatasource)); + + // add pages and actions + List newPageList = new ArrayList<>(pageNames.size()); + List actionList = new ArrayList<>(); + List actionCollectionList = new ArrayList<>(); + + for (String pageName : pageNames) { + NewPage newPage = new NewPage(); + newPage.setUnpublishedPage(new PageDTO()); + newPage.getUnpublishedPage().setName(pageName); + newPage.getUnpublishedPage().setLayouts(List.of()); + newPageList.add(newPage); + + NewAction action = new NewAction(); + action.setId(pageName + "_SampleQuery"); + action.setPluginType(PluginType.API); + action.setPluginId("restapi-plugin"); + action.setUnpublishedAction(new ActionDTO()); + action.getUnpublishedAction().setName("SampleQuery"); + action.getUnpublishedAction().setPageId(pageName); + action.getUnpublishedAction().setDatasource(new Datasource()); + action.getUnpublishedAction().getDatasource().setId("SampleDS"); + action.getUnpublishedAction().getDatasource().setPluginId("restapi-plugin"); + actionList.add(action); + + ActionCollection actionCollection = new ActionCollection(); + actionCollection.setId(pageName + "_SampleJS"); + actionCollection.setUnpublishedCollection(new ActionCollectionDTO()); + actionCollection.getUnpublishedCollection().setName("SampleJS"); + actionCollection.getUnpublishedCollection().setPageId(pageName); + actionCollection.getUnpublishedCollection().setPluginId("js-plugin"); + actionCollection.getUnpublishedCollection().setPluginType(PluginType.JS); + actionCollection.getUnpublishedCollection().setBody("export default {\\n\\t\\n}"); + actionCollectionList.add(actionCollection); + } + + applicationJson.setPageList(newPageList); + applicationJson.setActionList(actionList); + applicationJson.setActionCollectionList(actionCollectionList); + return applicationJson; + } + + @Test + @WithUserDetails("api_user") + public void mergeApplicationJsonWithApplication_WhenPageNameConflicts_PageNamesRenamed() { + String uniqueString = UUID.randomUUID().toString(); + + Application destApplication = new Application(); + destApplication.setName("App_" + uniqueString); + destApplication.setSlug("my-slug"); + destApplication.setIsPublic(false); + destApplication.setForkingEnabled(false); + Mono createAppAndPageMono = applicationPageService + .createApplication(destApplication, workspaceId) + .flatMap(application -> { + PageDTO pageDTO = new PageDTO(); + pageDTO.setName("Home"); + pageDTO.setApplicationId(application.getId()); + return applicationPageService.createPage(pageDTO).thenReturn(application); + }); + + // let's create an ApplicationJSON which we'll merge with application created by createAppAndPageMono + ApplicationJson applicationJson = createApplicationJSON(List.of("Home", "About")); + + Mono, List>> tuple2Mono = createAppAndPageMono + .flatMap(application -> + // merge the application json with the application we've created + importService + .mergeArtifactExchangeJsonWithImportableArtifact( + application.getWorkspaceId(), application.getId(), null, applicationJson, null) + .thenReturn(application)) + .flatMap(application -> + // fetch the application pages, this should contain pages from application json + Mono.zip( + newPageService.findApplicationPages( + application.getId(), null, null, ApplicationMode.EDIT), + newActionService + .findAllByApplicationIdAndViewMode( + application.getId(), false, MANAGE_ACTIONS, null) + .collectList(), + actionCollectionService + .findAllByApplicationIdAndViewMode( + application.getId(), false, MANAGE_ACTIONS, null) + .collectList())); + + StepVerifier.create(tuple2Mono) + .assertNext(objects -> { + ApplicationPagesDTO applicationPagesDTO = objects.getT1(); + List newActionList = objects.getT2(); + List actionCollectionList = objects.getT3(); + + assertThat(applicationPagesDTO.getApplication().getName()).isEqualTo(destApplication.getName()); + assertThat(applicationPagesDTO.getApplication().getSlug()).isEqualTo(destApplication.getSlug()); + assertThat(applicationPagesDTO.getApplication().getIsPublic()) + .isFalse(); + assertThat(applicationPagesDTO.getApplication().getForkingEnabled()) + .isFalse(); + assertThat(applicationPagesDTO.getPages().size()).isEqualTo(4); + List pageNames = applicationPagesDTO.getPages().stream() + .map(PageNameIdDTO::getName) + .collect(Collectors.toList()); + assertThat(pageNames).contains("Home", "Home2", "About"); + assertThat(newActionList.size()).isEqualTo(2); // we imported two pages and each page has one action + assertThat(actionCollectionList.size()) + .isEqualTo(2); // we imported two pages and each page has one Collection + }) + .verifyComplete(); + } + + @Test + @WithUserDetails("api_user") + public void mergeApplicationJsonWithApplication_WhenPageListIProvided_OnlyListedPagesAreMerged() { + String uniqueString = UUID.randomUUID().toString(); + + Application destApplication = new Application(); + destApplication.setName("App_" + uniqueString); + Mono createAppAndPageMono = applicationPageService + .createApplication(destApplication, workspaceId) + .flatMap(application -> { + PageDTO pageDTO = new PageDTO(); + pageDTO.setName("Home"); + pageDTO.setApplicationId(application.getId()); + return applicationPageService.createPage(pageDTO).thenReturn(application); + }); + + // let's create an ApplicationJSON which we'll merge with application created by createAppAndPageMono + ApplicationJson applicationJson = createApplicationJSON(List.of("Profile", "About", "Contact US")); + + Mono applicationPagesDTOMono = createAppAndPageMono + .flatMap(application -> + // merge the application json with the application we've created + importService + .mergeArtifactExchangeJsonWithImportableArtifact( + application.getWorkspaceId(), + application.getId(), + null, + applicationJson, + List.of("About", "Contact US")) + .thenReturn(application)) + .flatMap(application -> + // fetch the application pages, this should contain pages from application json + newPageService.findApplicationPages(application.getId(), null, null, ApplicationMode.EDIT)); + + StepVerifier.create(applicationPagesDTOMono) + .assertNext(applicationPagesDTO -> { + assertThat(applicationPagesDTO.getPages().size()).isEqualTo(4); + List pageNames = applicationPagesDTO.getPages().stream() + .map(PageNameIdDTO::getName) + .collect(Collectors.toList()); + assertThat(pageNames).contains("Home", "About", "Contact US"); + assertThat(pageNames).doesNotContain("Profile"); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplicationById_WhenThemeDoesNotExist_ExportedWithDefaultTheme() { + Theme customTheme = new Theme(); + customTheme.setName("my-custom-theme"); + + String randomId = UUID.randomUUID().toString(); + Application testApplication = new Application(); + testApplication.setName("Application_" + randomId); + Mono exportedAppJson = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application -> { + application.setEditModeThemeId("invalid-theme-id"); + application.setPublishedModeThemeId("invalid-theme-id"); + String branchName = null; + return applicationService + .save(application) + .then(exportApplicationService.exportApplicationById(application.getId(), branchName)); + }); + + StepVerifier.create(exportedAppJson) + .assertNext(applicationJson -> { + assertThat(applicationJson.getEditModeTheme().getName()) + .isEqualToIgnoringCase(Theme.DEFAULT_THEME_NAME); + assertThat(applicationJson.getPublishedTheme().getName()) + .isEqualToIgnoringCase(Theme.DEFAULT_THEME_NAME); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importApplication_invalidPluginReferenceForDatasource_throwException() { + Mockito.when(pluginService.findAllByIdsWithoutPermission(Mockito.any(), Mockito.anyList())) + .thenReturn(Flux.fromIterable(List.of(installedPlugin, installedJsPlugin))); + + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("Template Workspace"); + + ApplicationJson appJson = createAppJson("test_assets/ImportExportServiceTest/valid-application.json") + .block(); + assert appJson != null; + final String randomId = UUID.randomUUID().toString(); + appJson.getDatasourceList().get(0).setPluginId(randomId); + Workspace createdWorkspace = workspaceService.create(newWorkspace).block(); + final Mono resultMono = importService + .importNewArtifactInWorkspaceFromJson(createdWorkspace.getId(), appJson) + .map(importableArtifact -> (Application) importableArtifact); + + StepVerifier.create(resultMono) + .expectErrorMatches(throwable -> throwable instanceof AppsmithException + && throwable + .getMessage() + .contains(AppsmithError.GENERIC_JSON_IMPORT_ERROR.getMessage( + createdWorkspace.getId(), ""))) + .verify(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importApplication_importSameApplicationTwice_applicationImportedLaterWithSuffixCount() { + + Mono applicationJsonMono = + createAppJson("test_assets/ImportExportServiceTest/valid-application-without-action-collection.json"); + + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("Template Workspace"); + + Mono createWorkspaceMono = + workspaceService.create(newWorkspace).cache(); + final Mono importApplicationMono = createWorkspaceMono + .zipWith(applicationJsonMono) + .flatMap(tuple -> { + Workspace workspace = tuple.getT1(); + ApplicationJson applicationJson = tuple.getT2(); + return importService + .importNewArtifactInWorkspaceFromJson(workspace.getId(), applicationJson) + .map(importableArtifact -> (Application) importableArtifact); + }); + + StepVerifier.create(importApplicationMono.zipWhen(application -> importApplicationMono)) + .assertNext(tuple -> { + Application firstImportedApplication = tuple.getT1(); + Application secondImportedApplication = tuple.getT2(); + assertThat(firstImportedApplication.getName()).isEqualTo("valid_application"); + assertThat(secondImportedApplication.getName()).isEqualTo("valid_application (1)"); + assertThat(firstImportedApplication.getWorkspaceId()) + .isEqualTo(secondImportedApplication.getWorkspaceId()); + assertThat(firstImportedApplication.getWorkspaceId()).isNotNull(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void mergeApplication_existingApplication_pageAddedSuccessfully() { + + // Create application + Application application = new Application(); + application.setName("mergeApplication_existingApplication_pageAddedSuccessfully"); + application.setWorkspaceId(workspaceId); + application = applicationPageService.createApplication(application).block(); + + Mono applicationJson = + createAppJson("test_assets/ImportExportServiceTest/valid-application.json"); + + Application finalApplication = application; + Mono, List, List>> importedApplication = + applicationJson + .flatMap(applicationJson1 -> importService.mergeArtifactExchangeJsonWithImportableArtifact( + workspaceId, finalApplication.getId(), null, applicationJson1, new ArrayList<>())) + .map(importableArtifact -> (Application) importableArtifact) + .flatMap(application1 -> { + Mono> pageList = newPageService + .findNewPagesByApplicationId(application1.getId(), MANAGE_PAGES) + .collectList(); + Mono> actionList = newActionService + .findAllByApplicationIdAndViewMode( + application1.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + Mono> actionCollectionList = actionCollectionService + .findAllByApplicationIdAndViewMode( + application1.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + return Mono.zip(Mono.just(application1), pageList, actionList, actionCollectionList); + }); + + StepVerifier.create(importedApplication) + .assertNext(tuple -> { + Application application1 = tuple.getT1(); + List pageList = tuple.getT2(); + List actionList = tuple.getT3(); + List actionCollectionList = tuple.getT4(); + + assertThat(application1.getId()).isEqualTo(finalApplication.getId()); + assertThat(finalApplication.getPages().size()) + .isLessThan(application1.getPages().size()); + assertThat(finalApplication.getPages().size()) + .isEqualTo(application1.getPublishedPages().size()); + + // Verify the pages after merging the template + pageList.forEach(newPage -> { + assertThat(newPage.getUnpublishedPage().getName()).containsAnyOf("Page1", "Page12", "Page2"); + assertThat(newPage.getGitSyncId()).isNotNull(); + }); + + NewPage page = pageList.stream() + .filter(newPage -> + newPage.getUnpublishedPage().getName().equals("Page12")) + .collect(Collectors.toList()) + .get(0); + // Verify the actions after merging the template + actionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedAction().getName()) + .containsAnyOf("api_wo_auth", "get_users", "run"); + assertThat(newAction.getUnpublishedAction().getPageId()).isEqualTo(page.getId()); + }); + + // Verify the actionCollections after merging the template + actionCollectionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedCollection().getName()) + .containsAnyOf("JSObject1", "JSObject2"); + assertThat(newAction.getUnpublishedCollection().getPageId()) + .isEqualTo(page.getId()); + }); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void mergeApplication_gitConnectedApplication_pageAddedSuccessfully() { + + // Create application connected to git + Application testApplication = new Application(); + testApplication.setName("mergeApplication_gitConnectedApplication_pageAddedSuccessfully"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("master"); + gitData.setDefaultBranchName("master"); + testApplication.setGitApplicationMetadata(gitData); + + Application application = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + + Mono applicationJson = + createAppJson("test_assets/ImportExportServiceTest/valid-application.json"); + + Application finalApplication = application; + Mono, List, List>> importedApplication = + applicationJson + .flatMap(applicationJson1 -> importService.mergeArtifactExchangeJsonWithImportableArtifact( + workspaceId, finalApplication.getId(), "master", applicationJson1, new ArrayList<>())) + .map(importableArtifact -> (Application) importableArtifact) + .flatMap(application1 -> { + Mono> pageList = newPageService + .findNewPagesByApplicationId(application1.getId(), MANAGE_PAGES) + .collectList(); + Mono> actionList = newActionService + .findAllByApplicationIdAndViewMode( + application1.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + Mono> actionCollectionList = actionCollectionService + .findAllByApplicationIdAndViewMode( + application1.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + return Mono.zip(Mono.just(application1), pageList, actionList, actionCollectionList); + }); + + StepVerifier.create(importedApplication) + .assertNext(tuple -> { + Application application1 = tuple.getT1(); + List pageList = tuple.getT2(); + List actionList = tuple.getT3(); + List actionCollectionList = tuple.getT4(); + + assertThat(application1.getId()).isEqualTo(finalApplication.getId()); + assertThat(finalApplication.getPages().size()) + .isLessThan(application1.getPages().size()); + assertThat(finalApplication.getPages().size()) + .isEqualTo(application1.getPublishedPages().size()); + + // Verify the pages after merging the template + pageList.forEach(newPage -> { + assertThat(newPage.getUnpublishedPage().getName()).containsAnyOf("Page1", "Page12", "Page2"); + assertThat(newPage.getGitSyncId()).isNotNull(); + }); + + NewPage page = pageList.stream() + .filter(newPage -> + newPage.getUnpublishedPage().getName().equals("Page12")) + .collect(Collectors.toList()) + .get(0); + // Verify the actions after merging the template + actionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedAction().getName()) + .containsAnyOf("api_wo_auth", "get_users", "run"); + assertThat(newAction.getUnpublishedAction().getPageId()).isEqualTo(page.getId()); + }); + + // Verify the actionCollections after merging the template + actionCollectionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedCollection().getName()) + .containsAnyOf("JSObject1", "JSObject2"); + assertThat(newAction.getUnpublishedCollection().getPageId()) + .isEqualTo(page.getId()); + }); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void mergeApplication_gitConnectedApplicationChildBranch_pageAddedSuccessfully() { + + // Create application connected to git + Application testApplication = new Application(); + testApplication.setName("mergeApplication_gitConnectedApplicationChildBranch_pageAddedSuccessfully"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("master"); + gitData.setDefaultBranchName("master"); + testApplication.setGitApplicationMetadata(gitData); + + Application application = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + + // Create branch for the application + testApplication = new Application(); + testApplication.setName("mergeApplication_gitConnectedApplicationChildBranch_pageAddedSuccessfully1"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData1 = new GitApplicationMetadata(); + gitData1.setBranchName("feature"); + gitData1.setDefaultBranchName("master"); + testApplication.setGitApplicationMetadata(gitData1); + + Application branchApp = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application2 -> { + application2.getGitApplicationMetadata().setDefaultApplicationId(application.getId()); + return applicationService.save(application2); + }) + .block(); + + Mono applicationJson = + createAppJson("test_assets/ImportExportServiceTest/valid-application.json"); + + Application finalApplication = application; + Mono, List, List>> importedApplication = + applicationJson + .flatMap(applicationJson1 -> importService.mergeArtifactExchangeJsonWithImportableArtifact( + workspaceId, branchApp.getId(), "feature", applicationJson1, new ArrayList<>())) + .map(importableArtifact -> (Application) importableArtifact) + .flatMap(application2 -> { + Mono> pageList = newPageService + .findNewPagesByApplicationId(branchApp.getId(), MANAGE_PAGES) + .collectList(); + Mono> actionList = newActionService + .findAllByApplicationIdAndViewMode(branchApp.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + Mono> actionCollectionList = actionCollectionService + .findAllByApplicationIdAndViewMode(branchApp.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + return Mono.zip(Mono.just(application2), pageList, actionList, actionCollectionList); + }); + + StepVerifier.create(importedApplication) + .assertNext(tuple -> { + Application application3 = tuple.getT1(); + List pageList = tuple.getT2(); + List actionList = tuple.getT3(); + List actionCollectionList = tuple.getT4(); + + assertThat(application3.getId()).isNotEqualTo(finalApplication.getId()); + assertThat(finalApplication.getPages().size()) + .isLessThan(application3.getPages().size()); + assertThat(finalApplication.getPages().size()) + .isEqualTo(application3.getPublishedPages().size()); + + // Verify the pages after merging the template + pageList.forEach(newPage -> { + assertThat(newPage.getUnpublishedPage().getName()).containsAnyOf("Page1", "Page12", "Page2"); + assertThat(newPage.getGitSyncId()).isNotNull(); + }); + + NewPage page = pageList.stream() + .filter(newPage -> + newPage.getUnpublishedPage().getName().equals("Page12")) + .collect(Collectors.toList()) + .get(0); + // Verify the actions after merging the template + actionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedAction().getName()) + .containsAnyOf("api_wo_auth", "get_users", "run"); + assertThat(newAction.getUnpublishedAction().getPageId()).isEqualTo(page.getId()); + }); + + // Verify the actionCollections after merging the template + actionCollectionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedCollection().getName()) + .containsAnyOf("JSObject1", "JSObject2"); + assertThat(newAction.getUnpublishedCollection().getPageId()) + .isEqualTo(page.getId()); + }); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void mergeApplication_gitConnectedApplicationSelectedSpecificPages_selectedPageAddedSuccessfully() { + // Create application connected to git + Application testApplication = new Application(); + testApplication.setName( + "mergeApplication_gitConnectedApplicationSelectedSpecificPages_selectedPageAddedSuccessfully"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("master"); + gitData.setDefaultBranchName("master"); + testApplication.setGitApplicationMetadata(gitData); + + Application application = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + + // Create branch for the application + testApplication = new Application(); + testApplication.setName( + "mergeApplication_gitConnectedApplicationSelectedSpecificPages_selectedPageAddedSuccessfully1"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData1 = new GitApplicationMetadata(); + gitData1.setBranchName("feature"); + gitData1.setDefaultBranchName("master"); + testApplication.setGitApplicationMetadata(gitData1); + + Application branchApp = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application2 -> { + application2.getGitApplicationMetadata().setDefaultApplicationId(application.getId()); + return applicationService.save(application2); + }) + .block(); + + Mono applicationJson = + createAppJson("test_assets/ImportExportServiceTest/valid-application.json"); + + Application finalApplication = application; + Mono, List, List>> importedApplication = + applicationJson + .flatMap(applicationJson1 -> importService.mergeArtifactExchangeJsonWithImportableArtifact( + workspaceId, branchApp.getId(), "feature", applicationJson1, List.of("Page1"))) + .map(importableArtifact -> (Application) importableArtifact) + .flatMap(application2 -> { + Mono> pageList = newPageService + .findNewPagesByApplicationId(branchApp.getId(), MANAGE_PAGES) + .collectList(); + Mono> actionList = newActionService + .findAllByApplicationIdAndViewMode(branchApp.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + Mono> actionCollectionList = actionCollectionService + .findAllByApplicationIdAndViewMode(branchApp.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + return Mono.zip(Mono.just(application2), pageList, actionList, actionCollectionList); + }); + + StepVerifier.create(importedApplication) + .assertNext(tuple -> { + Application application3 = tuple.getT1(); + List pageList = tuple.getT2(); + List actionList = tuple.getT3(); + List actionCollectionList = tuple.getT4(); + + assertThat(application3.getId()).isNotEqualTo(finalApplication.getId()); + assertThat(finalApplication.getPages().size()) + .isLessThan(application3.getPages().size()); + assertThat(finalApplication.getPages().size()) + .isEqualTo(application3.getPublishedPages().size()); + + // Verify the pages after merging the template + pageList.forEach(newPage -> { + assertThat(newPage.getUnpublishedPage().getName()).containsAnyOf("Page1", "Page12"); + assertThat(newPage.getGitSyncId()).isNotNull(); + }); + + NewPage page = pageList.stream() + .filter(newPage -> + newPage.getUnpublishedPage().getName().equals("Page12")) + .collect(Collectors.toList()) + .get(0); + // Verify the actions after merging the template + actionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedAction().getName()) + .containsAnyOf("api_wo_auth", "get_users", "run"); + assertThat(newAction.getUnpublishedAction().getPageId()).isEqualTo(page.getId()); + }); + + // Verify the actionCollections after merging the template + actionCollectionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedCollection().getName()) + .containsAnyOf("JSObject1", "JSObject2"); + assertThat(newAction.getUnpublishedCollection().getPageId()) + .isEqualTo(page.getId()); + }); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void mergeApplication_gitConnectedApplicationSelectedAllPages_selectedPageAddedSuccessfully() { + // Create application connected to git + Application testApplication = new Application(); + testApplication.setName( + "mergeApplication_gitConnectedApplicationSelectedAllPages_selectedPageAddedSuccessfully"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("master"); + gitData.setDefaultBranchName("master"); + testApplication.setGitApplicationMetadata(gitData); + + Application application = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + + // Create branch for the application + testApplication = new Application(); + testApplication.setName( + "mergeApplication_gitConnectedApplicationSelectedAllPages_selectedPageAddedSuccessfully1"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData1 = new GitApplicationMetadata(); + gitData1.setBranchName("feature"); + gitData1.setDefaultBranchName("master"); + testApplication.setGitApplicationMetadata(gitData1); + + Application branchApp = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application2 -> { + application2.getGitApplicationMetadata().setDefaultApplicationId(application.getId()); + return applicationService.save(application2); + }) + .block(); + + Mono applicationJson = + createAppJson("test_assets/ImportExportServiceTest/valid-application.json"); + + Application finalApplication = application; + Mono, List, List>> importedApplication = + applicationJson + .flatMap(applicationJson1 -> importService.mergeArtifactExchangeJsonWithImportableArtifact( + workspaceId, branchApp.getId(), "feature", applicationJson1, List.of("Page1", "Page2"))) + .map(importableArtifact -> (Application) importableArtifact) + .flatMap(application2 -> { + Mono> pageList = newPageService + .findNewPagesByApplicationId(branchApp.getId(), MANAGE_PAGES) + .collectList(); + Mono> actionList = newActionService + .findAllByApplicationIdAndViewMode(branchApp.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + Mono> actionCollectionList = actionCollectionService + .findAllByApplicationIdAndViewMode(branchApp.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + return Mono.zip(Mono.just(application2), pageList, actionList, actionCollectionList); + }); + + StepVerifier.create(importedApplication) + .assertNext(tuple -> { + Application application3 = tuple.getT1(); + List pageList = tuple.getT2(); + List actionList = tuple.getT3(); + List actionCollectionList = tuple.getT4(); + + assertThat(application3.getId()).isNotEqualTo(finalApplication.getId()); + assertThat(finalApplication.getPages().size()) + .isLessThan(application3.getPages().size()); + assertThat(finalApplication.getPages().size()) + .isEqualTo(application3.getPublishedPages().size()); + + // Verify the pages after merging the template + pageList.forEach(newPage -> { + assertThat(newPage.getUnpublishedPage().getName()).containsAnyOf("Page1", "Page12", "Page2"); + assertThat(newPage.getGitSyncId()).isNotNull(); + }); + + NewPage page = pageList.stream() + .filter(newPage -> + newPage.getUnpublishedPage().getName().equals("Page12")) + .collect(Collectors.toList()) + .get(0); + // Verify the actions after merging the template + actionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedAction().getName()) + .containsAnyOf("api_wo_auth", "get_users", "run"); + assertThat(newAction.getUnpublishedAction().getPageId()).isEqualTo(page.getId()); + }); + + // Verify the actionCollections after merging the template + actionCollectionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedCollection().getName()) + .containsAnyOf("JSObject1", "JSObject2"); + assertThat(newAction.getUnpublishedCollection().getPageId()) + .isEqualTo(page.getId()); + }); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void mergeApplication_nonGitConnectedApplicationSelectedSpecificPages_selectedPageAddedSuccessfully() { + // Create application + Application application = new Application(); + application.setName( + "mergeApplication_nonGitConnectedApplicationSelectedSpecificPages_selectedPageAddedSuccessfully"); + application.setWorkspaceId(workspaceId); + application = applicationPageService.createApplication(application).block(); + + Mono applicationJson = + createAppJson("test_assets/ImportExportServiceTest/valid-application.json"); + + Application finalApplication = application; + Mono, List, List>> importedApplication = + applicationJson + .flatMap(applicationJson1 -> importService.mergeArtifactExchangeJsonWithImportableArtifact( + workspaceId, finalApplication.getId(), null, applicationJson1, List.of("Page1"))) + .map(importableArtifact -> (Application) importableArtifact) + .flatMap(application1 -> { + Mono> pageList = newPageService + .findNewPagesByApplicationId(application1.getId(), MANAGE_PAGES) + .collectList(); + Mono> actionList = newActionService + .findAllByApplicationIdAndViewMode( + application1.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + Mono> actionCollectionList = actionCollectionService + .findAllByApplicationIdAndViewMode( + application1.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + return Mono.zip(Mono.just(application1), pageList, actionList, actionCollectionList); + }); + + StepVerifier.create(importedApplication) + .assertNext(tuple -> { + Application application1 = tuple.getT1(); + List pageList = tuple.getT2(); + List actionList = tuple.getT3(); + List actionCollectionList = tuple.getT4(); + + assertThat(application1.getId()).isEqualTo(finalApplication.getId()); + assertThat(finalApplication.getPages().size()) + .isLessThan(application1.getPages().size()); + assertThat(finalApplication.getPages().size()) + .isEqualTo(application1.getPublishedPages().size()); + + // Verify the pages after merging the template + pageList.forEach(newPage -> { + assertThat(newPage.getUnpublishedPage().getName()).containsAnyOf("Page1", "Page12"); + assertThat(newPage.getGitSyncId()).isNotNull(); + }); + + NewPage page = pageList.stream() + .filter(newPage -> + newPage.getUnpublishedPage().getName().equals("Page12")) + .collect(Collectors.toList()) + .get(0); + // Verify the actions after merging the template + actionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedAction().getName()) + .containsAnyOf("api_wo_auth", "get_users", "run"); + assertThat(newAction.getUnpublishedAction().getPageId()).isEqualTo(page.getId()); + }); + + // Verify the actionCollections after merging the template + actionCollectionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedCollection().getName()) + .containsAnyOf("JSObject1", "JSObject2"); + assertThat(newAction.getUnpublishedCollection().getPageId()) + .isEqualTo(page.getId()); + }); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void mergeApplication_nonGitConnectedApplicationSelectedAllPages_selectedPageAddedSuccessfully() { + // Create application + Application application = new Application(); + application.setName( + "mergeApplication_nonGitConnectedApplicationSelectedAllPages_selectedPageAddedSuccessfully"); + application.setWorkspaceId(workspaceId); + application = applicationPageService.createApplication(application).block(); + + Mono applicationJson = + createAppJson("test_assets/ImportExportServiceTest/valid-application.json"); + + Application finalApplication = application; + Mono, List, List>> importedApplication = + applicationJson + .flatMap(applicationJson1 -> importService.mergeArtifactExchangeJsonWithImportableArtifact( + workspaceId, + finalApplication.getId(), + null, + applicationJson1, + List.of("Page1", "Page2"))) + .map(importableArtifact -> (Application) importableArtifact) + .flatMap(application1 -> { + Mono> pageList = newPageService + .findNewPagesByApplicationId(application1.getId(), MANAGE_PAGES) + .collectList(); + Mono> actionList = newActionService + .findAllByApplicationIdAndViewMode( + application1.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + Mono> actionCollectionList = actionCollectionService + .findAllByApplicationIdAndViewMode( + application1.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + return Mono.zip(Mono.just(application1), pageList, actionList, actionCollectionList); + }); + + StepVerifier.create(importedApplication) + .assertNext(tuple -> { + Application application1 = tuple.getT1(); + List pageList = tuple.getT2(); + List actionList = tuple.getT3(); + List actionCollectionList = tuple.getT4(); + + assertThat(application1.getId()).isEqualTo(finalApplication.getId()); + assertThat(finalApplication.getPages().size()) + .isLessThan(application1.getPages().size()); + assertThat(finalApplication.getPages().size()) + .isEqualTo(application1.getPublishedPages().size()); + + // Verify the pages after merging the template + pageList.forEach(newPage -> { + assertThat(newPage.getUnpublishedPage().getName()).containsAnyOf("Page1", "Page12", "Page2"); + assertThat(newPage.getGitSyncId()).isNotNull(); + }); + + NewPage page = pageList.stream() + .filter(newPage -> + newPage.getUnpublishedPage().getName().equals("Page12")) + .collect(Collectors.toList()) + .get(0); + // Verify the actions after merging the template + actionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedAction().getName()) + .containsAnyOf("api_wo_auth", "get_users", "run"); + assertThat(newAction.getUnpublishedAction().getPageId()).isEqualTo(page.getId()); + }); + + // Verify the actionCollections after merging the template + actionCollectionList.forEach(newAction -> { + assertThat(newAction.getUnpublishedCollection().getName()) + .containsAnyOf("JSObject1", "JSObject2"); + assertThat(newAction.getUnpublishedCollection().getPageId()) + .isEqualTo(page.getId()); + }); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importApplication_invalidJson_createdAppIsDeleted() { + FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/invalid-json-without-pages.json"); + + List applicationList = applicationService + .findAllApplicationsByWorkspaceId(workspaceId) + .collectList() + .block(); + + Mono resultMono = importService + .extractArtifactExchangeJsonAndSaveArtifact(filePart, workspaceId, null, ArtifactJsonType.APPLICATION) + .map(artifactImportDTO -> (ApplicationImportDTO) artifactImportDTO); + + StepVerifier.create(resultMono) + .expectErrorMatches(throwable -> throwable instanceof AppsmithException + && throwable + .getMessage() + .equals(AppsmithError.VALIDATION_FAILURE.getMessage( + "Field '" + FieldName.PAGE_LIST + "' is missing in the JSON."))) + .verify(); + + // Verify that the app card is not created + StepVerifier.create(applicationService + .findAllApplicationsByWorkspaceId(workspaceId) + .collectList()) + .assertNext(applications -> { + assertThat(applicationList.size()).isEqualTo(applications.size()); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplication_WithBearerTokenAndExportWithConfig_exportedWithDecryptedFields() { + String randomUUID = UUID.randomUUID().toString(); + + Workspace testWorkspace = new Workspace(); + testWorkspace.setName("workspace-" + randomUUID); + Workspace workspace = workspaceService.create(testWorkspace).block(); + + Application testApplication = new Application(); + testApplication.setName("application-" + randomUUID); + testApplication.setExportWithConfiguration(true); + testApplication.setWorkspaceId(workspace.getId()); + + Mono applicationMono = applicationPageService + .createApplication(testApplication) + .flatMap(application -> { + ApplicationAccessDTO accessDTO = new ApplicationAccessDTO(); + accessDTO.setPublicAccess(true); + return applicationService + .changeViewAccess(application.getId(), accessDTO) + .thenReturn(application); + }); + + Mono datasourceMono = workspaceService + .getDefaultEnvironmentId(workspace.getId(), environmentPermission.getExecutePermission()) + .zipWith(pluginRepository.findByPackageName("restapi-plugin")) + .flatMap(objects -> { + String defaultEnvironmentId = objects.getT1(); + Plugin plugin = objects.getT2(); + + Datasource datasource = new Datasource(); + datasource.setPluginId(plugin.getId()); + datasource.setName("RestAPIWithBearerToken"); + datasource.setWorkspaceId(workspace.getId()); + datasource.setIsConfigured(true); + + BearerTokenAuth bearerTokenAuth = new BearerTokenAuth(); + bearerTokenAuth.setBearerToken("token_" + randomUUID); + + SSLDetails sslDetails = new SSLDetails(); + sslDetails.setAuthType(SSLDetails.AuthType.DEFAULT); + Connection connection = new Connection(); + connection.setSsl(sslDetails); + + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setAuthentication(bearerTokenAuth); + datasourceConfiguration.setConnection(connection); + datasourceConfiguration.setConnection(new Connection()); + datasourceConfiguration.setUrl("https://mock-api.appsmith.com"); + + HashMap storages = new HashMap<>(); + storages.put( + defaultEnvironmentId, + new DatasourceStorageDTO(null, defaultEnvironmentId, datasourceConfiguration)); + datasource.setDatasourceStorages(storages); + + return datasourceService.create(datasource); + }); + + Mono exportAppMono = Mono.zip(applicationMono, datasourceMono) + .flatMap(objects -> { + ApplicationPage applicationPage = objects.getT1().getPages().get(0); + ActionDTO action = new ActionDTO(); + action.setName("validAction"); + action.setPageId(applicationPage.getId()); + action.setPluginId(objects.getT2().getPluginId()); + action.setDatasource(objects.getT2()); + + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + actionConfiguration.setPath("/test/path"); + action.setActionConfiguration(actionConfiguration); + + return layoutActionService + .createSingleAction(action, Boolean.FALSE) + .then(exportApplicationService.exportApplicationById( + objects.getT1().getId(), "")); + }); + + StepVerifier.create(exportAppMono) + .assertNext(applicationJson -> { + assertThat(applicationJson.getDecryptedFields()).isNotEmpty(); + DecryptedSensitiveFields fields = + applicationJson.getDecryptedFields().get("RestAPIWithBearerToken"); + assertThat(fields.getBearerTokenAuth().getBearerToken()).isEqualTo("token_" + randomUUID); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplicationTest_WithNavigationSettings() { + + Application application = new Application(); + application.setName("exportNavigationSettingsApplicationTest"); + Application.NavigationSetting navSetting = new Application.NavigationSetting(); + navSetting.setOrientation("top"); + application.setUnpublishedApplicationDetail(new ApplicationDetail()); + application.getUnpublishedApplicationDetail().setNavigationSetting(navSetting); + Application createdApplication = applicationPageService + .createApplication(application, workspaceId) + .block(); + + Mono resultMono = + exportApplicationService.exportApplicationById(createdApplication.getId(), ""); + + StepVerifier.create(resultMono) + .assertNext(applicationJson -> { + Application exportedApplication = applicationJson.getExportedApplication(); + assertThat(exportedApplication).isNotNull(); + assertThat(exportedApplication + .getUnpublishedApplicationDetail() + .getNavigationSetting()) + .isNotNull(); + assertThat(exportedApplication + .getUnpublishedApplicationDetail() + .getNavigationSetting() + .getOrientation()) + .isEqualTo("top"); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplication_WithPageIcon_ValidPageIcon() { + String randomId = UUID.randomUUID().toString(); + Application application = new Application(); + application.setName("exportPageIconApplicationTest"); + Application createdApplication = applicationPageService + .createApplication(application, workspaceId) + .block(); + + PageDTO pageDTO = new PageDTO(); + pageDTO.setName("page_" + randomId); + pageDTO.setIcon("flight"); + pageDTO.setApplicationId(createdApplication.getId()); + + PageDTO applicationPageDTO = applicationPageService.createPage(pageDTO).block(); + + Mono resultMono = + exportApplicationService.exportApplicationById(applicationPageDTO.getApplicationId(), ""); + + StepVerifier.create(resultMono) + .assertNext(applicationJson -> { + List pages = applicationJson.getPageList(); + assertThat(pages.size()).isEqualTo(2); + assertThat(pages.get(1).getUnpublishedPage().getName()).isEqualTo("page_" + randomId); + assertThat(pages.get(1).getUnpublishedPage().getIcon()).isEqualTo("flight"); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importApplication_existingApplication_ApplicationReplacedWithImportedOne() { + String randomUUID = UUID.randomUUID().toString(); + Mono applicationJson = + createAppJson("test_assets/ImportExportServiceTest/valid-application.json"); + + // Create the initial application + Application application = new Application(); + application.setName("Application_" + randomUUID); + application.setWorkspaceId(workspaceId); + + Mono, List, List>> importedApplication = + applicationPageService + .createApplication(application) + .flatMap(createdApp -> { + PageDTO pageDTO = new PageDTO(); + pageDTO.setApplicationId(application.getId()); + pageDTO.setName("Home Page"); + return applicationPageService.createPage(pageDTO).thenReturn(createdApp); + }) + .zipWith(applicationJson) + .flatMap(objects -> importService + .restoreSnapshot( + workspaceId, + objects.getT2(), + objects.getT1().getId(), + null) + .map(importableArtifact -> (Application) importableArtifact) + .zipWith(Mono.just(objects.getT1()))) + .flatMap(objects -> { + Application newApp = objects.getT1(); + Application oldApp = objects.getT2(); + // after import, application id should not change + assert Objects.equals(newApp.getId(), oldApp.getId()); + + Mono> pageList = newPageService + .findNewPagesByApplicationId(newApp.getId(), MANAGE_PAGES) + .collectList(); + Mono> actionList = newActionService + .findAllByApplicationIdAndViewMode(newApp.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + Mono> actionCollectionList = actionCollectionService + .findAllByApplicationIdAndViewMode(newApp.getId(), false, MANAGE_ACTIONS, null) + .collectList(); + return Mono.zip(Mono.just(newApp), pageList, actionList, actionCollectionList); + }); + + StepVerifier.create(importedApplication) + .assertNext(tuple -> { + List pageList = tuple.getT2(); + List actionList = tuple.getT3(); + List actionCollectionList = tuple.getT4(); + + assertThat(pageList.size()).isEqualTo(2); + assertThat(actionList.size()).isEqualTo(3); + + List pageNames = pageList.stream() + .map(p -> p.getUnpublishedPage().getName()) + .collect(Collectors.toList()); + + List actionNames = actionList.stream() + .map(p -> p.getUnpublishedAction().getName()) + .collect(Collectors.toList()); + + List actionCollectionNames = actionCollectionList.stream() + .map(p -> p.getUnpublishedCollection().getName()) + .collect(Collectors.toList()); + + // Verify the pages after importing the application + assertThat(pageNames).contains("Page1", "Page2"); + + // Verify the actions after importing the application + assertThat(actionNames).contains("api_wo_auth", "get_users", "run"); + + // Verify the actionCollections after importing the application + assertThat(actionCollectionNames).contains("JSObject1", "JSObject2"); + }) + .verifyComplete(); + } + + /** + * Testcase for updating the existing application: + * 1. Import application in org + * 2. Add new page to the imported application + * 3. User tries to import application from same application json file + * 4. Added page will be removed + *

+ * We don't have to test all the flows for other resources like actions, JSObjects, themes as these are already + * covered as a part of discard functionality + */ + @Test + @WithUserDetails(value = "api_user") + public void extractFileAndUpdateApplication_addNewPageAfterImport_addedPageRemoved() { + + /* + 1. Import application + 2. Add single page to imported app + 3. Import the application from same JSON with applicationId + 4. Added page should be deleted from DB + */ + + FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application.json"); + String workspaceId = createTemplateWorkspace().getId(); + final Mono resultMonoWithoutDiscardOperation = importService + .extractArtifactExchangeJsonAndSaveArtifact(filePart, workspaceId, null, ArtifactJsonType.APPLICATION) + .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO) + .flatMap(applicationImportDTO -> { + PageDTO page = new PageDTO(); + page.setName("discard-page-test"); + page.setApplicationId(applicationImportDTO.getApplication().getId()); + return applicationPageService.createPage(page); + }) + .flatMap(page -> applicationRepository.findById(page.getApplicationId())) + .cache(); + + StepVerifier.create(resultMonoWithoutDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + newPageService + .findByApplicationId(application.getId(), MANAGE_PAGES, false) + .collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List pageList = tuple.getT2(); + + assertThat(application.getName()).isEqualTo("valid_application"); + assertThat(application.getWorkspaceId()).isNotNull(); + assertThat(application.getPages()).hasSize(3); + assertThat(application.getPublishedPages()).hasSize(1); + assertThat(application.getModifiedBy()).isEqualTo("api_user"); + assertThat(application.getUpdatedAt()).isNotNull(); + assertThat(application.getEditModeThemeId()).isNotNull(); + assertThat(application.getPublishedModeThemeId()).isNotNull(); + + assertThat(pageList).hasSize(3); + + ApplicationPage defaultAppPage = application.getPages().stream() + .filter(ApplicationPage::getIsDefault) + .findFirst() + .orElse(null); + assertThat(defaultAppPage).isNotNull(); + + PageDTO defaultPageDTO = pageList.stream() + .filter(pageDTO -> pageDTO.getId().equals(defaultAppPage.getId())) + .findFirst() + .orElse(null); + + assertThat(defaultPageDTO).isNotNull(); + assertThat(defaultPageDTO.getLayouts().get(0).getLayoutOnLoadActions()) + .isNotEmpty(); + + List pageNames = new ArrayList<>(); + pageList.forEach(page -> pageNames.add(page.getName())); + assertThat(pageNames).contains("discard-page-test"); + }) + .verifyComplete(); + + // Import the same application again to find if the added page is deleted + final Mono resultMonoWithDiscardOperation = resultMonoWithoutDiscardOperation + .flatMap(importedApplication -> applicationService.save(importedApplication)) + .flatMap(savedApplication -> importService.extractArtifactExchangeJsonAndSaveArtifact( + filePart, workspaceId, savedApplication.getId(), ArtifactJsonType.APPLICATION)) + .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO) + .map(ApplicationImportDTO::getApplication); + + StepVerifier.create(resultMonoWithDiscardOperation.flatMap(application -> Mono.zip( + Mono.just(application), + newPageService + .findByApplicationId(application.getId(), MANAGE_PAGES, false) + .collectList()))) + .assertNext(tuple -> { + final Application application = tuple.getT1(); + final List pageList = tuple.getT2(); + + assertThat(application.getPages()).hasSize(2); + assertThat(application.getPublishedPages()).hasSize(1); + + assertThat(pageList).hasSize(2); + + List pageNames = new ArrayList<>(); + pageList.forEach(page -> pageNames.add(page.getName())); + assertThat(pageNames).doesNotContain("discard-page-test"); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void extractFileAndUpdateExistingApplication_gitConnectedApplication_throwUnsupportedOperationException() { + + /* + 1. Create application and mock git connectivity + 2. Import the application from valid JSON with saved applicationId + 3. Unsupported operation exception should be thrown + */ + + // Create application connected to git + Application testApplication = new Application(); + testApplication.setName( + "extractFileAndUpdateExistingApplication_gitConnectedApplication_throwUnsupportedOperationException"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setRemoteUrl("git@example.com:username/git-repo.git"); + testApplication.setGitApplicationMetadata(gitData); + Application application = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + + FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application.json"); + final Mono resultMono = importService + .extractArtifactExchangeJsonAndSaveArtifact( + filePart, workspaceId, application.getId(), ArtifactJsonType.APPLICATION) + .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); + + StepVerifier.create(resultMono) + .expectErrorMatches(throwable -> throwable instanceof AppsmithException + && throwable + .getMessage() + .equals( + AppsmithError.UNSUPPORTED_IMPORT_OPERATION_FOR_GIT_CONNECTED_APPLICATION + .getMessage())) + .verify(); + } + + @Test + @WithUserDetails(value = "api_user") + public void createExportAppJsonWithCustomJSLibTest() { + CustomJSLib jsLib = new CustomJSLib("TestLib", Set.of("accessor1"), "url", "docsUrl", "1.0", "defs_string"); + Mono addJSLibMonoCached = customJSLibService + .addJSLibsToContext(testAppId, CreatorContextType.APPLICATION, Set.of(jsLib), null, false) + .flatMap(isJSLibAdded -> + Mono.zip(Mono.just(isJSLibAdded), applicationPageService.publish(testAppId, true))) + .map(tuple2 -> { + Boolean isJSLibAdded = tuple2.getT1(); + Application application = tuple2.getT2(); + return isJSLibAdded; + }) + .cache(); + Mono getExportedAppMono = + addJSLibMonoCached.then(exportApplicationService.exportApplicationById(testAppId, "")); + StepVerifier.create(Mono.zip(addJSLibMonoCached, getExportedAppMono)) + .assertNext(tuple2 -> { + Boolean isJSLibAdded = tuple2.getT1(); + assertEquals(true, isJSLibAdded); + ApplicationJson exportedAppJson = tuple2.getT2(); + assertEquals(1, exportedAppJson.getCustomJSLibList().size()); + CustomJSLib exportedJSLib = + exportedAppJson.getCustomJSLibList().get(0); + assertEquals(jsLib.getName(), exportedJSLib.getName()); + assertEquals(jsLib.getAccessor(), exportedJSLib.getAccessor()); + assertEquals(jsLib.getUrl(), exportedJSLib.getUrl()); + assertEquals(jsLib.getDocsUrl(), exportedJSLib.getDocsUrl()); + assertEquals(jsLib.getVersion(), exportedJSLib.getVersion()); + assertEquals(jsLib.getDefs(), exportedJSLib.getDefs()); + assertEquals( + getDTOFromCustomJSLib(jsLib), + exportedAppJson + .getExportedApplication() + .getUnpublishedCustomJSLibs() + .toArray()[0]); + assertEquals( + 1, + exportedAppJson + .getExportedApplication() + .getUnpublishedCustomJSLibs() + .size()); + assertEquals( + 0, + exportedAppJson + .getExportedApplication() + .getPublishedCustomJSLibs() + .size()); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void extractFileAndUpdateExistingApplication_existingApplication_applicationNameAndSlugRemainsUnchanged() { + + /* + 1. Create application + 2. Import the application from valid JSON with saved applicationId + 3. Name and slug will not be updated by the incoming changes from the json file + */ + + Application testApplication = new Application(); + final String appName = UUID.randomUUID().toString(); + testApplication.setName(appName); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + Application application = applicationPageService + .createApplication(testApplication, workspaceId) + .block(); + + FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application.json"); + final Mono resultMono = importService + .extractArtifactExchangeJsonAndSaveArtifact( + filePart, workspaceId, application.getId(), ArtifactJsonType.APPLICATION) + .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); + + StepVerifier.create(resultMono) + .assertNext(applicationImportDTO -> { + Application application1 = applicationImportDTO.getApplication(); + assertThat(application1.getName()).isEqualTo(appName); + assertThat(application1.getSlug()).isEqualTo(appName); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void mergeApplicationJsonWithApplication_WhenNoPermissionToCreatePage_Fails() { + Application testApplication = new Application(); + final String appName = UUID.randomUUID().toString(); + testApplication.setName(appName); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + + Mono applicationImportDTOMono = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application -> { + // remove page create permission from this application for current user + application.getPolicies().removeIf(policy -> policy.getPermission() + .equals(applicationPermission + .getPageCreatePermission() + .getValue())); + return applicationRepository.save(application); + }) + .flatMap(application -> { + FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application.json"); + return importService + .extractArtifactExchangeJson(filePart, ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) + .flatMap(applicationJson -> importService.mergeArtifactExchangeJsonWithImportableArtifact( + workspaceId, application.getId(), null, applicationJson, null)) + .map(importableArtifact -> (Application) importableArtifact); + }); + + StepVerifier.create(applicationImportDTOMono) + .expectError(AppsmithException.class) + .verify(); + } + + @Test + @WithUserDetails(value = "api_user") + public void extractFileAndUpdateNonGitConnectedApplication_WhenNoPermissionToCreatePage_Fails() { + Application testApplication = new Application(); + final String appName = UUID.randomUUID().toString(); + testApplication.setName(appName); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + + Mono applicationImportDTOMono = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application -> { + // remove page create permission from this application for current user + application.getPolicies().removeIf(policy -> policy.getPermission() + .equals(applicationPermission + .getPageCreatePermission() + .getValue())); + return applicationRepository.save(application); + }) + .flatMap(application -> { + FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application.json"); + return importService + .extractArtifactExchangeJsonAndSaveArtifact( + filePart, workspaceId, application.getId(), ArtifactJsonType.APPLICATION) + .map(artifactImportDTO -> (ApplicationImportDTO) artifactImportDTO); + }); + + StepVerifier.create(applicationImportDTOMono) + .expectError(AppsmithException.class) + .verify(); + } + + private Mono createActionToPage(String actionName, String pageId) { + ActionDTO action = new ActionDTO(); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasourceMap.get("DS1")); + action.setName(actionName); + action.setPageId(pageId); + return layoutActionService.createAction(action); + } + + private Mono createActionCollectionToPage(Application application, int pageIndex) { + ActionCollectionDTO actionCollectionDTO1 = new ActionCollectionDTO(); + actionCollectionDTO1.setName("TestJsObject"); + actionCollectionDTO1.setPageId(application.getPages().get(pageIndex).getId()); + actionCollectionDTO1.setApplicationId(application.getId()); + actionCollectionDTO1.setWorkspaceId(application.getWorkspaceId()); + actionCollectionDTO1.setPluginId(jsDatasource.getPluginId()); + ActionDTO action1 = new ActionDTO(); + action1.setName("testMethod"); + action1.setActionConfiguration(new ActionConfiguration()); + action1.getActionConfiguration().setBody("mockBody"); + actionCollectionDTO1.setActions(List.of(action1)); + actionCollectionDTO1.setPluginType(PluginType.JS); + return layoutCollectionService.createCollection(actionCollectionDTO1, null); + } + + @Test + @WithUserDetails("api_user") + public void exportApplicationByWhen_WhenGitConnectedAndPageRenamed_QueriesAreInUpdatedResources() { + String renamedPageName = "Renamed Page"; + // create an application + Application testApplication = new Application(); + final String appName = UUID.randomUUID().toString(); + testApplication.setName(appName); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setClientSchemaVersion(JsonSchemaVersions.clientVersion); + testApplication.setServerSchemaVersion(JsonSchemaVersions.serverVersion); + + Mono applicationJsonMono = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application -> { + // add another page to the application + PageDTO pageDTO = new PageDTO(); + pageDTO.setName("second_page"); + pageDTO.setApplicationId(application.getId()); + return applicationPageService + .createPage(pageDTO) // get the updated application + .then(applicationService.findById(application.getId())); + }) + .flatMap(application -> { + assert application.getPages().size() == 2; + // add one action to each of the pages + return createActionToPage( + "first_page_action", + application.getPages().get(0).getId()) + .then(createActionToPage( + "second_page_action", + application.getPages().get(1).getId())) + .thenReturn(application); + }) + .flatMap(application -> { + // add one action collection to each of the pages + return createActionCollectionToPage(application, 0) + .then(createActionCollectionToPage(application, 1)) + .thenReturn(application); + }) + .flatMap(application -> { + // set git meta data for the application and set a last commit date + GitApplicationMetadata gitApplicationMetadata = new GitApplicationMetadata(); + // add buffer of 5 seconds so that the last commit date is definitely after the last updated date + gitApplicationMetadata.setLastCommittedAt(Instant.now()); + application.setGitApplicationMetadata(gitApplicationMetadata); + return applicationRepository.save(application); + }) + .delayElement(Duration.ofMillis( + 100)) // to make sure the last commit date is definitely after the last updated date + .flatMap(application -> { + // rename the page + ApplicationPage applicationPage = application.getPages().get(0); + PageDTO pageDTO = new PageDTO(); + pageDTO.setName(renamedPageName); + return newPageService + .updatePage(applicationPage.getId(), pageDTO) + // export the application + .then(exportApplicationService.exportApplicationById( + application.getId(), SerialiseApplicationObjective.VERSION_CONTROL)); + }); + + // verify that the exported json has the updated page name, and the queries are in the updated resources + StepVerifier.create(applicationJsonMono) + .assertNext(applicationJson -> { + Map> updatedResources = applicationJson.getUpdatedResources(); + assertThat(updatedResources).isNotNull(); + Set updatedPageNames = updatedResources.get(FieldName.PAGE_LIST); + Set updatedActionNames = updatedResources.get(FieldName.ACTION_LIST); + Set updatedActionCollectionNames = updatedResources.get(FieldName.ACTION_COLLECTION_LIST); + + assertThat(updatedPageNames).isNotNull(); + assertThat(updatedActionNames).isNotNull(); + assertThat(updatedActionCollectionNames).isNotNull(); + + // only the first page should be present in the updated resources + assertThat(updatedPageNames.size()).isEqualTo(1); + assertThat(updatedPageNames).contains(renamedPageName); + + // only actions from first page should be present in the updated resources + // 1 query + 1 method from action collection + assertThat(updatedActionNames.size()).isEqualTo(2); + assertThat(updatedActionNames).contains("first_page_action" + NAME_SEPARATOR + renamedPageName); + assertThat(updatedActionNames) + .contains("TestJsObject.testMethod" + NAME_SEPARATOR + renamedPageName); + + // only action collections from first page should be present in the updated resources + assertThat(updatedActionCollectionNames.size()).isEqualTo(1); + assertThat(updatedActionCollectionNames) + .contains("TestJsObject" + NAME_SEPARATOR + renamedPageName); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails("api_user") + public void exportApplicationByWhen_WhenGitConnectedAndDatasourceRenamed_QueriesAreInUpdatedResources() { + // create an application + Application testApplication = new Application(); + final String appName = UUID.randomUUID().toString(); + testApplication.setName(appName); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setClientSchemaVersion(JsonSchemaVersions.clientVersion); + testApplication.setServerSchemaVersion(JsonSchemaVersions.serverVersion); + + Mono applicationJsonMono = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application -> { + // add a datasource to the workspace + Datasource ds1 = new Datasource(); + ds1.setName("DS_FOR_RENAME_TEST"); + ds1.setWorkspaceId(workspaceId); + ds1.setPluginId(installedPlugin.getId()); + final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setUrl("http://example.org/get"); + datasourceConfiguration.setHeaders(List.of(new Property("X-Answer", "42"))); + + HashMap storages1 = new HashMap<>(); + storages1.put( + defaultEnvironmentId, + new DatasourceStorageDTO(null, defaultEnvironmentId, datasourceConfiguration)); + ds1.setDatasourceStorages(storages1); + return datasourceService.create(ds1).zipWith(Mono.just(application)); + }) + .flatMap(objects -> { + Datasource datasource = objects.getT1(); + Application application = objects.getT2(); + + // create an action with the datasource + ActionDTO action = new ActionDTO(); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasource); + action.setName("MyAction"); + action.setPageId(application.getPages().get(0).getId()); + return layoutActionService.createAction(action).thenReturn(objects); + }) + .flatMap(objects -> { + Application application = objects.getT2(); + // set git meta data for the application and set a last commit date + GitApplicationMetadata gitApplicationMetadata = new GitApplicationMetadata(); + // add buffer of 5 seconds so that the last commit date is definitely after the last updated date + gitApplicationMetadata.setLastCommittedAt(Instant.now()); + application.setGitApplicationMetadata(gitApplicationMetadata); + return applicationRepository.save(application).thenReturn(objects); + }) + .delayElement(Duration.ofMillis( + 100)) // to make sure the last commit date is definitely after the last updated date + .flatMap(objects -> { + // rename the datasource + Datasource datasource = objects.getT1(); + Application application = objects.getT2(); + datasource.setName("DS_FOR_RENAME_TEST_RENAMED"); + return datasourceService + .save(datasource) + .then(exportApplicationService.exportApplicationById( + application.getId(), SerialiseApplicationObjective.VERSION_CONTROL)); + }); + + // verify that the exported json has the updated page name, and the queries are in the updated resources + StepVerifier.create(applicationJsonMono) + .assertNext(applicationJson -> { + Map> updatedResources = applicationJson.getUpdatedResources(); + assertThat(updatedResources).isNotNull(); + Set updatedActionNames = updatedResources.get(FieldName.ACTION_LIST); + assertThat(updatedActionNames).isNotNull(); + + // action should be present in the updated resources although action not updated but datasource is + assertThat(updatedActionNames.size()).isEqualTo(1); + updatedActionNames.forEach(actionName -> { + assertThat(actionName).contains("MyAction"); + }); + }) + .verifyComplete(); + } +} From 4639965574fb58948927125a3827d42cc465d326 Mon Sep 17 00:00:00 2001 From: Aishwarya-U-R <91450662+Aishwarya-U-R@users.noreply.github.com> Date: Thu, 18 Jan 2024 21:18:03 +0530 Subject: [PATCH 14/16] test: Cypress | RepoLimitExceededErrorModal_spec.js flaky fix (#30429) ## Description - This PR fixes app/client/cypress/e2e/Regression/ClientSide/Git/GitSync/RepoLimitExceededErrorModal_spec.js flakiness in CI master runs - Multiple master based CI runs done [here](https://github.com/appsmithorg/appsmith-ee/pull/3337) #### Type of change - Script fix (non-breaking change which fixes an issue) ## Testing #### How Has This Been Tested? - [X] Cypress CI runs ## Checklist: #### QA activity: - [X] Added `Test Plan Approved` label after changes were reviewed ## Summary by CodeRabbit - **Refactor** - Improved the sign-up process in the GitSync feature for a smoother user experience. - Enhanced automated tests to align with the updated sign-up workflow. --- .../ClientSide/Git/GitSync/RepoLimitExceededErrorModal_spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/client/cypress/e2e/Regression/ClientSide/Git/GitSync/RepoLimitExceededErrorModal_spec.js b/app/client/cypress/e2e/Regression/ClientSide/Git/GitSync/RepoLimitExceededErrorModal_spec.js index 097c6a5249..7f67821435 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Git/GitSync/RepoLimitExceededErrorModal_spec.js +++ b/app/client/cypress/e2e/Regression/ClientSide/Git/GitSync/RepoLimitExceededErrorModal_spec.js @@ -27,7 +27,8 @@ describe( agHelper.Sleep(2000); // adding wait for app to load homePage.LogOutviaAPI(); cy.generateUUID().then((uid) => { - cy.Signup(`${uid}@appsmithtest.com`, uid); + homePage.SignUp(`${uid}@appsmithtest.com`, uid); + onboarding.closeIntroModal(); }); homePage.NavigateToHome(); homePage.CreateNewApplication(); From d5d6ee48752da60a97807afd7f6538a671f7ae64 Mon Sep 17 00:00:00 2001 From: Rohan Arthur <94514895+rohan-arthur@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:06:20 +0530 Subject: [PATCH 15/16] fix: typo in new query dropdown in datasource preview (#30434) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description > This should read “Create API in”. Similarly for query.. it should read ’Create query in”. ![Screenshot 2024-01-18 at 15 59 19](https://github.com/appsmithorg/appsmith/assets/94514895/d7de342e-1815-4dbe-96ea-56a23cd0ca1e) > > File changed: `app/client/src/pages/Editor/DataSourceEditor/NewActionButton.tsx` > Changed the following line: `{`Create a ${` To: `{`Create ${` Screenshots after making the changes: ![Screenshot 2024-01-18 at 16 45 16](https://github.com/appsmithorg/appsmith/assets/94514895/ecf8b1c5-2441-45c6-b8cf-e7766ba5f8aa) ![Screenshot 2024-01-18 at 16 45 25](https://github.com/appsmithorg/appsmith/assets/94514895/4ef58d3e-bbd3-44ce-bbb2-6a7475c04fda) #### PR fixes following issue(s) Fixes #30433 #### Type of change > Please delete options that are not relevant. - Bug fix (non-breaking change which fixes an issue) ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [x] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed ## Summary by CodeRabbit - **Refactor** - Improved the implementation of the New Action Button for enhanced user experience. --- .../src/pages/Editor/DataSourceEditor/NewActionButton.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/client/src/pages/Editor/DataSourceEditor/NewActionButton.tsx b/app/client/src/pages/Editor/DataSourceEditor/NewActionButton.tsx index 7411db03b2..253a9e513e 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/NewActionButton.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/NewActionButton.tsx @@ -125,10 +125,10 @@ function NewActionButton(props: NewActionButtonProps) { height={pages.length <= 4 ? "fit-content" : "186px"} side={"bottom"} > - {`Create a ${ + {`Create ${ pluginType === PluginType.DB || pluginType === PluginType.SAAS ? "query" - : "api" + : "API" } in`} {pageMenuItems.map((page, i) => { if (page) { From 75ad75c57ccb5e8b21d4f2c49d4932ca281cbf17 Mon Sep 17 00:00:00 2001 From: Rajat Agrawal Date: Fri, 19 Jan 2024 11:38:34 +0530 Subject: [PATCH 16/16] chore: Remove echarts feature flag (#30201) This PR removes the feature flag for ECharts. ECharts feature needs to enabled by default and doesn't need to be behind a feature flag anymore. ## Summary by CodeRabbit - **New Features** - Enhanced the Chart Widget by integrating Custom ECharts as a standard option. - **Bug Fixes** - Addressed the Chart Widget selection issue by marking the deprecated chart type accordingly. - **Refactor** - Streamlined Chart Widget properties and configuration for better usability. - Improved database query performance with new indexes. - **Tests** - Updated end-to-end tests to align with the Chart Widget changes. - **Chores** - Removed obsolete feature flags related to charting functionalities. --- .../Widgets/Chart/Chart_widget_spec_2.ts | 5 ++- .../Widgets/Chart/Custom3DChartSpec.ts | 8 ++--- .../2DCustomECharts.snap.png | Bin 17472 -> 17396 bytes .../3DCustomECharts-2.snap.png | Bin 41544 -> 30383 bytes .../3DCustomECharts.snap.png | Bin 38664 -> 36124 bytes .../FusionCharts.snap.png | Bin 15759 -> 15637 bytes app/client/src/ce/entities/FeatureFlag.ts | 5 --- app/client/src/entities/Widget/utils.test.ts | 7 +--- .../src/widgets/ChartWidget/constants.ts | 7 ---- .../widgets/ChartWidget/widget/index.test.ts | 4 --- .../src/widgets/ChartWidget/widget/index.tsx | 16 ++-------- .../ChartWidget/widget/propertyConfig.test.ts | 7 +--- .../ChartWidget/widget/propertyConfig.ts | 30 +++++------------- 13 files changed, 20 insertions(+), 69 deletions(-) diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Chart/Chart_widget_spec_2.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Chart/Chart_widget_spec_2.ts index ea912be917..e06da685d2 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Chart/Chart_widget_spec_2.ts +++ b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Chart/Chart_widget_spec_2.ts @@ -85,7 +85,10 @@ describe("", { tags: ["@tag.Widget", "@tag.Chart"] }, () => { agHelper.AssertElementAbsence( propPane._selectPropDropdown("x-axis label orientation"), ); - propPane.SelectPropertiesDropDown("Chart Type", "Custom Fusion Charts"); + propPane.SelectPropertiesDropDown( + "Chart Type", + "Custom Fusion Charts (deprecated)", + ); agHelper.AssertElementAbsence( propPane._selectPropDropdown("x-axis label orientation"), ); diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Chart/Custom3DChartSpec.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Chart/Custom3DChartSpec.ts index efd713bdab..c5841d32cc 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Chart/Custom3DChartSpec.ts +++ b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Chart/Custom3DChartSpec.ts @@ -10,9 +10,6 @@ describe( { tags: ["@tag.Widget", "@tag.Chart"] }, function () { it("1. 3D EChart Custom Chart Widget Functionality", function () { - featureFlagIntercept({ - release_custom_echarts_enabled: true, - }); _.agHelper.RefreshPage(); _.entityExplorer.DragDropWidgetNVerify(_.draggableWidgets.CHART); @@ -67,7 +64,10 @@ describe( _.locators._widgetInDeployed(_.draggableWidgets.CHART), ); - _.propPane.SelectPropertiesDropDown("Chart type", "Custom Fusion Charts"); + _.propPane.SelectPropertiesDropDown( + "Chart type", + "Custom Fusion Charts (deprecated)", + ); cy.wait(1000); cy.get(publicWidgetsPage.chartWidget).matchImageSnapshot("FusionCharts"); diff --git a/app/client/cypress/snapshots/Custom3DChartSpec.ts/2DCustomECharts.snap.png b/app/client/cypress/snapshots/Custom3DChartSpec.ts/2DCustomECharts.snap.png index f2051e95356aff9e8141325185610685d9638252..d70c91094b553d866547a4e7240f87a21e37a637 100644 GIT binary patch literal 17396 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 z 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 KdehkH*uMZF+I`0W literal 41544 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 |n1LkHcPZnkkan0!ZTJQNS0lcQ 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 ev0RYl9G%f>Rm@~e<;+|lnnm) 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 RUzg!OO*LKBY!%b1{}0Bo9=8Ah diff --git a/app/client/cypress/snapshots/Custom3DChartSpec.ts/FusionCharts.snap.png b/app/client/cypress/snapshots/Custom3DChartSpec.ts/FusionCharts.snap.png index 01cb6507587aa6f6a0b97e8c2b969352942ec9e2..99ca77a8d0f4737824e36f3f7107fdebb393aa80 100644 GIT binary patch literal 15637 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 lk#0sHyf2`6JD;GRC94Z1_Bx&UR{{fO3@n--4 literal 15759 zcmd731yq&qw=QZD3ew#nEiE0=4Uz)Vh=PQKbf|QLgmkz_5eX5I7HKI7L8M!{8|BQ; z-~aA?#=ifv&pl(@G46H9a=G~8{pOtSoX_*jxx((MD_~zEzjoom1#BfnS*;5fF4Dly z9Sk)1Bq+w?$%PBzdP=fVIvy9-n$XR3t~P9~PYJukr3A)ec6ML%Cl|paxVvop`C1LJ zENcy6tA7pKLGV*!}omY8JnEvmC3Z|g~=qm?>WC3x(Zb+Opi&( z&&{*V%;v4k?M~2}o^`jfa7hiB{w%H}UADnZ`&>$#_WWfg8qvbh=1RoTZ`2!a{;PNW zFGj90kZ$y_Tz2@(>w(3>7Hm$t!YU_alX6YY6FEsMA!LA;h~8d*QdMTiG#s@xMR+z> zV>Uq8`kAGp&GCtJ^@A(fxZD>mV_o)dqD9Uo=|N>NjGIE^SSXgyZ?&6 z)Y-+$n&&Lx{zXMBEXjm&O}Y4tvd!EW8%NhFM|4@jm1(WbNYP&0d5)&3r=u%3BpGZz zt+64^eJXm1fU~u5%UL8rk$pwC3dQ6uRbi+}cvVyKYJimT*E)R;V%2ce-IQRP(Nz|b zDMeaqT#PGah1aktWa(D82P(^IgXVjVk>_N;bcXf#_2jC_uUwUx? z8&!-!C+iyijpNFti)`A*G-!X9e?e^!S2}cs5bre}#uaX?%K_(kJLgvdq`*4T7@8CO z5kY3$irl1(vUzcLXrJRs>j=teVqCeTlfQFL8vFuRI$a6wjiL|4HP8NEzR(+{ft89OdB_badP&KwSGVpDlo*Tlswhiq68gm$l-A1|7?3F` zDMgZrzH)S|ok-_kL-FeBItfuS3fKnsJd_!tBA+dGWZ{cPXHqp=il$00vafZk+Zs@L z`s>?^DK=6p3tp@M#}qLq`ukN53k4q>CQ?}k3kKib<680m@!aHBQh=j+QnJUm0p zY^dR`zOK2p*D|Bkmgu^pjZbJsj9sv^o*j_6jy46LlerG66`NFs*O(2qMMaVdMB+6E zJDHvPtx#ZVuJ)t~$B&O2sopaD_@E<}?vs{wr-Wbn{_4o?=+t?15lc&ZyY{_q&(&Mx z0(Od?p2Fw{n~&O~sm|sixWJC%-hJXe^?u?}J~Jb8fX^k&(HcyNi44BRF!JN0rgk;$Me9@GEzH z{WVa@ZvXRxlqO-(&U)3hbaiIq8n@Qvr#mWG&9u2HNu2!QG3`UoK3`Cckvt%! z;V@Eka^iWbS2BO{TIS8#ul^i6HI$0`&}QT!LmUR#wi3--~n;l9Kp5w#>*|T4J-Z zG<@^v9@odsTBs671QlVCizRDk2s!yzZ`QYexq7{$w>SU2Q~>(!NaOFQjt)ij_U*a$ z>eXJ+(Q;qW#7gVYxRHmwx%FLg9Utw-JdT>sg%eVgtdflVgf-ODJ3N;%+Mj%{c6lVB z{|Q%nxZE<3QqnKIcB3wyYOG4zAmY_6Py0pHpS4DTkyPTTx8L3yd~K=V@M&BAVZ#aA z=;&x5|Kp)h5)-em7bK>tRwkuMWP|zT)-%#)ev)*l`8*c$ikay`cyGTecceY3YvSX3 zXy!H7Bj`E*I)sITqlMplQS}Cuh^*C!sXG-N*ZF>_k>~>q*NpJOeXXBW?SvRtNZp#T z*G?8G{Cdl2{H9S7z8=SUN$|*vI_F2T*G;l+M-0yF9)J2hd>?t-0+EE2&K?GZIM+4O zd|xuMaWRW4A-sIT-y0jw;*LDQj0Js){MJjO7eHYXvHG}=Re0wmR`$2BtPoC z1@q0DLtMH(ISFN`6L+VzM{w2Mk&?Pt+LzgrU^B?gde(umi6p-rZc^_ZpBCTjf4mD< zz{$(YtK0d?E_S*kX=(VukuFWZp=GF0lVPL&+^?i=g-zmojnBmM*h)QJs3{nmLVu?T z9gBkJQap-9XShh0-JyP60NL%ZbsZXa)gShywvL$k2HC9aEq|~XMAKdsu>WhyCFg1n z_YETl8o}s+IDE+V5q*+N@Pk-v+wOrowz-V_u}a-a00(4O-9eYCsVN8sF7fB4?2i@4#>3cKDrqfU5K2vNod zuj3KYr-@MWa{($K6)nS>xd)Bqlm-eZHuyqJp{X-Y@s`*N>Nw z*(N;XYse^pSatLGoGXJN zJ3G#=ZW({g?TjxUwd?lX6qh#1;tO$@sI?^LbK_uTWyNaTmRALDayH9Heuglnt0~#- zXU@VUAyKfR;WUsA?OH&A6^580%YUnNe==NhA4w3&g6^EuG3bK__G6@cj|tr)Y5FeX zNGLvdz?h%A*n@fea7i69`%O{Nw9Urz%-9@-n1MRE>waHXbl(;h7Jg1hP5qi5Me&A1 zg6`sAe~toVsC!zuEJ@ zztz*!)O^8u`*xxlg`k#!kMyN{xPg|Ik!nCPDdg1o^oKo#sw1u^IHaE?&$n^Dc6A}8 z1oH;^>EUKO+*`S7W8zJ*cMSC>BhGOL!Bbvdp@U`|q@W=_a9(`}<&o}BTCk993p1l5 z_|SRD*u5N_jpT*^bQ}vW%L6-(qOZ*{63QKqo}G)9)}OG!RSK1rlrSZiY8ezTQp9bE zD<1XC7I=tJJ5WlbYb0?!Y7WMJ;CFh9Aj>DNQS6X%1c&M?WUzeK)#3D#-VD(kIa$Z) z&lh)gcgplRR5L{q(;OPegilpW{{r>ja=Xmp(3p@?}&b9&#-pEEWJ%U(ynT?BU>8YHY+-sI=e5TJbP9b5`cLlwHJGoDJhIDZ@ zB*Wt@EnUG5h!uWP--@pCYHMjJvl&vW{wK4G;V4`!6O-QM@bK_25K)rMq*(YCq-c=G zV>?1IiYzJ(Fg$r*jhq&l4``8%w%;UxUt`GKI@ z#*Y`~q}|B^QS7C4_!0R|HJtTpydtg}-4L#dQ?*Gk5@)fm1;?Mi(ex3ci!V?$-}_O1 zW)gx!!<4HO&vf+LPukhpxlmbM{RSN-CT8eh4*lc$dnSGxvK2${x z;_K&s4jVB&r(cISiev<`giD8_SkBU~bJoSiUfo@?gokhE#sWKV8}LU#G3!>Kd8T?u zEza#JM$L;uH9L|DX_c-|Vi)UXYily+r!8q|;_j>%bJ8>F>gsC9)zsP$(Lbx6#IWBc zdiyj9FQ|~Aw23&1N@GbiJ$@#7CDB9U^ zH0nKm%r=5e=Fck071Q{8{YkRd-Y=;!w{>nRA;;(K)Sg#LM^rih#d&UwJ^~0DSQswd zp~BUm2mq4_iddx0AO>1Ma!K_fIY+8IWRiO(u0;8OP%=H%czh&ikYnOwB-L${F!R|< zYr;cAG1TwDmckruaAnVbKBWPuY5zrV;rH3Oxv=nXRBtyC} zUH+MPyt`;l?!NjCDUNY*_->m&<*XoLEn*^aK72^9Y>#&8O6IBCo%Aj&`nvn<`1%MA zne^w)N1d~+9Ht*54>(EuvSKD`-7OyvJb6%Q$WxfwJVOf+2B|*|@&zj$-3!m_#NVKrO81H%8olDYFTXncj>qcJqY9fj zc`<67e5f8fojVKZF1^BmEN67g=G2T9O(Us3*A_+Qe!9-DqB{Tk zaH@T%$kkSw+INiy;(%hfw0*`A7YhxOOvsUTRlv?{zesoOM?*$;w`yE`{4y{D*l4sh zQkPZTr(AFtp45bu>Xo*wyHz}yl2HB4SrgiC*@laU*VI?-bmVxlw;Iw(Ow{_OEruqh z)q-xmC5!+eagNh>hV6fRhPwI#jFgg+!pO`#m}h$r*?Ug|&;kKLb_^_ZjmEudD@tT9 zA6tJ4ajC*p8wp{go|ur(B6+XaY<?fiq2{Z$3uYzXXT!4mfF$vZZlS9=5HF*NT*{e$Z* ze}3}hQ%e<)8;VS?yM(fT$=yJL_rzXG4ukH?o&S813H6#UZ>L#`7Ts(i%!+-EL4!@V z^3tudGxXzwhyLO78apFMeL!_GCiprGVvD$%wCrFaYo zO+NdtgaGq^ioe#1v77u5_WShUWl~ZID<5C{hn_Ugy)foq{T0mD7yFiUgoTAUk_BT# z+hW=uSJDR-QnT&1K$<Q!z#yE**NAUu>#zu{L<>`#Ntu~ z6)-gG`Bz>5x3DbN|20(22mF&@_VBcyKMFUZ)n5|#GhjzK!2U)&*0^1-IIV_~dCsUo z9GRW92oxfKuzW-j<%Q7a`NjFI?!Dy%LXx?CFPk4_bZ)g7C=|UkRI9S~wm8$f&&4^T zc`Vyeg>3Q6>hm2l&5XC7^ggjF*Do7e$DsR@BrneWXQLKVX{;{g2@dsq(c99+vyu^bgjnMtF*PWq;+%%i;66i zRaGAhB(UlnOgG~&1Qtq=`=eo#(*_nAut{U!q5c2qg5iqn;QKq|0;nljWI5u^#*1=*I!YWU%zvGeSAKH=Zu^ z^z__V0aRlj``>EN0P;bcJQjeyee4qm3E*L0dM5zSepf0w+5P@PW~kSu&-RHnCThu` z(16mmv%71Plpd+1t820D7JlveF;4UJMAZ}%7gr1rhU0pWQedf}Ki}1jtJv$AY~+=d z#cGePyY&i??+2NKLZ+7%1;~4#{*eaA0IB(S7UlX|=$De;zkjc2Hyp{1hHWuAkQYhL z7d+K$QEpZ>hF6)^^Ds}KVzl_jy4%X7L@#=J-Z&M``UKdmOCEZ}rw8lJ4prJZIZ&3AII1b>$@)xBc-w6>>h}1~cSn;rN`cSIsXhm|sH+fPe7t@LsUrPP}Df z1M*!orG%=FkJ!gT7S;a#epy+0lnuE|@C~bNlpG?;A>n)ZVq@0Gp94V)3yUoev8O9T zVS4Wj@vzX4jJ)?on+Gd^r)peU7eS|x#1+9U8{%Hb=74SfasB7^-h3xQ{fUTzpql0u zvCTO&W&myhNCTRpwuJ?Ke7@@I_vkc!r2kkRyakZBh~N6#1v=RtEj;N^?z{iyUH7K}%1Qj-+{RN+Yv;gH|X&dz?G$gWpr*?uGSwrz0pG9y%5 z47KLD?brr)uBm5-R7*E+Ivo?;bKLM;q6;h>fGZx0@jk!#cDQ# z&@AYB<!9(z3Oc6SJcNnr>ohI6z$Zyg*at28~2yDav2Ke#`<) z?o(q&=6$%4Cl^6%~Z=l_v3@s{xY>Y!E`}?(X zu>I7>sC%ZKLH=hxKR?=rbjD}&oVczY4gNkH%(=&fFtXxe4in!6B7tJl7+lnbws3Qt zwic;?j}R!4Ydb1upc3j^2t0g0ujzl4~{IhcrqWOpZmi}$ep@Mxn0&rjLU zPXfv8Z9{K-&L`Y4=vD-x_&AuDf|)bD!i}eVngO1~`ko0vIOF5vf8&pW%5iOH*XCRH zMUwr^J^}(}dlP&LOtS4of`*kK?@dAzTH4gD*MV$M+rNGMzzCyG3u<=Xc4 zHY5W`vW@(Y^&vBzADy`Xat@LkL~u2tFK$lOKV2Wj2IFUIHNW!Ny4W5=v(z@z{ASCG z$G~3TmERs-lNs(mzjoW4>_LbxaExr0WPY{g#}%4)?>@&x`Q3eE&Vx;U*HBB^N|I7s zIl5iO$cXYEBO1Hzf3Mrw=`xbA#+?LJ!*>UhV)&gUC^3*-kdTnat}WAM2ED)l8-+#cR)1a7+j^)B_FA&`JCo|Tf%m*Eqg~u)om!B_A+Lx2 zl*=RjCFw|O+xQ1$xe(-mF!?~yJDIG9vJARD?%!>-cKj%7p?0&8nQ{8iveK{`cy8IK z9T*!uiu|CR#}X70T1I+$f1m-`MQo44ffFyc(^Nd0l1SyZ`9c(c4oG&%%&^+xYMP)u z%G{14r#+Hx*kS2MxnK@cs;bKB>h|VF?mY!XT0?A~ki^J?F3abPCm#UnfWVW*e&)<Wv+UYU!A7s6=oF-ncwk?8%5QCtdsTG0MdE z#0;Ryc;590l3QPvcJXHSy;8(on3@%$Wk*~Calb3%1m8NJC-|zYf@obWEiHEk5&5>F zMHW&Sv?KrrLg~Lb>`+ix7JJT(HR*e552Bmm<4H=$cmsvzDN}x3GN8l&YP#KXPqPXA zI*IdKnR)X6u1+xEV%#+{Qnl)mJbzWubo<`N*(3;v$9zCj&CTf=bN}y^==Rzd^$a24 zVaTmIVJV=YbW1B;6fE<%({Hz%}9W&UR z8`43j&5u~AdnQVuP>+C&4*XQ-iO(eX&jSLoi%C;I z+^Jr4yF}$JlS`zlD@=f9>Us0_`m74)4E((Q@5)(ADG3(Z_Ni~7w;-O!Gm!)$nRsIB zpYuVuX&2-?kCJ-m^nEVnO;rLdDEh%^$T+Kg@ z$SA+wNvp%PAjoFHN&Io;4}-u(`=Plj=eM&!9!c5RBw{}@n~{df3Vp6*Wo6}L2vh0M zn)bW8mmw6Kv*{_)9d{ZrKATy2TdvQMj0nxL4;Y!RU4vbI zEBNun=4lq3}odLAcxZEJk~FrtoK}v672Z;^$Rx^D5VP-Ra1+= zqCfxT5pcwh?4|&l!H>7NB+kX4d%y!t4anw4bHDokNy-mN(E>{9;t=DEvE^$ z|Ns({n=@$&t#WL_|cI`uZJV1k3>Hf$2Sie7MrR5Xk%uZ+^u2)UYf(Q#lrTb_%OW0}l%K6ET ziUXwp7L-)fPZY*MJ#o&A;aR+bv;hyl{fqqiy_-4-ma_WW$G`RI-yspj3q@Vz>95a$ z=+x6GWPIEJPPcoasf^r#rOCMeh6J>}BI%v*fp|Nsug3S&Ki3zswjLL%awvx>f*3HK?dF)H_LWFI5Y;wBRlug!`MNEqfZ z5e(pVo~p_4lGT4=1fhabeiG1soI(Bh{l*St8KWO>2>v3#bUr z+34C0=utx~JO{XCK`xLBP6qXFrF9lZf|iRpBrKk(9~2%4p$K_wq`E<*0bP}bG};WZ zM_}NUe}a@%%*h|reLO8KtuJlZ6xIM)=s<#!#n6SAnhDi3-m9e}5m4(2hX?>0LC+NgYBj}&51D{;@BSjt_?GiX1N7Ry zH=q{lb3dVmE=UYQyfnpiR>H1}x}vm`ICVZ+jtO|}F9)<*ymm?A^zq)J1#{w-j&!Z1 z@R3P4GuD(K+?8!#A7C8-&HuL4wf{dYX#nkimekdyLxbHfY~;SW{o=kCD+k9s6u5<< zhYJfP*REd|PMmO9vdhto`9G^%P@O(jjN1LHYUTV#)%yMz-v0;L>P}4k`sxEeusVes zG)xi_)c17=5#jcQM!7XPSRYJA4ICLnVghD0)PGk*fQ{n*@3_#v<46Br zefAAk(gwsSM0g4=FOQ_=3)ZhiIQ3mT-LKuEiAml_FzJ9|R1pw!~>{4dPg1Tk7|> z$7_QcVG$8?^PTY@U<5)knp_UrGk$Lw6r!_NMb?cu{od`>dxwRFX1k^doRPzD$y|!f zpf%u-?ap{e>n#w(){}q&b!l)Rs6Bqs<@Y2v9YJ4i z@^n*MUtjT)-NgH#_XApf5ikIc&f*VFaEB z$4ko-1vs1ayEkU=vCt4T&J|pwn<`+hJjbNg(bI$UsKsmDx@{WOCc(3@Tn6!GPSSMsFPY`Lj?O}i*UQP~SVGZHeUU3+F zdEt8sB-5NheygW9gHpmL85tS^aaex*DM4dUE2P@%oYQY_+j7_6eslvs?NP)J9`&@A5P|c$PIMq6OD;Au5~NtVhEsc zsKXnCeYX4)rg)(54`S8T-9^pVEDi8G(k)%=&g|(#(IZI;`YiePHYY*bLk%M>?)pbK zSC5YwnV6i9zR~HOt(uOzY}SJ~U*SE%2~g=Pfrfa{aw~~Rwvq)sm8jeNJHe?3)1P1L z{rVmO{Oe$CGK!THD-OJ*Xkfz%=p*?Lv>N9QkCXXb*{rOrl7D`%?<#rHFjL~aA@yWD zdFt!jcGB@Kt=*GK?tEIawEn#bul@a#$ ztB`<_m6zu|tsKD+HqenZfR3mzHI7tsHf(9ZrEc>t0XT8V$%-ZpoX|gqnJH$_0#6QB z+n>1Z%q!`Y=u5pFey2$o1hNk@;Lsc+E)SHw)_vV#Z1|nn_KN~o-AM|ZZB-br00Jg!`@|eA1Qc5xi13glW5u7NnB%-xZP;}Y5Pj>PR z=5G%TYG@}xmu7~mPcjC@0y@x!_w+>o>)e%(zlYDyKTw@o8&}$t@C^-(Y#FN6pmAFt zSK3|d>FnysQz$h_MyT`h4;U+(+pM`RrwQ;iUnDm#uN;~!^bGXBzLhfspy4phzwhE= z2I>v@4uoq&V9$p_j_C90FdYS9(O+dMX(vFz6f)S!HJbENDowy1m~>0RwBwfdf`^!D zniv^j&|*)T%c^}9-T*8EQVh3mg$r~2=^h=3;M%alAC)f5J!yi>T!Kl|^|-;xP-5Q_14yAO^Fyvo z>Y1v#pykp57F8R>=;z1Inu?Lr<+;$z4LMO8n(c4cd?Y~mE#%T^F<=lDPFWxOXikb2 zY+mNOss~Lh8(abzOXEi{LBOQpCP0U6X^DCCJ0T$fR)>y5&6o=c57I4)Hzx&|%OV^W z`6)M$?*34VnywA0A%~eqqz`?RA}mE%CDV;p{E0 zrGYN40+=sBT4{nXK@q)_)fbxPfU02)|8lEpqV06EUIDfVvwHgU@R);)CgIOY+jfBe z$s#uRU_@wN(p<87kvXt(E2nd}T zl2L;*({;IwFbwMm16pdn<7G+wWgf z9#)JVvBKPu&P@@KR5(Vu7%&bD4#1$4@xL9rrCSLE&8FP^w-?w2ngR=Ut`K#|#2U;a zRfAN?**JTxSKhO_8VN4SzOVG44dV$;sm3#@;Yw=Zxk^KQ2EP5^(Ic0SFhi?GA;=3u z0m$eZ47fDS$b<+{Kq2S{k7Q=@2D!1a`h4=>nQeR%Dl!8y)vuwv>yP+g8Wki1WS$bh zx0dKrS_X!I3eCqMH$JGS%WD#X5*-8s&FA@avbT`c{aun?wXiT~Jq+UF8LLk|UEH41 z2`mgk7^bq#RAxouMV6v)b5b7|N`iY~Xm&k#_;BYCO1jm!Q13(af_P}f%w~FTF3jOb zwk++^`Q_5f-rD)?KDE7Z{|OIt48x+L7VIiPviBh(N>(Q%1YWGEOSH`Z(?BpAHeLNF zE3d9NayAYhp#(k8&KO{zcis-bj3-QKmKCy<_NB^wJrb)i=}`j|>Ir z=@BzLX&{5_cl0t?XjKl%n$rMduUk>k&VcE;vN8AcY{c?Nqa<|oL2QQx67IR042F|D z;%Ok=g{k2{A7A{x_#&S?KfgG0>k!|$S2*l1C+9Y7H0MKRhkAvXUpnr1IQP4v-)^1j zY|UN05m*?*-}~HkRS+*O;VI`*=3wN?`}PcVT`gnQGd=I|)Sd^RznR1?dxBUCVC#!ONrN|nTZ)!3a@ z){6WQ-Ndrl=jRP)dT@oB4O%z`PAKvatA>9}ZTC|vlhO7zBT7Vn%&9B5i@-A#{PSqK z+aAt5%QXR6v)l7IdMu;q`5#|cT)z0mQ?QP-QON_aVDiDI!OrdZ z*&!-0aPohC##3$7`K#u#b`g6}QL#xqv4%P)QxbcM&b@2@I-iZ=TO(J^#^(@1 zz+!Mr&EN}WW|F~`+qOK|f4x=awQndUT#a1$X#oLv&%mHlX<%aNTk&XhOIz!1PxLLA zMY?$J^g$8tTHET;ZLH5ryHI2s6`*X{jhDRuwyc%7tew9Ftw3b54>r9Oppi?^j7ZM| zlS)6GXj*ox<#Vi#Bje8If=B<^)4!+Bpb$eu{?3!n`BjmDOYyi*p_bDKn z13z_sh9@f#?bx1o7I|6_aPjhqh2gqEMCV*5JRA7lcrya5vGH`4DL@;dA-6^g!)fIP zI1>yEj?Wd%_I9-{3&-Pw7w1qm^A@I4sIY-fIc$9B2qauPdP zmFQ*o`_Ha{hDvcH4Ab<(bkdDyNkMp6WwTjXF5kc3lLoBqyp&5kXY1>N)Wmfx9 zH)85y%XaJ0BBI|XhmYVJDdRkTVtQ{A2Xze%|BiH0$AsV{am)b42PjgvGoS)r6Z8F-t?@PckM9GV46$zrNH5GvE?)HLIlxFr-tFP$K%x6}@I-`Y9wd$N~`b;OLA& zu!M#4$_@1ph}X{eco{3mkp)Oj^l)}2bDML;BK?*$#qd4xOBqG!KT-dAt}~BKA)3;B zr1uH8_|7+(f6bV2Uk&gxtg>mA`+Z74KoIbTRx*ns1bgmtld4-SpU`IAB4N7Fi)EnF z2wP$5LG{-w;Hck&szOl66CAJ7&h$y3dBc3SKJ}Gor8i_JudJ%dF=`#!BH5JX{%)HaKEF%)^AsfUDVX;eYzb`rhkGi(6u)BvXC6-86sv-H3kb5{BO_QLqhiBMPzz z`|@_F0~;H6`?pv_7Bk9NZn&!~uB3$Xqr+I+&{OJ(i5_NFu0%}#3+rrf&+)MS(G;eg zRw_{zpChgZ9ATTL56}P94^>$5y!vXT7q{}u>|jNgEfF@{uEFZBHO5*OW`>X(=5~~6 z*p7}5lI77Af^D9{oBglY*k!53Nm8|(c<+et)@x)x==q#(NnH!VhyUL;;0kav{-8JX zpdQPBOXl@mX+V^MtslvVz7e zN%2z-?~~KfkMMmb<)EEe`givBq=L~sXpWR28d@A`+?uJm`BjGk!_u~``_}IY3o22W z!{Le6@ASil!z;fTmXp6Diu$0(KVYb-VcYungI=+`tsKKUBHP!MsF(2Ef)hhMF)`E& zE%6#VKSwJ#y88wjVWdXL@dE2jV`HjupRQYHi}xBCGt}p-D(sZ<3P&rw1xcnn0xWUF zW$%gfPz40kxh&)3jeYc-?!Z$=+V{O#1zPXkSCqzNW>UW&<>-?52Xn^mq z@Shwr#N6F1XpgC&_C8$C(P!EEkrOlfSk2Svr3`$RB=e77B!xmk*Mj4*g7@_HZZ{X_ zOupzb2cZf9dqJn_+W?O-FdNO^A>pMZ4Y{D<2nwNym-V`~#z_t4lTzcSvzX|5fYG4sWnIso=ob z7y4ILni@MitT(lkl?k-rD&vaRuSY1dzSPpP+@;~|52tN;&tVbW+%nI15*={N2umt?(qk0QcS}y4 zKX7CYL&>s+*r zZp;`ju<`pxu_q+472q@;?6GM-8S!&iyu=`rvp^X^o-LtbXk!y1w76 zxuH7_z79KSj~vE}1bo(;udK|KE?%$LI$XjNJ!GhpG)8}#ZGOeS`tfEIycwWB&~S!JZQ+H{Y7v=H`QbKrF{9q5T~KOS zY`j8u>GenY;7x@aQyPp|GLcL-uh0+T++#LVNp-CjdC{>K5KrqgOE15RFMXHb5?x&C bv&&(d`VKGt>|TN|T3=9-Q { ], setAdaptiveYMin: "0", }; - const customEChartEnabled = true; - const showFusionChartDeprecationMessage = true; - const config = [ - ...contentConfig(customEChartEnabled, showFusionChartDeprecationMessage), - ...styleConfig, - ]; + const config = [...contentConfig(), ...styleConfig]; const bindingPaths = { chartType: EvaluationSubstitutionType.TEMPLATE, diff --git a/app/client/src/widgets/ChartWidget/constants.ts b/app/client/src/widgets/ChartWidget/constants.ts index 27aac52ca7..560efd9faf 100644 --- a/app/client/src/widgets/ChartWidget/constants.ts +++ b/app/client/src/widgets/ChartWidget/constants.ts @@ -1,5 +1,4 @@ import { Colors } from "constants/Colors"; -import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag"; export type ChartType = | "LINE_CHART" @@ -85,12 +84,6 @@ export const messages = { }, }; -export const CUSTOM_ECHART_FEATURE_FLAG = - FEATURE_FLAG["release_custom_echarts_enabled"]; - -export const FUSION_CHART_DEPRECATION_FLAG = - FEATURE_FLAG["deprecate_custom_fusioncharts_enabled"]; - export const CUSTOM_CHART_TYPES = [ "area2d", "bar2d", diff --git a/app/client/src/widgets/ChartWidget/widget/index.test.ts b/app/client/src/widgets/ChartWidget/widget/index.test.ts index 0a7b039960..2ef5def288 100644 --- a/app/client/src/widgets/ChartWidget/widget/index.test.ts +++ b/app/client/src/widgets/ChartWidget/widget/index.test.ts @@ -148,10 +148,6 @@ describe("emptyChartData", () => { }); describe("Widget Callouts", () => { - ChartWidget.showCustomFusionChartDeprecationMessages = jest - .fn() - .mockReturnValue(true); - it("returns custom fusion chart deprecation notice when chart type is custom fusion chart", () => { const props = JSON.parse(JSON.stringify(defaultProps)); props.chartType = "CUSTOM_FUSION_CHART"; diff --git a/app/client/src/widgets/ChartWidget/widget/index.tsx b/app/client/src/widgets/ChartWidget/widget/index.tsx index edd0636e0f..1f1cd544a0 100644 --- a/app/client/src/widgets/ChartWidget/widget/index.tsx +++ b/app/client/src/widgets/ChartWidget/widget/index.tsx @@ -6,11 +6,9 @@ import { retryPromise } from "utils/AppsmithUtils"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { contentConfig, styleConfig } from "./propertyConfig"; import { - CUSTOM_ECHART_FEATURE_FLAG, DefaultEChartConfig, DefaultEChartsBasicChartsData, DefaultFusionChartConfig, - FUSION_CHART_DEPRECATION_FLAG, messages, } from "../constants"; import type { ChartSelectedDataPoint } from "../constants"; @@ -148,10 +146,7 @@ class ChartWidget extends BaseWidget { return { getEditorCallouts(props: WidgetProps): WidgetCallout[] { const callouts: WidgetCallout[] = []; - if ( - ChartWidget.showCustomFusionChartDeprecationMessages() && - props.chartType == "CUSTOM_FUSION_CHART" - ) { + if (props.chartType == "CUSTOM_FUSION_CHART") { callouts.push({ message: messages.customFusionChartDeprecationMessage, links: [ @@ -190,20 +185,13 @@ class ChartWidget extends BaseWidget { } static getPropertyPaneContentConfig() { - return contentConfig( - this.getFeatureFlag(CUSTOM_ECHART_FEATURE_FLAG), - this.showCustomFusionChartDeprecationMessages(), - ); + return contentConfig(); } static getPropertyPaneStyleConfig() { return styleConfig; } - static showCustomFusionChartDeprecationMessages() { - return this.getFeatureFlag(FUSION_CHART_DEPRECATION_FLAG); - } - static getStylesheetConfig(): Stylesheet { return { borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", diff --git a/app/client/src/widgets/ChartWidget/widget/propertyConfig.test.ts b/app/client/src/widgets/ChartWidget/widget/propertyConfig.test.ts index 0e7e74fd9e..1bcb8524a0 100644 --- a/app/client/src/widgets/ChartWidget/widget/propertyConfig.test.ts +++ b/app/client/src/widgets/ChartWidget/widget/propertyConfig.test.ts @@ -4,12 +4,7 @@ import { isString } from "lodash"; import { styleConfig, contentConfig } from "./propertyConfig"; import type { PropertyPaneControlConfig } from "constants/PropertyControlConstants"; -const customEChartsEnabled = true; -const showFusionChartDeprecationMessage = true; -const config = [ - ...contentConfig(customEChartsEnabled, showFusionChartDeprecationMessage), - ...styleConfig, -]; +const config = [...contentConfig(), ...styleConfig]; declare global { namespace jest { diff --git a/app/client/src/widgets/ChartWidget/widget/propertyConfig.ts b/app/client/src/widgets/ChartWidget/widget/propertyConfig.ts index e289d6ad2a..23f814bb43 100644 --- a/app/client/src/widgets/ChartWidget/widget/propertyConfig.ts +++ b/app/client/src/widgets/ChartWidget/widget/propertyConfig.ts @@ -5,17 +5,13 @@ import { CUSTOM_CHART_TYPES, LabelOrientation, LABEL_ORIENTATION_COMPATIBLE_CHARTS, - messages, } from "../constants"; import type { WidgetProps } from "widgets/BaseWidget"; export const isLabelOrientationApplicableFor = (chartType: string) => LABEL_ORIENTATION_COMPATIBLE_CHARTS.includes(chartType); -const labelOptions = ( - customEChartsEnabled: boolean, - showCustomFusionChartDeprecationMessage: boolean, -) => { +const labelOptions = () => { const options = [ { label: "Line chart", @@ -38,26 +34,19 @@ const labelOptions = ( value: "AREA_CHART", }, { - label: messages.customFusionChartOptionLabel( - showCustomFusionChartDeprecationMessage, - ), + label: "Custom EChart", + value: "CUSTOM_ECHART", + }, + { + label: "Custom Fusion Charts (deprecated)", value: "CUSTOM_FUSION_CHART", }, ]; - if (customEChartsEnabled) { - options.splice(options.length - 1, 0, { - label: "Custom EChart", - value: "CUSTOM_ECHART", - }); - } return options; }; -export const contentConfig = ( - customEChartsEnabled: boolean, - showCustomFusionChartDeprecationMessage: boolean, -) => { +export const contentConfig = () => { return [ { sectionName: "Data", @@ -67,10 +56,7 @@ export const contentConfig = ( propertyName: "chartType", label: "Chart type", controlType: "DROP_DOWN", - options: labelOptions( - customEChartsEnabled, - showCustomFusionChartDeprecationMessage, - ), + options: labelOptions(), isJSConvertible: true, isBindProperty: true, isTriggerProperty: false,