mirror of
https://github.com/ToolJet/ToolJet
synced 2026-04-21 13:37:28 +00:00
Query manager revamp (#6680)
* global store init * Moved query data to new component * Removed unwanted code * Removed data queries prop drilling * Moved query state out of editor * Added unsafe to componentWillReceiveProps * Selected first query when the version is changed * Fixed bug on renaming query * Fixed issue on dark theme * Fixed running query on page load in viewer * Query manager refactor init * Added global data source in store * Disabled devtools on production * Fixed bug on selecting query after deletion * Reset store when editor is loaded * Moved query manager to functional component * Fixed conflict issues * Fixed infinite loop on tooljetDB * Set the store name and updated devtools logic * Fixed issue on displaying draft query from data sources * Updated comments on the store * Fixed bug on changing data source and creating query from data source * Fixed bug on showing unsaved changes popup * Fixed issue on showing confirmation modal everytime without any changes * feat: autosave data query functionality * feat: show publish button only when the status in draft state * Fixed issues on query renaming * feat: removed discard popup for data query create/edit widget * stye: reduced autosave api call timeout and added draft tag * feat: added minor style changes * feat: fixed issues with restapi plugin, removed unused api calls * fix: fixed issue that breaks restapi creation * fix: reload selected query details after update query * perf: reduced debounce time for data query update apis * feat: removed full reloading of query list on query renaming * feat: duplicate data query feature added * Fixed issue on creating restAPI query * fix: fixed issue in transforming response from update queyr api * fix: refresh selected query details when the selected query is updated * fix: rename query on click enter * fix: full refresh of query list on update * fix: style changes * fix: subscribing to state to autsave * feat: updated the query manager styles to new design * feat: revamped the querypane header buttons * fix: fixed the padding for query panel maximize button * feat: updated search box style * refactor: moved function to render data source icon to its own component * fix: fixed querymanager widget breaking issue * merged with feat/query-manager-autosave * refactor: removed unused consoles * refactor: removed unused consoles * refactor: removed unused consoles * fix: removed commented code * fix: removed unused code * refactor: removed unused comments * fix: show change datasource select only if valid ds available * Update frontend/src/Editor/Inspector/EventManager.jsx Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> * Update frontend/src/Editor/QueryManager/Components/DataSourceLister.jsx Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> * Update frontend/src/Editor/QueryManager/Components/DataSourceLister.jsx Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> * Update frontend/src/Editor/QueryManager/Components/QueryManagerBody.jsx Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> * feat: modify behaviour of search icon in query panel * fix: fixed theme color mismatch in query manager * refactor: remove dead code * refactor: updated theme for data source listner * fix: theming in filter and sort popup * refactor: remove unused variables * fix: removed draftQuery logic from query manager * refactor: removed unused varibales * Update frontend/src/Editor/QueryManager/QueryEditors/Restapi/TabParams.jsx Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> * Update frontend/src/Editor/QueryPanel/QueryCard.jsx Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> * feat: diable preview for draft queries * fix: added tooltip for query panel button * fix: fixed issues in saving query manager events * fix: moved query save subscriber to QuerPanel component * feat: converted query run api to save and run * fix: made varibale an optional param in updateDataQuery dto * refactor: cleanup update dataquery status api response * refactor: moved query status to constants file * feat: prompt for queryname when creating new query * fix: store new queryname in state on create query pageload * fix: fixed alignment of Tooljet db component form * fix: correct translation and format file * refactor: removed consoles * merge: merge appbuilder-1.2 * style: updated rename input/button UX * style: revamped dataquery create widget styles * style: revamped data source selector styles * fix: removed code added for debugging * style: updated data query filter design * style: Add prop to control visibility of clear button in search box * style: implement new style for query filter * merge appbuilder-1.2 to feat/query-manager-sort-filter * refactor: remove unintended file change * fix: set default value for method in respapi * style: updated copilot info popup style * style: updated quer panel header icons * style: updated button styles * style: fixed query manager button styles * style: smoothened query preview modal view * fix: correct import for some funs * fix: fixed minor UX bugs * style: fixed styling of REST api GDS * style: fixed styleing of sort and filter popup * style: improved data queries sort filter UI/UX * fix: remove click listner when overlay is closed * fix: moved component declaration out of parent component * fix: set selected datasource for default sources * fix: filter DS based on saerch in create dropdown * fix: restrict draft query running to preview mode * fix: query renamed on input change in create screen * fix: set name to state as soon as user renames query * fix: make query notification message consistent * style: correct s3 bucket plugin layout config * fix: fixed issues with cloning of Static DS queries * fix: made change so that newly created query is reflected immediatly * style: updated spacing for query manager components * fix: hide rename input when no query selected * fix: check bothe selected query and DS before rendering query manager * fix: set isSaving to true only for api calls in querymanager * fix: added success message form in qm * fix: filter out draft queries from viewer on running * fix: fixed inconsistent gutter for runpy and runjs editors * fix: reload dataqueris on LDS deletion * fix: redesigned filter/sort popup * fix: fixed issue that resets filter on search * fix: fixed query manager breaking on plugin select * fix: diable json preview for text output * fix: reset to filter and sort main menu on close filter popup * refactor: rename varibales * stye: redesigned query create panel * feat: revert data query status column from backend * style: redesign query picker section * refactor: removed dead code * style: querypanel expand/collapse btn style * style: add query select and query filter popup style redesign * style: updated filter popup style * feat: removed draft query checks everywhere * style: empty dataqueries style changed * style: updated query selector popup and rest options styles * style: removed 100% height to query option remove btn * feat: added the query runnable status check * style: updated query manager footer style * feat: changed DS filter from kind to DS ID * style: minor ui tweaks in filter popup * style: disable DS filter if no DQs created * style: minor ui change * fix: rerender filter popup post DS api call. fixed rest api copy feature * fix: add local DS to filter popup * refactor: removed dead code/comments * add new row is crashing when no data is fed to table (#7102) * fix: fixed condition that blocked GDS run on load * fix: revert name back to og name if update fails in rename query * feat: added tooltip for show query btn * fix: added click interaction for pill btn as well * fix: minor UI tweaks to make UX better * style: fixed the styling of filter popup * style: minor UI tweaks in query filter popup * fix: fixed minor css issue in ds picker * style: wrap overflowing text in queryname * fix: update updated_at after query update api call success * fix: update remove the caller query from event query dropdown * style: minor ui spacing tweaks * fix: fix issue that cuased app crash when tjdb opened * fix: fixed update row styles * fix: fixed info popup dark theme bg * fix: fixed headers styling according to general QM styles * style: fixed stripe QM UI * fix: added tooltip for quernames * feat: add tooltip for select ds options * added consoles to debug debugger issue * fix: fixed :active style of ds select dropdown in QM * fix: fixed DS kind name in data source selector in QM * fix: fixed border color mismatch for ds select dd * fix: change tooltip msg for maximize/minize QM * Fix automation for query manager revamp. (#7223) * Add data-cy to support modified specs * Fix event handler * Fix RunPy and RunJS specs * Fix event handler label * Fix basic components spec * Fix basic components failure * Fix tabel spec failure. * Fix runjs and runpy actions * Fix table column options * Add data-cy * version: version updated to 2.13.0 * Version bump --------- Co-authored-by: Kavin Venkatachalam <kavin.saratha@gmail.com> Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> Co-authored-by: Manish Kushare <37823141+manishkushare@users.noreply.github.com> Co-authored-by: Midhun Kumar E <midhun752@gmail.com>
This commit is contained in:
parent
f3f23f199c
commit
55cdc7a0b5
101 changed files with 3972 additions and 2537 deletions
2
.version
2
.version
|
|
@ -1 +1 @@
|
|||
2.12.0
|
||||
2.13.0
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ export const commonText = {
|
|||
// iframeLinkLabel: "Get embeddable link for this application",
|
||||
// ifameLinkCopyButton: "copy",
|
||||
},
|
||||
groupInputFieldLabel: "Select Group"
|
||||
groupInputFieldLabel: "Select Group",
|
||||
};
|
||||
|
||||
export const commonWidgetText = {
|
||||
|
|
@ -199,7 +199,7 @@ export const commonWidgetText = {
|
|||
codeMirrorInputTrue: codeMirrorInputLabel(true),
|
||||
codeMirrorInputFalse: codeMirrorInputLabel("false"),
|
||||
|
||||
addEventHandlerLink: "+ Add event handler",
|
||||
addEventHandlerLink: "Add handler",
|
||||
inspectorComponentLabel: "components",
|
||||
componentValueLabel: "Value",
|
||||
labelDefaultValue: "Default Value",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const multipageText = {
|
|||
optionEventHandler: "Event Handlers",
|
||||
eventModalTitle: "Page Events",
|
||||
labelEvents: "Events",
|
||||
addEventHandlerLink: "+ Add event handler",
|
||||
addEventHandlerLink: "Add handler",
|
||||
noEventHandlerInfo: "This page doesn't have any event handlers",
|
||||
|
||||
optionDeletePage: "Delete Page",
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ describe("Multipage", () => {
|
|||
multipageText.labelEvents
|
||||
);
|
||||
cy.get(multipageSelector.addEventHandlerLink).verifyVisibleElement(
|
||||
"have.text",
|
||||
"contain.text",
|
||||
multipageText.addEventHandlerLink
|
||||
);
|
||||
cy.get(multipageSelector.noEventHandlerMessage).verifyVisibleElement(
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import {
|
|||
} from "Support/utils/events";
|
||||
|
||||
import {
|
||||
selectQuery,
|
||||
selectQueryFromLandingPage,
|
||||
deleteQuery,
|
||||
query,
|
||||
changeQueryToggles,
|
||||
|
|
@ -66,17 +66,15 @@ describe("RunJS", () => {
|
|||
cy.createApp();
|
||||
cy.viewport(1800, 1800);
|
||||
cy.dragAndDropWidget("Button");
|
||||
resizeQueryPanel("50");
|
||||
resizeQueryPanel("80");
|
||||
});
|
||||
|
||||
it("should verify basic runjs", () => {
|
||||
const data = {};
|
||||
data.customText = randomString(12);
|
||||
|
||||
selectQuery("Run JavaScript code");
|
||||
selectQueryFromLandingPage("runjs", "JavaScript");
|
||||
addInputOnQueryField("runjs", "return true");
|
||||
query("create");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Query Added");
|
||||
query("preview");
|
||||
verifypreview("raw", "true");
|
||||
query("run");
|
||||
|
|
@ -92,7 +90,7 @@ describe("RunJS", () => {
|
|||
const data = {};
|
||||
data.customText = randomString(12);
|
||||
|
||||
selectQuery("Run JavaScript code");
|
||||
selectQueryFromLandingPage("runjs", "JavaScript");
|
||||
addInputOnQueryField(
|
||||
"runjs",
|
||||
`setTimeout(() => {
|
||||
|
|
@ -101,7 +99,6 @@ describe("RunJS", () => {
|
|||
}, [0]) `
|
||||
);
|
||||
query("run");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Query Added");
|
||||
cy.get(commonWidgetSelector.sidebarinspector).click();
|
||||
cy.get(".tooltip-inner").invoke("hide");
|
||||
verifyNodeData("variables", "Object", "1 entry ");
|
||||
|
|
@ -134,7 +131,6 @@ describe("RunJS", () => {
|
|||
"actions.showAlert('success', 'alert from runjs');"
|
||||
);
|
||||
query("run");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Query Saved");
|
||||
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "alert from runjs");
|
||||
cy.get(multipageSelector.sidebarPageButton).click();
|
||||
|
|
@ -154,8 +150,6 @@ describe("RunJS", () => {
|
|||
|
||||
addInputOnQueryField("runjs", "actions.closeModal('modal1');");
|
||||
query("run");
|
||||
cy.intercept("GET", "api/data_queries?**").as("addQuery");
|
||||
cy.wait("@addQuery");
|
||||
cy.wait(200);
|
||||
cy.notVisible('[data-cy="modal-title"]');
|
||||
|
||||
|
|
@ -164,7 +158,6 @@ describe("RunJS", () => {
|
|||
"actions.copyToClipboard('data from runjs');"
|
||||
);
|
||||
query("run");
|
||||
cy.wait("@addQuery");
|
||||
|
||||
cy.window().then((win) => {
|
||||
win.navigator.clipboard.readText().then((text) => {
|
||||
|
|
@ -176,7 +169,6 @@ describe("RunJS", () => {
|
|||
"actions.setLocalStorage('localStorage','data from runjs');"
|
||||
);
|
||||
query("run");
|
||||
cy.wait("@addQuery");
|
||||
|
||||
cy.getAllLocalStorage().then((result) => {
|
||||
expect(result[Cypress.config().baseUrl].localStorage).to.deep.equal(
|
||||
|
|
@ -189,8 +181,6 @@ describe("RunJS", () => {
|
|||
"actions.generateFile('runjscsv', 'csv', [{ name: 'John', email: 'john@tooljet.com' }])"
|
||||
);
|
||||
query("run");
|
||||
cy.wait("@addQuery");
|
||||
cy.wait(3000);
|
||||
|
||||
cy.readFile("cypress/downloads/runjscsv.csv", "utf-8")
|
||||
.should("contain", "name,email")
|
||||
|
|
@ -201,11 +191,9 @@ describe("RunJS", () => {
|
|||
// "actions.goToApp('111234')"
|
||||
// );
|
||||
// query("run");
|
||||
// cy.wait("@addQuery");
|
||||
|
||||
addInputOnQueryField("runjs", "actions.logout()");
|
||||
query("run");
|
||||
cy.wait("@addQuery");
|
||||
cy.wait(3000);
|
||||
cy.get('[data-cy="sign-in-header"]').should("be.visible");
|
||||
});
|
||||
|
||||
|
|
@ -213,10 +201,8 @@ describe("RunJS", () => {
|
|||
const data = {};
|
||||
data.customText = randomString(12);
|
||||
|
||||
selectQuery("Run JavaScript code");
|
||||
selectQueryFromLandingPage("runjs", "JavaScript");
|
||||
addInputOnQueryField("runjs", "return [page.handle,page.name]");
|
||||
query("create");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Query Added");
|
||||
query("preview");
|
||||
verifypreview("raw", `["home","Home"]`);
|
||||
|
||||
|
|
@ -267,22 +253,20 @@ describe("RunJS", () => {
|
|||
const data = {};
|
||||
data.customText = randomString(12);
|
||||
|
||||
selectQuery("Run JavaScript code");
|
||||
selectQueryFromLandingPage("runjs", "JavaScript");
|
||||
addInputOnQueryField(
|
||||
"runjs",
|
||||
"actions.showAlert('success', 'alert from runjs');"
|
||||
);
|
||||
query("create");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Query Added");
|
||||
query("run");
|
||||
|
||||
openEditorSidebar("button1");
|
||||
selectEvent("On Click", "Run query", 1);
|
||||
cy.get('[data-cy="query-selection-field"]').type("runjs1{enter}");
|
||||
cy.get(commonWidgetSelector.draggableWidget("button1")).click();
|
||||
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "alert from runjs");
|
||||
renameQueryFromEditor("newrunjs");
|
||||
cy.wait(3000);
|
||||
cy.waitForAutoSave();
|
||||
cy.get('[data-cy="event-handler"]').click();
|
||||
|
||||
cy.get('[data-cy="query-selection-field"]').should("have.text", "newrunjs");
|
||||
|
|
@ -291,26 +275,25 @@ describe("RunJS", () => {
|
|||
});
|
||||
|
||||
it("should verify runjs toggle options", () => {
|
||||
cy.intercept("PATCH", "api/data_queries/**").as("editQuery");
|
||||
const data = {};
|
||||
data.customText = randomString(12);
|
||||
|
||||
selectQuery("Run JavaScript code");
|
||||
selectQueryFromLandingPage("runjs", "JavaScript");
|
||||
addInputOnQueryField(
|
||||
"runjs",
|
||||
"actions.showAlert('success', 'alert from runjs');"
|
||||
);
|
||||
query("create");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Query Added");
|
||||
changeQueryToggles("run-on-app-load");
|
||||
query("save");
|
||||
cy.wait(`@editQuery`);
|
||||
cy.waitForAutoSave();
|
||||
cy.reload();
|
||||
cy.wait(3000);
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "alert from runjs");
|
||||
|
||||
changeQueryToggles("confirmation-before-run");
|
||||
query("save");
|
||||
cy.wait(`@editQuery`);
|
||||
cy.waitForAutoSave();
|
||||
cy.reload();
|
||||
cy.wait(3000);
|
||||
cy.get('[data-cy="modal-message"]').verifyVisibleElement(
|
||||
"have.text",
|
||||
"Do you want to run this query - runjs1?"
|
||||
|
|
@ -318,13 +301,15 @@ describe("RunJS", () => {
|
|||
cy.get('[data-cy="modal-confirm-button"]').realClick();
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "alert from runjs");
|
||||
|
||||
resizeQueryPanel("80");
|
||||
changeQueryToggles("notification-on-success");
|
||||
cy.get('[data-cy="success-message-input-field"]').clearAndTypeOnCodeMirror(
|
||||
"Success alert"
|
||||
);
|
||||
query("save");
|
||||
cy.get('[data-cy="runjs-input-field"]').realClick();
|
||||
cy.wait(1000);
|
||||
cy.waitForAutoSave();
|
||||
cy.reload();
|
||||
cy.wait(3000);
|
||||
cy.get('[data-cy="modal-confirm-button"]').realClick();
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Success alert");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "alert from runjs");
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ import {
|
|||
} from "Support/utils/events";
|
||||
|
||||
import {
|
||||
selectQuery,
|
||||
deleteQuery,
|
||||
selectQueryFromLandingPage,
|
||||
query,
|
||||
changeQueryToggles,
|
||||
renameQueryFromEditor,
|
||||
addInputOnQueryField,
|
||||
waitForQueryAction,
|
||||
} from "Support/utils/queries";
|
||||
|
||||
import {
|
||||
|
|
@ -66,17 +66,17 @@ describe("runpy", () => {
|
|||
cy.createApp();
|
||||
cy.viewport(1800, 1800);
|
||||
cy.dragAndDropWidget("Button");
|
||||
resizeQueryPanel("50");
|
||||
resizeQueryPanel("80");
|
||||
cy.intercept("PATCH", "api/data_queries/**").as("editQuery");
|
||||
});
|
||||
|
||||
it("should verify basic runpy", () => {
|
||||
const data = {};
|
||||
data.customText = randomString(12);
|
||||
|
||||
selectQuery("Run Python code");
|
||||
selectQueryFromLandingPage("runpy", "Python");
|
||||
addInputOnQueryField("runpy", "True");
|
||||
query("create");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Query Added");
|
||||
cy.waitForAutoSave();
|
||||
query("preview");
|
||||
verifypreview("raw", "true");
|
||||
query("run");
|
||||
|
|
@ -92,14 +92,14 @@ describe("runpy", () => {
|
|||
const data = {};
|
||||
data.customText = randomString(12);
|
||||
|
||||
selectQuery("Run Python code");
|
||||
selectQueryFromLandingPage("runpy", "Python");
|
||||
addInputOnQueryField(
|
||||
"runpy",
|
||||
`actions.setVariable('var', 'test')
|
||||
actions.setPageVariable('pageVar', 'pageTest')`
|
||||
);
|
||||
cy.waitForAutoSave();
|
||||
query("run");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Query Added");
|
||||
cy.get(commonWidgetSelector.sidebarinspector).click();
|
||||
cy.get(".tooltip-inner").invoke("hide");
|
||||
verifyNodeData("variables", "Object", "1 entry ");
|
||||
|
|
@ -130,9 +130,12 @@ actions.unsetPageVariable('pageVar')`
|
|||
"actions.showAlert('success', 'alert from runpy')"
|
||||
);
|
||||
query("run");
|
||||
// cy.verifyToastMessage(commonSelectors.toastMessage, "Query Added");
|
||||
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "alert from runpy");
|
||||
cy.verifyToastMessage(
|
||||
commonSelectors.toastMessage,
|
||||
"alert from runpy",
|
||||
false
|
||||
);
|
||||
cy.get(multipageSelector.sidebarPageButton).click();
|
||||
addNewPage("test_page");
|
||||
cy.url().should("contain", "/test-page");
|
||||
|
|
@ -146,22 +149,21 @@ actions.unsetPageVariable('pageVar')`
|
|||
cy.waitForAutoSave();
|
||||
addInputOnQueryField("runpy", "actions.showModal('modal1')");
|
||||
query("run");
|
||||
cy.closeToastMessage();
|
||||
cy.get('[data-cy="modal-title"]').should("be.visible");
|
||||
cy.get('[data-cy="runpy-input-field"]').click({ force: true });
|
||||
|
||||
addInputOnQueryField("runpy", "actions.closeModal('modal1')");
|
||||
cy.wait(2000);
|
||||
cy.wait(`@editQuery`);
|
||||
cy.waitForAutoSave();
|
||||
query("run");
|
||||
cy.intercept("GET", "api/data_queries?**").as("addQuery");
|
||||
cy.wait("@addQuery");
|
||||
cy.wait(10000);
|
||||
waitForQueryAction("run");
|
||||
cy.notVisible('[data-cy="modal-title"]');
|
||||
|
||||
addInputOnQueryField("runpy", "actions.copyToClipboard('data from runpy')");
|
||||
cy.wait(`@editQuery`);
|
||||
cy.waitForAutoSave();
|
||||
query("run");
|
||||
cy.wait("@addQuery");
|
||||
cy.wait(10000);
|
||||
waitForQueryAction("run");
|
||||
cy.window().then((win) => {
|
||||
win.navigator.clipboard.readText().then((text) => {
|
||||
expect(text).to.eq("data from runpy");
|
||||
|
|
@ -171,9 +173,10 @@ actions.unsetPageVariable('pageVar')`
|
|||
"runpy",
|
||||
"actions.setLocalStorage('localStorage','data from runpy')"
|
||||
);
|
||||
cy.wait(`@editQuery`);
|
||||
cy.waitForAutoSave();
|
||||
query("run");
|
||||
cy.wait("@addQuery");
|
||||
cy.wait(10000);
|
||||
waitForQueryAction("run");
|
||||
|
||||
cy.getAllLocalStorage().then((result) => {
|
||||
expect(result[Cypress.config().baseUrl].localStorage).to.deep.equal(
|
||||
|
|
@ -186,7 +189,7 @@ actions.unsetPageVariable('pageVar')`
|
|||
// "actions.generateFile('runpycsv', 'csv', [{ 'name': 'John', 'email': 'john@tooljet.com' }])"
|
||||
// );
|
||||
// query("run");
|
||||
// cy.wait("@addQuery");
|
||||
|
||||
// cy.verifyToastMessage(
|
||||
// commonSelectors.toastMessage,
|
||||
// "Query (runpy1) completed."
|
||||
|
|
@ -203,11 +206,13 @@ actions.unsetPageVariable('pageVar')`
|
|||
// "actions.goToApp('111234')"
|
||||
// );
|
||||
// query("run");
|
||||
// cy.wait("@addQuery");
|
||||
|
||||
addInputOnQueryField("runpy", "actions.logout()");
|
||||
cy.wait(`@editQuery`);
|
||||
cy.wait(200);
|
||||
cy.waitForAutoSave();
|
||||
query("run");
|
||||
cy.wait("@addQuery");
|
||||
cy.wait(3000);
|
||||
waitForQueryAction("run");
|
||||
cy.get('[data-cy="sign-in-header"]').should("be.visible");
|
||||
});
|
||||
|
||||
|
|
@ -215,10 +220,9 @@ actions.unsetPageVariable('pageVar')`
|
|||
const data = {};
|
||||
data.customText = randomString(12);
|
||||
|
||||
selectQuery("Run Python code");
|
||||
selectQueryFromLandingPage("runpy", "Python");
|
||||
addInputOnQueryField("runpy", "tj_globals.theme");
|
||||
query("create");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Query Added");
|
||||
cy.waitForAutoSave();
|
||||
query("preview");
|
||||
verifypreview("raw", `{"name":"light"}`);
|
||||
|
||||
|
|
@ -236,8 +240,11 @@ actions.unsetPageVariable('pageVar')`
|
|||
verifypreview("raw", `Developer`);
|
||||
addInputOnQueryField("runpy", "tj_globals.currentUser.groups");
|
||||
query("preview");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Query completed.");
|
||||
cy.wait(10000);
|
||||
cy.verifyToastMessage(
|
||||
commonSelectors.toastMessage,
|
||||
"Query (runpy1) completed."
|
||||
);
|
||||
waitForQueryAction("preview");
|
||||
verifypreview("raw", `["all_users","admin"]`);
|
||||
if (Cypress.env("environment") != "Community") {
|
||||
addInputOnQueryField("runpy", "tj_globals.mode.value");
|
||||
|
|
@ -261,21 +268,20 @@ actions.unsetPageVariable('pageVar')`
|
|||
const data = {};
|
||||
data.customText = randomString(12);
|
||||
|
||||
selectQuery("Run Python code");
|
||||
selectQueryFromLandingPage("runpy", "Python");
|
||||
addInputOnQueryField(
|
||||
"runpy",
|
||||
"actions.showAlert('success', 'alert from runpy');"
|
||||
);
|
||||
query("create");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Query Added");
|
||||
cy.waitForAutoSave();
|
||||
query("run");
|
||||
|
||||
openEditorSidebar("button1");
|
||||
selectEvent("On Click", "Run query", 1);
|
||||
cy.get('[data-cy="query-selection-field"]').type("runpy1{enter}");
|
||||
cy.get(commonWidgetSelector.draggableWidget("button1")).click();
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "alert from runpy");
|
||||
renameQueryFromEditor("newrunpy");
|
||||
cy.wait(3000);
|
||||
cy.get('[data-cy="event-handler"]').click();
|
||||
|
||||
cy.get('[data-cy="query-selection-field"]').should("have.text", "newrunpy");
|
||||
|
|
@ -287,23 +293,26 @@ actions.unsetPageVariable('pageVar')`
|
|||
const data = {};
|
||||
data.customText = randomString(12);
|
||||
|
||||
selectQuery("Run Python code");
|
||||
selectQueryFromLandingPage("runpy", "Python");
|
||||
cy.waitForAutoSave();
|
||||
addInputOnQueryField(
|
||||
"runpy",
|
||||
"actions.showAlert('success', 'alert from runpy');"
|
||||
);
|
||||
query("create");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Query Added");
|
||||
cy.wait("@editQuery");
|
||||
cy.wait(200);
|
||||
cy.waitForAutoSave();
|
||||
changeQueryToggles("run-on-app-load");
|
||||
query("save");
|
||||
cy.wait("@editQuery");
|
||||
cy.waitForAutoSave();
|
||||
cy.reload();
|
||||
cy.wait(3000);
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "alert from runpy");
|
||||
|
||||
changeQueryToggles("confirmation-before-run");
|
||||
query("save");
|
||||
cy.wait("@editQuery");
|
||||
cy.wait(200);
|
||||
cy.waitForAutoSave();
|
||||
cy.reload();
|
||||
cy.wait(3000);
|
||||
cy.get('[data-cy="modal-message"]').verifyVisibleElement(
|
||||
"have.text",
|
||||
"Do you want to run this query - runpy1?"
|
||||
|
|
@ -315,10 +324,12 @@ actions.unsetPageVariable('pageVar')`
|
|||
cy.get('[data-cy="success-message-input-field"]').clearAndTypeOnCodeMirror(
|
||||
"Success alert"
|
||||
);
|
||||
query("save");
|
||||
cy.forceClickOnCanvas();
|
||||
cy.wait("@editQuery");
|
||||
cy.wait(200);
|
||||
cy.waitForAutoSave();
|
||||
cy.reload();
|
||||
cy.wait(4000);
|
||||
cy.get('[data-cy="modal-confirm-button"]').realClick();
|
||||
cy.get('[data-cy="modal-confirm-button"]', { timeout: 10000 }).realClick();
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Success alert", false);
|
||||
cy.verifyToastMessage(
|
||||
commonSelectors.toastMessage,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import {
|
|||
commonWidgetText,
|
||||
codeMirrorInputLabel,
|
||||
} from "Texts/common";
|
||||
import { resizeQueryPanel } from "Support/utils/dataSource";
|
||||
|
||||
describe("Basic components", () => {
|
||||
const data = {};
|
||||
|
|
@ -37,7 +38,6 @@ describe("Basic components", () => {
|
|||
cy.appUILogin();
|
||||
cy.createApp();
|
||||
cy.modifyCanvasSize(900, 900);
|
||||
cy.get('[data-tooltip-id="tooltip-for-hide-query-editor"]').click();
|
||||
cy.renameApp(data.appName);
|
||||
cy.intercept("GET", "/api/comments/*").as("loadComments");
|
||||
});
|
||||
|
|
@ -617,7 +617,9 @@ describe("Basic components", () => {
|
|||
});
|
||||
|
||||
it("Should verify Tabs", () => {
|
||||
cy.dragAndDropWidget("Tabs", 50, 50);
|
||||
cy.viewport(1200, 1300);
|
||||
resizeQueryPanel("0");
|
||||
cy.dragAndDropWidget("Tabs", 100, 100);
|
||||
verifyComponent("tabs1");
|
||||
deleteComponentAndVerify("image1");
|
||||
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ describe("Table", () => {
|
|||
verifyAndEnterColumnOptionInput("Text color", "red");
|
||||
verifyAndEnterColumnOptionInput(
|
||||
"Cell Background Color",
|
||||
"{backspace}{backspace}{backspace}{backspace}{backspace}yellow"
|
||||
"{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}yellow"
|
||||
);
|
||||
cy.get(
|
||||
'[data-cy="input-and-label-cell-background-color"] > .form-label'
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ export const verifyAndModifyToggleFx = (
|
|||
|
||||
export const addDefaultEventHandler = (message) => {
|
||||
cy.get(commonWidgetSelector.addEventHandlerLink)
|
||||
.should("have.text", commonWidgetText.addEventHandlerLink)
|
||||
.should("contain.text", commonWidgetText.addEventHandlerLink)
|
||||
.click();
|
||||
cy.get(commonWidgetSelector.eventHandlerCard).click();
|
||||
cy.get(commonWidgetSelector.alertMessageInputField)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { postgreSqlSelector } from "Selectors/postgreSql";
|
||||
|
||||
export const selectQuery = (dbName) => {
|
||||
cy.get(postgreSqlSelector.buttonAddNewQueries).click();
|
||||
export const selectQueryFromLandingPage = (dbName, label) => {
|
||||
cy.get(
|
||||
`[data-cy="${dbName.toLowerCase().replace(/\s+/g, "-")}-add-query-card"]`
|
||||
)
|
||||
.should("contain", dbName)
|
||||
.should("contain", label)
|
||||
.click();
|
||||
cy.waitForAutoSave();
|
||||
};
|
||||
|
||||
export const deleteQuery = (queryName) => {
|
||||
|
|
@ -34,4 +34,12 @@ export const addInputOnQueryField = (field, data) => {
|
|||
.click()
|
||||
.clearAndTypeOnCodeMirror(`{backSpace}`);
|
||||
cy.get(`[data-cy="${field}-input-field"]`).clearAndTypeOnCodeMirror(data);
|
||||
cy.forceClickOnCanvas();
|
||||
};
|
||||
|
||||
export const waitForQueryAction = (action) => {
|
||||
cy.get(`[data-cy="query-${action}-button"]`, { timeout: 20000 }).should(
|
||||
"not.have.class",
|
||||
"button-loading"
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -67,8 +67,10 @@ export const verifyAndEnterColumnOptionInput = (label, value) => {
|
|||
cy.get(`[data-cy="input-and-label-${cyParamName(label)}"]`)
|
||||
.realClick()
|
||||
.realPress(["Meta", "A"])
|
||||
.realType(`{backspace}{backspace}{backspace}{backspace}`)
|
||||
.realPress(["Meta", "A"])
|
||||
.realType(
|
||||
`{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}${value}`
|
||||
`{backspace}{rightarrow}{backspace}{rightarrow}{backspace}{rightarrow}{backspace}{rightarrow}{backspace}{rightarrow}{backspace}{rightarrow}{backspace}{rightarrow}{backspace}{backspace}{rightarrow}{backspace}{rightarrow}{backspace}{rightarrow}{backspace}{rightarrow}{backspace}{rightarrow}{backspace}{rightarrow}{backspace}{rightarrow}{backspace}{rightarrow}{backspace}{rightarrow}${value}`
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@
|
|||
"table": "Tabelle",
|
||||
"pageIndex": "Seitenindex",
|
||||
"component": "Komponente",
|
||||
"addHandler": "+ Handler hinzufügen",
|
||||
"addHandler": "Handler hinzufügen",
|
||||
"addEventHandler": "+ Ereignishandler hinzufügen",
|
||||
"emptyMessage": "Dieser {{componentName}} hat keine Ereignishandler"
|
||||
}
|
||||
|
|
@ -914,4 +914,4 @@
|
|||
"tip": "Zurück zur Startseite"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -181,7 +181,7 @@
|
|||
"events": "Events",
|
||||
"transformation": {
|
||||
"transformationToolTip": "Transformations can be enabled on queries to transform the query results. ToolJet allows you to transform the query results using two programming languages: JavaScript and Python",
|
||||
"transformations": "Enable Transformations"
|
||||
"transformations": "Transformations"
|
||||
}
|
||||
},
|
||||
"inspector": {
|
||||
|
|
@ -204,7 +204,7 @@
|
|||
"table": "Table",
|
||||
"pageIndex": "Page index",
|
||||
"component": "Component",
|
||||
"addHandler": "+ Add handler",
|
||||
"addHandler": "Add handler",
|
||||
"addEventHandler": "+ Add event handler",
|
||||
"emptyMessage": "This {{componentName}} doesn't have any event handlers",
|
||||
"page": "Page"
|
||||
|
|
@ -259,7 +259,7 @@
|
|||
"enterLastName": "Enter Last Name",
|
||||
"enterEmail": "Enter email id",
|
||||
"enterFulltName": "Enter full name",
|
||||
"inviteNewUsers":"Invite new users"
|
||||
"inviteNewUsers": "Invite new users"
|
||||
},
|
||||
"manageGroups": {
|
||||
"permissions": {
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@
|
|||
"table": "Tabla",
|
||||
"pageIndex": "Índice de página",
|
||||
"component": "Componente",
|
||||
"addHandler": "+ Añadir manejador",
|
||||
"addHandler": "Añadir manejador",
|
||||
"addEventHandler": "+ Añadir manejador de eventos",
|
||||
"emptyMessage": "Este {{componentName}} no tiene manejadores de eventos."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@
|
|||
"table": "Table",
|
||||
"pageIndex": "Index de page",
|
||||
"component": "Composante",
|
||||
"addHandler": "+ Ajouter le gestionnaire",
|
||||
"addHandler": "Ajouter le gestionnaire",
|
||||
"addEventHandler": "+ Ajouter un gestionnaire d'événements",
|
||||
"emptyMessage": "Ce {{componentName}} n'a pas de gestionnaires d'événements"
|
||||
}
|
||||
|
|
@ -909,10 +909,9 @@
|
|||
"maxHeightOfCanvas": "Hauteur maximale de la toile",
|
||||
"backgroundColorOfCanvas": "Couleur d'arrière-plan de la toile"
|
||||
},
|
||||
"Back":
|
||||
{
|
||||
"Back": {
|
||||
"text": "Retour",
|
||||
"tip": "De retour à la maison"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@
|
|||
"table": "Tabella",
|
||||
"pageIndex": "Indice delle Pagine",
|
||||
"component": "Componente",
|
||||
"addHandler": "+ Aggiungi handler",
|
||||
"addHandler": "Aggiungi handler",
|
||||
"addEventHandler": "+ Aggiungi handler eventi",
|
||||
"emptyMessage": "Questo {{componentName}} non ha handler eventi"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -305,7 +305,7 @@ export function CodeHinter({
|
|||
className={`row${height === '150px' || height === '300px' ? ' tablr-gutter-x-0' : ''} custom-row`}
|
||||
style={{ width: width, display: codeShow ? 'flex' : 'none' }}
|
||||
>
|
||||
<div className={`col code-hinter-col`} style={{ marginBottom: '0.5rem' }}>
|
||||
<div className={`col code-hinter-col`}>
|
||||
<div
|
||||
className="code-hinter-wrapper position-relative"
|
||||
style={{ width: '100%', backgroundColor: darkMode && '#272822' }}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import cx from 'classnames';
|
||||
var tinycolor = require('tinycolor2');
|
||||
const tinycolor = require('tinycolor2');
|
||||
|
||||
export const Button = function Button(props) {
|
||||
const { height, properties, styles, fireEvent, registerAction, id, dataCy, setExposedVariable } = props;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { isEqual } from 'lodash';
|
|||
import iframeContent from './iframe.html';
|
||||
|
||||
import { useDataQueries } from '@/_stores/dataQueriesStore';
|
||||
import { isQueryRunnable } from '@/_helpers/utils';
|
||||
|
||||
export const CustomComponent = (props) => {
|
||||
const dataQueries = useDataQueries();
|
||||
|
|
@ -44,7 +45,9 @@ export const CustomComponent = (props) => {
|
|||
if (e.data.message === 'UPDATE_DATA') {
|
||||
setCustomProps({ ...customPropRef.current, ...e.data.updatedObj });
|
||||
} else if (e.data.message === 'RUN_QUERY') {
|
||||
const filteredQuery = dataQueryRef.current.filter((query) => query.name === e.data.queryName);
|
||||
const filteredQuery = dataQueryRef.current.filter(
|
||||
(query) => query.name === e.data.queryName && isQueryRunnable(query)
|
||||
);
|
||||
const parameters = e.data.parameters ? JSON.parse(e.data.parameters) : {};
|
||||
filteredQuery.length === 1 &&
|
||||
fireEvent('onTrigger', {
|
||||
|
|
|
|||
|
|
@ -99,10 +99,10 @@ export default function generateColumnsData({
|
|||
const rowChangeSet = updatedChangeSet ? updatedChangeSet[cell.row.index] : null;
|
||||
let cellValue = rowChangeSet ? rowChangeSet[column.key || column.name] ?? cell.value : cell.value;
|
||||
|
||||
const rowData = tableData[cell.row.index];
|
||||
const rowData = tableData?.[cell?.row?.index];
|
||||
if (
|
||||
cell.row.index === 0 &&
|
||||
variablesExposedForPreview &&
|
||||
!_.isEmpty(variablesExposedForPreview) &&
|
||||
!_.isEqual(variablesExposedForPreview[id]?.rowData, rowData)
|
||||
) {
|
||||
const customResolvables = {};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { appService, authenticationService, appVersionService, orgEnvironmentVar
|
|||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { defaults, cloneDeep, isEqual, isEmpty, debounce, omit } from 'lodash';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { Container } from './Container';
|
||||
import { EditorKeyHooks } from './EditorKeyHooks';
|
||||
import { CustomDragLayer } from './CustomDragLayer';
|
||||
|
|
@ -21,6 +22,7 @@ import {
|
|||
debuggerActions,
|
||||
cloneComponents,
|
||||
removeSelectedComponent,
|
||||
computeQueryState,
|
||||
} from '@/_helpers/appUtils';
|
||||
import { Confirm } from './Viewer/Confirm';
|
||||
import { Tooltip as ReactTooltip } from 'react-tooltip';
|
||||
|
|
@ -48,10 +50,10 @@ import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
|||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { useEditorStore } from '@/_stores/editorStore';
|
||||
import { useQueryPanelStore } from '@/_stores/queryPanelStore';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
import { useCurrentStateStore, useCurrentState } from '@/_stores/currentStateStore';
|
||||
import { resetAllStores } from '@/_stores/utils';
|
||||
import { setCookie } from '@/_helpers/cookie';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
setAutoFreeze(false);
|
||||
enablePatches();
|
||||
|
|
@ -61,6 +63,8 @@ class EditorComponent extends React.Component {
|
|||
super(props);
|
||||
resetAllStores();
|
||||
const appId = this.props.params.id;
|
||||
|
||||
useAppDataStore.getState().actions.setAppId(appId);
|
||||
useEditorStore.getState().actions.setIsEditorActive(true);
|
||||
const { socket } = createWebsocketConnection(appId);
|
||||
|
||||
|
|
@ -112,13 +116,10 @@ class EditorComponent extends React.Component {
|
|||
apps: [],
|
||||
queryConfirmationList: [],
|
||||
isSourceSelected: false,
|
||||
isSaving: false,
|
||||
isUnsavedQueriesAvailable: false,
|
||||
selectionInProgress: false,
|
||||
scrollOptions: {},
|
||||
currentPageId: defaultPageId,
|
||||
pages: {},
|
||||
draftQuery: null,
|
||||
selectedDataSource: null,
|
||||
};
|
||||
|
||||
|
|
@ -190,6 +191,7 @@ class EditorComponent extends React.Component {
|
|||
threshold: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const globals = {
|
||||
...this.props.currentState.globals,
|
||||
theme: { name: this.props.darkMode ? 'dark' : 'light' },
|
||||
|
|
@ -201,6 +203,16 @@ class EditorComponent extends React.Component {
|
|||
variables: {},
|
||||
};
|
||||
useCurrentStateStore.getState().actions.setCurrentState({ globals, page });
|
||||
|
||||
this.appDataStoreListner = useAppDataStore.subscribe(({ isSaving } = {}) => {
|
||||
if (isSaving !== this.state.isSaving) {
|
||||
this.setState({ isSaving });
|
||||
}
|
||||
});
|
||||
|
||||
this.dataQueriesStoreListner = useDataQueriesStore.subscribe(({ dataQueries }) => {
|
||||
computeQueryState(dataQueries, this);
|
||||
}, shallow);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -252,9 +264,10 @@ class EditorComponent extends React.Component {
|
|||
initEventListeners() {
|
||||
this.socket?.addEventListener('message', (event) => {
|
||||
const data = event.data.replace(/^"(.+(?="$))"$/, '$1');
|
||||
if (data === 'versionReleased') this.fetchApp();
|
||||
else if (data === 'dataQueriesChanged') {
|
||||
this.fetchDataQueries(this.props.editingVersion?.id);
|
||||
if (data === 'versionReleased') {
|
||||
this.fetchApp();
|
||||
// } else if (data === 'dataQueriesChanged') { //Commented since this need additional BE changes to work.
|
||||
// this.fetchDataQueries(this.state.editingVersion?.id); //Also needs revamping to exclude notifying the client of their own changes.
|
||||
} else if (data === 'dataSourcesChanged') {
|
||||
this.fetchDataSources(this.props.editingVersion?.id);
|
||||
}
|
||||
|
|
@ -266,6 +279,8 @@ class EditorComponent extends React.Component {
|
|||
this.socket && this.socket?.close();
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
if (config.ENABLE_MULTIPLAYER_EDITING) this.props?.provider?.disconnect();
|
||||
this.appDataStoreListner && this.appDataStoreListner();
|
||||
this.dataQueriesStoreListner && this.dataQueriesStoreListner();
|
||||
useEditorStore.getState().actions.setIsEditorActive(false);
|
||||
}
|
||||
|
||||
|
|
@ -355,11 +370,7 @@ class EditorComponent extends React.Component {
|
|||
async () => {
|
||||
computeComponentState(this, this.state.appDefinition.pages[homePageId]?.components ?? {}).then(async () => {
|
||||
this.setWindowTitle(data.name);
|
||||
|
||||
useEditorStore.getState().actions.setShowComments(!!queryString.parse(this.props.location.search).threadId);
|
||||
for (const event of dataDefinition.pages[homePageId]?.events ?? []) {
|
||||
await this.handleEvent(event.eventId, event);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
@ -401,19 +412,13 @@ class EditorComponent extends React.Component {
|
|||
if (version?.id === this.state.app?.current_version_id) {
|
||||
(this.canUndo = false), (this.canRedo = false);
|
||||
}
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
useAppVersionStore.getState().actions.updateEditingVersion(version);
|
||||
|
||||
this.setState(
|
||||
{
|
||||
isSaving: false,
|
||||
},
|
||||
() => {
|
||||
shouldWeEditVersion && this.saveEditingVersion(true);
|
||||
this.fetchDataSources(this.props.editingVersion?.id);
|
||||
this.fetchDataQueries(this.props.editingVersion?.id, true);
|
||||
this.initComponentVersioning();
|
||||
}
|
||||
);
|
||||
shouldWeEditVersion && this.saveEditingVersion(true);
|
||||
this.fetchDataSources(this.props.editingVersion?.id);
|
||||
this.fetchDataQueries(this.props.editingVersion?.id, true);
|
||||
this.initComponentVersioning();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -437,10 +442,10 @@ class EditorComponent extends React.Component {
|
|||
this.fetchGlobalDataSources();
|
||||
};
|
||||
|
||||
/**
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
|
||||
*/
|
||||
dataQueriesChanged = () => {
|
||||
dataQueriesChanged = (options) => {
|
||||
/**
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
|
||||
*/
|
||||
if (this.socket instanceof WebSocket && this.socket?.readyState === WebSocket.OPEN) {
|
||||
this.socket?.send(
|
||||
JSON.stringify({
|
||||
|
|
@ -448,9 +453,8 @@ class EditorComponent extends React.Component {
|
|||
data: { message: 'dataQueriesChanged', appId: this.state.appId },
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.fetchDataQueries(this.props.editingVersion?.id);
|
||||
}
|
||||
options?.isReloadSelf && this.fetchDataQueries(this.props.editingVersion?.id, true);
|
||||
};
|
||||
|
||||
switchSidebarTab = (tabIndex) => {
|
||||
|
|
@ -509,10 +513,10 @@ class EditorComponent extends React.Component {
|
|||
this.currentVersion[this.state.currentPageId] = currentVersion - 1;
|
||||
|
||||
if (!appDefinition) return;
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
appDefinition,
|
||||
isSaving: true,
|
||||
},
|
||||
() => {
|
||||
this.props.ymap?.set('appDef', {
|
||||
|
|
@ -540,10 +544,10 @@ class EditorComponent extends React.Component {
|
|||
this.currentVersion[this.state.currentPageId] = currentVersion + 1;
|
||||
|
||||
if (!appDefinition) return;
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
appDefinition,
|
||||
isSaving: true,
|
||||
},
|
||||
() => {
|
||||
this.props.ymap?.set('appDef', {
|
||||
|
|
@ -569,10 +573,9 @@ class EditorComponent extends React.Component {
|
|||
|
||||
if (opts?.versionChanged) {
|
||||
currentPageId = newDefinition.homePageId;
|
||||
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
isSaving: true,
|
||||
currentPageId: currentPageId,
|
||||
appDefinition: newDefinition,
|
||||
appDefinitionLocalVersion: uuid(),
|
||||
|
|
@ -592,9 +595,16 @@ class EditorComponent extends React.Component {
|
|||
},
|
||||
this.handleAddPatch
|
||||
);
|
||||
this.setState({ isSaving: true, appDefinition: newDefinition, appDefinitionLocalVersion: uuid() }, () => {
|
||||
if (!opts.skipAutoSave) this.autoSave();
|
||||
});
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
appDefinition: newDefinition,
|
||||
appDefinitionLocalVersion: uuid(),
|
||||
},
|
||||
() => {
|
||||
if (!opts.skipAutoSave) this.autoSave();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
handleInspectorView = () => {
|
||||
|
|
@ -696,7 +706,8 @@ class EditorComponent extends React.Component {
|
|||
);
|
||||
setStateAsync(_self, newDefinition).then(() => {
|
||||
computeComponentState(_self, _self.state.appDefinition.pages[currentPageId].components);
|
||||
this.setState({ isSaving: true, appDefinitionLocalVersion: uuid() });
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState({ appDefinitionLocalVersion: uuid() });
|
||||
this.autoSave();
|
||||
this.props.ymap?.set('appDef', {
|
||||
newDefinition: newDefinition.appDefinition,
|
||||
|
|
@ -775,9 +786,9 @@ class EditorComponent extends React.Component {
|
|||
const hexCode = `${value?.[0]}${this.decimalToHex(value?.[1]?.a)}`;
|
||||
appDefinition.globalSettings[key] = hexCode;
|
||||
}
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
isSaving: true,
|
||||
appDefinition,
|
||||
},
|
||||
() => {
|
||||
|
|
@ -865,7 +876,7 @@ class EditorComponent extends React.Component {
|
|||
|
||||
saveEditingVersion = (isUserSwitchedVersion = false) => {
|
||||
if (this.props.isVersionReleased && !isUserSwitchedVersion) {
|
||||
this.setState({ isSaving: false });
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
} else if (!isEmpty(this.props?.editingVersion)) {
|
||||
appVersionService
|
||||
.save(
|
||||
|
|
@ -885,14 +896,13 @@ class EditorComponent extends React.Component {
|
|||
saveError: false,
|
||||
},
|
||||
() => {
|
||||
this.setState({
|
||||
isSaving: false,
|
||||
});
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({ saveError: true, isSaving: false }, () => {
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
this.setState({ saveError: true }, () => {
|
||||
toast.error('App could not save.');
|
||||
});
|
||||
});
|
||||
|
|
@ -945,10 +955,6 @@ class EditorComponent extends React.Component {
|
|||
|
||||
runQuery = (queryId, queryName) => runQuery(this, queryId, queryName);
|
||||
|
||||
dataSourceModalHandler = () => {
|
||||
this.dataSourceModalRef.current.dataSourceModalToggleStateHandler();
|
||||
};
|
||||
|
||||
onAreaSelectionStart = (e) => {
|
||||
const isMultiSelect = e.inputEvent.shiftKey || this.state.selectedComponents.length > 0;
|
||||
this.setState((prevState) => {
|
||||
|
|
@ -1030,9 +1036,9 @@ class EditorComponent extends React.Component {
|
|||
},
|
||||
};
|
||||
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
isSaving: true,
|
||||
appDefinition: newAppDefinition,
|
||||
appDefinitionLocalVersion: uuid(),
|
||||
},
|
||||
|
|
@ -1089,10 +1095,10 @@ class EditorComponent extends React.Component {
|
|||
? Object.keys(this.state.appDefinition.pages)[0]
|
||||
: this.state.appDefinition.homePageId;
|
||||
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
currentPageId: newCurrentPageId,
|
||||
isSaving: true,
|
||||
appDefinition: newAppDefinition,
|
||||
appDefinitionLocalVersion: uuid(),
|
||||
isDeletingPage: false,
|
||||
|
|
@ -1107,9 +1113,9 @@ class EditorComponent extends React.Component {
|
|||
};
|
||||
|
||||
updateHomePage = (pageId) => {
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
isSaving: true,
|
||||
appDefinition: {
|
||||
...this.state.appDefinition,
|
||||
homePageId: pageId,
|
||||
|
|
@ -1178,9 +1184,9 @@ class EditorComponent extends React.Component {
|
|||
},
|
||||
};
|
||||
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
isSaving: true,
|
||||
appDefinition: newAppDefinition,
|
||||
appDefinitionLocalVersion: uuid(),
|
||||
},
|
||||
|
|
@ -1203,9 +1209,9 @@ class EditorComponent extends React.Component {
|
|||
return;
|
||||
}
|
||||
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
isSaving: true,
|
||||
appDefinition: {
|
||||
...this.state.appDefinition,
|
||||
pages: {
|
||||
|
|
@ -1227,9 +1233,9 @@ class EditorComponent extends React.Component {
|
|||
};
|
||||
|
||||
updateOnPageLoadEvents = (pageId, events) => {
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
isSaving: true,
|
||||
appDefinition: {
|
||||
...this.state.appDefinition,
|
||||
pages: {
|
||||
|
|
@ -1254,9 +1260,9 @@ class EditorComponent extends React.Component {
|
|||
showViewerNavigation: !this.state.appDefinition.showViewerNavigation,
|
||||
};
|
||||
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
isSaving: true,
|
||||
appDefinition: newAppDefinition,
|
||||
appDefinitionLocalVersion: uuid(),
|
||||
},
|
||||
|
|
@ -1284,9 +1290,9 @@ class EditorComponent extends React.Component {
|
|||
},
|
||||
};
|
||||
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
isSaving: true,
|
||||
appDefinition: newAppDefinition,
|
||||
appDefinitionLocalVersion: uuid(),
|
||||
},
|
||||
|
|
@ -1308,9 +1314,9 @@ class EditorComponent extends React.Component {
|
|||
},
|
||||
};
|
||||
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
isSaving: true,
|
||||
appDefinition: newAppDefinition,
|
||||
appDefinitionLocalVersion: uuid(),
|
||||
},
|
||||
|
|
@ -1332,9 +1338,9 @@ class EditorComponent extends React.Component {
|
|||
},
|
||||
};
|
||||
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
isSaving: true,
|
||||
appDefinition: newAppDefinition,
|
||||
appDefinitionLocalVersion: uuid(),
|
||||
},
|
||||
|
|
@ -1410,9 +1416,9 @@ class EditorComponent extends React.Component {
|
|||
pages: pagesObj,
|
||||
};
|
||||
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
this.setState(
|
||||
{
|
||||
isSaving: true,
|
||||
appDefinition: newAppDefinition,
|
||||
appDefinitionLocalVersion: uuid(),
|
||||
},
|
||||
|
|
@ -1695,7 +1701,6 @@ class EditorComponent extends React.Component {
|
|||
allComponents={appDefinition.pages[this.state.currentPageId]?.components ?? {}}
|
||||
appId={appId}
|
||||
appDefinition={appDefinition}
|
||||
dataSourceModalHandler={this.dataSourceModalHandler}
|
||||
editorRef={this}
|
||||
/>
|
||||
<ReactTooltip id="tooltip-for-add-query" className="tooltip" />
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { ToolTip } from '@/_components';
|
|||
import { appService } from '@/_services';
|
||||
import { handleHttpErrorMessages, validateName } from '../../_helpers/utils';
|
||||
|
||||
function EditAppName({ appId, appName, onNameChanged }) {
|
||||
function EditAppName({ appId, appName = '', onNameChanged }) {
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
const [name, setName] = React.useState(appName);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ function RunjsParameters({ event, darkMode, index, handlerChanged }) {
|
|||
|
||||
return (
|
||||
<div className="row mt-3">
|
||||
<label className="form-label mt-2">Parameters</label>
|
||||
<label className="form-label mt-2" data-cy="label-run-js-parameters">
|
||||
Parameters
|
||||
</label>
|
||||
{dataQuery?.options?.parameters.map((param) => (
|
||||
<React.Fragment key={param.name}>
|
||||
<div className="col-3 p-2">{param.name}</div>
|
||||
|
|
|
|||
|
|
@ -13,8 +13,13 @@ import Select from '@/_ui/Select';
|
|||
import defaultStyles from '@/_ui/Select/styles';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useDataQueries } from '@/_stores/dataQueriesStore';
|
||||
import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
||||
import AddRectangle from '@/_ui/Icon/bulkIcons/AddRectangle';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import RunjsParameters from './ActionConfigurationPanels/RunjsParamters';
|
||||
import { isQueryRunnable } from '@/_helpers/utils';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
export const EventManager = ({
|
||||
component,
|
||||
|
|
@ -27,8 +32,15 @@ export const EventManager = ({
|
|||
popoverPlacement,
|
||||
pages,
|
||||
hideEmptyEventsAlert,
|
||||
callerQueryId,
|
||||
}) => {
|
||||
const dataQueries = useDataQueries();
|
||||
const dataQueries = useDataQueriesStore(({ dataQueries = [] }) => {
|
||||
if (callerQueryId) {
|
||||
//filter the same query getting attached to itself
|
||||
return dataQueries.filter((query) => query.id != callerQueryId);
|
||||
}
|
||||
return dataQueries;
|
||||
}, shallow);
|
||||
const [events, setEvents] = useState(() => component.component.definition.events || []);
|
||||
const [focusedEventIndex, setFocusedEventIndex] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -294,7 +306,7 @@ export const EventManager = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{actionLookup[event.actionId].options?.length > 0 && (
|
||||
{actionLookup[event.actionId]?.options?.length > 0 && (
|
||||
<div className="hr-text" data-cy="action-option">
|
||||
{t('editor.inspector.eventManager.actionOptions', 'Action options')}
|
||||
</div>
|
||||
|
|
@ -419,15 +431,18 @@ export const EventManager = ({
|
|||
<div className="col-9" data-cy="query-selection-field">
|
||||
<Select
|
||||
className={`${darkMode ? 'select-search-dark' : 'select-search'} w-100`}
|
||||
options={dataQueries.map((query) => {
|
||||
return { name: query.name, value: query.id };
|
||||
})}
|
||||
options={dataQueries
|
||||
.filter((qry) => isQueryRunnable(qry))
|
||||
.map((qry) => ({ name: qry.name, value: qry.id }))}
|
||||
value={event.queryId}
|
||||
search={true}
|
||||
onChange={(value) => {
|
||||
const query = dataQueries.find((dataquery) => dataquery.id === value);
|
||||
const parameters = (query?.options?.parameters ?? []).reduce(
|
||||
(paramObj, param) => ({ ...paramObj, [param.name]: param.defaultValue }),
|
||||
(paramObj, param) => ({
|
||||
...paramObj,
|
||||
[param.name]: param.defaultValue,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
handlerChanged(index, 'queryId', query.id);
|
||||
|
|
@ -825,7 +840,7 @@ export const EventManager = ({
|
|||
{...provided.dragHandleProps}
|
||||
className="mb-1"
|
||||
>
|
||||
<div className="card column-sort-row">
|
||||
<div className="card column-sort-row border-0 bg-slate2">
|
||||
<div className={rowClassName} data-cy="event-handler-card">
|
||||
<div className="row p-2" role="button">
|
||||
<div className="col-auto" style={{ cursor: 'grab' }}>
|
||||
|
|
@ -871,7 +886,7 @@ export const EventManager = ({
|
|||
<div className="col text-truncate" data-cy="event-handler">
|
||||
{componentMeta.events[event.eventId]['displayName']}
|
||||
</div>
|
||||
<div className="col text-truncate" data-cy="event-name">
|
||||
<div className="col text-truncate color-slate11" data-cy="event-name">
|
||||
<small className="event-action font-weight-light text-truncate">
|
||||
{actionMeta.name}
|
||||
</small>
|
||||
|
|
@ -884,6 +899,8 @@ export const EventManager = ({
|
|||
removeHandler(index);
|
||||
}}
|
||||
data-cy="delete-button"
|
||||
data-tooltip-id="event-delete-btn-icon"
|
||||
data-tooltip-content="Delete"
|
||||
>
|
||||
<svg
|
||||
width="10"
|
||||
|
|
@ -894,10 +911,11 @@ export const EventManager = ({
|
|||
>
|
||||
<path
|
||||
d="M0 13.8333C0 14.75 0.75 15.5 1.66667 15.5H8.33333C9.25 15.5 10 14.75 10 13.8333V3.83333H0V13.8333ZM1.66667 5.5H8.33333V13.8333H1.66667V5.5ZM7.91667 1.33333L7.08333 0.5H2.91667L2.08333 1.33333H0V3H10V1.33333H7.91667Z"
|
||||
fill="#8092AC"
|
||||
fill="var(--slate8)"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<Tooltip id="event-delete-btn-icon" className="tooltip" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -917,20 +935,29 @@ export const EventManager = ({
|
|||
);
|
||||
};
|
||||
|
||||
const renderAddHandlerBtn = () => {
|
||||
return (
|
||||
<div className={`mb-3 ${events.length === 0 ? '' : 'mt-2'}`}>
|
||||
<ButtonSolid
|
||||
variant="ghostBlue"
|
||||
size="sm"
|
||||
onClick={addHandler}
|
||||
data-cy={events.length === 0 ? 'add-event-handler' : 'add-more-event-handler'}
|
||||
>
|
||||
<AddRectangle width="15" fill="#3E63DD" opacity="1" secondaryFill="#ffffff" />
|
||||
|
||||
{t('editor.inspector.eventManager.addHandler', 'Add handler')}
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const componentName = componentMeta.name ? componentMeta.name : 'query';
|
||||
|
||||
if (events.length === 0) {
|
||||
return (
|
||||
<>
|
||||
<div className="text-left mb-3">
|
||||
<button
|
||||
className="btn btn-sm border-0 font-weight-normal padding-2 col-auto color-primary inspector-add-button"
|
||||
onClick={addHandler}
|
||||
data-cy="add-event-handler"
|
||||
>
|
||||
{t('editor.inspector.eventManager.addEventHandler', '+ Add event handler')}
|
||||
</button>
|
||||
</div>
|
||||
{renderAddHandlerBtn()}
|
||||
{!hideEmptyEventsAlert ? (
|
||||
<div className="text-left">
|
||||
<small className="color-disabled" data-cy="no-event-handler-message">
|
||||
|
|
@ -950,16 +977,8 @@ export const EventManager = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="text-right mb-3">
|
||||
<button
|
||||
className="btn btn-sm border-0 font-weight-normal padding-2 col-auto color-primary inspector-add-button"
|
||||
onClick={addHandler}
|
||||
data-cy="add-more-event-handler"
|
||||
>
|
||||
{t('editor.inspector.eventManager.addHandler', '+ Add handler')}
|
||||
</button>
|
||||
</div>
|
||||
{renderHandlers(events)}
|
||||
{renderAddHandlerBtn()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export const LeftSidebarDataSources = ({
|
|||
setSelectedDataSource(null);
|
||||
dataSourcesChanged();
|
||||
globalDataSourcesChanged();
|
||||
dataQueriesChanged();
|
||||
dataQueriesChanged({ isReloadSelf: true });
|
||||
})
|
||||
.catch(({ error }) => {
|
||||
setDeletingDatasource(false);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import cx from 'classnames';
|
|||
|
||||
function Logs({ logProps, idx, darkMode }) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
console.log('Debug debugger: open:', open);
|
||||
|
||||
const title = ` [${capitalize(logProps?.type)} ${logProps?.key}]`;
|
||||
const message = logProps?.isQuerySuccessLog
|
||||
|
|
@ -26,7 +27,10 @@ function Logs({ logProps, idx, darkMode }) {
|
|||
<div className="tab-content debugger-content mb-1" key={`${logProps?.key}-${idx}`}>
|
||||
<p
|
||||
className="m-0 d-flex"
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
onClick={(e) => {
|
||||
console.log('Debug debugger: setOpen:', e);
|
||||
setOpen((prev) => !prev);
|
||||
}}
|
||||
style={{ pointerEvents: logProps?.isQuerySuccessLog ? 'none' : 'default' }}
|
||||
>
|
||||
<span className={cx('mx-1 position-absolute')} style={defaultStyles}>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export const LeftSidebarInspector = ({
|
|||
pinned,
|
||||
}) => {
|
||||
const dataSources = useGlobalDataSources();
|
||||
|
||||
const dataQueries = useDataQueries();
|
||||
const { isVersionReleased } = useAppVersionStore(
|
||||
(state) => ({
|
||||
|
|
@ -44,17 +45,17 @@ export const LeftSidebarInspector = ({
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appDefinition['selectedComponent']]);
|
||||
const currentState = useCurrentState();
|
||||
const queries = {};
|
||||
|
||||
if (!_.isEmpty(dataQueries)) {
|
||||
dataQueries.forEach((query) => {
|
||||
queries[query.name] = { id: query.id };
|
||||
});
|
||||
}
|
||||
|
||||
const memoizedJSONData = React.useMemo(() => {
|
||||
const data = _.merge(currentState, { queries });
|
||||
const jsontreeData = { ...data };
|
||||
const updatedQueries = {};
|
||||
const { queries: currentQueries } = currentState;
|
||||
if (!_.isEmpty(dataQueries)) {
|
||||
dataQueries.forEach((query) => {
|
||||
updatedQueries[query.name] = _.merge(currentQueries[query.name], { id: query.id });
|
||||
});
|
||||
}
|
||||
// const data = _.merge(currentState, { queries: updatedQueries });
|
||||
const jsontreeData = { ...currentState, queries: updatedQueries };
|
||||
delete jsontreeData.errors;
|
||||
delete jsontreeData.client;
|
||||
delete jsontreeData.server;
|
||||
|
|
@ -87,7 +88,7 @@ export const LeftSidebarInspector = ({
|
|||
|
||||
return jsontreeData;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentState]);
|
||||
}, [currentState, JSON.stringify(dataQueries)]);
|
||||
|
||||
const queryIcons = Object.entries(currentState['queries']).map(([key, value]) => {
|
||||
const allDs = [...staticDataSources, ...dataSources];
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export const SettingsModal = ({
|
|||
onHide={handleClose}
|
||||
size="sm"
|
||||
centered
|
||||
className={`${darkMode && 'theme-dark'} page-handle-edit-modal`}
|
||||
className={`${darkMode && 'theme-dark dark-theme'} page-handle-edit-modal`}
|
||||
backdrop="static"
|
||||
enforceFocus={false}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -153,7 +153,6 @@ export const LeftSidebar = forwardRef((props, ref) => {
|
|||
updateOnSortingPages={updateOnSortingPages}
|
||||
updateOnPageLoadEvents={updateOnPageLoadEvents}
|
||||
apps={apps}
|
||||
popoverContentHeight={popoverContentHeight}
|
||||
setPinned={handlePin}
|
||||
pinned={pinned}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import React from 'react';
|
||||
import Select from '@/_ui/Select';
|
||||
|
||||
export const ChangeDataSource = ({ dataSources, onChange, value, selectedQuery }) => {
|
||||
export const ChangeDataSource = ({ dataSources, onChange, value }) => {
|
||||
return (
|
||||
<Select
|
||||
className="px-4"
|
||||
options={dataSources
|
||||
.filter((ds) => ds.kind === selectedQuery?.kind)
|
||||
.map((ds) => ({ label: ds.name, value: ds.id }))}
|
||||
value={value.id}
|
||||
className="w-100"
|
||||
options={dataSources.map((ds) => ({ label: ds.name, value: ds.id }))}
|
||||
value={value?.id}
|
||||
onChange={(value) => {
|
||||
const dataSource = dataSources.find((ds) => ds.id === value);
|
||||
onChange(dataSource);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import { getSvgIcon } from '@/_helpers/appUtils';
|
||||
import RunjsIcon from '@/Editor/Icons/runjs.svg';
|
||||
import RunTooljetDbIcon from '@/Editor/Icons/tooljetdb.svg';
|
||||
import RunpyIcon from '@/Editor/Icons/runpy.svg';
|
||||
|
||||
const DataSourceIcon = ({ source, height = 25, styles }) => {
|
||||
const iconFile = source?.plugin?.iconFile?.data ?? source?.plugin?.icon_file?.data;
|
||||
const Icon = () => getSvgIcon(source.kind, height, height, iconFile, styles);
|
||||
|
||||
switch (source.kind) {
|
||||
case 'runjs':
|
||||
return <RunjsIcon style={{ height: height, width: height, marginTop: '-3px' }} />;
|
||||
case 'runpy':
|
||||
return <RunpyIcon style={{ height: height, width: height, marginTop: '-3px' }} />;
|
||||
case 'tooljetdb':
|
||||
return <RunTooljetDbIcon style={{ height: height, width: height, marginTop: '-3px' }} />;
|
||||
default:
|
||||
return <Icon />;
|
||||
}
|
||||
};
|
||||
|
||||
export default DataSourceIcon;
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import RunjsIcon from '@/Editor/Icons/runjs.svg';
|
||||
import RunTooljetDbIcon from '@/Editor/Icons/tooljetdb.svg';
|
||||
import RunpyIcon from '@/Editor/Icons/runpy.svg';
|
||||
import AddIcon from '@assets/images/icons/add-source.svg';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getSvgIcon } from '@/_helpers/appUtils';
|
||||
|
||||
function DataSourceLister({
|
||||
dataSources,
|
||||
staticDataSources,
|
||||
changeDataSource,
|
||||
handleBackButton,
|
||||
darkMode,
|
||||
dataSourceModalHandler,
|
||||
showAddDatasourceBtn = true,
|
||||
dataSourceBtnComponent = null,
|
||||
}) {
|
||||
const [allSources, setAllSources] = useState([...dataSources, ...staticDataSources]);
|
||||
const { t } = useTranslation();
|
||||
const computedStyles = {
|
||||
background: darkMode ? '#2f3c4c' : 'white',
|
||||
color: darkMode ? 'white' : '#1f2936',
|
||||
border: darkMode && '1px solid #2f3c4c',
|
||||
};
|
||||
const handleChangeDataSource = (source) => {
|
||||
changeDataSource(source);
|
||||
handleBackButton();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setAllSources([...dataSources, ...staticDataSources]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dataSources]);
|
||||
|
||||
const fetchIconForSource = (source) => {
|
||||
const iconFile = source?.plugin?.iconFile?.data ?? undefined;
|
||||
const Icon = () => getSvgIcon(source.kind, 20, 20, iconFile);
|
||||
|
||||
switch (source.kind) {
|
||||
case 'runjs':
|
||||
return <RunjsIcon style={{ height: 25, width: 25, marginTop: '-3px' }} />;
|
||||
case 'runpy':
|
||||
return <RunpyIcon style={{ height: 25, width: 25, marginTop: '-3px' }} />;
|
||||
case 'tooljetdb':
|
||||
return <RunTooljetDbIcon />;
|
||||
default:
|
||||
return <Icon />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="query-datasource-card-container">
|
||||
{showAddDatasourceBtn && dataSourceBtnComponent && dataSourceBtnComponent}
|
||||
{allSources.map((source) => {
|
||||
return (
|
||||
<div
|
||||
className="query-datasource-card"
|
||||
style={computedStyles}
|
||||
key={`${source.id}-${source.kind}`}
|
||||
onClick={() => {
|
||||
handleChangeDataSource(source);
|
||||
}}
|
||||
>
|
||||
{fetchIconForSource(source)}
|
||||
<p data-cy={`${String(source.name).toLocaleLowerCase().replace(/\s+/g, '-')}-add-query-card`}>
|
||||
{' '}
|
||||
{source.name}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{showAddDatasourceBtn && !dataSourceBtnComponent && (
|
||||
<div className="query-datasource-card" style={computedStyles} onClick={dataSourceModalHandler}>
|
||||
<AddIcon style={{ height: 25, width: 25, marginTop: '-3px' }} />
|
||||
<p>{t('editor.queryManager.addDatasource', 'Add datasource')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DataSourceLister;
|
||||
175
frontend/src/Editor/QueryManager/Components/DataSourcePicker.jsx
Normal file
175
frontend/src/Editor/QueryManager/Components/DataSourcePicker.jsx
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import Plus from '@/_ui/Icon/solidIcons/Plus';
|
||||
import Information from '@/_ui/Icon/solidIcons/Information';
|
||||
import Search from '@/_ui/Icon/solidIcons/Search';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getWorkspaceId } from '@/_helpers/utils';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import { SearchBox as SearchBox2 } from '@/_components/SearchBox';
|
||||
import DataSourceIcon from './DataSourceIcon';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Col, Container, Row } from 'react-bootstrap';
|
||||
import { useDataQueriesActions } from '@/_stores/dataQueriesStore';
|
||||
import { useQueryPanelActions } from '@/_stores/queryPanelStore';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
|
||||
function DataSourcePicker({ dataSources, staticDataSources, darkMode, globalDataSources }) {
|
||||
const allUserDefinedSources = [...dataSources, ...globalDataSources];
|
||||
const [searchTerm, setSearchTerm] = useState();
|
||||
const [filteredUserDefinedDataSources, setFilteredUserDefinedDataSources] = useState(allUserDefinedSources);
|
||||
const navigate = useNavigate();
|
||||
const { createDataQuery } = useDataQueriesActions();
|
||||
const { setPreviewData } = useQueryPanelActions();
|
||||
|
||||
const handleChangeDataSource = (source) => {
|
||||
createDataQuery(source);
|
||||
setPreviewData(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (searchTerm) {
|
||||
const formattedSearchTerm = searchTerm.toLowerCase();
|
||||
const filteredResults = allUserDefinedSources.filter(
|
||||
({ name, kind }) =>
|
||||
name.toLowerCase().includes(formattedSearchTerm) || kind.toLowerCase().includes(formattedSearchTerm)
|
||||
);
|
||||
setFilteredUserDefinedDataSources(filteredResults);
|
||||
} else {
|
||||
setFilteredUserDefinedDataSources(allUserDefinedSources);
|
||||
}
|
||||
}, [searchTerm, globalDataSources, dataSources]);
|
||||
|
||||
const handleAddClick = () => {
|
||||
const workspaceId = getWorkspaceId();
|
||||
navigate(`/${workspaceId}/global-datasources`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4 className="w-100 text-center" data-cy={'label-select-datasource'} style={{ fontWeight: 500 }}>
|
||||
Connect to a datasource
|
||||
</h4>
|
||||
<p className="mb-3" style={{ textAlign: 'center' }}>
|
||||
Select a datasource to start creating a new query. To know more about queries in ToolJet, you can read our
|
||||
|
||||
<a target="_blank" href="https://docs.tooljet.com/docs/app-builder/query-panel" rel="noreferrer">
|
||||
documentation
|
||||
</a>
|
||||
</p>
|
||||
<div>
|
||||
<label className="form-label" data-cy={`landing-page-label-default`}>
|
||||
Default
|
||||
</label>
|
||||
<div className="query-datasource-card-container d-flex justify-content-between mb-3 mt-2">
|
||||
{staticDataSources.map((source) => {
|
||||
return (
|
||||
<ButtonSolid
|
||||
key={`${source.id}-${source.kind}`}
|
||||
variant="tertiary"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
handleChangeDataSource(source);
|
||||
}}
|
||||
className="text-truncate"
|
||||
data-cy={`${source.kind.toLowerCase().replace(/\s+/g, '-')}-add-query-card`}
|
||||
>
|
||||
<DataSourceIcon source={source} height={14} /> {source.shortName}
|
||||
</ButtonSolid>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="d-flex d-flex justify-content-between">
|
||||
<label className="form-label py-1" style={{ width: 'auto' }} data-cy={`label-avilable-ds`}>
|
||||
{`Available Datasources ${!isEmpty(allUserDefinedSources) ? '(' + allUserDefinedSources.length + ')' : 0}`}
|
||||
</label>
|
||||
<ButtonSolid
|
||||
size="sm"
|
||||
variant="ghostBlue"
|
||||
onClick={handleAddClick}
|
||||
data-cy={`landing-page-add-new-ds-button`}
|
||||
>
|
||||
<Plus style={{ height: '16px' }} fill="var(--indigo9)" />
|
||||
Add new
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
{isEmpty(allUserDefinedSources) ? (
|
||||
<EmptyDataSourceBanner />
|
||||
) : (
|
||||
<Container className="p-0">
|
||||
{allUserDefinedSources.length > 4 && (
|
||||
<SearchBox
|
||||
onSearch={setSearchTerm}
|
||||
darkMode={darkMode}
|
||||
searchTerm={searchTerm}
|
||||
dataCy={`gds-querymanager`}
|
||||
/>
|
||||
)}
|
||||
<Row className="mt-2">
|
||||
{filteredUserDefinedDataSources.map((source) => (
|
||||
<Col sm="6" key={source.id} className="ps-1">
|
||||
<ButtonSolid
|
||||
key={`${source.id}-${source.kind}`}
|
||||
variant="ghostBlack"
|
||||
size="sm"
|
||||
className="font-weight-400 py-3 mb-1 w-100 justify-content-start"
|
||||
onClick={() => {
|
||||
handleChangeDataSource(source);
|
||||
}}
|
||||
data-tooltip-id="tooltip-for-query-panel-ds-picker-btn"
|
||||
data-tooltip-content={source.name}
|
||||
data-cy={`${String(source.name).toLowerCase().replace(/\s+/g, '-')}-add-query-card`}
|
||||
>
|
||||
<DataSourceIcon source={source} height={14} styles={{ minWidth: 14 }} />
|
||||
<span className="text-truncate">{source.name}</span>
|
||||
<Tooltip id="tooltip-for-query-panel-ds-picker-btn" className="tooltip" />
|
||||
</ButtonSolid>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Container>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const EmptyDataSourceBanner = () => (
|
||||
<div className="bg-slate3 p-3 d-flex align-items-center lh-lg mt-2" style={{ borderRadius: '6px' }}>
|
||||
<div className="me-2">
|
||||
<Information fill="var(--slate9)" />
|
||||
</div>
|
||||
<div>
|
||||
No global datasources have been added yet. <br />
|
||||
Add new datasources to connect to your app! 🚀
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const SearchBox = ({ onSearch, darkMode, searchTerm, dataCy }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Row>
|
||||
<Col className="mt-2 mb-2">
|
||||
<SearchBox2
|
||||
width="100%"
|
||||
type="text"
|
||||
className={`form-control ${darkMode && 'dark-theme-placeholder'}`}
|
||||
placeholder={t('globals.search', 'Search') + '...'}
|
||||
value={searchTerm}
|
||||
callBack={(e) => onSearch(e.target.value)}
|
||||
onClearCallback={() => onSearch('')}
|
||||
dataCy={dataCy}
|
||||
/>
|
||||
{/* <span
|
||||
className="position-absolute"
|
||||
style={{ top: '50%', transform: 'translate(0%, -50%)', paddingLeft: '10px' }}
|
||||
>
|
||||
<Search style={{ width: '16px' }} />
|
||||
</span> */}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataSourcePicker;
|
||||
266
frontend/src/Editor/QueryManager/Components/DataSourceSelect.jsx
Normal file
266
frontend/src/Editor/QueryManager/Components/DataSourceSelect.jsx
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import Select, { components } from 'react-select';
|
||||
import { groupBy, isEmpty } from 'lodash';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import DataSourceIcon from './DataSourceIcon';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { getWorkspaceId } from '@/_helpers/utils';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import { useDataSources, useGlobalDataSources } from '@/_stores/dataSourcesStore';
|
||||
import { useDataQueriesActions } from '@/_stores/dataQueriesStore';
|
||||
import { staticDataSources } from '../constants';
|
||||
import { useQueryPanelActions } from '@/_stores/queryPanelStore';
|
||||
import Search from '@/_ui/Icon/solidIcons/Search';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { DataBaseSources, ApiSources, CloudStorageSources } from '@/Editor/DataSourceManager/SourceComponents';
|
||||
|
||||
function DataSourceSelect({ darkMode, isDisabled, selectRef, closePopup }) {
|
||||
const dataSources = useDataSources();
|
||||
const globalDataSources = useGlobalDataSources();
|
||||
const [userDefinedSources, setUserDefinedSources] = useState([...dataSources, ...globalDataSources]);
|
||||
const [dataSourcesKinds, setDataSourcesKinds] = useState([]);
|
||||
const [userDefinedSourcesOpts, setUserDefinedSourcesOpts] = useState([]);
|
||||
const { createDataQuery } = useDataQueriesActions();
|
||||
const { setPreviewData } = useQueryPanelActions();
|
||||
const handleChangeDataSource = (source) => {
|
||||
createDataQuery(source);
|
||||
setPreviewData(null);
|
||||
closePopup();
|
||||
};
|
||||
|
||||
console.log(dataSourcesKinds);
|
||||
|
||||
useEffect(() => {
|
||||
const allDataSources = [...dataSources, ...globalDataSources];
|
||||
setUserDefinedSources(allDataSources);
|
||||
const dataSourceKindsList = [...DataBaseSources, ...ApiSources, ...CloudStorageSources];
|
||||
allDataSources.forEach(({ plugin }) => {
|
||||
//plugin names are fetched from list data source api call only
|
||||
if (isEmpty(plugin)) {
|
||||
return;
|
||||
}
|
||||
dataSourceKindsList.push({ name: plugin.name, kind: plugin.pluginId });
|
||||
});
|
||||
setDataSourcesKinds(dataSourceKindsList);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dataSources]);
|
||||
|
||||
useEffect(() => {
|
||||
setUserDefinedSourcesOpts(
|
||||
Object.entries(groupBy(userDefinedSources, 'kind')).map(([kind, sources], index) => ({
|
||||
label: (
|
||||
<div>
|
||||
{index === 0 && (
|
||||
<div className="color-slate9 mb-2 pb-1" style={{ fontWeight: 500, marginTop: '-8px' }}>
|
||||
Global datasources
|
||||
</div>
|
||||
)}
|
||||
<DataSourceIcon source={sources?.[0]} height={16} />
|
||||
<span className="ms-1 small">{dataSourcesKinds.find((dsk) => dsk.kind === kind)?.name || kind}</span>
|
||||
</div>
|
||||
),
|
||||
options: sources.map((source) => ({
|
||||
label: (
|
||||
<div
|
||||
className="py-2 px-2 rounded option-nested-datasource-selector small text-truncate"
|
||||
data-tooltip-id="tooltip-for-add-query-dd-option"
|
||||
data-tooltip-content={source.name}
|
||||
>
|
||||
{source.name}
|
||||
<Tooltip id="tooltip-for-add-query-dd-option" className="tooltip query-manager-ds-select-tooltip" />
|
||||
</div>
|
||||
),
|
||||
value: source.id,
|
||||
isNested: true,
|
||||
source,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
}, [userDefinedSources]);
|
||||
|
||||
const DataSourceOptions = [
|
||||
{
|
||||
label: (
|
||||
<span className="color-slate9" style={{ fontWeight: 500 }}>
|
||||
Defaults
|
||||
</span>
|
||||
),
|
||||
isDisabled: true,
|
||||
options: [
|
||||
...staticDataSources.map((source) => ({
|
||||
label: (
|
||||
<div>
|
||||
<DataSourceIcon source={source} height={16} /> <span className="ms-1 small">{source.name}</span>
|
||||
</div>
|
||||
),
|
||||
value: source.id,
|
||||
source,
|
||||
})),
|
||||
],
|
||||
},
|
||||
...userDefinedSourcesOpts,
|
||||
];
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
closePopup();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Select
|
||||
onChange={({ source } = {}) => handleChangeDataSource(source)}
|
||||
classNames={{
|
||||
menu: () => 'tj-scrollbar',
|
||||
}}
|
||||
ref={selectRef}
|
||||
controlShouldRenderValue={false}
|
||||
menuPlacement="auto"
|
||||
components={{
|
||||
MenuList: MenuList,
|
||||
IndicatorSeparator: () => null,
|
||||
DropdownIndicator,
|
||||
}}
|
||||
styles={{
|
||||
control: (style) => ({
|
||||
...style,
|
||||
width: '240px',
|
||||
background: 'var(--base)',
|
||||
color: 'var(--slate9)',
|
||||
borderWidth: '0',
|
||||
borderBottom: '1px solid var(--slate7)',
|
||||
marginBottom: '1px',
|
||||
boxShadow: 'none',
|
||||
borderRadius: '4px 4px 0 0',
|
||||
':hover': {
|
||||
borderColor: 'var(--slate7)',
|
||||
},
|
||||
flexDirection: 'row-reverse',
|
||||
}),
|
||||
menu: (style) => ({
|
||||
...style,
|
||||
position: 'static',
|
||||
backgroundColor: 'var(--base)',
|
||||
color: 'var(--slate12)',
|
||||
boxShadow: 'none',
|
||||
border: '0',
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
width: '240px',
|
||||
borderTopRightRadius: 0,
|
||||
borderTopLeftRadius: 0,
|
||||
}),
|
||||
input: (style) => ({
|
||||
...style,
|
||||
color: 'var(--slate12)',
|
||||
'caret-color': 'var(--slate9)',
|
||||
':placeholder': { color: 'var(--slate9)' },
|
||||
}),
|
||||
groupHeading: (style) => ({
|
||||
...style,
|
||||
fontSize: '100%',
|
||||
textTransform: '',
|
||||
color: 'inherit',
|
||||
fontWeight: '400',
|
||||
}),
|
||||
option: (style, { data: { isNested }, isFocused, isDisabled }) => ({
|
||||
...style,
|
||||
cursor: 'pointer',
|
||||
backgroundColor: isFocused && !isNested ? 'var(--slate4)' : 'transparent',
|
||||
...(isNested
|
||||
? { padding: '0 8px', marginLeft: '19px', borderLeft: '1px solid var(--slate5)', width: 'auto' }
|
||||
: {}),
|
||||
...(!isNested && { borderRadius: '4px' }),
|
||||
':hover': {
|
||||
backgroundColor: isNested ? 'transparent' : 'var(--slate4)',
|
||||
'.option-nested-datasource-selector': { backgroundColor: 'var(--slate4)' },
|
||||
},
|
||||
...(isFocused &&
|
||||
isNested && {
|
||||
'.option-nested-datasource-selector': { backgroundColor: 'var(--slate4)' },
|
||||
}),
|
||||
}),
|
||||
container: (styles) => ({
|
||||
...styles,
|
||||
borderRadius: '6px',
|
||||
border: '1px solid var(--slate3)',
|
||||
boxShadow: '0px 2px 4px -2px rgba(16, 24, 40, 0.06), 0px 4px 8px -2px rgba(16, 24, 40, 0.10)',
|
||||
}),
|
||||
valueContainer: (styles) => ({
|
||||
...styles,
|
||||
paddingLeft: 0,
|
||||
}),
|
||||
}}
|
||||
placeholder="Search"
|
||||
options={DataSourceOptions}
|
||||
isDisabled={isDisabled}
|
||||
menuIsOpen
|
||||
maxMenuHeight={400}
|
||||
minMenuHeight={300}
|
||||
onKeyDown={handleKeyDown}
|
||||
onInputChange={() => {
|
||||
const queryDsSelectMenu = document.getElementById('query-ds-select-menu');
|
||||
if (queryDsSelectMenu && !queryDsSelectMenu?.style?.height) {
|
||||
queryDsSelectMenu.style.height = queryDsSelectMenu.offsetHeight + 'px';
|
||||
}
|
||||
}}
|
||||
filterOption={(data, search) => {
|
||||
if (data?.data?.source) {
|
||||
//Disabled below eslint check since already checking in above line)
|
||||
// eslint-disable-next-line no-unsafe-optional-chaining
|
||||
const { name, kind } = data?.data?.source;
|
||||
const searchTerm = search.toLowerCase();
|
||||
return name.toLowerCase().includes(searchTerm) || kind.toLowerCase().includes(searchTerm);
|
||||
}
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const MenuList = ({ children, getStyles, innerRef, ...props }) => {
|
||||
const navigate = useNavigate();
|
||||
const menuListStyles = getStyles('menuList', props);
|
||||
|
||||
const { admin } = authenticationService.currentSessionValue;
|
||||
const workspaceId = getWorkspaceId();
|
||||
|
||||
if (admin) {
|
||||
//offseting for height of button since react-select calculates only the size of options list
|
||||
menuListStyles.maxHeight = 400 - 48;
|
||||
}
|
||||
|
||||
menuListStyles.padding = '4px';
|
||||
|
||||
const handleAddClick = () => navigate(`/${workspaceId}/global-datasources`);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={innerRef} style={menuListStyles} id="query-ds-select-menu">
|
||||
{children}
|
||||
</div>
|
||||
{admin && (
|
||||
<div className="p-2 mt-2 border-slate3-top">
|
||||
<ButtonSolid variant="secondary" size="md" className="w-100" onClick={handleAddClick}>
|
||||
+ Add new datasource
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const DropdownIndicator = (props) => {
|
||||
return (
|
||||
components.DropdownIndicator && (
|
||||
<components.DropdownIndicator {...props}>
|
||||
<Search style={{ width: '16px' }} />
|
||||
</components.DropdownIndicator>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default DataSourceSelect;
|
||||
|
|
@ -1,19 +1,34 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { JSONTree } from 'react-json-tree';
|
||||
import { Tab, ListGroup, Row } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Tab, ListGroup, Row, Col } from 'react-bootstrap';
|
||||
import { usePreviewLoading, usePreviewData, useQueryPanelActions } from '@/_stores/queryPanelStore';
|
||||
import { getTheme, tabs } from '../constants';
|
||||
import RemoveRectangle from '@/_ui/Icon/solidIcons/RemoveRectangle';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
|
||||
const Preview = ({ previewPanelRef, previewLoading, queryPreviewData, darkMode }) => {
|
||||
const { t } = useTranslation();
|
||||
const Preview = ({ darkMode }) => {
|
||||
const [key, setKey] = useState('raw');
|
||||
const [isJson, setIsJson] = useState(false);
|
||||
const [theme, setTheme] = useState(() => getTheme(darkMode));
|
||||
const queryPreviewData = usePreviewData();
|
||||
const previewLoading = usePreviewLoading();
|
||||
const { setPreviewData } = useQueryPanelActions();
|
||||
const previewPanelRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
setTheme(() => getTheme(darkMode));
|
||||
}, [darkMode]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (queryPreviewData || previewLoading) {
|
||||
previewPanelRef.current.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
inline: 'nearest',
|
||||
});
|
||||
}
|
||||
}, [queryPreviewData, previewLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (queryPreviewData !== null && typeof queryPreviewData === 'object') {
|
||||
setKey('json');
|
||||
|
|
@ -31,43 +46,65 @@ const Preview = ({ previewPanelRef, previewLoading, queryPreviewData, darkMode }
|
|||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="preview-header preview-section d-flex align-items-baseline font-weight-500" ref={previewPanelRef}>
|
||||
<div className={`py-2 font-weight-400 ${darkMode ? 'color-dark-slate12' : 'color-light-slate-12'}`}>
|
||||
{t('editor.preview', 'Preview')}
|
||||
</div>
|
||||
<div className="preview-header preview-section d-flex align-items-baseline font-weight-500" ref={previewPanelRef}>
|
||||
<div className="w-100 border rounded-top">
|
||||
<Tab.Container activeKey={key} onSelect={(k) => setKey(k)} defaultActiveKey="raw">
|
||||
<Row style={{ width: '100%', paddingLeft: '20px' }}>
|
||||
<div className="keys">
|
||||
<ListGroup className={`query-preview-list-group ${darkMode ? 'dark' : ''}`} variant="flush">
|
||||
{tabs.map((tab) => (
|
||||
<ListGroup.Item key={tab} eventKey={tab.toLowerCase()} disabled={!queryPreviewData}>
|
||||
<span data-cy={`preview-tab-${String(tab).toLowerCase()}`}>{tab}</span>
|
||||
</ListGroup.Item>
|
||||
))}
|
||||
</ListGroup>
|
||||
</div>
|
||||
<div className="position-relative">
|
||||
{previewLoading && (
|
||||
<center>
|
||||
<center className="position-absolute w-100">
|
||||
<div className="spinner-border text-azure mt-5" role="status"></div>
|
||||
</center>
|
||||
)}
|
||||
<Tab.Content style={{ overflowWrap: 'anywhere' }}>
|
||||
{!queryPreviewData && <div className="col preview-default-container"></div>}
|
||||
<Tab.Pane eventKey="json" transition={false}>
|
||||
{previewLoading === false && isJson && (
|
||||
<div className="w-100 " data-cy="preview-json-data-container">
|
||||
<Row className="py-2 border-bottom preview-section-header m-0">
|
||||
<Col className="d-flex align-items-center color-slate9">Preview</Col>
|
||||
<Col className="keys text-center d-flex align-items-center">
|
||||
<ListGroup
|
||||
className={`query-preview-list-group rounded ${darkMode ? 'dark' : ''}`}
|
||||
variant="flush"
|
||||
style={{ backgroundColor: '#ECEEF0', padding: '2px' }}
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
<ListGroup.Item
|
||||
key={tab}
|
||||
eventKey={tab.toLowerCase()}
|
||||
disabled={!queryPreviewData || (tab == 'JSON' && !isJson)}
|
||||
style={{ minWidth: '74px', textAlign: 'center' }}
|
||||
className="rounded"
|
||||
>
|
||||
<span
|
||||
data-cy={`preview-tab-${String(tab).toLowerCase()}`}
|
||||
style={{ width: '100%' }}
|
||||
className="rounded"
|
||||
>
|
||||
{tab}
|
||||
</span>
|
||||
</ListGroup.Item>
|
||||
))}
|
||||
</ListGroup>
|
||||
</Col>
|
||||
<Col className="text-right d-flex align-items-center justify-content-end">
|
||||
{queryPreviewData && (
|
||||
<ButtonSolid variant="ghostBlack" size="sm" onClick={() => setPreviewData()}>
|
||||
<RemoveRectangle width={17} viewBox="0 0 28 28" fill="var(--slate8)" /> Clear
|
||||
</ButtonSolid>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="m-0">
|
||||
<Tab.Content style={{ overflowWrap: 'anywhere', padding: 0 }}>
|
||||
<Tab.Pane eventKey="json" transition={false}>
|
||||
<div className="w-100 preview-data-container" data-cy="preview-json-data-container">
|
||||
<JSONTree theme={theme} data={queryPreviewData} invertTheme={!darkMode} collectionLimit={100} />
|
||||
</div>
|
||||
)}
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="raw" transition={false}>
|
||||
<div className={`p-3 raw-container `} data-cy="preview-raw-data-container">
|
||||
{renderRawData()}
|
||||
</div>
|
||||
</Tab.Pane>
|
||||
</Tab.Content>
|
||||
</Row>
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="raw" transition={false}>
|
||||
<div className={`p-3 raw-container preview-data-container`} data-cy="preview-raw-data-container">
|
||||
{renderRawData()}
|
||||
</div>
|
||||
</Tab.Pane>
|
||||
</Tab.Content>
|
||||
</Row>
|
||||
</div>
|
||||
</Tab.Container>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,434 +1,310 @@
|
|||
import React, { useEffect, useState, useRef, forwardRef } from 'react';
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import cx from 'classnames';
|
||||
import { capitalize, isEqual } from 'lodash';
|
||||
import { cloneDeep, isEmpty } from 'lodash';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { diff } from 'deep-object-diff';
|
||||
import { allSources, source } from '../QueryEditors';
|
||||
import DataSourceLister from './DataSourceLister';
|
||||
import DataSourcePicker from './DataSourcePicker';
|
||||
import { Transformation } from './Transformation';
|
||||
import Preview from './Preview';
|
||||
import { ChangeDataSource } from './ChangeDataSource';
|
||||
import { CustomToggleSwitch } from './CustomToggleSwitch';
|
||||
import AddGlobalDataSourceButton from './AddGlobalDataSourceButton';
|
||||
import EmptyGlobalDataSources from './EmptyGlobalDataSources';
|
||||
import { CodeHinter } from '@/Editor/CodeBuilder/CodeHinter';
|
||||
import { EventManager } from '@/Editor/Inspector/EventManager';
|
||||
import { allOperations } from '@tooljet/plugins/client';
|
||||
import { staticDataSources, customToggles, mockDataQueryAsComponent, schemaUnavailableOptions } from '../constants';
|
||||
import { staticDataSources, customToggles, mockDataQueryAsComponent } from '../constants';
|
||||
import { DataSourceTypes } from '../../DataSourceManager/SourceComponents';
|
||||
import { useDataSources, useGlobalDataSources } from '@/_stores/dataSourcesStore';
|
||||
import { useDataQueries, useDataQueriesActions } from '@/_stores/dataQueriesStore';
|
||||
import {
|
||||
useUnsavedChanges,
|
||||
useSelectedQuery,
|
||||
useSelectedDataSource,
|
||||
useQueryPanelActions,
|
||||
} from '@/_stores/queryPanelStore';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
import { useDataQueriesActions, useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
||||
import { useSelectedQuery, useSelectedDataSource } from '@/_stores/queryPanelStore';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import SuccessNotificationInputs from './SuccessNotificationInputs';
|
||||
|
||||
export const QueryManagerBody = forwardRef(
|
||||
(
|
||||
{
|
||||
darkMode,
|
||||
mode,
|
||||
dataSourceModalHandler,
|
||||
options,
|
||||
previewLoading,
|
||||
queryPreviewData,
|
||||
allComponents,
|
||||
apps,
|
||||
appDefinition,
|
||||
createDraftQuery,
|
||||
setOptions,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
const dataQueries = useDataQueries();
|
||||
const dataSources = useDataSources();
|
||||
const globalDataSources = useGlobalDataSources();
|
||||
const selectedQuery = useSelectedQuery();
|
||||
const isUnsavedQueriesAvailable = useUnsavedChanges();
|
||||
const selectedDataSource = useSelectedDataSource();
|
||||
const { setSelectedDataSource, setUnSavedChanges, setPreviewData } = useQueryPanelActions();
|
||||
const { changeDataQuery } = useDataQueriesActions();
|
||||
export const QueryManagerBody = ({
|
||||
darkMode,
|
||||
options,
|
||||
currentState,
|
||||
allComponents,
|
||||
apps,
|
||||
appDefinition,
|
||||
setOptions,
|
||||
appId,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dataSources = useDataSources();
|
||||
const globalDataSources = useGlobalDataSources();
|
||||
const selectedQuery = useSelectedQuery();
|
||||
const selectedDataSource = useSelectedDataSource();
|
||||
const { changeDataQuery, updateDataQuery } = useDataQueriesActions();
|
||||
|
||||
const [dataSourceMeta, setDataSourceMeta] = useState(null);
|
||||
const currentState = useCurrentState();
|
||||
/* - Added the below line to cause re-rendering when the query is switched
|
||||
const [dataSourceMeta, setDataSourceMeta] = useState(null);
|
||||
/* - Added the below line to cause re-rendering when the query is switched
|
||||
- QueryEditors are not updating when the query is switched
|
||||
- TODO: Remove the below line and make query editors update when the query is switched
|
||||
- Ref PR #6763
|
||||
*/
|
||||
const [selectedQueryId, setSelectedQueryId] = useState(selectedQuery?.id);
|
||||
const [selectedQueryId, setSelectedQueryId] = useState(selectedQuery?.id);
|
||||
|
||||
const queryName = selectedQuery?.name ?? '';
|
||||
const sourcecomponentName = selectedDataSource?.kind.charAt(0).toUpperCase() + selectedDataSource?.kind.slice(1);
|
||||
const ElementToRender = selectedDataSource?.pluginId ? source : allSources[sourcecomponentName];
|
||||
const queryName = selectedQuery?.name ?? '';
|
||||
const sourcecomponentName = selectedDataSource?.kind?.charAt(0).toUpperCase() + selectedDataSource?.kind?.slice(1);
|
||||
|
||||
const defaultOptions = useRef({});
|
||||
const { isVersionReleased } = useAppVersionStore(
|
||||
(state) => ({
|
||||
isVersionReleased: state.isVersionReleased,
|
||||
}),
|
||||
shallow
|
||||
const ElementToRender = selectedDataSource?.pluginId ? source : allSources[sourcecomponentName];
|
||||
|
||||
const defaultOptions = useRef({});
|
||||
|
||||
const { isVersionReleased } = useAppVersionStore(
|
||||
(state) => ({
|
||||
isVersionReleased: state.isVersionReleased,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setDataSourceMeta(
|
||||
selectedQuery?.pluginId
|
||||
? selectedQuery?.manifestFile?.data?.source
|
||||
: DataSourceTypes.find((source) => source.kind === selectedQuery?.kind)
|
||||
);
|
||||
setSelectedQueryId(selectedQuery?.id);
|
||||
defaultOptions.current = selectedQuery?.options && JSON.parse(JSON.stringify(selectedQuery?.options));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
setDataSourceMeta(
|
||||
selectedQuery?.pluginId
|
||||
? selectedQuery?.manifestFile?.data?.source
|
||||
: DataSourceTypes.find((source) => source.kind === selectedQuery?.kind)
|
||||
);
|
||||
setSelectedQueryId(selectedQuery?.id);
|
||||
defaultOptions.current = selectedQuery?.options && JSON.parse(JSON.stringify(selectedQuery?.options));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedQuery]);
|
||||
|
||||
const computeQueryName = (kind) => {
|
||||
const currentQueriesForKind = dataQueries.filter((query) => query.kind === kind);
|
||||
let currentNumber = currentQueriesForKind.length + 1;
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const newName = `${kind}${currentNumber}`;
|
||||
if (dataQueries.find((query) => query.name === newName) === undefined) {
|
||||
return newName;
|
||||
}
|
||||
currentNumber += 1;
|
||||
// Clear the focus field value from options
|
||||
const cleanFocusedFields = (newOptions) => {
|
||||
const diffFields = diff(newOptions, defaultOptions.current);
|
||||
const updatedOptions = { ...newOptions };
|
||||
Object.keys(diffFields).forEach((key) => {
|
||||
if (newOptions[key] === '' && defaultOptions.current[key] === undefined) {
|
||||
delete updatedOptions[key];
|
||||
}
|
||||
};
|
||||
});
|
||||
return updatedOptions;
|
||||
};
|
||||
|
||||
const changeDataSource = (source) => {
|
||||
const isSchemaUnavailable = Object.keys(schemaUnavailableOptions).includes(source.kind);
|
||||
let newOptions = {};
|
||||
const validateNewOptions = (newOptions) => {
|
||||
const updatedOptions = cleanFocusedFields(newOptions);
|
||||
setOptions((options) => ({ ...options, ...updatedOptions }));
|
||||
|
||||
if (isSchemaUnavailable) {
|
||||
newOptions = {
|
||||
...{ ...schemaUnavailableOptions[source.kind] },
|
||||
...(source?.kind != 'runjs' && { transformationLanguage: 'javascript', enableTransformation: false }),
|
||||
};
|
||||
} else {
|
||||
const selectedSourceDefault =
|
||||
source?.plugin?.operationsFile?.data?.defaults ?? allOperations[capitalize(source.kind)]?.defaults;
|
||||
if (selectedSourceDefault) {
|
||||
newOptions = {
|
||||
...{ ...selectedSourceDefault },
|
||||
...(source?.kind != 'runjs' && { transformationLanguage: 'javascript', enableTransformation: false }),
|
||||
};
|
||||
} else {
|
||||
newOptions = {
|
||||
...(source?.kind != 'runjs' && { transformationLanguage: 'javascript', enableTransformation: false }),
|
||||
};
|
||||
}
|
||||
}
|
||||
updateDataQuery(cloneDeep({ ...options, ...updatedOptions }));
|
||||
};
|
||||
|
||||
const newQueryName = computeQueryName(source.kind);
|
||||
defaultOptions.current = { ...newOptions };
|
||||
const optionchanged = (option, value) => {
|
||||
const newOptions = { ...options, [option]: value };
|
||||
validateNewOptions(newOptions);
|
||||
};
|
||||
|
||||
setSelectedDataSource(source);
|
||||
setOptions({ ...newOptions });
|
||||
const optionsChanged = (newOptions) => {
|
||||
validateNewOptions(newOptions);
|
||||
};
|
||||
|
||||
createDraftQuery(
|
||||
{ ...source, data_source_id: source.id, name: newQueryName, id: 'draftQuery', options: { ...newOptions } },
|
||||
source
|
||||
);
|
||||
};
|
||||
const eventsChanged = (events) => {
|
||||
optionchanged('events', events);
|
||||
//added this here since the subscriber added in QueryManager component does not detect this change
|
||||
useDataQueriesStore
|
||||
.getState()
|
||||
.actions.saveData({ ...selectedQuery, options: { ...selectedQuery.options, events: events } });
|
||||
};
|
||||
|
||||
// Clear the focus field value from options
|
||||
const cleanFocusedFields = (newOptions) => {
|
||||
const diffFields = diff(newOptions, defaultOptions.current);
|
||||
const updatedOptions = { ...newOptions };
|
||||
Object.keys(diffFields || {}).forEach((key) => {
|
||||
if (newOptions[key] === '' && defaultOptions.current[key] === undefined) {
|
||||
delete updatedOptions[key];
|
||||
}
|
||||
});
|
||||
return updatedOptions;
|
||||
};
|
||||
const toggleOption = (option) => {
|
||||
const currentValue = selectedQuery?.options?.[option] ?? false;
|
||||
optionchanged(option, !currentValue);
|
||||
};
|
||||
|
||||
const removeRestKey = (options) => {
|
||||
delete options.arrayValuesChanged;
|
||||
return options;
|
||||
};
|
||||
const renderDataSourcesList = () => {
|
||||
return (
|
||||
<div
|
||||
className={cx(`datasource-picker p-0`, {
|
||||
'disabled ': isVersionReleased,
|
||||
})}
|
||||
>
|
||||
<DataSourcePicker
|
||||
dataSources={dataSources}
|
||||
staticDataSources={staticDataSources}
|
||||
globalDataSources={globalDataSources}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const validateNewOptions = (newOptions) => {
|
||||
const headersChanged = newOptions.arrayValuesChanged ?? false;
|
||||
const updatedOptions = cleanFocusedFields(newOptions);
|
||||
let isFieldsChanged = false;
|
||||
if (selectedQuery) {
|
||||
const isQueryChanged = !isEqual(removeRestKey(updatedOptions), removeRestKey(defaultOptions.current));
|
||||
if (isQueryChanged) {
|
||||
isFieldsChanged = true;
|
||||
} else if (selectedQuery?.kind === 'restapi') {
|
||||
if (headersChanged) {
|
||||
isFieldsChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
setOptions((options) => ({ ...options, ...updatedOptions }));
|
||||
if (isFieldsChanged !== isUnsavedQueriesAvailable) setUnSavedChanges(isFieldsChanged);
|
||||
};
|
||||
const renderTransformation = () => {
|
||||
if (
|
||||
dataSourceMeta?.disableTransformations ||
|
||||
selectedDataSource?.kind === 'runjs' ||
|
||||
selectedDataSource?.kind === 'runpy'
|
||||
)
|
||||
return;
|
||||
return (
|
||||
<Transformation
|
||||
changeOption={optionchanged}
|
||||
options={options ?? {}}
|
||||
currentState={currentState}
|
||||
darkMode={darkMode}
|
||||
queryId={selectedQuery?.id}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const optionchanged = (option, value) => {
|
||||
const newOptions = { ...options, [option]: value };
|
||||
validateNewOptions(newOptions);
|
||||
};
|
||||
const handleBlur = () => {
|
||||
updateDataQuery(options);
|
||||
};
|
||||
|
||||
const optionsChanged = (newOptions) => {
|
||||
validateNewOptions(newOptions);
|
||||
};
|
||||
const renderQueryElement = () => {
|
||||
return (
|
||||
<div style={{ padding: '0 32px' }}>
|
||||
<div>
|
||||
<div
|
||||
className={cx({
|
||||
'disabled ': isVersionReleased,
|
||||
})}
|
||||
>
|
||||
<ElementToRender
|
||||
key={selectedQuery?.id}
|
||||
pluginSchema={selectedDataSource?.plugin?.operationsFile?.data}
|
||||
selectedDataSource={selectedDataSource}
|
||||
options={selectedQuery?.options}
|
||||
optionsChanged={optionsChanged}
|
||||
optionchanged={optionchanged}
|
||||
currentState={currentState}
|
||||
darkMode={darkMode}
|
||||
isEditMode={true} // Made TRUE always to avoid setting default options again
|
||||
queryName={queryName}
|
||||
onBlur={handleBlur} // Applies only to textarea, text box, etc. where `optionchanged` is triggered for every character change.
|
||||
/>
|
||||
{renderTransformation()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const handleBackButton = () => {
|
||||
setPreviewData(null);
|
||||
};
|
||||
const renderEventManager = () => {
|
||||
const queryComponent = mockDataQueryAsComponent(options?.events || []);
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<div className={`form-label`}>{t('editor.queryManager.eventsHandler', 'Events')}</div>
|
||||
<div className="query-manager-events pb-4 flex-grow-1">
|
||||
<EventManager
|
||||
eventsChanged={eventsChanged}
|
||||
component={queryComponent.component}
|
||||
componentMeta={queryComponent.componentMeta}
|
||||
currentState={currentState}
|
||||
components={allComponents}
|
||||
callerQueryId={selectedQueryId}
|
||||
apps={apps}
|
||||
popoverPlacement="top"
|
||||
pages={
|
||||
appDefinition?.pages
|
||||
? Object.entries(appDefinition?.pages).map(([id, page]) => ({
|
||||
...page,
|
||||
id,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const eventsChanged = (events) => {
|
||||
optionchanged('events', events);
|
||||
};
|
||||
|
||||
const toggleOption = (option) => {
|
||||
const currentValue = options[option] ? options[option] : false;
|
||||
optionchanged(option, !currentValue);
|
||||
};
|
||||
|
||||
const renderDataSources = (labelText, dataSourcesList, staticList = [], isGlobalDataSource = false) => {
|
||||
return (
|
||||
const renderQueryOptions = () => {
|
||||
return (
|
||||
<div style={{ padding: '0 32px' }}>
|
||||
<div
|
||||
className={cx(`datasource-picker`, {
|
||||
className={cx(`d-flex pb-1`, {
|
||||
'disabled ': isVersionReleased,
|
||||
})}
|
||||
>
|
||||
<label className="form-label col-md-3" data-cy={'label-select-datasource'}>
|
||||
{labelText}
|
||||
</label>
|
||||
{isGlobalDataSource && dataSourcesList?.length < 1 ? (
|
||||
<EmptyGlobalDataSources darkMode={darkMode} />
|
||||
) : (
|
||||
<DataSourceLister
|
||||
dataSources={dataSourcesList}
|
||||
staticDataSources={staticList}
|
||||
changeDataSource={changeDataSource}
|
||||
handleBackButton={handleBackButton}
|
||||
darkMode={darkMode}
|
||||
dataSourceModalHandler={dataSourceModalHandler}
|
||||
showAddDatasourceBtn={isGlobalDataSource}
|
||||
dataSourceBtnComponent={isGlobalDataSource ? <AddGlobalDataSourceButton /> : null}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderDataSourcesList = () => (
|
||||
<>
|
||||
{renderDataSources(
|
||||
t('editor.queryManager.selectDatasource', 'Select Datasource'),
|
||||
dataSources,
|
||||
staticDataSources
|
||||
)}
|
||||
{renderDataSources(
|
||||
t('editor.queryManager.selectGlobalDatasource', 'Select Global Datasource'),
|
||||
globalDataSources,
|
||||
[],
|
||||
true
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const renderTransformation = () => {
|
||||
if (
|
||||
dataSourceMeta?.disableTransformations ||
|
||||
selectedDataSource?.kind === 'runjs' ||
|
||||
selectedDataSource?.kind === 'runpy'
|
||||
)
|
||||
return;
|
||||
return (
|
||||
<Transformation
|
||||
changeOption={optionchanged}
|
||||
options={options ?? {}}
|
||||
darkMode={darkMode}
|
||||
queryId={selectedQuery?.id}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderQueryElement = () => {
|
||||
return (
|
||||
<div style={{ padding: '0 32px' }}>
|
||||
<div>
|
||||
<div
|
||||
className={cx({
|
||||
'disabled ': isVersionReleased,
|
||||
})}
|
||||
>
|
||||
<ElementToRender
|
||||
pluginSchema={selectedDataSource?.plugin?.operationsFile?.data}
|
||||
selectedDataSource={selectedDataSource}
|
||||
options={options}
|
||||
optionsChanged={optionsChanged}
|
||||
optionchanged={optionchanged}
|
||||
currentState={currentState}
|
||||
<div className="form-label">{t('editor.queryManager.settings', 'Settings')}</div>
|
||||
<div className="flex-grow-1">
|
||||
{Object.keys(customToggles).map((toggle, index) => (
|
||||
<CustomToggleFlag
|
||||
{...customToggles[toggle]}
|
||||
toggleOption={toggleOption}
|
||||
value={selectedQuery?.options?.[customToggles[toggle]?.action]}
|
||||
index={index}
|
||||
key={toggle}
|
||||
darkMode={darkMode}
|
||||
isEditMode={true} // Made TRUE always to avoid setting default options again
|
||||
queryName={queryName}
|
||||
mode={mode}
|
||||
/>
|
||||
{renderTransformation()}
|
||||
</div>
|
||||
<Preview
|
||||
previewPanelRef={ref}
|
||||
previewLoading={previewLoading}
|
||||
queryPreviewData={queryPreviewData}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderEventManager = () => {
|
||||
const queryComponent = mockDataQueryAsComponent(options?.events || []);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`border-top query-manager-border-color hr-text-left px-4 ${
|
||||
darkMode ? 'color-white' : 'color-light-slate-12'
|
||||
}`}
|
||||
style={{ paddingTop: '28px' }}
|
||||
>
|
||||
{t('editor.queryManager.eventsHandler', 'Events Handler')}
|
||||
</div>
|
||||
<div className="query-manager-events px-4 mt-2 pb-4">
|
||||
<EventManager
|
||||
eventsChanged={eventsChanged}
|
||||
component={queryComponent.component}
|
||||
componentMeta={queryComponent.componentMeta}
|
||||
dataQueries={dataQueries}
|
||||
components={allComponents}
|
||||
apps={apps}
|
||||
popoverPlacement="top"
|
||||
pages={
|
||||
appDefinition?.pages ? Object.entries(appDefinition?.pages).map(([id, page]) => ({ ...page, id })) : []
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderSuccessNotification = () => (
|
||||
<div className="mx-4" style={{ paddingLeft: '100px', paddingTop: '12px' }}>
|
||||
<div className="row mt-1">
|
||||
<div className="col-auto" style={{ width: '200px' }}>
|
||||
<label className="form-label p-2 font-size-12" data-cy={'label-success-message-input'}>
|
||||
{t('editor.queryManager.successMessage', 'Success Message')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="col">
|
||||
<CodeHinter
|
||||
initialValue={options.successMessage}
|
||||
height="36px"
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
onChange={(value) => optionchanged('successMessage', value)}
|
||||
placeholder={t('editor.queryManager.queryRanSuccessfully', 'Query ran successfully')}
|
||||
cyLabel={'success-message'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row mt-3">
|
||||
<div className="col-auto" style={{ width: '200px' }}>
|
||||
<label className="form-label p-2 font-size-12" data-cy={'label-notification-duration-input'}>
|
||||
{t('editor.queryManager.notificationDuration', 'Notification duration (s)')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="col query-manager-input-elem">
|
||||
<input
|
||||
type="number"
|
||||
disabled={!options.showSuccessNotification}
|
||||
onChange={(e) => optionchanged('notificationDuration', e.target.value)}
|
||||
placeholder={5}
|
||||
className="form-control"
|
||||
value={options.notificationDuration}
|
||||
data-cy={'notification-duration-input-field'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderCustomToggle = ({ dataCy, action, translatedLabel, label }, index) => (
|
||||
<div className={cx('mx-4', { 'pb-3 pt-3': index === 1 })}>
|
||||
<CustomToggleSwitch
|
||||
dataCy={dataCy}
|
||||
isChecked={options && options[action]}
|
||||
toggleSwitchFunction={toggleOption}
|
||||
action={action}
|
||||
<SuccessNotificationInputs
|
||||
currentState={currentState}
|
||||
options={options}
|
||||
darkMode={darkMode}
|
||||
label={t(translatedLabel, label)}
|
||||
optionchanged={optionchanged}
|
||||
/>
|
||||
{renderEventManager()}
|
||||
<Preview darkMode={darkMode} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderQueryOptions = () => {
|
||||
return (
|
||||
<div
|
||||
className={cx(`advanced-options-container font-weight-400 border-top query-manager-border-color`, {
|
||||
'disabled ': isVersionReleased,
|
||||
})}
|
||||
>
|
||||
<div className="advance-options-input-form-container">
|
||||
{Object.keys(customToggles).map((toggle, index) => renderCustomToggle(customToggles[toggle], index))}
|
||||
{options?.showSuccessNotification && renderSuccessNotification()}
|
||||
</div>
|
||||
{renderEventManager()}
|
||||
const renderChangeDataSource = () => {
|
||||
const selectableDataSources = [...globalDataSources, ...dataSources].filter(
|
||||
(ds) => ds.kind === selectedQuery?.kind
|
||||
);
|
||||
if (isEmpty(selectableDataSources)) {
|
||||
return '';
|
||||
}
|
||||
return (
|
||||
<div className={cx('mt-2 d-flex px-4 mb-3', { 'disabled ': isVersionReleased })}>
|
||||
<div className={`d-flex query-manager-border-color hr-text-left py-2 form-label font-weight-500`}>
|
||||
Datasource
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderChangeDataSource = () => {
|
||||
return (
|
||||
<div
|
||||
className={cx(`mt-2 pb-4`, {
|
||||
'disabled ': isVersionReleased,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={`border-top query-manager-border-color px-4 hr-text-left py-2 ${
|
||||
darkMode ? 'color-white' : 'color-light-slate-12'
|
||||
}`}
|
||||
>
|
||||
Change Datasource
|
||||
</div>
|
||||
<div className="d-flex flex-grow-1">
|
||||
<ChangeDataSource
|
||||
dataSources={[...globalDataSources, ...dataSources]}
|
||||
dataSources={selectableDataSources}
|
||||
value={selectedDataSource}
|
||||
selectedQuery={selectedQuery}
|
||||
onChange={(newDataSource) => {
|
||||
changeDataQuery(newDataSource);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (selectedQueryId !== selectedQuery?.id) return;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`row row-deck px-2 mt-0 query-details ${
|
||||
selectedDataSource?.kind === 'tooljetdb' ? 'tooljetdb-query-details' : ''
|
||||
}`}
|
||||
>
|
||||
{selectedDataSource === null ? renderDataSourcesList() : renderQueryElement()}
|
||||
{selectedDataSource !== null ? renderQueryOptions() : null}
|
||||
{selectedQuery?.data_source_id && mode === 'edit' && selectedDataSource !== null
|
||||
? renderChangeDataSource()
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
if (selectedQueryId !== selectedQuery?.id) return;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`row row-deck px-2 mt-0 query-details ${
|
||||
selectedDataSource?.kind === 'tooljetdb' ? 'tooljetdb-query-details' : ''
|
||||
}`}
|
||||
>
|
||||
{selectedQuery?.data_source_id && selectedDataSource !== null ? renderChangeDataSource() : null}
|
||||
|
||||
{selectedDataSource === null || !selectedQuery ? renderDataSourcesList() : renderQueryElement()}
|
||||
{selectedDataSource !== null ? renderQueryOptions() : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomToggleFlag = ({ dataCy, action, translatedLabel, label, value, toggleOption, darkMode, index }) => {
|
||||
const [flag, setFlag] = useState(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setFlag(value);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div className={cx({ 'pb-3 pt-3': index === 1 })}>
|
||||
<CustomToggleSwitch
|
||||
dataCy={dataCy}
|
||||
isChecked={flag}
|
||||
toggleSwitchFunction={(flag) => {
|
||||
setFlag((state) => !state);
|
||||
toggleOption(flag);
|
||||
}}
|
||||
action={action}
|
||||
darkMode={darkMode}
|
||||
label={t(translatedLabel, label)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,244 +1,245 @@
|
|||
import React, { useState, forwardRef } from 'react';
|
||||
import RunIcon from '../Icons/RunIcon';
|
||||
import BreadcrumbsIcon from '../Icons/BreadcrumbsIcon';
|
||||
import React, { useState, forwardRef, useRef, useEffect } from 'react';
|
||||
import RenameIcon from '../Icons/RenameIcon';
|
||||
import PreviewIcon from '../Icons/PreviewIcon';
|
||||
import CreateIcon from '../Icons/CreateIcon';
|
||||
import FloppyDisk from '@/_ui/Icon/solidIcons/FloppyDisk';
|
||||
import Eye1 from '@/_ui/Icon/solidIcons/Eye1';
|
||||
import Play from '@/_ui/Icon/solidIcons/Play';
|
||||
import cx from 'classnames';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { previewQuery, checkExistingQueryName, runQuery } from '@/_helpers/appUtils';
|
||||
|
||||
import { useDataQueriesActions, useQueryCreationLoading, useQueryUpdationLoading } from '@/_stores/dataQueriesStore';
|
||||
import { useSelectedQuery, useSelectedDataSource, useUnsavedChanges } from '@/_stores/queryPanelStore';
|
||||
import ToggleQueryEditorIcon from '../Icons/ToggleQueryEditorIcon';
|
||||
import {
|
||||
useSelectedQuery,
|
||||
useSelectedDataSource,
|
||||
usePreviewLoading,
|
||||
useShowCreateQuery,
|
||||
useNameInputFocussed,
|
||||
} from '@/_stores/queryPanelStore';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { Button } from 'react-bootstrap';
|
||||
|
||||
export const QueryManagerHeader = forwardRef(
|
||||
(
|
||||
{
|
||||
darkMode,
|
||||
mode,
|
||||
addNewQueryAndDeselectSelectedQuery,
|
||||
updateDraftQueryName,
|
||||
toggleQueryEditor,
|
||||
previewLoading = false,
|
||||
options,
|
||||
appId,
|
||||
editorRef,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { renameQuery, updateDataQuery, createDataQuery } = useDataQueriesActions();
|
||||
const selectedQuery = useSelectedQuery();
|
||||
const isCreationInProcess = useQueryCreationLoading();
|
||||
const isUpdationInProcess = useQueryUpdationLoading();
|
||||
const isUnsavedQueriesAvailable = useUnsavedChanges();
|
||||
const selectedDataSource = useSelectedDataSource();
|
||||
const { t } = useTranslation();
|
||||
const queryName = selectedQuery?.name ?? '';
|
||||
const [renamingQuery, setRenamingQuery] = useState(false);
|
||||
const { queries } = useCurrentState((state) => ({ queries: state.queries }), shallow);
|
||||
const { isVersionReleased, editingVersionId } = useAppVersionStore(
|
||||
(state) => ({
|
||||
isVersionReleased: state.isVersionReleased,
|
||||
editingVersionId: state.editingVersion?.id,
|
||||
}),
|
||||
shallow
|
||||
export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef }, ref) => {
|
||||
const { renameQuery } = useDataQueriesActions();
|
||||
const selectedQuery = useSelectedQuery();
|
||||
const selectedDataSource = useSelectedDataSource();
|
||||
const [showCreateQuery, setShowCreateQuery] = useShowCreateQuery();
|
||||
const queryName = selectedQuery?.name ?? '';
|
||||
const { queries } = useCurrentState((state) => ({ queries: state.queries }), shallow);
|
||||
const { isVersionReleased } = useAppVersionStore(
|
||||
(state) => ({
|
||||
isVersionReleased: state.isVersionReleased,
|
||||
editingVersionId: state.editingVersion?.id,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedQuery?.name) {
|
||||
setShowCreateQuery(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedQuery?.name]);
|
||||
|
||||
const isInDraft = selectedQuery?.status === 'draft';
|
||||
|
||||
const executeQueryNameUpdation = (newName) => {
|
||||
const { name } = selectedQuery;
|
||||
if (name === newName || !newName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isNewQueryNameAlreadyExists = checkExistingQueryName(newName);
|
||||
if (isNewQueryNameAlreadyExists) {
|
||||
toast.error('Query name already exists');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newName) {
|
||||
renameQuery(selectedQuery?.id, newName, editorRef);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const buttonLoadingState = (loading, disabled = false) => {
|
||||
return cx(
|
||||
`${loading ? (darkMode ? 'btn-loading' : 'button-loading') : ''}`,
|
||||
{ 'theme-dark ': darkMode },
|
||||
{ disabled: disabled || !selectedDataSource }
|
||||
);
|
||||
};
|
||||
|
||||
const buttonText = mode === 'edit' ? 'Save' : 'Create';
|
||||
const buttonDisabled = isUpdationInProcess || isCreationInProcess;
|
||||
|
||||
const executeQueryNameUpdation = (newName) => {
|
||||
const { id, name } = selectedQuery;
|
||||
if (name === newName) {
|
||||
return setRenamingQuery(false);
|
||||
}
|
||||
const isNewQueryNameAlreadyExists = checkExistingQueryName(newName);
|
||||
if (newName && !isNewQueryNameAlreadyExists) {
|
||||
if (id === 'draftQuery') {
|
||||
toast.success('Query Name Updated');
|
||||
updateDraftQueryName(newName);
|
||||
} else {
|
||||
renameQuery(selectedQuery?.id, newName, editorRef);
|
||||
}
|
||||
setRenamingQuery(false);
|
||||
} else {
|
||||
if (isNewQueryNameAlreadyExists) {
|
||||
toast.error('Query name already exists');
|
||||
}
|
||||
setRenamingQuery(false);
|
||||
}
|
||||
const previewButtonOnClick = () => {
|
||||
const _options = { ...options };
|
||||
const query = {
|
||||
data_source_id: selectedDataSource.id === 'null' ? null : selectedDataSource.id,
|
||||
pluginId: selectedDataSource.pluginId,
|
||||
options: _options,
|
||||
kind: selectedDataSource.kind,
|
||||
name: queryName,
|
||||
};
|
||||
const hasParamSupport = selectedQuery?.options?.hasParamSupport;
|
||||
previewQuery(editorRef, query, false, undefined, hasParamSupport)
|
||||
.then(() => {
|
||||
ref.current.scrollIntoView();
|
||||
})
|
||||
.catch(({ error, data }) => {
|
||||
console.log(error, data);
|
||||
});
|
||||
};
|
||||
|
||||
const createOrUpdateDataQuery = (shouldRunQuery = false) => {
|
||||
if (selectedQuery?.id === 'draftQuery') return createDataQuery(appId, editingVersionId, options, shouldRunQuery);
|
||||
if (isUnsavedQueriesAvailable) return updateDataQuery(options, shouldRunQuery);
|
||||
shouldRunQuery && runQuery(editorRef, selectedQuery?.id, selectedQuery?.name);
|
||||
};
|
||||
|
||||
const renderRenameInput = () => (
|
||||
<input
|
||||
data-cy={`query-rename-input`}
|
||||
type="text"
|
||||
className={cx('border-indigo-09 bg-transparent', { 'text-white': darkMode })}
|
||||
autoFocus
|
||||
defaultValue={queryName}
|
||||
onKeyUp={(event) => {
|
||||
event.persist();
|
||||
if (event.keyCode === 13) {
|
||||
executeQueryNameUpdation(event.target.value);
|
||||
}
|
||||
}}
|
||||
onBlur={({ target }) => executeQueryNameUpdation(target.value)}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderBreadcrumb = () => {
|
||||
if (selectedQuery === null) return;
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={`${darkMode ? 'color-light-gray-c3c3c3' : 'color-light-slate-11'}
|
||||
cursor-pointer font-weight-400`}
|
||||
onClick={addNewQueryAndDeselectSelectedQuery}
|
||||
data-cy={`query-type-header`}
|
||||
>
|
||||
{mode === 'create' ? 'New Query' : 'Queries'}
|
||||
</span>
|
||||
<span className="breadcrum">
|
||||
<BreadcrumbsIcon />
|
||||
</span>
|
||||
<div className="query-name-breadcrum d-flex align-items-center">
|
||||
<span
|
||||
className={cx('query-manager-header-query-name font-weight-400', { ellipsis: !renamingQuery })}
|
||||
data-cy={`query-name-label`}
|
||||
>
|
||||
{renamingQuery ? renderRenameInput() : queryName}
|
||||
</span>
|
||||
{!isVersionReleased && (
|
||||
<span
|
||||
className={cx('breadcrum-rename-query-icon', { 'd-none': renamingQuery && isVersionReleased })}
|
||||
onClick={() => setRenamingQuery(true)}
|
||||
>
|
||||
<RenameIcon />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const buttonLoadingState = (loading, disabled = false) => {
|
||||
return cx(
|
||||
`${loading ? (darkMode ? 'btn-loading' : 'button-loading') : ''}`,
|
||||
{ 'theme-dark ': darkMode },
|
||||
{ disabled: disabled || !selectedDataSource }
|
||||
);
|
||||
};
|
||||
|
||||
const previewButtonOnClick = () => {
|
||||
const _options = { ...options };
|
||||
const query = {
|
||||
data_source_id: selectedDataSource.id === 'null' ? null : selectedDataSource.id,
|
||||
pluginId: selectedDataSource.pluginId,
|
||||
options: _options,
|
||||
kind: selectedDataSource.kind,
|
||||
};
|
||||
const hasParamSupport = mode === 'create' || selectedQuery?.options?.hasParamSupport;
|
||||
previewQuery(editorRef, query, false, undefined, hasParamSupport)
|
||||
.then(() => {
|
||||
ref.current.scrollIntoView();
|
||||
})
|
||||
.catch(({ error, data }) => {
|
||||
console.log(error, data);
|
||||
});
|
||||
};
|
||||
|
||||
const renderPreviewButton = () => {
|
||||
return (
|
||||
const renderRunButton = () => {
|
||||
const { isLoading } = queries[selectedQuery?.name] ?? false;
|
||||
return (
|
||||
<span
|
||||
{...(isInDraft && {
|
||||
'data-tooltip-id': 'query-header-btn-run',
|
||||
'data-tooltip-content': 'Connect a data source to run',
|
||||
})}
|
||||
>
|
||||
<button
|
||||
onClick={previewButtonOnClick}
|
||||
className={`default-tertiary-button float-right1 ${buttonLoadingState(previewLoading)}`}
|
||||
data-cy={'query-preview-button'}
|
||||
>
|
||||
<span className="query-preview-svg d-flex align-items-center query-icon-wrapper">
|
||||
<PreviewIcon />
|
||||
</span>
|
||||
<span>{t('editor.queryManager.preview', 'Preview')}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const renderSaveButton = () => {
|
||||
return (
|
||||
<button
|
||||
className={`default-tertiary-button ${buttonLoadingState(
|
||||
isCreationInProcess || isUpdationInProcess,
|
||||
onClick={() => runQuery(editorRef, selectedQuery?.id, selectedQuery?.name)}
|
||||
className={`border-0 default-secondary-button float-right1 ${buttonLoadingState(
|
||||
isLoading,
|
||||
isVersionReleased
|
||||
)}`}
|
||||
onClick={() => createOrUpdateDataQuery(false)}
|
||||
disabled={buttonDisabled}
|
||||
data-cy={`query-${buttonText.toLowerCase()}-button`}
|
||||
>
|
||||
<span className="d-flex query-create-run-svg query-icon-wrapper">
|
||||
<CreateIcon />
|
||||
</span>
|
||||
<span>{buttonText}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const renderRunButton = () => {
|
||||
const { isLoading } = queries[selectedQuery?.name] ?? false;
|
||||
return (
|
||||
<button
|
||||
onClick={() => createOrUpdateDataQuery(true)}
|
||||
className={`border-0 default-secondary-button float-right1 ${buttonLoadingState(isLoading)}`}
|
||||
data-cy="query-run-button"
|
||||
disabled={isInDraft}
|
||||
{...(isInDraft && {
|
||||
'data-tooltip-id': 'query-header-btn-run',
|
||||
'data-tooltip-content': 'Publish the query to run',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
className={cx('query-manager-btn-svg-wrapper d-flex align-item-center query-icon-wrapper query-run-svg', {
|
||||
className={cx({
|
||||
invisible: isLoading,
|
||||
})}
|
||||
>
|
||||
<RunIcon />
|
||||
<Play width={14} fill="var(--indigo9)" viewBox="0 0 14 14" />
|
||||
</span>
|
||||
<span className="query-manager-btn-name">{isLoading ? ' ' : 'Run'}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const renderButtons = () => {
|
||||
if (selectedQuery === null) return;
|
||||
return (
|
||||
<>
|
||||
{renderPreviewButton()}
|
||||
{renderSaveButton()}
|
||||
{renderRunButton()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="row header">
|
||||
<div className="col font-weight-500">{renderBreadcrumb()}</div>
|
||||
<div className="query-header-buttons">
|
||||
{renderButtons()}
|
||||
<span
|
||||
onClick={toggleQueryEditor}
|
||||
className={`toggle-query-editor-svg m-3`}
|
||||
data-tooltip-id="tooltip-for-hide-query-editor"
|
||||
data-tooltip-content="Hide query editor"
|
||||
>
|
||||
<ToggleQueryEditorIcon />
|
||||
</span>
|
||||
<Tooltip id="tooltip-for-hide-query-editor" className="tooltip" />
|
||||
</div>
|
||||
</div>
|
||||
{isInDraft && <Tooltip id="query-header-btn-run" className="tooltip" />}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const renderButtons = () => {
|
||||
if (selectedQuery === null || showCreateQuery) return;
|
||||
return (
|
||||
<>
|
||||
<PreviewButton onClick={previewButtonOnClick} buttonLoadingState={buttonLoadingState} />
|
||||
{renderRunButton()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="row header">
|
||||
<div className="col font-weight-500">
|
||||
{selectedQuery && <NameInput onInput={executeQueryNameUpdation} value={queryName} darkMode={darkMode} />}
|
||||
</div>
|
||||
<div className="query-header-buttons me-3">{renderButtons()}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const PreviewButton = ({ buttonLoadingState, onClick }) => {
|
||||
const previewLoading = usePreviewLoading();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`default-tertiary-button float-right1 ${buttonLoadingState(previewLoading)}`}
|
||||
data-cy={'query-preview-button'}
|
||||
>
|
||||
<span className="query-preview-svg d-flex align-items-center query-icon-wrapper">
|
||||
<Eye1 width={14} fill="var(--slate9)" />
|
||||
</span>
|
||||
<span>{t('editor.queryManager.preview', 'Preview')}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const NameInput = ({ onInput, value, darkMode }) => {
|
||||
const [isFocussed, setIsFocussed] = useNameInputFocussed(false);
|
||||
const [name, setName] = useState(value);
|
||||
const isVersionReleased = useAppVersionStore((state) => state.isVersionReleased);
|
||||
const inputRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
setName(value);
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocussed) {
|
||||
inputRef.current?.focus();
|
||||
inputRef.current?.select();
|
||||
}
|
||||
}, [isFocussed]);
|
||||
|
||||
const handleChange = (event) => {
|
||||
const sanitizedValue = event.target.value.replace(/[ \t&]/g, '');
|
||||
setName(sanitizedValue);
|
||||
};
|
||||
|
||||
const handleInput = (newName) => {
|
||||
const result = onInput(newName);
|
||||
if (!result) {
|
||||
setName(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="query-name-breadcrum d-flex align-items-center ms-1">
|
||||
<span
|
||||
className="query-manager-header-query-name font-weight-400"
|
||||
data-cy={`query-name-label`}
|
||||
style={{ width: '150px' }}
|
||||
>
|
||||
{isFocussed ? (
|
||||
<input
|
||||
data-cy={`query-rename-input`}
|
||||
type="text"
|
||||
className={cx('border-indigo-09 bg-transparent query-rename-input py-1 px-2 rounded', {
|
||||
'text-white': darkMode,
|
||||
})}
|
||||
autoFocus
|
||||
ref={inputRef}
|
||||
onChange={handleChange}
|
||||
value={name}
|
||||
onKeyDown={(event) => {
|
||||
event.persist();
|
||||
if (event.keyCode === 13) {
|
||||
setIsFocussed(false);
|
||||
handleInput(event.target.value);
|
||||
}
|
||||
}}
|
||||
onBlur={({ target }) => {
|
||||
setIsFocussed(false);
|
||||
handleInput(target.value);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => setIsFocussed(true)}
|
||||
className={'bg-transparent justify-content-between color-slate12 w-100 px-2 py-1 rounded font-weight-500'}
|
||||
>
|
||||
<span className="text-truncate">{name} </span>
|
||||
<span
|
||||
className={cx('breadcrum-rename-query-icon', { 'd-none': isFocussed && isVersionReleased })}
|
||||
style={{ minWidth: 14 }}
|
||||
>
|
||||
<RenameIcon />
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CodeHinter } from '../../CodeBuilder/CodeHinter';
|
||||
|
||||
export default function SuccessNotificationInputs({ currentState, options, darkMode, optionchanged }) {
|
||||
const { t } = useTranslation();
|
||||
if (!options?.showSuccessNotification) {
|
||||
return <div className="mb-3"></div>;
|
||||
}
|
||||
return (
|
||||
<div className="me-4 mb-3 mt-2 pt-1" style={{ paddingLeft: '112px' }}>
|
||||
<div className="d-flex">
|
||||
<label className="form-label" data-cy={'label-success-message-input'} style={{ width: 150 }}>
|
||||
{t('editor.queryManager.successMessage', 'Message')}
|
||||
</label>
|
||||
<div className="flex-grow-1">
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
initialValue={options.successMessage}
|
||||
height="36px"
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
onChange={(value) => optionchanged('successMessage', value)}
|
||||
placeholder={t('editor.queryManager.queryRanSuccessfully', 'Query ran successfully')}
|
||||
cyLabel={'success-message'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
<label className="form-label" data-cy={'label-notification-duration-input'} style={{ width: 150 }}>
|
||||
{t('editor.queryManager.notificationDuration', 'duration (s)')}
|
||||
</label>
|
||||
{/* </div> */}
|
||||
<div className="flex-grow-1 query-manager-input-elem">
|
||||
<input
|
||||
type="number"
|
||||
disabled={!options.showSuccessNotification}
|
||||
onChange={(e) => optionchanged('notificationDuration', e.target.value)}
|
||||
placeholder={5}
|
||||
className="form-control"
|
||||
value={options.notificationDuration}
|
||||
data-cy={'notification-duration-input-field'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import { queryManagerSelectComponentStyle } from '@/_ui/Select/styles';
|
|||
import { Button } from '@/_ui/LeftSidebar';
|
||||
import { Tooltip as ReactTooltip } from 'react-tooltip';
|
||||
import { authenticationService } from '@/_services';
|
||||
import Information from '@/_ui/Icon/solidIcons/Information';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
|
||||
export const Transformation = ({ changeOption, options, darkMode, queryId }) => {
|
||||
|
|
@ -76,10 +77,6 @@ return [row for row in data if row['amount'] > 1000]
|
|||
useEffect(() => {
|
||||
const selectedQueryId = localStorage.getItem('selectedQuery') ?? null;
|
||||
|
||||
if (queryId === 'draftQuery') {
|
||||
setState(defaultValue);
|
||||
return;
|
||||
}
|
||||
if (selectedQueryId !== queryId) {
|
||||
const nonLangdefaultCode = getNonActiveTransformations(options?.transformationLanguage ?? 'javascript');
|
||||
const finalState = _.merge(
|
||||
|
|
@ -138,9 +135,12 @@ return [row for row in data if row['amount'] > 1000]
|
|||
};
|
||||
};
|
||||
|
||||
const popover = (
|
||||
<Popover id="transformation-popover-container">
|
||||
<p className="transformation-popover" data-cy={`transformation-popover`}>
|
||||
const labelPopoverContent = (
|
||||
<Popover
|
||||
id="transformation-popover-container"
|
||||
className={`${darkMode && 'popover-dark-themed theme-dark dark-theme tj-dark-mode'} p-0`}
|
||||
>
|
||||
<p className={`transformation-popover`} data-cy={`transformation-popover`}>
|
||||
{t(
|
||||
'editor.queryManager.transformation.transformationToolTip',
|
||||
'Transformations can be enabled on queries to transform the query results. ToolJet allows you to transform the query results using two programming languages: JavaScript and Python'
|
||||
|
|
@ -154,9 +154,136 @@ return [row for row in data if row['amount'] > 1000]
|
|||
</Popover>
|
||||
);
|
||||
|
||||
const popoverForRecommendation = (
|
||||
<Popover id="transformation-popover-container">
|
||||
<div className="transformation-popover card text-center">
|
||||
return (
|
||||
<div className="field transformation-editor">
|
||||
<div className="align-items-center gap-2" style={{ display: 'flex', position: 'relative', height: '20px' }}>
|
||||
<div className="d-flex flex-fill">
|
||||
<OverlayTrigger
|
||||
trigger="click"
|
||||
placement="top"
|
||||
rootClose
|
||||
overlay={labelPopoverContent}
|
||||
container={document.getElementsByClassName('query-details')[0]}
|
||||
>
|
||||
<span
|
||||
className="color-slate9 font-weight-500 form-label"
|
||||
data-cy={'label-query-transformation'}
|
||||
style={{ textDecoration: 'underline 2px dashed', textDecorationColor: 'var(--slate8)' }}
|
||||
>
|
||||
{t('editor.queryManager.transformation.transformations', 'Transformations')}
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
<div className="flex-grow-l">
|
||||
<div className=" d-flex">
|
||||
<div className="mb-0">
|
||||
<span className="d-flex">
|
||||
<CustomToggleSwitch
|
||||
isChecked={enableTransformation}
|
||||
toggleSwitchFunction={toggleEnableTransformation}
|
||||
action="enableTransformation"
|
||||
darkMode={darkMode}
|
||||
dataCy={'transformation'}
|
||||
/>
|
||||
<span className="ps-1">Enable</span>
|
||||
</span>
|
||||
</div>
|
||||
<EducativeLabel darkMode={darkMode} />
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br></br>
|
||||
<div className="d-flex">
|
||||
<div className="form-label"></div>
|
||||
<div className="col flex-grow-1">
|
||||
{enableTransformation && (
|
||||
<div
|
||||
className="rounded-3"
|
||||
style={{ marginBottom: '20px', background: `${darkMode ? '#272822' : '#F8F9FA'}` }}
|
||||
>
|
||||
<div className="py-3 px-3 d-flex justify-content-between">
|
||||
<div className="d-flex">
|
||||
<div className="d-flex align-items-center border transformation-language-select-wrapper">
|
||||
<span className="px-2">Language</span>
|
||||
</div>
|
||||
<Select
|
||||
options={[
|
||||
{ name: 'JavaScript', value: 'javascript' },
|
||||
{ name: 'Python', value: 'python' },
|
||||
]}
|
||||
value={lang}
|
||||
search={true}
|
||||
onChange={(value) => {
|
||||
setLang(value);
|
||||
changeOption('transformationLanguage', value);
|
||||
changeOption('transformation', state[value]);
|
||||
}}
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
styles={computeSelectStyles(darkMode, 140)}
|
||||
useCustomStyles={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-tooltip-id="tooltip-for-active-copilot"
|
||||
data-tooltip-content="Activate Copilot in the workspace settings"
|
||||
>
|
||||
<Button
|
||||
onClick={handleCallToGPT}
|
||||
darkMode={darkMode}
|
||||
size="sm"
|
||||
classNames={`${fetchingRecommendation ? (darkMode ? 'btn-loading' : 'button-loading') : ''}`}
|
||||
styles={{
|
||||
width: '100%',
|
||||
fontSize: '12px',
|
||||
fontWeight: 500,
|
||||
borderColor: darkMode && 'transparent',
|
||||
}}
|
||||
disabled={!isCopilotEnabled}
|
||||
>
|
||||
<Button.Content title={'Generate code'} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{!isCopilotEnabled && (
|
||||
<ReactTooltip
|
||||
id="tooltip-for-active-copilot"
|
||||
className="tooltip"
|
||||
style={{ backgroundColor: '#e6eefe', color: '#222' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="border-top mx-3"></div>
|
||||
<CodeHinter
|
||||
initialValue={state[lang]}
|
||||
mode={lang}
|
||||
theme={darkMode ? 'monokai' : 'base16-light'}
|
||||
lineNumbers={true}
|
||||
height={'300px'}
|
||||
className="query-hinter"
|
||||
ignoreBraces={true}
|
||||
onChange={(value) => changeOption('transformation', value)}
|
||||
componentName={`transformation`}
|
||||
cyLabel={'transformation-input'}
|
||||
callgpt={handleCallToGPT}
|
||||
isCopilotEnabled={isCopilotEnabled}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const EducativeLabel = ({ darkMode }) => {
|
||||
const popoverContent = (
|
||||
<Popover
|
||||
id={`transformation-popover-container`}
|
||||
className={`${darkMode && 'popover-dark-themed theme-dark dark-theme'} p-0`}
|
||||
>
|
||||
<div className={`transformation-popover card text-center ${darkMode && 'tj-dark-mode'}`}>
|
||||
<img src="/assets/images/icons/copilot.svg" alt="AI copilot" height={64} width={64} />
|
||||
<div className="d-flex flex-column card-body">
|
||||
<h4 className="mb-2">ToolJet x OpenAI</h4>
|
||||
|
|
@ -179,149 +306,30 @@ return [row for row in data if row['amount'] > 1000]
|
|||
</Popover>
|
||||
);
|
||||
|
||||
const EducativeLebel = () => {
|
||||
const title = () => {
|
||||
return (
|
||||
<>
|
||||
Powered by <strong style={{ fontWeight: 700, color: '#3E63DD' }}>AI copilot</strong>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const title = () => {
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<Button.UnstyledButton styles={{ height: '28px' }} darkMode={darkMode} classNames="mx-1">
|
||||
<Button.Content title={title} iconSrc={'assets/images/icons/flash.svg'} direction="left" />
|
||||
</Button.UnstyledButton>
|
||||
<OverlayTrigger trigger="click" placement="left" overlay={popoverForRecommendation} rootClose>
|
||||
<svg
|
||||
width="16.7"
|
||||
height="16.7"
|
||||
viewBox="0 0 20 21"
|
||||
fill="#3E63DD"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ cursor: 'pointer' }}
|
||||
data-cy={`transformation-info-icon`}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 2.5C5.58172 2.5 2 6.08172 2 10.5C2 14.9183 5.58172 18.5 10 18.5C14.4183 18.5 18 14.9183 18 10.5C18 6.08172 14.4183 2.5 10 2.5ZM0 10.5C0 4.97715 4.47715 0.5 10 0.5C15.5228 0.5 20 4.97715 20 10.5C20 16.0228 15.5228 20.5 10 20.5C4.47715 20.5 0 16.0228 0 10.5ZM9 6.5C9 5.94772 9.44771 5.5 10 5.5H10.01C10.5623 5.5 11.01 5.94772 11.01 6.5C11.01 7.05228 10.5623 7.5 10.01 7.5H10C9.44771 7.5 9 7.05228 9 6.5ZM8 10.5C8 9.94771 8.44772 9.5 9 9.5H10C10.5523 9.5 11 9.94771 11 10.5V13.5C11.5523 13.5 12 13.9477 12 14.5C12 15.0523 11.5523 15.5 11 15.5H10C9.44771 15.5 9 15.0523 9 14.5V11.5C8.44772 11.5 8 11.0523 8 10.5Z"
|
||||
fill="#3E63DD"
|
||||
/>
|
||||
</svg>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
<>
|
||||
Powered by <strong style={{ fontWeight: 700, color: '#3E63DD' }}>AI copilot</strong>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="field transformation-editor">
|
||||
<div className="align-items-center gap-2" style={{ display: 'flex', position: 'relative', height: '20px' }}>
|
||||
<div className="d-flex flex-fill">
|
||||
<div className="mb-0">
|
||||
<CustomToggleSwitch
|
||||
isChecked={enableTransformation}
|
||||
toggleSwitchFunction={toggleEnableTransformation}
|
||||
action="enableTransformation"
|
||||
darkMode={darkMode}
|
||||
dataCy={'transformation'}
|
||||
/>
|
||||
</div>
|
||||
<span className="mx-1 font-weight-400 tranformation-label" data-cy={'label-query-transformation'}>
|
||||
{t('editor.queryManager.transformation.transformations', 'Transformations')}
|
||||
</span>
|
||||
<OverlayTrigger trigger="click" placement="top" overlay={popover} rootClose>
|
||||
<svg
|
||||
width="16.7"
|
||||
height="16.7"
|
||||
viewBox="0 0 20 21"
|
||||
fill="#3E63DD"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ cursor: 'pointer' }}
|
||||
data-cy={`transformation-info-icon`}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 2.5C5.58172 2.5 2 6.08172 2 10.5C2 14.9183 5.58172 18.5 10 18.5C14.4183 18.5 18 14.9183 18 10.5C18 6.08172 14.4183 2.5 10 2.5ZM0 10.5C0 4.97715 4.47715 0.5 10 0.5C15.5228 0.5 20 4.97715 20 10.5C20 16.0228 15.5228 20.5 10 20.5C4.47715 20.5 0 16.0228 0 10.5ZM9 6.5C9 5.94772 9.44771 5.5 10 5.5H10.01C10.5623 5.5 11.01 5.94772 11.01 6.5C11.01 7.05228 10.5623 7.5 10.01 7.5H10C9.44771 7.5 9 7.05228 9 6.5ZM8 10.5C8 9.94771 8.44772 9.5 9 9.5H10C10.5523 9.5 11 9.94771 11 10.5V13.5C11.5523 13.5 12 13.9477 12 14.5C12 15.0523 11.5523 15.5 11 15.5H10C9.44771 15.5 9 15.0523 9 14.5V11.5C8.44772 11.5 8 11.0523 8 10.5Z"
|
||||
fill="#3E63DD"
|
||||
/>
|
||||
</svg>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
|
||||
<EducativeLebel />
|
||||
</div>
|
||||
<br></br>
|
||||
{enableTransformation && (
|
||||
<div
|
||||
className="rounded-3"
|
||||
style={{ marginLeft: '3rem', marginBottom: '20px', background: `${darkMode ? '#272822' : '#F8F9FA'}` }}
|
||||
>
|
||||
<div className="py-3 px-3 d-flex justify-content-between">
|
||||
<div className="d-flex">
|
||||
<div className="d-flex align-items-center border transformation-language-select-wrapper">
|
||||
<span className="px-2">Language</span>
|
||||
</div>
|
||||
<Select
|
||||
options={[
|
||||
{ name: 'JavaScript', value: 'javascript' },
|
||||
{ name: 'Python', value: 'python' },
|
||||
]}
|
||||
value={lang}
|
||||
search={true}
|
||||
onChange={(value) => {
|
||||
setLang(value);
|
||||
changeOption('transformationLanguage', value);
|
||||
changeOption('transformation', state[value]);
|
||||
}}
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
styles={computeSelectStyles(darkMode, 140)}
|
||||
useCustomStyles={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-tooltip-id="tooltip-for-active-copilot"
|
||||
data-tooltip-content="Activate Copilot in the workspace settings"
|
||||
>
|
||||
<Button
|
||||
onClick={handleCallToGPT}
|
||||
darkMode={darkMode}
|
||||
size="sm"
|
||||
classNames={`${fetchingRecommendation ? (darkMode ? 'btn-loading' : 'button-loading') : ''}`}
|
||||
styles={{ width: '100%', fontSize: '12px', fontWeight: 500, borderColor: darkMode && 'transparent' }}
|
||||
disabled={!isCopilotEnabled}
|
||||
>
|
||||
<Button.Content title={'Generate code'} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{!isCopilotEnabled && (
|
||||
<ReactTooltip
|
||||
id="tooltip-for-active-copilot"
|
||||
className="tooltip"
|
||||
style={{ backgroundColor: '#e6eefe', color: '#222' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="border-top mx-3"></div>
|
||||
<CodeHinter
|
||||
initialValue={state[lang]}
|
||||
mode={lang}
|
||||
theme={darkMode ? 'monokai' : 'base16-light'}
|
||||
lineNumbers={true}
|
||||
height={'300px'}
|
||||
className="query-hinter"
|
||||
ignoreBraces={true}
|
||||
onChange={(value) => changeOption('transformation', value)}
|
||||
componentName={`transformation`}
|
||||
cyLabel={'transformation-input'}
|
||||
callgpt={handleCallToGPT}
|
||||
isCopilotEnabled={isCopilotEnabled}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="d-flex">
|
||||
<Button.UnstyledButton styles={{ height: '28px' }} darkMode={darkMode} classNames="mx-1">
|
||||
<Button.Content title={title} iconSrc={'assets/images/icons/flash.svg'} direction="left" />
|
||||
</Button.UnstyledButton>
|
||||
<OverlayTrigger
|
||||
overlay={popoverContent}
|
||||
rootClose
|
||||
trigger="click"
|
||||
placement="right"
|
||||
container={document.getElementsByClassName('query-details')[0]}
|
||||
>
|
||||
<span style={{ cursor: 'pointer' }} data-cy={`transformation-info-icon`} className="lh-1">
|
||||
<Information width={18} fill={'var(--indigo9)'} />
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ export const BaseUrl = ({ dataSourceURL, theme }) => {
|
|||
style={{
|
||||
padding: '5px',
|
||||
border: theme === 'default' ? '1px solid rgb(217 220 222)' : '1px solid white',
|
||||
borderRightWidth: 0,
|
||||
background: theme === 'default' ? 'rgb(246 247 251)' : '#20211e',
|
||||
color: theme === 'default' ? '#9ca1a6' : '#9e9e9e',
|
||||
height: '32px',
|
||||
borderRadius: '6px 0 0 6px',
|
||||
}}
|
||||
>
|
||||
{dataSourceURL}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import GroupHeader from './GroupHeader';
|
||||
import TabContent from './TabContent';
|
||||
|
||||
export default ({
|
||||
|
|
@ -12,11 +11,9 @@ export default ({
|
|||
onJsonBodyChange,
|
||||
componentName,
|
||||
bodyToggle,
|
||||
setBodyToggle,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<GroupHeader paramType={'body'} descText={'Raw JSON'} bodyToggle={bodyToggle} setBodyToggle={setBodyToggle} />
|
||||
<TabContent
|
||||
options={options}
|
||||
theme={theme}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CodeHinter } from '../../../CodeBuilder/CodeHinter';
|
||||
import AddRectangle from '@/_ui/Icon/bulkIcons/AddRectangle';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import Trash from '@/_ui/Icon/solidIcons/Trash';
|
||||
|
||||
export default ({
|
||||
options = [],
|
||||
|
|
@ -14,6 +18,7 @@ export default ({
|
|||
bodyToggle,
|
||||
addNewKeyValuePair,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
|
||||
return (
|
||||
|
|
@ -22,10 +27,9 @@ export default ({
|
|||
options.map((option, index) => {
|
||||
return (
|
||||
<>
|
||||
<div className="row-container border-bottom query-manager-border-color" key={index}>
|
||||
<div className="fields-container ">
|
||||
<div className="d-flex justify-content-center align-items-center query-number">{index + 1}</div>
|
||||
<div className="field col-4 overflow-hidden">
|
||||
<div className="row-container query-manager-border-color" key={index}>
|
||||
<div className="fields-container mb-2">
|
||||
<div className="field col-4 overflow-hidden border-top border-bottom border-start rounded-start">
|
||||
<CodeHinter
|
||||
initialValue={option[0]}
|
||||
theme={theme}
|
||||
|
|
@ -35,7 +39,7 @@ export default ({
|
|||
componentName={`${componentName}/${tabType}::key::${index}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="field col overflow-hidden">
|
||||
<div className="field col overflow-hidden border ">
|
||||
<CodeHinter
|
||||
initialValue={option[1]}
|
||||
theme={theme}
|
||||
|
|
@ -45,30 +49,17 @@ export default ({
|
|||
componentName={`${componentName}/${tabType}::value::${index}`}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center delete-field-option"
|
||||
<button
|
||||
className={`d-flex justify-content-center align-items-center delete-field-option bg-transparent border-0 rounded-0 border-top border-bottom border-end rounded-end ${
|
||||
darkMode ? 'delete-field-option-dark' : ''
|
||||
}`}
|
||||
role="button"
|
||||
onClick={() => {
|
||||
removeKeyValuePair(paramType, index);
|
||||
}}
|
||||
>
|
||||
<span className="rest-api-delete-field-option query-icon-wrapper d-flex">
|
||||
<svg
|
||||
width="auto"
|
||||
height="auto"
|
||||
viewBox="0 0 18 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.58579 0.585786C5.96086 0.210714 6.46957 0 7 0H11C11.5304 0 12.0391 0.210714 12.4142 0.585786C12.7893 0.960859 13 1.46957 13 2V4H15.9883C15.9953 3.99993 16.0024 3.99993 16.0095 4H17C17.5523 4 18 4.44772 18 5C18 5.55228 17.5523 6 17 6H16.9201L15.9997 17.0458C15.9878 17.8249 15.6731 18.5695 15.1213 19.1213C14.5587 19.6839 13.7957 20 13 20H5C4.20435 20 3.44129 19.6839 2.87868 19.1213C2.32687 18.5695 2.01223 17.8249 2.00035 17.0458L1.07987 6H1C0.447715 6 0 5.55228 0 5C0 4.44772 0.447715 4 1 4H1.99054C1.9976 3.99993 2.00466 3.99993 2.0117 4H5V2C5 1.46957 5.21071 0.960859 5.58579 0.585786ZM3.0868 6L3.99655 16.917C3.99885 16.9446 4 16.9723 4 17C4 17.2652 4.10536 17.5196 4.29289 17.7071C4.48043 17.8946 4.73478 18 5 18H13C13.2652 18 13.5196 17.8946 13.7071 17.7071C13.8946 17.5196 14 17.2652 14 17C14 16.9723 14.0012 16.9446 14.0035 16.917L14.9132 6H3.0868ZM11 4H7V2H11V4ZM6.29289 10.7071C5.90237 10.3166 5.90237 9.68342 6.29289 9.29289C6.68342 8.90237 7.31658 8.90237 7.70711 9.29289L9 10.5858L10.2929 9.29289C10.6834 8.90237 11.3166 8.90237 11.7071 9.29289C12.0976 9.68342 12.0976 10.3166 11.7071 10.7071L10.4142 12L11.7071 13.2929C12.0976 13.6834 12.0976 14.3166 11.7071 14.7071C11.3166 15.0976 10.6834 15.0976 10.2929 14.7071L9 13.4142L7.70711 14.7071C7.31658 15.0976 6.68342 15.0976 6.29289 14.7071C5.90237 14.3166 5.90237 13.6834 6.29289 13.2929L7.58579 12L6.29289 10.7071Z"
|
||||
fill="#DB4324"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<Trash fill="var(--slate9)" style={{ height: '16px' }} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
@ -88,25 +79,11 @@ export default ({
|
|||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="d-flex" style={{ maxHeight: '32px' }}>
|
||||
<div
|
||||
className="d-flex align-items-center justify-content-center add-tabs "
|
||||
style={{ flex: '0 0 32px', background: darkMode ? 'inherit' : '#F8F9FA', height: '32px' }}
|
||||
onClick={() => addNewKeyValuePair(paramType)}
|
||||
role="button"
|
||||
>
|
||||
<span className="rest-api-add-field-svg">
|
||||
<svg width="auto" height="auto" viewBox="0 0 24 25" fill="#5677E1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12 4.5C12.5523 4.5 13 4.94772 13 5.5V11.5H19C19.5523 11.5 20 11.9477 20 12.5C20 13.0523 19.5523 13.5 19 13.5H13V19.5C13 20.0523 12.5523 20.5 12 20.5C11.4477 20.5 11 20.0523 11 19.5V13.5H5C4.44772 13.5 4 13.0523 4 12.5C4 11.9477 4.44772 11.5 5 11.5H11V5.5C11 4.94772 11.4477 4.5 12 4.5Z"
|
||||
fill="#3E63DD"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div className="col" style={{ flex: '1', background: darkMode ? '' : '#ffffff' }}></div>
|
||||
<div className="d-flex mb-2" style={{ maxHeight: '32px' }}>
|
||||
<ButtonSolid variant="ghostBlue" size="sm" onClick={() => addNewKeyValuePair(paramType)}>
|
||||
<AddRectangle width="15" fill="#3E63DD" opacity="1" secondaryFill="#ffffff" />
|
||||
{t('editor.inspector.eventManager.addKeyValueParam', 'Add more')}
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import React from 'react';
|
||||
import TabContent from './TabContent';
|
||||
import GroupHeader from './GroupHeader';
|
||||
|
||||
export default ({ options = [], theme, removeKeyValuePair, addNewKeyValuePair, onChange, componentName }) => {
|
||||
return (
|
||||
<>
|
||||
<GroupHeader paramType={'headers'} descText="Query Headers" />
|
||||
<TabContent
|
||||
options={options}
|
||||
theme={theme}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import React from 'react';
|
||||
import GroupHeader from './GroupHeader';
|
||||
import TabContent from './TabContent';
|
||||
|
||||
export default ({ options = [], theme, removeKeyValuePair, addNewKeyValuePair, onChange, componentName }) => {
|
||||
return (
|
||||
<>
|
||||
<GroupHeader paramType={'url_params'} descText={'Query Parameters'} />
|
||||
<TabContent
|
||||
options={options}
|
||||
theme={theme}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import Headers from './TabHeaders';
|
|||
import Params from './TabParams';
|
||||
import Body from './TabBody';
|
||||
import { Tab, ListGroup, Row } from 'react-bootstrap';
|
||||
import { CustomToggleSwitch } from '@/Editor/QueryManager/Components/CustomToggleSwitch';
|
||||
|
||||
function ControlledTabs({
|
||||
options,
|
||||
|
|
@ -22,21 +23,29 @@ function ControlledTabs({
|
|||
return (
|
||||
<Tab.Container activeKey={key} onSelect={(k) => setKey(k)} defaultActiveKey="headers">
|
||||
<Row>
|
||||
<div className="keys">
|
||||
<ListGroup className="query-pane-rest-api-keys-list-group mx-1" variant="flush">
|
||||
<div className="keys d-flex justify-content-between">
|
||||
<ListGroup className="query-pane-rest-api-keys-list-group mx-1 mb-2" variant="flush">
|
||||
{tabs.map((tab) => (
|
||||
<ListGroup.Item key={tab} eventKey={tab.toLowerCase()}>
|
||||
<span>{tab}</span>
|
||||
</ListGroup.Item>
|
||||
))}
|
||||
</ListGroup>
|
||||
{key === 'body' && (
|
||||
<div className="text-nowrap d-flex align-items-center">
|
||||
Raw JSON
|
||||
<CustomToggleSwitch
|
||||
toggleSwitchFunction={setBodyToggle}
|
||||
action="bodyToggle"
|
||||
darkMode={darkMode}
|
||||
isChecked={bodyToggle}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={`col ${darkMode && 'theme-dark'}`}>
|
||||
<Tab.Content
|
||||
bsPrefix="rest-api-tab-content"
|
||||
className="border overflow-hidden query-manager-border-color rounded"
|
||||
>
|
||||
<Tab.Content bsPrefix="rest-api-tab-content" className="query-manager-border-color rounded">
|
||||
<Tab.Pane eventKey="headers" t bsPrefix="rest-api-tabpanes" transition={false}>
|
||||
<Headers
|
||||
removeKeyValuePair={removeKeyValuePair}
|
||||
|
|
@ -69,7 +78,6 @@ function ControlledTabs({
|
|||
jsonBody={options['json_body']}
|
||||
theme={theme}
|
||||
bodyToggle={bodyToggle}
|
||||
setBodyToggle={setBodyToggle}
|
||||
darkMode={darkMode}
|
||||
componentName={componentName}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class Restapi extends React.Component {
|
|||
super(props);
|
||||
const options = defaults(
|
||||
{ ...props.options },
|
||||
{ headers: [], url_params: [], body: [], json_body: null, body_toggle: false }
|
||||
{ headers: [['', '']], url_params: [], body: [], json_body: null, body_toggle: false }
|
||||
);
|
||||
this.state = {
|
||||
options,
|
||||
|
|
@ -26,6 +26,9 @@ class Restapi extends React.Component {
|
|||
if (isEmpty(this.state.options['headers'])) {
|
||||
this.addNewKeyValuePair('headers');
|
||||
}
|
||||
if (isEmpty(this.state.options['method'])) {
|
||||
changeOption(this, 'method', 'get');
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (isEmpty(this.state.options['url_params'])) {
|
||||
this.addNewKeyValuePair('url_params');
|
||||
|
|
@ -54,7 +57,10 @@ class Restapi extends React.Component {
|
|||
const newOptions = { ...options, [option]: [...options[option], ['', '']] };
|
||||
|
||||
this.setState({ options: newOptions }, () => {
|
||||
this.props.optionsChanged(newOptions);
|
||||
//these values are set to empty array so that user can type in directly without adding new entry, hence no need to pass to parent state
|
||||
if (!['headers', 'url_params', 'body'].includes(option)) {
|
||||
this.props.optionsChanged(newOptions);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -88,7 +94,9 @@ class Restapi extends React.Component {
|
|||
|
||||
handleChange = (key, keyIndex, idx) => (value) => {
|
||||
const lastPair = this.state.options[key][idx];
|
||||
if (this.state.options[key].length - 1 === idx && (lastPair[0] || lastPair[1])) this.addNewKeyValuePair(key);
|
||||
if (this.state.options[key].length - 1 === idx && (lastPair[0] || lastPair[1])) {
|
||||
this.addNewKeyValuePair(key);
|
||||
}
|
||||
this.keyValuePairValueChanged(value, keyIndex, key, idx);
|
||||
};
|
||||
|
||||
|
|
@ -98,11 +106,11 @@ class Restapi extends React.Component {
|
|||
control: (provided) => ({
|
||||
...provided,
|
||||
boxShadow: 'none',
|
||||
backgroundColor: darkMode ? '#2b3547' : '#F1F3F5',
|
||||
borderRadius: '6px 0 0 6px',
|
||||
...(darkMode && { backgroundColor: '#2b3547' }),
|
||||
borderRadius: '6px',
|
||||
height: 32,
|
||||
minHeight: 32,
|
||||
borderColor: darkMode ? 'inherit' : ' #D7DBDF',
|
||||
borderColor: 'var(--slate7)',
|
||||
borderWidth: '1px',
|
||||
'&:hover': {
|
||||
backgroundColor: darkMode ? '' : '#F8F9FA',
|
||||
|
|
@ -125,64 +133,72 @@ class Restapi extends React.Component {
|
|||
const currentValue = { label: options.method?.toUpperCase(), value: options.method };
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="rest-api-methods-select-element-container">
|
||||
<div className={`${this.props.darkMode && 'dark'}`} style={{ width: '90px', height: '32px' }}>
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'GET', value: 'get' },
|
||||
{ label: 'POST', value: 'post' },
|
||||
{ label: 'PUT', value: 'put' },
|
||||
{ label: 'PATCH', value: 'patch' },
|
||||
{ label: 'DELETE', value: 'delete' },
|
||||
]}
|
||||
onChange={(value) => {
|
||||
changeOption(this, 'method', value);
|
||||
}}
|
||||
value={currentValue}
|
||||
defaultValue={{ label: 'GET', value: 'get' }}
|
||||
placeholder="Method"
|
||||
width={100}
|
||||
height={32}
|
||||
styles={this.customSelectStyles(this.props.darkMode, 91)}
|
||||
useCustomStyles={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={`col field w-100 d-flex rest-methods-url ${this.props.darkMode && 'dark'}`}>
|
||||
{dataSourceURL && (
|
||||
<BaseUrl theme={this.props.darkMode ? 'monokai' : 'default'} dataSourceURL={dataSourceURL} />
|
||||
)}
|
||||
<div className="col">
|
||||
<CodeHinter
|
||||
initialValue={options.url}
|
||||
theme={this.props.darkMode ? 'monokai' : 'default'}
|
||||
<div className={`d-flex`}>
|
||||
<div className="form-label">Request</div>
|
||||
<div className="flex-grow-1">
|
||||
<div className="rest-api-methods-select-element-container">
|
||||
<div className={`me-2`} style={{ width: '90px', height: '32px' }}>
|
||||
<label className="font-weight-bold color-slate12">Method</label>
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'GET', value: 'get' },
|
||||
{ label: 'POST', value: 'post' },
|
||||
{ label: 'PUT', value: 'put' },
|
||||
{ label: 'PATCH', value: 'patch' },
|
||||
{ label: 'DELETE', value: 'delete' },
|
||||
]}
|
||||
onChange={(value) => {
|
||||
changeOption(this, 'url', value);
|
||||
changeOption(this, 'method', value);
|
||||
}}
|
||||
placeholder="Enter request URL"
|
||||
componentName={`${queryName}::url`}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
height={'32px'}
|
||||
value={currentValue}
|
||||
defaultValue={{ label: 'GET', value: 'get' }}
|
||||
placeholder="Method"
|
||||
width={100}
|
||||
height={32}
|
||||
styles={this.customSelectStyles(this.props.darkMode, 91)}
|
||||
useCustomStyles={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`query-pane-restapi-tabs ${this.props.darkMode ? 'dark' : ''}`}>
|
||||
<Tabs
|
||||
theme={this.props.darkMode ? 'monokai' : 'default'}
|
||||
options={this.state.options}
|
||||
onChange={this.handleChange}
|
||||
onJsonBodyChange={this.handleJsonBodyChanged}
|
||||
removeKeyValuePair={this.removeKeyValuePair}
|
||||
addNewKeyValuePair={this.addNewKeyValuePair}
|
||||
darkMode={this.props.darkMode}
|
||||
componentName={queryName}
|
||||
bodyToggle={this.state.options.body_toggle}
|
||||
setBodyToggle={this.onBodyToggleChanged}
|
||||
/>
|
||||
<div className={`field w-100 rest-methods-url`}>
|
||||
<div className="font-weight-bold color-slate12">URL</div>
|
||||
<div className="d-flex">
|
||||
{dataSourceURL && (
|
||||
<BaseUrl theme={this.props.darkMode ? 'monokai' : 'default'} dataSourceURL={dataSourceURL} />
|
||||
)}
|
||||
<div className={`flex-grow-1 ${dataSourceURL ? 'url-input-group' : ''}`}>
|
||||
<CodeHinter
|
||||
currentState={this.props.currentState}
|
||||
initialValue={options.url}
|
||||
theme={this.props.darkMode ? 'monokai' : 'default'}
|
||||
onChange={(value) => {
|
||||
changeOption(this, 'url', value);
|
||||
}}
|
||||
placeholder={dataSourceURL ? 'Enter request endpoint' : 'Enter request URL'}
|
||||
componentName={`${queryName}::url`}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
height={'32px'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`query-pane-restapi-tabs ${this.props.darkMode ? 'dark' : ''}`}>
|
||||
<Tabs
|
||||
theme={this.props.darkMode ? 'monokai' : 'default'}
|
||||
options={this.state.options}
|
||||
onChange={this.handleChange}
|
||||
onJsonBodyChange={this.handleJsonBodyChanged}
|
||||
removeKeyValuePair={this.removeKeyValuePair}
|
||||
addNewKeyValuePair={this.addNewKeyValuePair}
|
||||
darkMode={this.props.darkMode}
|
||||
componentName={queryName}
|
||||
bodyToggle={this.state.options.body_toggle}
|
||||
setBodyToggle={this.onBodyToggleChanged}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ const ParameterDetails = ({ darkMode, onSubmit, isEdit, name, defaultValue, onRe
|
|||
onClick={() => setShowModal((show) => !show)}
|
||||
className="ms-2"
|
||||
id="runjs-param-add-btn"
|
||||
data-cy={`runjs-add-param-button`}
|
||||
>
|
||||
<span className="m-0">
|
||||
<PlusRectangle fill={'#3E63DD'} width={15} />
|
||||
|
|
@ -93,11 +94,15 @@ const ParameterDetails = ({ darkMode, onSubmit, isEdit, name, defaultValue, onRe
|
|||
);
|
||||
};
|
||||
|
||||
export const PillButton = ({ name, onClick, onRemove, marginBottom }) => (
|
||||
<ButtonGroup aria-label="Parameter" className={cx('ms-2', { 'mb-2': marginBottom })}>
|
||||
export const PillButton = ({ name, onClick, onRemove, marginBottom, className, size }) => (
|
||||
<ButtonGroup
|
||||
aria-label="Parameter"
|
||||
className={cx('ms-2 bg-slate3', { 'mb-2': marginBottom, ...(className && { [className]: true }) })}
|
||||
style={{ borderRadius: '15px' }}
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-slate3 color-slate12 runjs-parameter-badge"
|
||||
className={cx('bg-transparent color-slate12 runjs-parameter-badge', { 'py-0 px-2': size === 'sm' })}
|
||||
onClick={onClick}
|
||||
style={{
|
||||
borderTopLeftRadius: '15px',
|
||||
|
|
@ -108,13 +113,16 @@ export const PillButton = ({ name, onClick, onRemove, marginBottom }) => (
|
|||
...(!onRemove && { borderRadius: '15px' }),
|
||||
}}
|
||||
>
|
||||
<span className="text-truncate">{name}</span>
|
||||
<span data-cy={`query-param-${String(name).toLowerCase()}`} className="text-truncate">
|
||||
{name}
|
||||
</span>
|
||||
</Button>
|
||||
{onRemove && (
|
||||
<Button
|
||||
data-cy={`query-param-${String(name).toLowerCase()}-remove-button`}
|
||||
onClick={onRemove}
|
||||
size="sm"
|
||||
className="bg-slate3 color-slate12"
|
||||
className={cx('bg-transparent color-slate12', { 'p-0 pe-1': size === 'sm' })}
|
||||
style={{
|
||||
borderTopRightRadius: '15px',
|
||||
borderBottomRightRadius: '15px',
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ const Runjs = (props) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<Card className="runjs-editor">
|
||||
<Card className="runjs-editor mb-3">
|
||||
{(options.hasParamSupport || props.mode === 'create') && (
|
||||
<ParameterList
|
||||
parameters={options.parameters}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export class Runpy extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className="runps-editor">
|
||||
<CodeHinter
|
||||
initialValue={this.props.options.code}
|
||||
mode="python"
|
||||
|
|
|
|||
|
|
@ -186,11 +186,11 @@ class StripeComponent extends React.Component {
|
|||
|
||||
{options && !loadingSpec && (
|
||||
<div>
|
||||
<div className="row g-2">
|
||||
<div className="col-12">
|
||||
<div className="d-flex g-2">
|
||||
<div className="col-12 form-label">
|
||||
<label className="form-label">{this.props.t('globals.operation', 'Operation')}</label>
|
||||
</div>
|
||||
<div className="col stripe-operation-options" style={{ width: '90px', marginTop: 0 }}>
|
||||
<div className="col stripe-operation-options flex-grow-1" style={{ width: '90px', marginTop: 0 }}>
|
||||
<Select
|
||||
options={this.computeOperationSelectionOptions(specJson)}
|
||||
value={currentValue}
|
||||
|
|
@ -325,13 +325,13 @@ class StripeComponent extends React.Component {
|
|||
|
||||
{requestBody.schema.properties && (
|
||||
<div
|
||||
className={`request-body-fields ${
|
||||
className={`request-body-fields d-flex ${
|
||||
Object.keys(requestBody.schema.properties).length === 0 && 'd-none'
|
||||
} `}
|
||||
>
|
||||
<h5 className="text-heading">{this.props.t('globals.requestBody', 'REQUEST BODY')}</h5>
|
||||
<h5 className="text-heading form-label">{this.props.t('globals.requestBody', 'REQUEST BODY')}</h5>
|
||||
<div
|
||||
className={`${
|
||||
className={`flex-grow-1 ${
|
||||
Object.keys(requestBody.schema.properties).length >= 1 && 'input-group-parent-container'
|
||||
}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ import React, { useState, useEffect, useContext } from 'react';
|
|||
import { CodeHinter } from '@/Editor/CodeBuilder/CodeHinter';
|
||||
import { TooljetDatabaseContext } from '@/TooljetDatabase/index';
|
||||
import Select from '@/_ui/Select';
|
||||
import { uniqueId } from 'lodash';
|
||||
import { isEmpty, uniqueId } from 'lodash';
|
||||
import { useMounted } from '@/_hooks/use-mount';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
|
||||
export const CreateRow = React.memo(({ optionchanged, options, darkMode }) => {
|
||||
const mounted = useMounted();
|
||||
|
|
@ -42,105 +43,35 @@ export const CreateRow = React.memo(({ optionchanged, options, darkMode }) => {
|
|||
handleColumnOptionChange({ ...existingColumnOption, ...{ [uniqueId()]: emptyColumnOption } });
|
||||
}
|
||||
|
||||
const RenderColumnOptions = ({ column, value, id }) => {
|
||||
const filteredColumns = columns.filter(({ isPrimaryKey }) => !isPrimaryKey);
|
||||
const existingColumnOption = Object.values ? Object.values(columnOptions) : [];
|
||||
let displayColumns = filteredColumns.map(({ accessor }) => ({
|
||||
value: accessor,
|
||||
label: accessor,
|
||||
}));
|
||||
|
||||
if (existingColumnOption.length > 0) {
|
||||
displayColumns = displayColumns.filter(
|
||||
({ value }) => !existingColumnOption.map((item) => item.column !== column && item.column).includes(value)
|
||||
);
|
||||
}
|
||||
|
||||
const handleColumnChange = (selectedOption) => {
|
||||
const updatedOption = {
|
||||
...columnOptions[id],
|
||||
column: selectedOption,
|
||||
};
|
||||
|
||||
const newColumnOptions = { ...columnOptions, [id]: updatedOption };
|
||||
|
||||
handleColumnOptionChange(newColumnOptions);
|
||||
};
|
||||
|
||||
const handleValueChange = (newValue) => {
|
||||
const updatedOption = {
|
||||
...columnOptions[id],
|
||||
value: newValue,
|
||||
};
|
||||
|
||||
const newColumnOptions = { ...columnOptions, [id]: updatedOption };
|
||||
|
||||
handleColumnOptionChange(newColumnOptions);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-1 row-container">
|
||||
<div className="d-flex fields-container">
|
||||
<div className="field col-4">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select column"
|
||||
value={column}
|
||||
options={displayColumns}
|
||||
onChange={handleColumnChange}
|
||||
customWrap={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field col-4">
|
||||
<CodeHinter
|
||||
initialValue={value ? (typeof value === 'string' ? value : JSON.stringify(value)) : value}
|
||||
className="codehinter-plugins"
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
height={'32px'}
|
||||
placeholder="key"
|
||||
onChange={(newValue) => handleValueChange(newValue)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col cursor-pointer m-1 mx-3">
|
||||
<svg
|
||||
onClick={() => {
|
||||
removeColumnOptionsPair(id);
|
||||
}}
|
||||
width="12"
|
||||
height="14"
|
||||
viewBox="0 0 12 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.72386 0.884665C3.97391 0.634616 4.31304 0.494141 4.66667 0.494141H7.33333C7.68696 0.494141 8.02609 0.634616 8.27614 0.884665C8.52619 1.13471 8.66667 1.47385 8.66667 1.82747V3.16081H10.6589C10.6636 3.16076 10.6683 3.16076 10.673 3.16081H11.3333C11.7015 3.16081 12 3.45928 12 3.82747C12 4.19566 11.7015 4.49414 11.3333 4.49414H11.2801L10.6664 11.858C10.6585 12.3774 10.4488 12.8738 10.0809 13.2417C9.70581 13.6168 9.1971 13.8275 8.66667 13.8275H3.33333C2.8029 13.8275 2.29419 13.6168 1.91912 13.2417C1.55125 12.8738 1.34148 12.3774 1.33357 11.858L0.719911 4.49414H0.666667C0.298477 4.49414 0 4.19566 0 3.82747C0 3.45928 0.298477 3.16081 0.666667 3.16081H1.32702C1.33174 3.16076 1.33644 3.16076 1.34113 3.16081H3.33333V1.82747C3.33333 1.47385 3.47381 1.13471 3.72386 0.884665ZM2.05787 4.49414L2.66436 11.7721C2.6659 11.7905 2.66667 11.809 2.66667 11.8275C2.66667 12.0043 2.7369 12.1739 2.86193 12.2989C2.98695 12.4239 3.15652 12.4941 3.33333 12.4941H8.66667C8.84348 12.4941 9.01305 12.4239 9.13807 12.2989C9.2631 12.1739 9.33333 12.0043 9.33333 11.8275C9.33333 11.809 9.3341 11.7905 9.33564 11.7721L9.94213 4.49414H2.05787ZM7.33333 3.16081H4.66667V1.82747H7.33333V3.16081ZM4.19526 7.63221C3.93491 7.37186 3.93491 6.94975 4.19526 6.6894C4.45561 6.42905 4.87772 6.42905 5.13807 6.6894L6 7.55133L6.86193 6.6894C7.12228 6.42905 7.54439 6.42905 7.80474 6.6894C8.06509 6.94975 8.06509 7.37186 7.80474 7.63221L6.94281 8.49414L7.80474 9.35607C8.06509 9.61642 8.06509 10.0385 7.80474 10.2989C7.54439 10.5592 7.12228 10.5592 6.86193 10.2989L6 9.43695L5.13807 10.2989C4.87772 10.5592 4.45561 10.5592 4.19526 10.2989C3.93491 10.0385 3.93491 9.61642 4.19526 9.35607L5.05719 8.49414L4.19526 7.63221Z"
|
||||
fill="#E54D2E"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="row tj-db-field-wrapper">
|
||||
<div className="tab-content-wrapper mt-2">
|
||||
<div className="tab-content-wrapper mt-2 d-flex">
|
||||
<label className="form-label" data-cy="label-column-filter">
|
||||
Columns
|
||||
</label>
|
||||
|
||||
<div className="field-container">
|
||||
{Object.entries(columnOptions).map(([key, value]) => {
|
||||
return <RenderColumnOptions key={key} column={value.column} value={value.value} id={key} />;
|
||||
})}
|
||||
<div className="field-container flex-grow-1">
|
||||
{Object.entries(columnOptions).map(([key, value]) => (
|
||||
<RenderColumnOptions
|
||||
key={key}
|
||||
columnOptions={columnOptions}
|
||||
column={value.column}
|
||||
columns={columns}
|
||||
value={value.value}
|
||||
handleColumnOptionChange={handleColumnOptionChange}
|
||||
darkMode={darkMode}
|
||||
removeColumnOptionsPair={removeColumnOptionsPair}
|
||||
id={key}
|
||||
/>
|
||||
))}
|
||||
|
||||
{Object.keys(columnOptions).length !== columns.length && (
|
||||
<div className="cursor-pointer pb-3 fit-content" onClick={addNewColumnOptionsPair}>
|
||||
<ButtonSolid
|
||||
variant="ghostBlue"
|
||||
size="sm"
|
||||
onClick={addNewColumnOptionsPair}
|
||||
className={isEmpty(columnOptions) ? '' : 'mt-2'}
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5.34554 10.0207C5.15665 10.0207 4.99832 9.95678 4.87054 9.829C4.74276 9.70123 4.67887 9.54289 4.67887 9.354V5.854H1.17887C0.989985 5.854 0.831651 5.79011 0.703874 5.66234C0.576096 5.53456 0.512207 5.37623 0.512207 5.18734C0.512207 4.99845 0.576096 4.84012 0.703874 4.71234C0.831651 4.58456 0.989985 4.52067 1.17887 4.52067H4.67887V1.02067C4.67887 0.831782 4.74276 0.673448 4.87054 0.54567C4.99832 0.417893 5.15665 0.354004 5.34554 0.354004C5.53443 0.354004 5.69276 0.417893 5.82054 0.54567C5.94832 0.673448 6.01221 0.831782 6.01221 1.02067V4.52067H9.51221C9.7011 4.52067 9.85943 4.58456 9.98721 4.71234C10.115 4.84012 10.1789 4.99845 10.1789 5.18734C10.1789 5.37623 10.115 5.53456 9.98721 5.66234C9.85943 5.79011 9.7011 5.854 9.51221 5.854H6.01221V9.354C6.01221 9.54289 5.94832 9.70123 5.82054 9.829C5.69276 9.95678 5.53443 10.0207 5.34554 10.0207Z"
|
||||
|
|
@ -148,10 +79,104 @@ export const CreateRow = React.memo(({ optionchanged, options, darkMode }) => {
|
|||
/>
|
||||
</svg>
|
||||
Add column
|
||||
</div>
|
||||
</ButtonSolid>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const RenderColumnOptions = ({
|
||||
column,
|
||||
value,
|
||||
id,
|
||||
columns,
|
||||
columnOptions,
|
||||
handleColumnOptionChange,
|
||||
darkMode,
|
||||
removeColumnOptionsPair,
|
||||
}) => {
|
||||
const filteredColumns = columns.filter(({ isPrimaryKey }) => !isPrimaryKey);
|
||||
const existingColumnOption = Object.values ? Object.values(columnOptions) : [];
|
||||
let displayColumns = filteredColumns.map(({ accessor }) => ({
|
||||
value: accessor,
|
||||
label: accessor,
|
||||
}));
|
||||
|
||||
if (existingColumnOption.length > 0) {
|
||||
displayColumns = displayColumns.filter(
|
||||
({ value }) => !existingColumnOption.map((item) => item.column !== column && item.column).includes(value)
|
||||
);
|
||||
}
|
||||
|
||||
const handleColumnChange = (selectedOption) => {
|
||||
const updatedOption = {
|
||||
...columnOptions[id],
|
||||
column: selectedOption,
|
||||
};
|
||||
|
||||
const newColumnOptions = { ...columnOptions, [id]: updatedOption };
|
||||
|
||||
handleColumnOptionChange(newColumnOptions);
|
||||
};
|
||||
|
||||
const handleValueChange = (newValue) => {
|
||||
const updatedOption = {
|
||||
...columnOptions[id],
|
||||
value: newValue,
|
||||
};
|
||||
|
||||
const newColumnOptions = { ...columnOptions, [id]: updatedOption };
|
||||
|
||||
handleColumnOptionChange(newColumnOptions);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-1 row-container">
|
||||
<div className="d-flex fields-container">
|
||||
<div className="field col-4 me-3">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select column"
|
||||
value={column}
|
||||
options={displayColumns}
|
||||
onChange={handleColumnChange}
|
||||
customWrap={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field col-6 mx-1">
|
||||
<CodeHinter
|
||||
initialValue={value ? (typeof value === 'string' ? value : JSON.stringify(value)) : value}
|
||||
className="codehinter-plugins"
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
height={'32px'}
|
||||
placeholder="key"
|
||||
onChange={(newValue) => handleValueChange(newValue)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col cursor-pointer m-1 mx-3">
|
||||
<svg
|
||||
onClick={() => {
|
||||
removeColumnOptionsPair(id);
|
||||
}}
|
||||
width="12"
|
||||
height="14"
|
||||
viewBox="0 0 12 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.72386 0.884665C3.97391 0.634616 4.31304 0.494141 4.66667 0.494141H7.33333C7.68696 0.494141 8.02609 0.634616 8.27614 0.884665C8.52619 1.13471 8.66667 1.47385 8.66667 1.82747V3.16081H10.6589C10.6636 3.16076 10.6683 3.16076 10.673 3.16081H11.3333C11.7015 3.16081 12 3.45928 12 3.82747C12 4.19566 11.7015 4.49414 11.3333 4.49414H11.2801L10.6664 11.858C10.6585 12.3774 10.4488 12.8738 10.0809 13.2417C9.70581 13.6168 9.1971 13.8275 8.66667 13.8275H3.33333C2.8029 13.8275 2.29419 13.6168 1.91912 13.2417C1.55125 12.8738 1.34148 12.3774 1.33357 11.858L0.719911 4.49414H0.666667C0.298477 4.49414 0 4.19566 0 3.82747C0 3.45928 0.298477 3.16081 0.666667 3.16081H1.32702C1.33174 3.16076 1.33644 3.16076 1.34113 3.16081H3.33333V1.82747C3.33333 1.47385 3.47381 1.13471 3.72386 0.884665ZM2.05787 4.49414L2.66436 11.7721C2.6659 11.7905 2.66667 11.809 2.66667 11.8275C2.66667 12.0043 2.7369 12.1739 2.86193 12.2989C2.98695 12.4239 3.15652 12.4941 3.33333 12.4941H8.66667C8.84348 12.4941 9.01305 12.4239 9.13807 12.2989C9.2631 12.1739 9.33333 12.0043 9.33333 11.8275C9.33333 11.809 9.3341 11.7905 9.33564 11.7721L9.94213 4.49414H2.05787ZM7.33333 3.16081H4.66667V1.82747H7.33333V3.16081ZM4.19526 7.63221C3.93491 7.37186 3.93491 6.94975 4.19526 6.6894C4.45561 6.42905 4.87772 6.42905 5.13807 6.6894L6 7.55133L6.86193 6.6894C7.12228 6.42905 7.54439 6.42905 7.80474 6.6894C8.06509 6.94975 8.06509 7.37186 7.80474 7.63221L6.94281 8.49414L7.80474 9.35607C8.06509 9.61642 8.06509 10.0385 7.80474 10.2989C7.54439 10.5592 7.12228 10.5592 6.86193 10.2989L6 9.43695L5.13807 10.2989C4.87772 10.5592 4.45561 10.5592 4.19526 10.2989C3.93491 10.0385 3.93491 9.61642 4.19526 9.35607L5.05719 8.49414L4.19526 7.63221Z"
|
||||
fill="#E54D2E"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { TooljetDatabaseContext } from '@/TooljetDatabase/index';
|
||||
import { uniqueId } from 'lodash';
|
||||
import { isEmpty, uniqueId } from 'lodash';
|
||||
import { CodeHinter } from '@/Editor/CodeBuilder/CodeHinter';
|
||||
import Select from '@/_ui/Select';
|
||||
import { operators } from '@/TooljetDatabase/constants';
|
||||
import { isOperatorOptions } from './util';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
|
||||
export const DeleteRows = React.memo(({ darkMode }) => {
|
||||
const { columns, deleteOperationLimitOptionChanged, deleteRowsOptions, handleDeleteRowsOptionsChange } =
|
||||
|
|
@ -44,118 +45,49 @@ export const DeleteRows = React.memo(({ darkMode }) => {
|
|||
handleWhereFiltersChange(updatedFiltersObject);
|
||||
}
|
||||
|
||||
const RenderFilterFields = ({ column, operator, value, id }) => {
|
||||
let displayColumns = columns.map(({ accessor }) => ({
|
||||
value: accessor,
|
||||
label: accessor,
|
||||
}));
|
||||
|
||||
const handleColumnChange = (selectedOption) => {
|
||||
updateFilterOptionsChanged({ ...deleteRowsOptions?.where_filters[id], ...{ column: selectedOption } });
|
||||
};
|
||||
|
||||
const handleOperatorChange = (selectedOption) => {
|
||||
updateFilterOptionsChanged({ ...deleteRowsOptions?.where_filters[id], ...{ operator: selectedOption } });
|
||||
};
|
||||
|
||||
const handleValueChange = (newValue) => {
|
||||
updateFilterOptionsChanged({ ...deleteRowsOptions?.where_filters[id], ...{ value: newValue } });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-1 row-container">
|
||||
<div className="d-flex fields-container">
|
||||
<div className="field col">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select column"
|
||||
value={column}
|
||||
options={displayColumns}
|
||||
onChange={handleColumnChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="field col mx-1">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select operation"
|
||||
value={operator}
|
||||
options={operators}
|
||||
onChange={handleOperatorChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="field col-4">
|
||||
{operator === 'is' ? (
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select value"
|
||||
value={value}
|
||||
options={isOperatorOptions}
|
||||
onChange={handleValueChange}
|
||||
/>
|
||||
) : (
|
||||
<CodeHinter
|
||||
initialValue={value ? (typeof value === 'string' ? value : JSON.stringify(value)) : value}
|
||||
className="codehinter-plugins"
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
height={'32px'}
|
||||
placeholder="key"
|
||||
onChange={(newValue) => handleValueChange(newValue)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-1 cursor-pointer m-1 mr-2">
|
||||
<svg
|
||||
onClick={() => removeFilterConditionPair(id)}
|
||||
width="12"
|
||||
height="14"
|
||||
viewBox="0 0 12 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.72386 0.884665C3.97391 0.634616 4.31304 0.494141 4.66667 0.494141H7.33333C7.68696 0.494141 8.02609 0.634616 8.27614 0.884665C8.52619 1.13471 8.66667 1.47385 8.66667 1.82747V3.16081H10.6589C10.6636 3.16076 10.6683 3.16076 10.673 3.16081H11.3333C11.7015 3.16081 12 3.45928 12 3.82747C12 4.19566 11.7015 4.49414 11.3333 4.49414H11.2801L10.6664 11.858C10.6585 12.3774 10.4488 12.8738 10.0809 13.2417C9.70581 13.6168 9.1971 13.8275 8.66667 13.8275H3.33333C2.8029 13.8275 2.29419 13.6168 1.91912 13.2417C1.55125 12.8738 1.34148 12.3774 1.33357 11.858L0.719911 4.49414H0.666667C0.298477 4.49414 0 4.19566 0 3.82747C0 3.45928 0.298477 3.16081 0.666667 3.16081H1.32702C1.33174 3.16076 1.33644 3.16076 1.34113 3.16081H3.33333V1.82747C3.33333 1.47385 3.47381 1.13471 3.72386 0.884665ZM2.05787 4.49414L2.66436 11.7721C2.6659 11.7905 2.66667 11.809 2.66667 11.8275C2.66667 12.0043 2.7369 12.1739 2.86193 12.2989C2.98695 12.4239 3.15652 12.4941 3.33333 12.4941H8.66667C8.84348 12.4941 9.01305 12.4239 9.13807 12.2989C9.2631 12.1739 9.33333 12.0043 9.33333 11.8275C9.33333 11.809 9.3341 11.7905 9.33564 11.7721L9.94213 4.49414H2.05787ZM7.33333 3.16081H4.66667V1.82747H7.33333V3.16081ZM4.19526 7.63221C3.93491 7.37186 3.93491 6.94975 4.19526 6.6894C4.45561 6.42905 4.87772 6.42905 5.13807 6.6894L6 7.55133L6.86193 6.6894C7.12228 6.42905 7.54439 6.42905 7.80474 6.6894C8.06509 6.94975 8.06509 7.37186 7.80474 7.63221L6.94281 8.49414L7.80474 9.35607C8.06509 9.61642 8.06509 10.0385 7.80474 10.2989C7.54439 10.5592 7.12228 10.5592 6.86193 10.2989L6 9.43695L5.13807 10.2989C4.87772 10.5592 4.45561 10.5592 4.19526 10.2989C3.93491 10.0385 3.93491 9.61642 4.19526 9.35607L5.05719 8.49414L4.19526 7.63221Z"
|
||||
fill="#E54D2E"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="tab-content-wrapper tj-db-field-wrapper mt-2">
|
||||
<label className="form-label" data-cy="label-column-filter">
|
||||
Filter
|
||||
</label>
|
||||
<div className="d-flex">
|
||||
<label className="form-label" data-cy="label-column-filter">
|
||||
Filter
|
||||
</label>
|
||||
|
||||
<div className="field-container">
|
||||
{Object.values(deleteRowsOptions?.where_filters || {}).map((filter) => (
|
||||
<RenderFilterFields key={filter.id} {...filter} />
|
||||
))}
|
||||
|
||||
<div
|
||||
className="cursor-pointer pb-3 fit-content"
|
||||
onClick={() => {
|
||||
addNewFilterConditionPair();
|
||||
}}
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5.34554 10.0207C5.15665 10.0207 4.99832 9.95678 4.87054 9.829C4.74276 9.70123 4.67887 9.54289 4.67887 9.354V5.854H1.17887C0.989985 5.854 0.831651 5.79011 0.703874 5.66234C0.576096 5.53456 0.512207 5.37623 0.512207 5.18734C0.512207 4.99845 0.576096 4.84012 0.703874 4.71234C0.831651 4.58456 0.989985 4.52067 1.17887 4.52067H4.67887V1.02067C4.67887 0.831782 4.74276 0.673448 4.87054 0.54567C4.99832 0.417893 5.15665 0.354004 5.34554 0.354004C5.53443 0.354004 5.69276 0.417893 5.82054 0.54567C5.94832 0.673448 6.01221 0.831782 6.01221 1.02067V4.52067H9.51221C9.7011 4.52067 9.85943 4.58456 9.98721 4.71234C10.115 4.84012 10.1789 4.99845 10.1789 5.18734C10.1789 5.37623 10.115 5.53456 9.98721 5.66234C9.85943 5.79011 9.7011 5.854 9.51221 5.854H6.01221V9.354C6.01221 9.54289 5.94832 9.70123 5.82054 9.829C5.69276 9.95678 5.53443 10.0207 5.34554 10.0207Z"
|
||||
fill="#466BF2"
|
||||
<div className="field-container flex-grow-1 mb-2">
|
||||
{Object.values(deleteRowsOptions?.where_filters || {}).map((filter) => (
|
||||
<RenderFilterFields
|
||||
key={filter.id}
|
||||
{...filter}
|
||||
removeFilterConditionPair={removeFilterConditionPair}
|
||||
updateFilterOptionsChanged={updateFilterOptionsChanged}
|
||||
deleteRowsOptions={deleteRowsOptions}
|
||||
columns={columns}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
</svg>
|
||||
Add Condition
|
||||
))}
|
||||
|
||||
<ButtonSolid
|
||||
variant="ghostBlue"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
addNewFilterConditionPair();
|
||||
}}
|
||||
className={isEmpty(deleteRowsOptions?.where_filters || {}) ? '' : 'mt-2'}
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5.34554 10.0207C5.15665 10.0207 4.99832 9.95678 4.87054 9.829C4.74276 9.70123 4.67887 9.54289 4.67887 9.354V5.854H1.17887C0.989985 5.854 0.831651 5.79011 0.703874 5.66234C0.576096 5.53456 0.512207 5.37623 0.512207 5.18734C0.512207 4.99845 0.576096 4.84012 0.703874 4.71234C0.831651 4.58456 0.989985 4.52067 1.17887 4.52067H4.67887V1.02067C4.67887 0.831782 4.74276 0.673448 4.87054 0.54567C4.99832 0.417893 5.15665 0.354004 5.34554 0.354004C5.53443 0.354004 5.69276 0.417893 5.82054 0.54567C5.94832 0.673448 6.01221 0.831782 6.01221 1.02067V4.52067H9.51221C9.7011 4.52067 9.85943 4.58456 9.98721 4.71234C10.115 4.84012 10.1789 4.99845 10.1789 5.18734C10.1789 5.37623 10.115 5.53456 9.98721 5.66234C9.85943 5.79011 9.7011 5.854 9.51221 5.854H6.01221V9.354C6.01221 9.54289 5.94832 9.70123 5.82054 9.829C5.69276 9.95678 5.53443 10.0207 5.34554 10.0207Z"
|
||||
fill="#466BF2"
|
||||
/>
|
||||
</svg>
|
||||
Add Condition
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field-container ">
|
||||
<div className="field-container d-flex">
|
||||
<label className="form-label" data-cy="label-column-limit">
|
||||
Limit
|
||||
</label>
|
||||
<div className="field col-4">
|
||||
<div className="field flex-grow-1">
|
||||
<CodeHinter
|
||||
initialValue={deleteRowsOptions?.limit ?? 1}
|
||||
className="codehinter-plugins"
|
||||
|
|
@ -169,3 +101,97 @@ export const DeleteRows = React.memo(({ darkMode }) => {
|
|||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const RenderFilterFields = ({
|
||||
column,
|
||||
operator,
|
||||
value,
|
||||
id,
|
||||
removeFilterConditionPair,
|
||||
columns,
|
||||
updateFilterOptionsChanged,
|
||||
deleteRowsOptions,
|
||||
darkMode,
|
||||
}) => {
|
||||
let displayColumns = columns.map(({ accessor }) => ({
|
||||
value: accessor,
|
||||
label: accessor,
|
||||
}));
|
||||
|
||||
const handleColumnChange = (selectedOption) => {
|
||||
updateFilterOptionsChanged({ ...deleteRowsOptions?.where_filters[id], ...{ column: selectedOption } });
|
||||
};
|
||||
|
||||
const handleOperatorChange = (selectedOption) => {
|
||||
updateFilterOptionsChanged({ ...deleteRowsOptions?.where_filters[id], ...{ operator: selectedOption } });
|
||||
};
|
||||
|
||||
const handleValueChange = (newValue) => {
|
||||
updateFilterOptionsChanged({ ...deleteRowsOptions?.where_filters[id], ...{ value: newValue } });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-1 row-container">
|
||||
<div className="d-flex fields-container">
|
||||
<div className="field col">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select column"
|
||||
value={column}
|
||||
options={displayColumns}
|
||||
onChange={handleColumnChange}
|
||||
width="auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="field col mx-1">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select operation"
|
||||
value={operator}
|
||||
options={operators}
|
||||
onChange={handleOperatorChange}
|
||||
width="auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="field col-4">
|
||||
{operator === 'is' ? (
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select value"
|
||||
value={value}
|
||||
options={isOperatorOptions}
|
||||
onChange={handleValueChange}
|
||||
width="auto"
|
||||
/>
|
||||
) : (
|
||||
<CodeHinter
|
||||
initialValue={value ? (typeof value === 'string' ? value : JSON.stringify(value)) : value}
|
||||
className="codehinter-plugins"
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
height={'32px'}
|
||||
placeholder="key"
|
||||
onChange={(newValue) => handleValueChange(newValue)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-1 cursor-pointer m-1 mr-2">
|
||||
<svg
|
||||
onClick={() => removeFilterConditionPair(id)}
|
||||
width="12"
|
||||
height="14"
|
||||
viewBox="0 0 12 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.72386 0.884665C3.97391 0.634616 4.31304 0.494141 4.66667 0.494141H7.33333C7.68696 0.494141 8.02609 0.634616 8.27614 0.884665C8.52619 1.13471 8.66667 1.47385 8.66667 1.82747V3.16081H10.6589C10.6636 3.16076 10.6683 3.16076 10.673 3.16081H11.3333C11.7015 3.16081 12 3.45928 12 3.82747C12 4.19566 11.7015 4.49414 11.3333 4.49414H11.2801L10.6664 11.858C10.6585 12.3774 10.4488 12.8738 10.0809 13.2417C9.70581 13.6168 9.1971 13.8275 8.66667 13.8275H3.33333C2.8029 13.8275 2.29419 13.6168 1.91912 13.2417C1.55125 12.8738 1.34148 12.3774 1.33357 11.858L0.719911 4.49414H0.666667C0.298477 4.49414 0 4.19566 0 3.82747C0 3.45928 0.298477 3.16081 0.666667 3.16081H1.32702C1.33174 3.16076 1.33644 3.16076 1.34113 3.16081H3.33333V1.82747C3.33333 1.47385 3.47381 1.13471 3.72386 0.884665ZM2.05787 4.49414L2.66436 11.7721C2.6659 11.7905 2.66667 11.809 2.66667 11.8275C2.66667 12.0043 2.7369 12.1739 2.86193 12.2989C2.98695 12.4239 3.15652 12.4941 3.33333 12.4941H8.66667C8.84348 12.4941 9.01305 12.4239 9.13807 12.2989C9.2631 12.1739 9.33333 12.0043 9.33333 11.8275C9.33333 11.809 9.3341 11.7905 9.33564 11.7721L9.94213 4.49414H2.05787ZM7.33333 3.16081H4.66667V1.82747H7.33333V3.16081ZM4.19526 7.63221C3.93491 7.37186 3.93491 6.94975 4.19526 6.6894C4.45561 6.42905 4.87772 6.42905 5.13807 6.6894L6 7.55133L6.86193 6.6894C7.12228 6.42905 7.54439 6.42905 7.80474 6.6894C8.06509 6.94975 8.06509 7.37186 7.80474 7.63221L6.94281 8.49414L7.80474 9.35607C8.06509 9.61642 8.06509 10.0385 7.80474 10.2989C7.54439 10.5592 7.12228 10.5592 6.86193 10.2989L6 9.43695L5.13807 10.2989C4.87772 10.5592 4.45561 10.5592 4.19526 10.2989C3.93491 10.0385 3.93491 9.61642 4.19526 9.35607L5.05719 8.49414L4.19526 7.63221Z"
|
||||
fill="#E54D2E"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { CodeHinter } from '@/Editor/CodeBuilder/CodeHinter';
|
||||
import { TooljetDatabaseContext } from '@/TooljetDatabase/index';
|
||||
import { uniqueId } from 'lodash';
|
||||
import { isEmpty, uniqueId } from 'lodash';
|
||||
import Select from '@/_ui/Select';
|
||||
import { operators } from '@/TooljetDatabase/constants';
|
||||
import { isOperatorOptions } from './util';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
|
||||
export const ListRows = React.memo(({ darkMode }) => {
|
||||
const { columns, listRowsOptions, limitOptionChanged, handleOptionsChange } = useContext(TooljetDatabaseContext);
|
||||
|
|
@ -77,197 +78,34 @@ export const ListRows = React.memo(({ darkMode }) => {
|
|||
handleOrderFiltersChange(updatedFiltersObject);
|
||||
}
|
||||
|
||||
const RenderFilterFields = ({ column, operator, value, id }) => {
|
||||
let displayColumns = columns.map(({ accessor }) => ({
|
||||
value: accessor,
|
||||
label: accessor,
|
||||
}));
|
||||
|
||||
const handleColumnChange = (selectedOption) => {
|
||||
updateFilterOptionsChanged({ ...listRowsOptions?.where_filters[id], ...{ column: selectedOption } });
|
||||
};
|
||||
|
||||
const handleOperatorChange = (selectedOption) => {
|
||||
updateFilterOptionsChanged({ ...listRowsOptions?.where_filters[id], ...{ operator: selectedOption } });
|
||||
};
|
||||
|
||||
const handleValueChange = (newValue) => {
|
||||
updateFilterOptionsChanged({ ...listRowsOptions?.where_filters[id], ...{ value: newValue } });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-1 row-container">
|
||||
<div className="d-flex fields-container">
|
||||
<div className="field col">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select column"
|
||||
value={column}
|
||||
options={displayColumns}
|
||||
onChange={handleColumnChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="field col mx-1">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select operation"
|
||||
value={operator}
|
||||
options={operators}
|
||||
onChange={handleOperatorChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="field col-4">
|
||||
{operator === 'is' ? (
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select value"
|
||||
value={value}
|
||||
options={isOperatorOptions}
|
||||
onChange={handleValueChange}
|
||||
/>
|
||||
) : (
|
||||
<CodeHinter
|
||||
initialValue={value ? (typeof value === 'string' ? value : JSON.stringify(value)) : value}
|
||||
className="codehinter-plugins"
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
height={'32px'}
|
||||
placeholder="key"
|
||||
onChange={(newValue) => handleValueChange(newValue)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-1 cursor-pointer m-1 mr-2">
|
||||
<svg
|
||||
onClick={() => removeFilterConditionPair(id)}
|
||||
width="12"
|
||||
height="14"
|
||||
viewBox="0 0 12 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.72386 0.884665C3.97391 0.634616 4.31304 0.494141 4.66667 0.494141H7.33333C7.68696 0.494141 8.02609 0.634616 8.27614 0.884665C8.52619 1.13471 8.66667 1.47385 8.66667 1.82747V3.16081H10.6589C10.6636 3.16076 10.6683 3.16076 10.673 3.16081H11.3333C11.7015 3.16081 12 3.45928 12 3.82747C12 4.19566 11.7015 4.49414 11.3333 4.49414H11.2801L10.6664 11.858C10.6585 12.3774 10.4488 12.8738 10.0809 13.2417C9.70581 13.6168 9.1971 13.8275 8.66667 13.8275H3.33333C2.8029 13.8275 2.29419 13.6168 1.91912 13.2417C1.55125 12.8738 1.34148 12.3774 1.33357 11.858L0.719911 4.49414H0.666667C0.298477 4.49414 0 4.19566 0 3.82747C0 3.45928 0.298477 3.16081 0.666667 3.16081H1.32702C1.33174 3.16076 1.33644 3.16076 1.34113 3.16081H3.33333V1.82747C3.33333 1.47385 3.47381 1.13471 3.72386 0.884665ZM2.05787 4.49414L2.66436 11.7721C2.6659 11.7905 2.66667 11.809 2.66667 11.8275C2.66667 12.0043 2.7369 12.1739 2.86193 12.2989C2.98695 12.4239 3.15652 12.4941 3.33333 12.4941H8.66667C8.84348 12.4941 9.01305 12.4239 9.13807 12.2989C9.2631 12.1739 9.33333 12.0043 9.33333 11.8275C9.33333 11.809 9.3341 11.7905 9.33564 11.7721L9.94213 4.49414H2.05787ZM7.33333 3.16081H4.66667V1.82747H7.33333V3.16081ZM4.19526 7.63221C3.93491 7.37186 3.93491 6.94975 4.19526 6.6894C4.45561 6.42905 4.87772 6.42905 5.13807 6.6894L6 7.55133L6.86193 6.6894C7.12228 6.42905 7.54439 6.42905 7.80474 6.6894C8.06509 6.94975 8.06509 7.37186 7.80474 7.63221L6.94281 8.49414L7.80474 9.35607C8.06509 9.61642 8.06509 10.0385 7.80474 10.2989C7.54439 10.5592 7.12228 10.5592 6.86193 10.2989L6 9.43695L5.13807 10.2989C4.87772 10.5592 4.45561 10.5592 4.19526 10.2989C3.93491 10.0385 3.93491 9.61642 4.19526 9.35607L5.05719 8.49414L4.19526 7.63221Z"
|
||||
fill="#E54D2E"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RenderSortFields = ({ column, order, id }) => {
|
||||
const orders = [
|
||||
{ value: 'asc', label: 'Ascending' },
|
||||
{ value: 'desc', label: 'Descending' },
|
||||
];
|
||||
const existingColumnOptions = Object.values(listRowsOptions?.order_filters).map((item) => item.column);
|
||||
let displayColumns = columns.map(({ accessor }) => ({
|
||||
value: accessor,
|
||||
label: accessor,
|
||||
}));
|
||||
|
||||
if (existingColumnOptions.length > 0) {
|
||||
displayColumns = displayColumns.filter(
|
||||
({ value }) => !existingColumnOptions.map((item) => item !== column && item).includes(value)
|
||||
);
|
||||
}
|
||||
|
||||
const handleColumnChange = (selectedOption) => {
|
||||
updateSortOptionsChanged({ ...listRowsOptions?.order_filters[id], ...{ column: selectedOption } });
|
||||
};
|
||||
|
||||
const handleDirectionChange = (selectedOption) => {
|
||||
updateSortOptionsChanged({ ...listRowsOptions?.order_filters[id], ...{ order: selectedOption } });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-1 row-container">
|
||||
<div className="d-flex fields-container">
|
||||
<div className="field col-4">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select column"
|
||||
value={column}
|
||||
options={displayColumns}
|
||||
onChange={handleColumnChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="field col-4">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select direction"
|
||||
value={order}
|
||||
options={orders}
|
||||
onChange={handleDirectionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="col cursor-pointer m-1 mr-2">
|
||||
<svg
|
||||
onClick={() => removeSortConditionPair(id)}
|
||||
width="12"
|
||||
height="14"
|
||||
viewBox="0 0 12 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.72386 0.884665C3.97391 0.634616 4.31304 0.494141 4.66667 0.494141H7.33333C7.68696 0.494141 8.02609 0.634616 8.27614 0.884665C8.52619 1.13471 8.66667 1.47385 8.66667 1.82747V3.16081H10.6589C10.6636 3.16076 10.6683 3.16076 10.673 3.16081H11.3333C11.7015 3.16081 12 3.45928 12 3.82747C12 4.19566 11.7015 4.49414 11.3333 4.49414H11.2801L10.6664 11.858C10.6585 12.3774 10.4488 12.8738 10.0809 13.2417C9.70581 13.6168 9.1971 13.8275 8.66667 13.8275H3.33333C2.8029 13.8275 2.29419 13.6168 1.91912 13.2417C1.55125 12.8738 1.34148 12.3774 1.33357 11.858L0.719911 4.49414H0.666667C0.298477 4.49414 0 4.19566 0 3.82747C0 3.45928 0.298477 3.16081 0.666667 3.16081H1.32702C1.33174 3.16076 1.33644 3.16076 1.34113 3.16081H3.33333V1.82747C3.33333 1.47385 3.47381 1.13471 3.72386 0.884665ZM2.05787 4.49414L2.66436 11.7721C2.6659 11.7905 2.66667 11.809 2.66667 11.8275C2.66667 12.0043 2.7369 12.1739 2.86193 12.2989C2.98695 12.4239 3.15652 12.4941 3.33333 12.4941H8.66667C8.84348 12.4941 9.01305 12.4239 9.13807 12.2989C9.2631 12.1739 9.33333 12.0043 9.33333 11.8275C9.33333 11.809 9.3341 11.7905 9.33564 11.7721L9.94213 4.49414H2.05787ZM7.33333 3.16081H4.66667V1.82747H7.33333V3.16081ZM4.19526 7.63221C3.93491 7.37186 3.93491 6.94975 4.19526 6.6894C4.45561 6.42905 4.87772 6.42905 5.13807 6.6894L6 7.55133L6.86193 6.6894C7.12228 6.42905 7.54439 6.42905 7.80474 6.6894C8.06509 6.94975 8.06509 7.37186 7.80474 7.63221L6.94281 8.49414L7.80474 9.35607C8.06509 9.61642 8.06509 10.0385 7.80474 10.2989C7.54439 10.5592 7.12228 10.5592 6.86193 10.2989L6 9.43695L5.13807 10.2989C4.87772 10.5592 4.45561 10.5592 4.19526 10.2989C3.93491 10.0385 3.93491 9.61642 4.19526 9.35607L5.05719 8.49414L4.19526 7.63221Z"
|
||||
fill="#E54D2E"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="row my-2 tj-db-field-wrapper">
|
||||
<div className="tab-content-wrapper">
|
||||
<label className="form-label" data-cy="label-column-filter">
|
||||
Filter
|
||||
</label>
|
||||
<div className="field-container">
|
||||
{Object.values(listRowsOptions?.where_filters || {}).map((filter) => (
|
||||
<RenderFilterFields key={filter.id} {...filter} />
|
||||
))}
|
||||
|
||||
<div
|
||||
className="cursor-pointer pb-3 fit-content"
|
||||
onClick={() => {
|
||||
addNewFilterConditionPair();
|
||||
}}
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5.34554 10.0207C5.15665 10.0207 4.99832 9.95678 4.87054 9.829C4.74276 9.70123 4.67887 9.54289 4.67887 9.354V5.854H1.17887C0.989985 5.854 0.831651 5.79011 0.703874 5.66234C0.576096 5.53456 0.512207 5.37623 0.512207 5.18734C0.512207 4.99845 0.576096 4.84012 0.703874 4.71234C0.831651 4.58456 0.989985 4.52067 1.17887 4.52067H4.67887V1.02067C4.67887 0.831782 4.74276 0.673448 4.87054 0.54567C4.99832 0.417893 5.15665 0.354004 5.34554 0.354004C5.53443 0.354004 5.69276 0.417893 5.82054 0.54567C5.94832 0.673448 6.01221 0.831782 6.01221 1.02067V4.52067H9.51221C9.7011 4.52067 9.85943 4.58456 9.98721 4.71234C10.115 4.84012 10.1789 4.99845 10.1789 5.18734C10.1789 5.37623 10.115 5.53456 9.98721 5.66234C9.85943 5.79011 9.7011 5.854 9.51221 5.854H6.01221V9.354C6.01221 9.54289 5.94832 9.70123 5.82054 9.829C5.69276 9.95678 5.53443 10.0207 5.34554 10.0207Z"
|
||||
fill="#466BF2"
|
||||
/>
|
||||
</svg>
|
||||
Add Condition
|
||||
</div>
|
||||
</div>
|
||||
{/* sort */}
|
||||
|
||||
<div className="fields-container">
|
||||
<label className="form-label" data-cy="label-column-sort">
|
||||
Sort
|
||||
<div className="d-flex mb-2">
|
||||
<label className="form-label" data-cy="label-column-filter">
|
||||
Filter
|
||||
</label>
|
||||
<div className="field-container">
|
||||
{Object.values(listRowsOptions?.order_filters || {}).map((filter) => (
|
||||
<RenderSortFields key={filter.id} {...filter} />
|
||||
<div className="field-container flex-grow-1">
|
||||
{Object.values(listRowsOptions?.where_filters || {}).map((filter) => (
|
||||
<RenderFilterFields
|
||||
key={filter.id}
|
||||
{...filter}
|
||||
columns={columns}
|
||||
listRowsOptions={listRowsOptions}
|
||||
updateFilterOptionsChanged={updateFilterOptionsChanged}
|
||||
darkMode={darkMode}
|
||||
removeFilterConditionPair={removeFilterConditionPair}
|
||||
/>
|
||||
))}
|
||||
<div
|
||||
className="cursor-pointer pb-3 fit-content"
|
||||
|
||||
<ButtonSolid
|
||||
variant="ghostBlue"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
addNewSortConditionPair();
|
||||
addNewFilterConditionPair();
|
||||
}}
|
||||
className={isEmpty(listRowsOptions?.where_filters || {}) ? '' : 'mt-2'}
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
|
|
@ -276,16 +114,51 @@ export const ListRows = React.memo(({ darkMode }) => {
|
|||
/>
|
||||
</svg>
|
||||
Add Condition
|
||||
</div>
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* sort */}
|
||||
<div className="fields-container d-flex mb-2">
|
||||
<label className="form-label" data-cy="label-column-sort">
|
||||
Sort
|
||||
</label>
|
||||
<div className="field-container flex-grow-1">
|
||||
{Object.values(listRowsOptions?.order_filters || {}).map((filter) => (
|
||||
<RenderSortFields
|
||||
key={filter.id}
|
||||
{...filter}
|
||||
removeSortConditionPair={removeSortConditionPair}
|
||||
listRowsOptions={listRowsOptions}
|
||||
columns={columns}
|
||||
updateSortOptionsChanged={updateSortOptionsChanged}
|
||||
/>
|
||||
))}
|
||||
<ButtonSolid
|
||||
variant="ghostBlue"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
addNewSortConditionPair();
|
||||
}}
|
||||
className={isEmpty(listRowsOptions?.order_filters || {}) ? '' : 'mt-2'}
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5.34554 10.0207C5.15665 10.0207 4.99832 9.95678 4.87054 9.829C4.74276 9.70123 4.67887 9.54289 4.67887 9.354V5.854H1.17887C0.989985 5.854 0.831651 5.79011 0.703874 5.66234C0.576096 5.53456 0.512207 5.37623 0.512207 5.18734C0.512207 4.99845 0.576096 4.84012 0.703874 4.71234C0.831651 4.58456 0.989985 4.52067 1.17887 4.52067H4.67887V1.02067C4.67887 0.831782 4.74276 0.673448 4.87054 0.54567C4.99832 0.417893 5.15665 0.354004 5.34554 0.354004C5.53443 0.354004 5.69276 0.417893 5.82054 0.54567C5.94832 0.673448 6.01221 0.831782 6.01221 1.02067V4.52067H9.51221C9.7011 4.52067 9.85943 4.58456 9.98721 4.71234C10.115 4.84012 10.1789 4.99845 10.1789 5.18734C10.1789 5.37623 10.115 5.53456 9.98721 5.66234C9.85943 5.79011 9.7011 5.854 9.51221 5.854H6.01221V9.354C6.01221 9.54289 5.94832 9.70123 5.82054 9.829C5.69276 9.95678 5.53443 10.0207 5.34554 10.0207Z"
|
||||
fill="#466BF2"
|
||||
/>
|
||||
</svg>
|
||||
Add Condition
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Limit */}
|
||||
<div className="field-container ">
|
||||
<div className="field-container d-flex">
|
||||
<label className="form-label" data-cy="label-column-limit">
|
||||
Limit
|
||||
</label>
|
||||
<div className="field col-4">
|
||||
<div className="field flex-grow-1">
|
||||
<CodeHinter
|
||||
initialValue={listRowsOptions?.limit ?? ''}
|
||||
className="codehinter-plugins"
|
||||
|
|
@ -301,3 +174,175 @@ export const ListRows = React.memo(({ darkMode }) => {
|
|||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const RenderSortFields = ({
|
||||
column,
|
||||
order,
|
||||
id,
|
||||
removeSortConditionPair,
|
||||
listRowsOptions,
|
||||
columns,
|
||||
updateSortOptionsChanged,
|
||||
}) => {
|
||||
const orders = [
|
||||
{ value: 'asc', label: 'Ascending' },
|
||||
{ value: 'desc', label: 'Descending' },
|
||||
];
|
||||
const existingColumnOptions = Object.values(listRowsOptions?.order_filters).map((item) => item.column);
|
||||
let displayColumns = columns.map(({ accessor }) => ({
|
||||
value: accessor,
|
||||
label: accessor,
|
||||
}));
|
||||
|
||||
if (existingColumnOptions.length > 0) {
|
||||
displayColumns = displayColumns.filter(
|
||||
({ value }) => !existingColumnOptions.map((item) => item !== column && item).includes(value)
|
||||
);
|
||||
}
|
||||
|
||||
const handleColumnChange = (selectedOption) => {
|
||||
updateSortOptionsChanged({ ...listRowsOptions?.order_filters[id], ...{ column: selectedOption } });
|
||||
};
|
||||
|
||||
const handleDirectionChange = (selectedOption) => {
|
||||
updateSortOptionsChanged({ ...listRowsOptions?.order_filters[id], ...{ order: selectedOption } });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-1 row-container">
|
||||
<div className="d-flex fields-container mb-2">
|
||||
<div className="field col">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select column"
|
||||
value={column}
|
||||
options={displayColumns}
|
||||
onChange={handleColumnChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="field col mx-1">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select direction"
|
||||
value={order}
|
||||
options={orders}
|
||||
onChange={handleDirectionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="col cursor-pointer m-1 ms-1">
|
||||
<svg
|
||||
onClick={() => removeSortConditionPair(id)}
|
||||
width="12"
|
||||
height="14"
|
||||
viewBox="0 0 12 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.72386 0.884665C3.97391 0.634616 4.31304 0.494141 4.66667 0.494141H7.33333C7.68696 0.494141 8.02609 0.634616 8.27614 0.884665C8.52619 1.13471 8.66667 1.47385 8.66667 1.82747V3.16081H10.6589C10.6636 3.16076 10.6683 3.16076 10.673 3.16081H11.3333C11.7015 3.16081 12 3.45928 12 3.82747C12 4.19566 11.7015 4.49414 11.3333 4.49414H11.2801L10.6664 11.858C10.6585 12.3774 10.4488 12.8738 10.0809 13.2417C9.70581 13.6168 9.1971 13.8275 8.66667 13.8275H3.33333C2.8029 13.8275 2.29419 13.6168 1.91912 13.2417C1.55125 12.8738 1.34148 12.3774 1.33357 11.858L0.719911 4.49414H0.666667C0.298477 4.49414 0 4.19566 0 3.82747C0 3.45928 0.298477 3.16081 0.666667 3.16081H1.32702C1.33174 3.16076 1.33644 3.16076 1.34113 3.16081H3.33333V1.82747C3.33333 1.47385 3.47381 1.13471 3.72386 0.884665ZM2.05787 4.49414L2.66436 11.7721C2.6659 11.7905 2.66667 11.809 2.66667 11.8275C2.66667 12.0043 2.7369 12.1739 2.86193 12.2989C2.98695 12.4239 3.15652 12.4941 3.33333 12.4941H8.66667C8.84348 12.4941 9.01305 12.4239 9.13807 12.2989C9.2631 12.1739 9.33333 12.0043 9.33333 11.8275C9.33333 11.809 9.3341 11.7905 9.33564 11.7721L9.94213 4.49414H2.05787ZM7.33333 3.16081H4.66667V1.82747H7.33333V3.16081ZM4.19526 7.63221C3.93491 7.37186 3.93491 6.94975 4.19526 6.6894C4.45561 6.42905 4.87772 6.42905 5.13807 6.6894L6 7.55133L6.86193 6.6894C7.12228 6.42905 7.54439 6.42905 7.80474 6.6894C8.06509 6.94975 8.06509 7.37186 7.80474 7.63221L6.94281 8.49414L7.80474 9.35607C8.06509 9.61642 8.06509 10.0385 7.80474 10.2989C7.54439 10.5592 7.12228 10.5592 6.86193 10.2989L6 9.43695L5.13807 10.2989C4.87772 10.5592 4.45561 10.5592 4.19526 10.2989C3.93491 10.0385 3.93491 9.61642 4.19526 9.35607L5.05719 8.49414L4.19526 7.63221Z"
|
||||
fill="#E54D2E"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RenderFilterFields = ({
|
||||
column,
|
||||
operator,
|
||||
value,
|
||||
id,
|
||||
columns,
|
||||
listRowsOptions,
|
||||
updateFilterOptionsChanged,
|
||||
removeFilterConditionPair,
|
||||
darkMode,
|
||||
}) => {
|
||||
let displayColumns = columns.map(({ accessor }) => ({
|
||||
value: accessor,
|
||||
label: accessor,
|
||||
}));
|
||||
|
||||
const handleColumnChange = (selectedOption) => {
|
||||
updateFilterOptionsChanged({ ...listRowsOptions?.where_filters[id], ...{ column: selectedOption } });
|
||||
};
|
||||
|
||||
const handleOperatorChange = (selectedOption) => {
|
||||
updateFilterOptionsChanged({ ...listRowsOptions?.where_filters[id], ...{ operator: selectedOption } });
|
||||
};
|
||||
|
||||
const handleValueChange = (newValue) => {
|
||||
updateFilterOptionsChanged({ ...listRowsOptions?.where_filters[id], ...{ value: newValue } });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-1 row-container">
|
||||
<div className="d-flex fields-container">
|
||||
<div className="field col">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select column"
|
||||
value={column}
|
||||
options={displayColumns}
|
||||
onChange={handleColumnChange}
|
||||
// useCustomStyles
|
||||
// styles={{ container: (styles) => ({ width: 'auto', ...styles }) }}
|
||||
width={'auto'}
|
||||
/>
|
||||
</div>
|
||||
<div className="field col mx-1">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select operation"
|
||||
value={operator}
|
||||
options={operators}
|
||||
onChange={handleOperatorChange}
|
||||
width={'auto'}
|
||||
/>
|
||||
</div>
|
||||
<div className="field col-4">
|
||||
{operator === 'is' ? (
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select value"
|
||||
value={value}
|
||||
options={isOperatorOptions}
|
||||
onChange={handleValueChange}
|
||||
width={'auto'}
|
||||
/>
|
||||
) : (
|
||||
<CodeHinter
|
||||
initialValue={value ? (typeof value === 'string' ? value : JSON.stringify(value)) : value}
|
||||
className="codehinter-plugins"
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
height={'32px'}
|
||||
placeholder="key"
|
||||
onChange={(newValue) => handleValueChange(newValue)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-1 cursor-pointer m-1 mr-2">
|
||||
<svg
|
||||
onClick={() => removeFilterConditionPair(id)}
|
||||
width="12"
|
||||
height="14"
|
||||
viewBox="0 0 12 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.72386 0.884665C3.97391 0.634616 4.31304 0.494141 4.66667 0.494141H7.33333C7.68696 0.494141 8.02609 0.634616 8.27614 0.884665C8.52619 1.13471 8.66667 1.47385 8.66667 1.82747V3.16081H10.6589C10.6636 3.16076 10.6683 3.16076 10.673 3.16081H11.3333C11.7015 3.16081 12 3.45928 12 3.82747C12 4.19566 11.7015 4.49414 11.3333 4.49414H11.2801L10.6664 11.858C10.6585 12.3774 10.4488 12.8738 10.0809 13.2417C9.70581 13.6168 9.1971 13.8275 8.66667 13.8275H3.33333C2.8029 13.8275 2.29419 13.6168 1.91912 13.2417C1.55125 12.8738 1.34148 12.3774 1.33357 11.858L0.719911 4.49414H0.666667C0.298477 4.49414 0 4.19566 0 3.82747C0 3.45928 0.298477 3.16081 0.666667 3.16081H1.32702C1.33174 3.16076 1.33644 3.16076 1.34113 3.16081H3.33333V1.82747C3.33333 1.47385 3.47381 1.13471 3.72386 0.884665ZM2.05787 4.49414L2.66436 11.7721C2.6659 11.7905 2.66667 11.809 2.66667 11.8275C2.66667 12.0043 2.7369 12.1739 2.86193 12.2989C2.98695 12.4239 3.15652 12.4941 3.33333 12.4941H8.66667C8.84348 12.4941 9.01305 12.4239 9.13807 12.2989C9.2631 12.1739 9.33333 12.0043 9.33333 11.8275C9.33333 11.809 9.3341 11.7905 9.33564 11.7721L9.94213 4.49414H2.05787ZM7.33333 3.16081H4.66667V1.82747H7.33333V3.16081ZM4.19526 7.63221C3.93491 7.37186 3.93491 6.94975 4.19526 6.6894C4.45561 6.42905 4.87772 6.42905 5.13807 6.6894L6 7.55133L6.86193 6.6894C7.12228 6.42905 7.54439 6.42905 7.80474 6.6894C8.06509 6.94975 8.06509 7.37186 7.80474 7.63221L6.94281 8.49414L7.80474 9.35607C8.06509 9.61642 8.06509 10.0385 7.80474 10.2989C7.54439 10.5592 7.12228 10.5592 6.86193 10.2989L6 9.43695L5.13807 10.2989C4.87772 10.5592 4.45561 10.5592 4.19526 10.2989C3.93491 10.0385 3.93491 9.61642 4.19526 9.35607L5.05719 8.49414L4.19526 7.63221Z"
|
||||
fill="#E54D2E"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { tooljetDatabaseService, authenticationService } from '@/_services';
|
||||
import { TooljetDatabaseContext } from '@/TooljetDatabase/index';
|
||||
import { ListRows } from './ListRows';
|
||||
|
|
@ -11,7 +12,7 @@ import { queryManagerSelectComponentStyle } from '@/_ui/Select/styles';
|
|||
import { useMounted } from '@/_hooks/use-mount';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
|
||||
const ToolJetDbOperations = ({ optionchanged, options, darkMode }) => {
|
||||
const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLayout }) => {
|
||||
const computeSelectStyles = (darkMode, width) => {
|
||||
return queryManagerSelectComponentStyle(darkMode, width);
|
||||
};
|
||||
|
|
@ -178,40 +179,46 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode }) => {
|
|||
return (
|
||||
<TooljetDatabaseContext.Provider value={value}>
|
||||
{/* table name dropdown */}
|
||||
<div className="row">
|
||||
<div className="col-4">
|
||||
<label className="form-label">Table name</label>
|
||||
|
||||
<Select
|
||||
options={generateListForDropdown(tables)}
|
||||
value={selectedTable}
|
||||
onChange={(value) => handleTableNameSelect(value)}
|
||||
width="100%"
|
||||
// useMenuPortal={false}
|
||||
useCustomStyles={true}
|
||||
styles={computeSelectStyles(darkMode, '100%')}
|
||||
/>
|
||||
<div className={cx({ row: !isHorizontalLayout })}>
|
||||
<div className={cx({ 'col-4': !isHorizontalLayout, 'd-flex': isHorizontalLayout })}>
|
||||
<label className={cx('form-label')}>Table name</label>
|
||||
<div className={cx({ 'flex-grow-1': isHorizontalLayout })}>
|
||||
<Select
|
||||
options={generateListForDropdown(tables)}
|
||||
value={selectedTable}
|
||||
onChange={(value) => handleTableNameSelect(value)}
|
||||
width="100%"
|
||||
// useMenuPortal={false}
|
||||
useCustomStyles={true}
|
||||
styles={computeSelectStyles(darkMode, '100%')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* operation selection dropdown */}
|
||||
<div className="row">
|
||||
<div className="my-2 col-4">
|
||||
<label className="form-label">Operations</label>
|
||||
<Select
|
||||
options={[
|
||||
{ name: 'List rows', value: 'list_rows' },
|
||||
{ name: 'Create row', value: 'create_row' },
|
||||
{ name: 'Update rows', value: 'update_rows' },
|
||||
{ name: 'Delete rows', value: 'delete_rows' },
|
||||
]}
|
||||
value={operation}
|
||||
onChange={(value) => setOperation(value)}
|
||||
width="100%"
|
||||
// useMenuPortal={false}
|
||||
useCustomStyles={true}
|
||||
styles={computeSelectStyles(darkMode, '100%')}
|
||||
/>
|
||||
<div className={cx('my-3 py-1', { row: !isHorizontalLayout })}>
|
||||
<div
|
||||
/* className="my-2 col-4" */
|
||||
className={cx({ 'col-4': !isHorizontalLayout, 'd-flex': isHorizontalLayout })}
|
||||
>
|
||||
<label className={cx('form-label')}>Operations</label>
|
||||
<div className={cx({ 'flex-grow-1': isHorizontalLayout })}>
|
||||
<Select
|
||||
options={[
|
||||
{ name: 'List rows', value: 'list_rows' },
|
||||
{ name: 'Create row', value: 'create_row' },
|
||||
{ name: 'Update rows', value: 'update_rows' },
|
||||
{ name: 'Delete rows', value: 'delete_rows' },
|
||||
]}
|
||||
value={operation}
|
||||
onChange={(value) => setOperation(value)}
|
||||
width="100%"
|
||||
// useMenuPortal={false}
|
||||
useCustomStyles={true}
|
||||
styles={computeSelectStyles(darkMode, '100%')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ import { CodeHinter } from '@/Editor/CodeBuilder/CodeHinter';
|
|||
import { TooljetDatabaseContext } from '@/TooljetDatabase/index';
|
||||
import Select from '@/_ui/Select';
|
||||
import { operators } from '@/TooljetDatabase/constants';
|
||||
import { uniqueId } from 'lodash';
|
||||
import { isEmpty, uniqueId } from 'lodash';
|
||||
import { isOperatorOptions } from './util';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
|
||||
export const UpdateRows = React.memo(({ darkMode }) => {
|
||||
const { columns, updateRowsOptions, handleUpdateRowsOptionsChange } = useContext(TooljetDatabaseContext);
|
||||
|
|
@ -70,139 +71,150 @@ export const UpdateRows = React.memo(({ darkMode }) => {
|
|||
handleWhereFiltersChange(updatedFiltersObject);
|
||||
}
|
||||
|
||||
const RenderFilterFields = ({ column, operator, value, id }) => {
|
||||
let displayColumns = columns.map(({ accessor }) => ({
|
||||
value: accessor,
|
||||
label: accessor,
|
||||
}));
|
||||
return (
|
||||
<div className="tab-content-wrapper tj-db-field-wrapper mt-2">
|
||||
<div className="d-flex mb-2">
|
||||
<label className="form-label" data-cy="label-column-filter">
|
||||
Filter
|
||||
</label>
|
||||
|
||||
const handleColumnChange = (selectedOption) => {
|
||||
updateFilterOptionsChanged({ ...updateRowsOptions?.where_filters[id], ...{ column: selectedOption } });
|
||||
};
|
||||
|
||||
const handleOperatorChange = (selectedOption) => {
|
||||
updateFilterOptionsChanged({ ...updateRowsOptions?.where_filters[id], ...{ operator: selectedOption } });
|
||||
};
|
||||
|
||||
const handleValueChange = (newValue) => {
|
||||
updateFilterOptionsChanged({ ...updateRowsOptions?.where_filters[id], ...{ value: newValue } });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-1 row-container">
|
||||
<div className="d-flex fields-container">
|
||||
<div className="field col">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select column"
|
||||
value={column}
|
||||
options={displayColumns}
|
||||
onChange={handleColumnChange}
|
||||
<div className="field-container flex-grow-1">
|
||||
{Object.values(updateRowsOptions?.where_filters || {}).map((filter) => (
|
||||
<RenderFilterFields
|
||||
key={filter.id}
|
||||
{...filter}
|
||||
columns={columns}
|
||||
updateFilterOptionsChanged={updateFilterOptionsChanged}
|
||||
updateRowsOptions={updateRowsOptions}
|
||||
darkMode={darkMode}
|
||||
removeFilterConditionPair={removeFilterConditionPair}
|
||||
/>
|
||||
</div>
|
||||
<div className="field col mx-1">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select operation"
|
||||
value={operator}
|
||||
options={operators}
|
||||
onChange={handleOperatorChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="field col-4">
|
||||
{operator === 'is' ? (
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select value"
|
||||
value={value}
|
||||
options={isOperatorOptions}
|
||||
onChange={handleValueChange}
|
||||
/>
|
||||
) : (
|
||||
<CodeHinter
|
||||
initialValue={value ? (typeof value === 'string' ? value : JSON.stringify(value)) : value}
|
||||
className="codehinter-plugins"
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
height={'32px'}
|
||||
placeholder="key"
|
||||
onChange={(newValue) => handleValueChange(newValue)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-1 cursor-pointer m-1 mr-2">
|
||||
<svg
|
||||
onClick={() => removeFilterConditionPair(id)}
|
||||
width="12"
|
||||
height="14"
|
||||
viewBox="0 0 12 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
))}
|
||||
|
||||
<ButtonSolid
|
||||
variant="ghostBlue"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
addNewFilterConditionPair();
|
||||
}}
|
||||
className={`cursor-pointer fit-content ${isEmpty(updateRowsOptions?.where_filters) ? '' : 'mt-2'}`}
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.72386 0.884665C3.97391 0.634616 4.31304 0.494141 4.66667 0.494141H7.33333C7.68696 0.494141 8.02609 0.634616 8.27614 0.884665C8.52619 1.13471 8.66667 1.47385 8.66667 1.82747V3.16081H10.6589C10.6636 3.16076 10.6683 3.16076 10.673 3.16081H11.3333C11.7015 3.16081 12 3.45928 12 3.82747C12 4.19566 11.7015 4.49414 11.3333 4.49414H11.2801L10.6664 11.858C10.6585 12.3774 10.4488 12.8738 10.0809 13.2417C9.70581 13.6168 9.1971 13.8275 8.66667 13.8275H3.33333C2.8029 13.8275 2.29419 13.6168 1.91912 13.2417C1.55125 12.8738 1.34148 12.3774 1.33357 11.858L0.719911 4.49414H0.666667C0.298477 4.49414 0 4.19566 0 3.82747C0 3.45928 0.298477 3.16081 0.666667 3.16081H1.32702C1.33174 3.16076 1.33644 3.16076 1.34113 3.16081H3.33333V1.82747C3.33333 1.47385 3.47381 1.13471 3.72386 0.884665ZM2.05787 4.49414L2.66436 11.7721C2.6659 11.7905 2.66667 11.809 2.66667 11.8275C2.66667 12.0043 2.7369 12.1739 2.86193 12.2989C2.98695 12.4239 3.15652 12.4941 3.33333 12.4941H8.66667C8.84348 12.4941 9.01305 12.4239 9.13807 12.2989C9.2631 12.1739 9.33333 12.0043 9.33333 11.8275C9.33333 11.809 9.3341 11.7905 9.33564 11.7721L9.94213 4.49414H2.05787ZM7.33333 3.16081H4.66667V1.82747H7.33333V3.16081ZM4.19526 7.63221C3.93491 7.37186 3.93491 6.94975 4.19526 6.6894C4.45561 6.42905 4.87772 6.42905 5.13807 6.6894L6 7.55133L6.86193 6.6894C7.12228 6.42905 7.54439 6.42905 7.80474 6.6894C8.06509 6.94975 8.06509 7.37186 7.80474 7.63221L6.94281 8.49414L7.80474 9.35607C8.06509 9.61642 8.06509 10.0385 7.80474 10.2989C7.54439 10.5592 7.12228 10.5592 6.86193 10.2989L6 9.43695L5.13807 10.2989C4.87772 10.5592 4.45561 10.5592 4.19526 10.2989C3.93491 10.0385 3.93491 9.61642 4.19526 9.35607L5.05719 8.49414L4.19526 7.63221Z"
|
||||
fill="#E54D2E"
|
||||
d="M5.34554 10.0207C5.15665 10.0207 4.99832 9.95678 4.87054 9.829C4.74276 9.70123 4.67887 9.54289 4.67887 9.354V5.854H1.17887C0.989985 5.854 0.831651 5.79011 0.703874 5.66234C0.576096 5.53456 0.512207 5.37623 0.512207 5.18734C0.512207 4.99845 0.576096 4.84012 0.703874 4.71234C0.831651 4.58456 0.989985 4.52067 1.17887 4.52067H4.67887V1.02067C4.67887 0.831782 4.74276 0.673448 4.87054 0.54567C4.99832 0.417893 5.15665 0.354004 5.34554 0.354004C5.53443 0.354004 5.69276 0.417893 5.82054 0.54567C5.94832 0.673448 6.01221 0.831782 6.01221 1.02067V4.52067H9.51221C9.7011 4.52067 9.85943 4.58456 9.98721 4.71234C10.115 4.84012 10.1789 4.99845 10.1789 5.18734C10.1789 5.37623 10.115 5.53456 9.98721 5.66234C9.85943 5.79011 9.7011 5.854 9.51221 5.854H6.01221V9.354C6.01221 9.54289 5.94832 9.70123 5.82054 9.829C5.69276 9.95678 5.53443 10.0207 5.34554 10.0207Z"
|
||||
fill="#466BF2"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
Add Condition
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
<div className="fields-container d-flex">
|
||||
<label className="form-label" data-cy="label-column-filter">
|
||||
Columns
|
||||
</label>
|
||||
<div className="field-container flex-grow-1">
|
||||
{Object.entries(updateRowsOptions?.columns).map(([key, value]) => {
|
||||
return (
|
||||
<RenderColumnOptions
|
||||
key={key}
|
||||
column={value.column}
|
||||
value={value.value}
|
||||
id={key}
|
||||
columns={columns}
|
||||
updateRowsOptions={updateRowsOptions}
|
||||
handleColumnOptionChange={handleColumnOptionChange}
|
||||
darkMode={darkMode}
|
||||
removeColumnOptionsPair={removeColumnOptionsPair}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{Object.keys(updateRowsOptions?.columns).length !== columns.length && (
|
||||
<ButtonSolid
|
||||
variant="ghostBlue"
|
||||
size="sm"
|
||||
onClick={addNewColumnOptionsPair}
|
||||
className={`cursor-pointer fit-content ${isEmpty(updateRowsOptions?.columns) ? '' : 'mt-2'}`}
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5.34554 10.0207C5.15665 10.0207 4.99832 9.95678 4.87054 9.829C4.74276 9.70123 4.67887 9.54289 4.67887 9.354V5.854H1.17887C0.989985 5.854 0.831651 5.79011 0.703874 5.66234C0.576096 5.53456 0.512207 5.37623 0.512207 5.18734C0.512207 4.99845 0.576096 4.84012 0.703874 4.71234C0.831651 4.58456 0.989985 4.52067 1.17887 4.52067H4.67887V1.02067C4.67887 0.831782 4.74276 0.673448 4.87054 0.54567C4.99832 0.417893 5.15665 0.354004 5.34554 0.354004C5.53443 0.354004 5.69276 0.417893 5.82054 0.54567C5.94832 0.673448 6.01221 0.831782 6.01221 1.02067V4.52067H9.51221C9.7011 4.52067 9.85943 4.58456 9.98721 4.71234C10.115 4.84012 10.1789 4.99845 10.1789 5.18734C10.1789 5.37623 10.115 5.53456 9.98721 5.66234C9.85943 5.79011 9.7011 5.854 9.51221 5.854H6.01221V9.354C6.01221 9.54289 5.94832 9.70123 5.82054 9.829C5.69276 9.95678 5.53443 10.0207 5.34554 10.0207Z"
|
||||
fill="#466BF2"
|
||||
/>
|
||||
</svg>
|
||||
Add column
|
||||
</ButtonSolid>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const RenderFilterFields = ({
|
||||
column,
|
||||
operator,
|
||||
value,
|
||||
id,
|
||||
columns,
|
||||
updateFilterOptionsChanged,
|
||||
updateRowsOptions,
|
||||
darkMode,
|
||||
removeFilterConditionPair,
|
||||
}) => {
|
||||
let displayColumns = columns.map(({ accessor }) => ({
|
||||
value: accessor,
|
||||
label: accessor,
|
||||
}));
|
||||
|
||||
const handleColumnChange = (selectedOption) => {
|
||||
updateFilterOptionsChanged({ ...updateRowsOptions?.where_filters[id], ...{ column: selectedOption } });
|
||||
};
|
||||
|
||||
const RenderColumnOptions = ({ column, value, id }) => {
|
||||
const filteredColumns = columns.filter(({ isPrimaryKey }) => !isPrimaryKey);
|
||||
const existingColumnOptions = Object.values(updateRowsOptions?.columns).map(({ column }) => column);
|
||||
let displayColumns = filteredColumns.map(({ accessor }) => ({
|
||||
value: accessor,
|
||||
label: accessor,
|
||||
}));
|
||||
const handleOperatorChange = (selectedOption) => {
|
||||
updateFilterOptionsChanged({ ...updateRowsOptions?.where_filters[id], ...{ operator: selectedOption } });
|
||||
};
|
||||
|
||||
if (existingColumnOptions.length > 0) {
|
||||
displayColumns = displayColumns.filter(
|
||||
({ value }) => !existingColumnOptions.map((item) => item !== column && item).includes(value)
|
||||
);
|
||||
}
|
||||
const handleValueChange = (newValue) => {
|
||||
updateFilterOptionsChanged({ ...updateRowsOptions?.where_filters[id], ...{ value: newValue } });
|
||||
};
|
||||
|
||||
const handleColumnChange = (selectedOption) => {
|
||||
const columnOptions = updateRowsOptions?.columns;
|
||||
const updatedOption = {
|
||||
...columnOptions[id],
|
||||
column: selectedOption,
|
||||
};
|
||||
|
||||
const newColumnOptions = { ...columnOptions, [id]: updatedOption };
|
||||
|
||||
handleColumnOptionChange(newColumnOptions);
|
||||
};
|
||||
|
||||
const handleValueChange = (newValue) => {
|
||||
const columnOptions = updateRowsOptions?.columns;
|
||||
const updatedOption = {
|
||||
...columnOptions[id],
|
||||
value: newValue,
|
||||
};
|
||||
|
||||
const newColumnOptions = { ...columnOptions, [id]: updatedOption };
|
||||
|
||||
handleColumnOptionChange(newColumnOptions);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-1 row-container">
|
||||
<div className="d-flex fields-container">
|
||||
<div className="field col-4">
|
||||
return (
|
||||
<div className="mt-1 row-container">
|
||||
<div className="d-flex fields-container">
|
||||
<div className="field col">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select column"
|
||||
value={column}
|
||||
options={displayColumns}
|
||||
onChange={handleColumnChange}
|
||||
width="auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="field col mx-1">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select operation"
|
||||
value={operator}
|
||||
options={operators}
|
||||
onChange={handleOperatorChange}
|
||||
width="auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="field col-4">
|
||||
{operator === 'is' ? (
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select column"
|
||||
value={column}
|
||||
options={displayColumns}
|
||||
onChange={handleColumnChange}
|
||||
placeholder="Select value"
|
||||
value={value}
|
||||
options={isOperatorOptions}
|
||||
onChange={handleValueChange}
|
||||
width="auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field col-4">
|
||||
) : (
|
||||
<CodeHinter
|
||||
initialValue={value ? (typeof value === 'string' ? value : JSON.stringify(value)) : value}
|
||||
className="codehinter-plugins"
|
||||
|
|
@ -211,81 +223,121 @@ export const UpdateRows = React.memo(({ darkMode }) => {
|
|||
placeholder="key"
|
||||
onChange={(newValue) => handleValueChange(newValue)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col cursor-pointer m-1 mx-3">
|
||||
<svg
|
||||
onClick={() => {
|
||||
removeColumnOptionsPair(id);
|
||||
}}
|
||||
width="12"
|
||||
height="14"
|
||||
viewBox="0 0 12 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.72386 0.884665C3.97391 0.634616 4.31304 0.494141 4.66667 0.494141H7.33333C7.68696 0.494141 8.02609 0.634616 8.27614 0.884665C8.52619 1.13471 8.66667 1.47385 8.66667 1.82747V3.16081H10.6589C10.6636 3.16076 10.6683 3.16076 10.673 3.16081H11.3333C11.7015 3.16081 12 3.45928 12 3.82747C12 4.19566 11.7015 4.49414 11.3333 4.49414H11.2801L10.6664 11.858C10.6585 12.3774 10.4488 12.8738 10.0809 13.2417C9.70581 13.6168 9.1971 13.8275 8.66667 13.8275H3.33333C2.8029 13.8275 2.29419 13.6168 1.91912 13.2417C1.55125 12.8738 1.34148 12.3774 1.33357 11.858L0.719911 4.49414H0.666667C0.298477 4.49414 0 4.19566 0 3.82747C0 3.45928 0.298477 3.16081 0.666667 3.16081H1.32702C1.33174 3.16076 1.33644 3.16076 1.34113 3.16081H3.33333V1.82747C3.33333 1.47385 3.47381 1.13471 3.72386 0.884665ZM2.05787 4.49414L2.66436 11.7721C2.6659 11.7905 2.66667 11.809 2.66667 11.8275C2.66667 12.0043 2.7369 12.1739 2.86193 12.2989C2.98695 12.4239 3.15652 12.4941 3.33333 12.4941H8.66667C8.84348 12.4941 9.01305 12.4239 9.13807 12.2989C9.2631 12.1739 9.33333 12.0043 9.33333 11.8275C9.33333 11.809 9.3341 11.7905 9.33564 11.7721L9.94213 4.49414H2.05787ZM7.33333 3.16081H4.66667V1.82747H7.33333V3.16081ZM4.19526 7.63221C3.93491 7.37186 3.93491 6.94975 4.19526 6.6894C4.45561 6.42905 4.87772 6.42905 5.13807 6.6894L6 7.55133L6.86193 6.6894C7.12228 6.42905 7.54439 6.42905 7.80474 6.6894C8.06509 6.94975 8.06509 7.37186 7.80474 7.63221L6.94281 8.49414L7.80474 9.35607C8.06509 9.61642 8.06509 10.0385 7.80474 10.2989C7.54439 10.5592 7.12228 10.5592 6.86193 10.2989L6 9.43695L5.13807 10.2989C4.87772 10.5592 4.45561 10.5592 4.19526 10.2989C3.93491 10.0385 3.93491 9.61642 4.19526 9.35607L5.05719 8.49414L4.19526 7.63221Z"
|
||||
fill="#E54D2E"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="tab-content-wrapper tj-db-field-wrapper mt-2">
|
||||
<label className="form-label" data-cy="label-column-filter">
|
||||
Filter
|
||||
</label>
|
||||
|
||||
<div className="field-container">
|
||||
{Object.values(updateRowsOptions?.where_filters || {}).map((filter) => (
|
||||
<RenderFilterFields key={filter.id} {...filter} />
|
||||
))}
|
||||
|
||||
<div
|
||||
className="cursor-pointer pb-3 fit-content"
|
||||
onClick={() => {
|
||||
addNewFilterConditionPair();
|
||||
}}
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<div className="col-1 cursor-pointer m-1 mr-2">
|
||||
<svg
|
||||
onClick={() => removeFilterConditionPair(id)}
|
||||
width="12"
|
||||
height="14"
|
||||
viewBox="0 0 12 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.34554 10.0207C5.15665 10.0207 4.99832 9.95678 4.87054 9.829C4.74276 9.70123 4.67887 9.54289 4.67887 9.354V5.854H1.17887C0.989985 5.854 0.831651 5.79011 0.703874 5.66234C0.576096 5.53456 0.512207 5.37623 0.512207 5.18734C0.512207 4.99845 0.576096 4.84012 0.703874 4.71234C0.831651 4.58456 0.989985 4.52067 1.17887 4.52067H4.67887V1.02067C4.67887 0.831782 4.74276 0.673448 4.87054 0.54567C4.99832 0.417893 5.15665 0.354004 5.34554 0.354004C5.53443 0.354004 5.69276 0.417893 5.82054 0.54567C5.94832 0.673448 6.01221 0.831782 6.01221 1.02067V4.52067H9.51221C9.7011 4.52067 9.85943 4.58456 9.98721 4.71234C10.115 4.84012 10.1789 4.99845 10.1789 5.18734C10.1789 5.37623 10.115 5.53456 9.98721 5.66234C9.85943 5.79011 9.7011 5.854 9.51221 5.854H6.01221V9.354C6.01221 9.54289 5.94832 9.70123 5.82054 9.829C5.69276 9.95678 5.53443 10.0207 5.34554 10.0207Z"
|
||||
fill="#466BF2"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.72386 0.884665C3.97391 0.634616 4.31304 0.494141 4.66667 0.494141H7.33333C7.68696 0.494141 8.02609 0.634616 8.27614 0.884665C8.52619 1.13471 8.66667 1.47385 8.66667 1.82747V3.16081H10.6589C10.6636 3.16076 10.6683 3.16076 10.673 3.16081H11.3333C11.7015 3.16081 12 3.45928 12 3.82747C12 4.19566 11.7015 4.49414 11.3333 4.49414H11.2801L10.6664 11.858C10.6585 12.3774 10.4488 12.8738 10.0809 13.2417C9.70581 13.6168 9.1971 13.8275 8.66667 13.8275H3.33333C2.8029 13.8275 2.29419 13.6168 1.91912 13.2417C1.55125 12.8738 1.34148 12.3774 1.33357 11.858L0.719911 4.49414H0.666667C0.298477 4.49414 0 4.19566 0 3.82747C0 3.45928 0.298477 3.16081 0.666667 3.16081H1.32702C1.33174 3.16076 1.33644 3.16076 1.34113 3.16081H3.33333V1.82747C3.33333 1.47385 3.47381 1.13471 3.72386 0.884665ZM2.05787 4.49414L2.66436 11.7721C2.6659 11.7905 2.66667 11.809 2.66667 11.8275C2.66667 12.0043 2.7369 12.1739 2.86193 12.2989C2.98695 12.4239 3.15652 12.4941 3.33333 12.4941H8.66667C8.84348 12.4941 9.01305 12.4239 9.13807 12.2989C9.2631 12.1739 9.33333 12.0043 9.33333 11.8275C9.33333 11.809 9.3341 11.7905 9.33564 11.7721L9.94213 4.49414H2.05787ZM7.33333 3.16081H4.66667V1.82747H7.33333V3.16081ZM4.19526 7.63221C3.93491 7.37186 3.93491 6.94975 4.19526 6.6894C4.45561 6.42905 4.87772 6.42905 5.13807 6.6894L6 7.55133L6.86193 6.6894C7.12228 6.42905 7.54439 6.42905 7.80474 6.6894C8.06509 6.94975 8.06509 7.37186 7.80474 7.63221L6.94281 8.49414L7.80474 9.35607C8.06509 9.61642 8.06509 10.0385 7.80474 10.2989C7.54439 10.5592 7.12228 10.5592 6.86193 10.2989L6 9.43695L5.13807 10.2989C4.87772 10.5592 4.45561 10.5592 4.19526 10.2989C3.93491 10.0385 3.93491 9.61642 4.19526 9.35607L5.05719 8.49414L4.19526 7.63221Z"
|
||||
fill="#E54D2E"
|
||||
/>
|
||||
</svg>
|
||||
Add Condition
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="fields-container">
|
||||
<label className="form-label" data-cy="label-column-filter">
|
||||
Columns
|
||||
</label>
|
||||
<div className="field-container">
|
||||
{Object.entries(updateRowsOptions?.columns).map(([key, value]) => {
|
||||
return <RenderColumnOptions key={key} column={value.column} value={value.value} id={key} />;
|
||||
})}
|
||||
|
||||
{Object.keys(updateRowsOptions?.columns).length !== columns.length && (
|
||||
<div className="cursor-pointer pb-3 fit-content" onClick={addNewColumnOptionsPair}>
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5.34554 10.0207C5.15665 10.0207 4.99832 9.95678 4.87054 9.829C4.74276 9.70123 4.67887 9.54289 4.67887 9.354V5.854H1.17887C0.989985 5.854 0.831651 5.79011 0.703874 5.66234C0.576096 5.53456 0.512207 5.37623 0.512207 5.18734C0.512207 4.99845 0.576096 4.84012 0.703874 4.71234C0.831651 4.58456 0.989985 4.52067 1.17887 4.52067H4.67887V1.02067C4.67887 0.831782 4.74276 0.673448 4.87054 0.54567C4.99832 0.417893 5.15665 0.354004 5.34554 0.354004C5.53443 0.354004 5.69276 0.417893 5.82054 0.54567C5.94832 0.673448 6.01221 0.831782 6.01221 1.02067V4.52067H9.51221C9.7011 4.52067 9.85943 4.58456 9.98721 4.71234C10.115 4.84012 10.1789 4.99845 10.1789 5.18734C10.1789 5.37623 10.115 5.53456 9.98721 5.66234C9.85943 5.79011 9.7011 5.854 9.51221 5.854H6.01221V9.354C6.01221 9.54289 5.94832 9.70123 5.82054 9.829C5.69276 9.95678 5.53443 10.0207 5.34554 10.0207Z"
|
||||
fill="#466BF2"
|
||||
/>
|
||||
</svg>
|
||||
Add column
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const RenderColumnOptions = ({
|
||||
column,
|
||||
value,
|
||||
id,
|
||||
columns,
|
||||
updateRowsOptions,
|
||||
handleColumnOptionChange,
|
||||
darkMode,
|
||||
removeColumnOptionsPair,
|
||||
}) => {
|
||||
const filteredColumns = columns.filter(({ isPrimaryKey }) => !isPrimaryKey);
|
||||
const existingColumnOptions = Object.values(updateRowsOptions?.columns).map(({ column }) => column);
|
||||
let displayColumns = filteredColumns.map(({ accessor }) => ({
|
||||
value: accessor,
|
||||
label: accessor,
|
||||
}));
|
||||
|
||||
if (existingColumnOptions.length > 0) {
|
||||
displayColumns = displayColumns.filter(
|
||||
({ value }) => !existingColumnOptions.map((item) => item !== column && item).includes(value)
|
||||
);
|
||||
}
|
||||
|
||||
const handleColumnChange = (selectedOption) => {
|
||||
const columnOptions = updateRowsOptions?.columns;
|
||||
const updatedOption = {
|
||||
...columnOptions[id],
|
||||
column: selectedOption,
|
||||
};
|
||||
|
||||
const newColumnOptions = { ...columnOptions, [id]: updatedOption };
|
||||
|
||||
handleColumnOptionChange(newColumnOptions);
|
||||
};
|
||||
|
||||
const handleValueChange = (newValue) => {
|
||||
const columnOptions = updateRowsOptions?.columns;
|
||||
const updatedOption = {
|
||||
...columnOptions[id],
|
||||
value: newValue,
|
||||
};
|
||||
|
||||
const newColumnOptions = { ...columnOptions, [id]: updatedOption };
|
||||
|
||||
handleColumnOptionChange(newColumnOptions);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-1 row-container">
|
||||
<div className="d-flex fields-container">
|
||||
<div className="field col-4 me-3">
|
||||
<Select
|
||||
useMenuPortal={true}
|
||||
placeholder="Select column"
|
||||
value={column}
|
||||
options={displayColumns}
|
||||
onChange={handleColumnChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field col-6 mx-1">
|
||||
<CodeHinter
|
||||
initialValue={value ? (typeof value === 'string' ? value : JSON.stringify(value)) : value}
|
||||
className="codehinter-plugins"
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
height={'32px'}
|
||||
placeholder="key"
|
||||
onChange={(newValue) => handleValueChange(newValue)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col cursor-pointer m-1 mx-3">
|
||||
<svg
|
||||
onClick={() => {
|
||||
removeColumnOptionsPair(id);
|
||||
}}
|
||||
width="12"
|
||||
height="14"
|
||||
viewBox="0 0 12 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.72386 0.884665C3.97391 0.634616 4.31304 0.494141 4.66667 0.494141H7.33333C7.68696 0.494141 8.02609 0.634616 8.27614 0.884665C8.52619 1.13471 8.66667 1.47385 8.66667 1.82747V3.16081H10.6589C10.6636 3.16076 10.6683 3.16076 10.673 3.16081H11.3333C11.7015 3.16081 12 3.45928 12 3.82747C12 4.19566 11.7015 4.49414 11.3333 4.49414H11.2801L10.6664 11.858C10.6585 12.3774 10.4488 12.8738 10.0809 13.2417C9.70581 13.6168 9.1971 13.8275 8.66667 13.8275H3.33333C2.8029 13.8275 2.29419 13.6168 1.91912 13.2417C1.55125 12.8738 1.34148 12.3774 1.33357 11.858L0.719911 4.49414H0.666667C0.298477 4.49414 0 4.19566 0 3.82747C0 3.45928 0.298477 3.16081 0.666667 3.16081H1.32702C1.33174 3.16076 1.33644 3.16076 1.34113 3.16081H3.33333V1.82747C3.33333 1.47385 3.47381 1.13471 3.72386 0.884665ZM2.05787 4.49414L2.66436 11.7721C2.6659 11.7905 2.66667 11.809 2.66667 11.8275C2.66667 12.0043 2.7369 12.1739 2.86193 12.2989C2.98695 12.4239 3.15652 12.4941 3.33333 12.4941H8.66667C8.84348 12.4941 9.01305 12.4239 9.13807 12.2989C9.2631 12.1739 9.33333 12.0043 9.33333 11.8275C9.33333 11.809 9.3341 11.7905 9.33564 11.7721L9.94213 4.49414H2.05787ZM7.33333 3.16081H4.66667V1.82747H7.33333V3.16081ZM4.19526 7.63221C3.93491 7.37186 3.93491 6.94975 4.19526 6.6894C4.45561 6.42905 4.87772 6.42905 5.13807 6.6894L6 7.55133L6.86193 6.6894C7.12228 6.42905 7.54439 6.42905 7.80474 6.6894C8.06509 6.94975 8.06509 7.37186 7.80474 7.63221L6.94281 8.49414L7.80474 9.35607C8.06509 9.61642 8.06509 10.0385 7.80474 10.2989C7.54439 10.5592 7.12228 10.5592 6.86193 10.2989L6 9.43695L5.13807 10.2989C4.87772 10.5592 4.45561 10.5592 4.19526 10.2989C3.93491 10.0385 3.93491 9.61642 4.19526 9.35607L5.05719 8.49414L4.19526 7.63221Z"
|
||||
fill="#E54D2E"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,12 +22,17 @@ export const allSources = {
|
|||
...Object.keys(allOperations).reduce((accumulator, currentValue) => {
|
||||
accumulator[currentValue] = (props) => (
|
||||
<div className="query-editor-dynamic-form-container">
|
||||
<DynamicForm schema={allOperations[currentValue]} {...props} computeSelectStyles={computeSelectStyles} />
|
||||
<DynamicForm
|
||||
schema={allOperations[currentValue]}
|
||||
{...props}
|
||||
computeSelectStyles={computeSelectStyles}
|
||||
layout="horizontal"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return accumulator;
|
||||
}, {}),
|
||||
Tooljetdb: (props) => <DynamicForm schema={tooljetDbOperations} {...props} />,
|
||||
Tooljetdb: (props) => <DynamicForm schema={tooljetDbOperations} {...props} layout="horizontal" />,
|
||||
Restapi,
|
||||
Runjs,
|
||||
Runpy,
|
||||
|
|
@ -38,6 +43,6 @@ export const allSources = {
|
|||
|
||||
export const source = (props) => (
|
||||
<div className="query-editor-dynamic-form-container">
|
||||
<DynamicForm schema={props.pluginSchema} {...props} computeSelectStyles={computeSelectStyles} />
|
||||
<DynamicForm schema={props.pluginSchema} {...props} computeSelectStyles={computeSelectStyles} layout="horizontal" />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,44 +7,22 @@ import { defaultSources } from './constants';
|
|||
|
||||
import { useQueryCreationLoading, useQueryUpdationLoading } from '@/_stores/dataQueriesStore';
|
||||
import { useDataSources, useGlobalDataSources, useLoadingDataSources } from '@/_stores/dataSourcesStore';
|
||||
import {
|
||||
useQueryToBeRun,
|
||||
usePreviewLoading,
|
||||
usePreviewData,
|
||||
useSelectedQuery,
|
||||
useQueryPanelActions,
|
||||
} from '@/_stores/queryPanelStore';
|
||||
import { useQueryToBeRun, useSelectedQuery, useQueryPanelActions } from '@/_stores/queryPanelStore';
|
||||
|
||||
const QueryManager = ({
|
||||
addNewQueryAndDeselectSelectedQuery,
|
||||
toggleQueryEditor,
|
||||
mode,
|
||||
dataQueriesChanged,
|
||||
appId,
|
||||
darkMode,
|
||||
apps,
|
||||
allComponents,
|
||||
dataSourceModalHandler,
|
||||
appDefinition,
|
||||
editorRef,
|
||||
createDraftQuery,
|
||||
updateDraftQueryName,
|
||||
}) => {
|
||||
const QueryManager = ({ mode, dataQueriesChanged, appId, darkMode, apps, allComponents, appDefinition, editorRef }) => {
|
||||
const loadingDataSources = useLoadingDataSources();
|
||||
const dataSources = useDataSources();
|
||||
const globalDataSources = useGlobalDataSources();
|
||||
const queryToBeRun = useQueryToBeRun();
|
||||
const isCreationInProcess = useQueryCreationLoading();
|
||||
const isUpdationInProcess = useQueryUpdationLoading();
|
||||
const previewLoading = usePreviewLoading();
|
||||
const queryPreviewData = usePreviewData();
|
||||
const selectedQuery = useSelectedQuery();
|
||||
const { setSelectedDataSource, setQueryToBeRun } = useQueryPanelActions();
|
||||
|
||||
const [options, setOptions] = useState({});
|
||||
const mounted = useRef(false);
|
||||
const previewPanelRef = useRef(null);
|
||||
|
||||
/** TODO: Below effect primarily used only for websocket invocation post update. Can be removed onece websocket logic is revamped */
|
||||
useEffect(() => {
|
||||
if (mounted.current && !isCreationInProcess && !isUpdationInProcess) {
|
||||
return dataQueriesChanged();
|
||||
|
|
@ -67,16 +45,20 @@ const QueryManager = ({
|
|||
|
||||
useEffect(() => {
|
||||
if (selectedQuery) {
|
||||
if (selectedQuery?.kind in defaultSources && !selectedQuery?.data_source_id) {
|
||||
const selectedDS = [...dataSources, ...globalDataSources].find(
|
||||
(datasource) => datasource.id === selectedQuery?.data_source_id
|
||||
);
|
||||
//TODO: currently type is not taken into account. May create issues in importing REST apis. to be revamped when import app is revamped
|
||||
if (
|
||||
selectedQuery?.kind in defaultSources &&
|
||||
(!selectedQuery?.data_source_id || ['runjs', 'runpy'].includes(selectedQuery?.data_source_id) || !selectedDS)
|
||||
) {
|
||||
return setSelectedDataSource(defaultSources[selectedQuery?.kind]);
|
||||
}
|
||||
mode === 'edit' &&
|
||||
setSelectedDataSource(
|
||||
[...dataSources, ...globalDataSources].find(
|
||||
(datasource) => datasource.id === selectedQuery?.data_source_id
|
||||
) || null
|
||||
);
|
||||
} else if (selectedQuery === null) setSelectedDataSource(null);
|
||||
setSelectedDataSource(selectedDS || null);
|
||||
} else if (selectedQuery === null) {
|
||||
setSelectedDataSource(null);
|
||||
}
|
||||
}, [selectedQuery, dataSources, globalDataSources, setSelectedDataSource, mode]);
|
||||
|
||||
return (
|
||||
|
|
@ -85,31 +67,15 @@ const QueryManager = ({
|
|||
'd-none': loadingDataSources,
|
||||
})}
|
||||
>
|
||||
<QueryManagerHeader
|
||||
darkMode={darkMode}
|
||||
mode={mode}
|
||||
addNewQueryAndDeselectSelectedQuery={addNewQueryAndDeselectSelectedQuery}
|
||||
updateDraftQueryName={updateDraftQueryName}
|
||||
toggleQueryEditor={toggleQueryEditor}
|
||||
previewLoading={previewLoading}
|
||||
options={options}
|
||||
appId={appId}
|
||||
ref={previewPanelRef}
|
||||
editorRef={editorRef}
|
||||
/>
|
||||
<QueryManagerHeader darkMode={darkMode} options={options} editorRef={editorRef} appId={appId} />
|
||||
<QueryManagerBody
|
||||
darkMode={darkMode}
|
||||
mode={mode}
|
||||
dataSourceModalHandler={dataSourceModalHandler}
|
||||
options={options}
|
||||
previewLoading={previewLoading}
|
||||
queryPreviewData={queryPreviewData}
|
||||
allComponents={allComponents}
|
||||
apps={apps}
|
||||
appId={appId}
|
||||
appDefinition={appDefinition}
|
||||
createDraftQuery={createDraftQuery}
|
||||
setOptions={setOptions}
|
||||
ref={previewPanelRef}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
export const staticDataSources = [
|
||||
{ kind: 'tooljetdb', id: 'null', name: 'Tooljet Database' },
|
||||
{ kind: 'restapi', id: 'null', name: 'REST API' },
|
||||
{ kind: 'runjs', id: 'runjs', name: 'Run JavaScript code' },
|
||||
{ kind: 'runpy', id: 'runpy', name: 'Run Python code' },
|
||||
{ kind: 'restapi', id: 'null', name: 'REST API', shortName: 'REST API' },
|
||||
{ kind: 'runjs', id: 'runjs', name: 'Run JavaScript code', shortName: 'JavaScript' },
|
||||
{ kind: 'runpy', id: 'runpy', name: 'Run Python code', shortName: 'Python' },
|
||||
{ kind: 'tooljetdb', id: 'null', name: 'Tooljet Database', shortName: 'ToolJet DB' },
|
||||
];
|
||||
|
||||
export const tabs = ['JSON', 'Raw'];
|
||||
|
|
@ -53,7 +53,7 @@ export const customToggles = {
|
|||
|
||||
export const mockDataQueryAsComponent = (events) => {
|
||||
return {
|
||||
component: { component: { definition: { events } } },
|
||||
component: { component: { definition: { events: events } } },
|
||||
componentMeta: {
|
||||
events: {
|
||||
onDataQuerySuccess: { displayName: 'Query Success' },
|
||||
|
|
|
|||
318
frontend/src/Editor/QueryPanel/FilterandSortPopup.jsx
Normal file
318
frontend/src/Editor/QueryPanel/FilterandSortPopup.jsx
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { OverlayTrigger, Popover, Form } from 'react-bootstrap';
|
||||
import cx from 'classnames';
|
||||
import { Button } from '@/_ui/LeftSidebar';
|
||||
import { useDataSources, useGlobalDataSources } from '@/_stores/dataSourcesStore';
|
||||
import Filter from '@/_ui/Icon/solidIcons/Filter';
|
||||
import Arrowleft from '@/_ui/Icon/bulkIcons/Arrowleft';
|
||||
import { useDataQueriesActions, useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
||||
import Tick from '@/_ui/Icon/solidIcons/Tick';
|
||||
import useShowPopover from '@/_hooks/useShowPopover';
|
||||
import DataSourceIcon from '../QueryManager/Components/DataSourceIcon';
|
||||
import { staticDataSources } from '../QueryManager/constants';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { PillButton } from '../QueryManager/QueryEditors/Runjs/ParameterDetails';
|
||||
|
||||
const FilterandSortPopup = ({ darkMode, selectedDataSources, onFilterDatasourcesChange, clearSelectedDataSources }) => {
|
||||
const [showMenu, setShowMenu] = useShowPopover(false, '#query-sort-filter-popover', '#query-sort-filter-popover-btn');
|
||||
const closeMenu = () => setShowMenu(false);
|
||||
const [action, setAction] = useState();
|
||||
const [search, setSearch] = useState('');
|
||||
const { sortDataQueries } = useDataQueriesActions();
|
||||
const dataSources = useDataSources();
|
||||
const globalDataSources = useGlobalDataSources();
|
||||
const [sources, setSources] = useState();
|
||||
|
||||
const searchBoxRef = useRef(null);
|
||||
|
||||
const { sortBy, sortOrder, dataQueries } = useDataQueriesStore();
|
||||
|
||||
useState(() => {
|
||||
if (action === 'filter-by-datasource' && searchBoxRef.current) {
|
||||
searchBoxRef.current.focus();
|
||||
}
|
||||
}, [action]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showMenu) {
|
||||
const seen = new Set();
|
||||
const createdSources = dataQueries.map((query) => {
|
||||
const globalDS = [...dataSources, ...globalDataSources].find((source) => source.id === query.data_source_id);
|
||||
if (globalDS) {
|
||||
return globalDS;
|
||||
}
|
||||
return {
|
||||
...staticDataSources.find((source) => source.kind === query.kind),
|
||||
id: null,
|
||||
};
|
||||
});
|
||||
setSearch('');
|
||||
setSources(
|
||||
createdSources
|
||||
.filter((source) => {
|
||||
const key = source.kind + '-' + source.id;
|
||||
if (seen.has(key)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(key);
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const aChecked = selectedDataSources.some((item) => item.id === a.id && item.kind === a.kind);
|
||||
const bChecked = selectedDataSources.some((item) => item.id === b.id && item.kind === b.kind);
|
||||
if (aChecked && !bChecked) {
|
||||
return -1;
|
||||
}
|
||||
if (!aChecked && bChecked) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
setAction();
|
||||
}
|
||||
}, [dataQueries, dataSources, globalDataSources, showMenu]);
|
||||
|
||||
const handlePageCallback = (action) => {
|
||||
setAction(action);
|
||||
};
|
||||
|
||||
const handleSort = (sortBy, sortOrder) => {
|
||||
sortDataQueries(sortBy, sortOrder);
|
||||
closeMenu();
|
||||
};
|
||||
|
||||
const renderPopupComponent = (action) => {
|
||||
switch (action) {
|
||||
case 'filter-by-datasource':
|
||||
return (
|
||||
<DataSourceSelector
|
||||
search={search}
|
||||
setSearch={setSearch}
|
||||
sources={sources}
|
||||
onFilterDatasourcesChange={onFilterDatasourcesChange}
|
||||
onBackBtnClick={() => setAction()}
|
||||
selectedDataSources={selectedDataSources}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<div
|
||||
className="card-body p-0 tj-scrollbar query-editor-sort-filter-popup"
|
||||
style={{ height: '315px', overflowY: 'auto' }}
|
||||
>
|
||||
<div className="color-slate9 px-3 pb-2 w-100">
|
||||
<small data-cy="label-filter-by">Filter By</small>
|
||||
</div>
|
||||
<div className={`tj-list-btn mx-1 ${selectedDataSources.length ? 'd-flex' : ''}`}>
|
||||
<MenuButton
|
||||
id="filter-by-datasource"
|
||||
text="Data Source"
|
||||
callback={handlePageCallback}
|
||||
disabled={dataQueries.length === 0}
|
||||
noMargin
|
||||
/>
|
||||
{selectedDataSources.length ? (
|
||||
<PillButton
|
||||
name={selectedDataSources.length}
|
||||
onRemove={clearSelectedDataSources}
|
||||
onClick={() => handlePageCallback('filter-by-datasource')}
|
||||
className="m-1 bg-slate6"
|
||||
size="sm"
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
<div class="border-bottom mt-1"></div>
|
||||
<div className="color-slate9 px-3 pb-2 pt-1 w-100">
|
||||
<small data-cy="label-sort-by">Sort By</small>
|
||||
</div>
|
||||
<MenuButton
|
||||
id="name"
|
||||
order="asc"
|
||||
text="Name: A-Z"
|
||||
callback={handleSort}
|
||||
active={sortBy === 'name' && sortOrder === 'asc'}
|
||||
/>
|
||||
<MenuButton
|
||||
id="name"
|
||||
order="desc"
|
||||
text="Name: Z-A"
|
||||
callback={handleSort}
|
||||
active={sortBy === 'name' && sortOrder === 'desc'}
|
||||
/>
|
||||
<MenuButton
|
||||
id="kind"
|
||||
order="asc"
|
||||
text="Type: A-Z"
|
||||
callback={handleSort}
|
||||
active={sortBy === 'kind' && sortOrder === 'asc'}
|
||||
/>
|
||||
<MenuButton
|
||||
id="kind"
|
||||
order="desc"
|
||||
text="Type: Z-A"
|
||||
callback={handleSort}
|
||||
active={sortBy === 'kind' && sortOrder === 'desc'}
|
||||
/>
|
||||
<MenuButton
|
||||
id="updated_at"
|
||||
order="asc"
|
||||
text="Last modified: oldest first"
|
||||
callback={handleSort}
|
||||
active={sortBy === 'updated_at' && sortOrder === 'asc'}
|
||||
/>
|
||||
<MenuButton
|
||||
id="updated_at"
|
||||
order="desc"
|
||||
text="Last modified: newest first"
|
||||
callback={handleSort}
|
||||
active={sortBy === 'updated_at' && sortOrder === 'desc'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<OverlayTrigger
|
||||
placement="right-end"
|
||||
// rootClose={true}
|
||||
show={showMenu}
|
||||
overlay={
|
||||
<Popover
|
||||
key={'page.i'}
|
||||
id="query-sort-filter-popover"
|
||||
className={`query-manager-sort-filter-popup ${darkMode && 'popover-dark-themed dark-theme tj-dark-mode'}`}
|
||||
>
|
||||
<Popover.Body key={'1'} bsPrefix="popover-body" className="pt-1 p-0">
|
||||
{renderPopupComponent(action)}
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<button
|
||||
id="query-sort-filter-popover-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowMenu((showMenu) => !showMenu);
|
||||
}}
|
||||
className={cx('position-relative btn-query-panel-header', {
|
||||
active: showMenu,
|
||||
})}
|
||||
style={{ ...(showMenu && { background: 'var(--slate5)' }) }}
|
||||
data-tooltip-id="tooltip-for-open-filter"
|
||||
data-tooltip-content="Show sort/filter"
|
||||
data-cy={`query-filter-button`}
|
||||
>
|
||||
<Filter width="13" height="13" fill="var(--slate12)" />
|
||||
{selectedDataSources.length > 0 && <div className="notification-dot"></div>}
|
||||
</button>
|
||||
<Tooltip id="tooltip-for-open-filter" className="tooltip" />
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
const DataSourceSelector = ({
|
||||
sources: _sources,
|
||||
search,
|
||||
setSearch,
|
||||
onFilterDatasourcesChange,
|
||||
onBackBtnClick,
|
||||
selectedDataSources,
|
||||
}) => {
|
||||
const searchBoxRef = useRef(null);
|
||||
const [sources, setSources] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
searchBoxRef.current.focus();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setSources(
|
||||
_sources.filter((source) => {
|
||||
if (!search || !source?.name) {
|
||||
return true;
|
||||
}
|
||||
return source.name.toLowerCase().includes(search.toLowerCase());
|
||||
})
|
||||
);
|
||||
}, [_sources, search]);
|
||||
|
||||
return (
|
||||
<div className="card-body p-0 mt-1">
|
||||
<div className="border-bottom d-flex px-2">
|
||||
<div className="d-flex align-items-center mb-1">
|
||||
<button className="border-0 bg-transparent rounded-0 p-0" onClick={onBackBtnClick}>
|
||||
<Arrowleft fill="#3E63DD" tailOpacity={1} />
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
className="bg-transparent border-0 form-control form-control-sm"
|
||||
placeholder="Select datasource"
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
ref={searchBoxRef}
|
||||
value={search}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tj-scrollbar py-2" style={{ height: '281px', overflowY: 'auto' }}>
|
||||
{sources.map((source) => (
|
||||
<label
|
||||
className={cx('px-2 py-2 tj-list-btn d-block mx-1')}
|
||||
key={source.id || source.kind}
|
||||
role="button"
|
||||
for={`default-${source.id || source.kind}`}
|
||||
>
|
||||
<Form.Check // prettier-ignore
|
||||
type={'checkbox'}
|
||||
id={`default-${source.id || source.kind}`}
|
||||
onChange={(e) => onFilterDatasourcesChange(source, e.target.value)}
|
||||
className="m-0"
|
||||
checked={selectedDataSources.some((item) => item.id === source.id && item.kind === source.kind)}
|
||||
label={
|
||||
<div className="d-flex align-items-center">
|
||||
<DataSourceIcon source={source} height={12} styles={{ minWidth: 12 }} />
|
||||
<span className="ms-1 text-truncate">{source.name}</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MenuButton = ({
|
||||
id,
|
||||
order,
|
||||
text,
|
||||
iconSrc,
|
||||
disabled = false,
|
||||
callback = () => null,
|
||||
active,
|
||||
noMargin = false,
|
||||
}) => {
|
||||
const handleOnClick = (e) => {
|
||||
e.stopPropagation();
|
||||
callback(id, order);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`field p-2 ${noMargin ? '' : 'mx-1'} tj-list-btn`}>
|
||||
<Button.UnstyledButton onClick={handleOnClick} disabled={disabled} classNames="d-flex justify-content-between">
|
||||
<Button.Content title={text} iconSrc={iconSrc} direction="left" />
|
||||
{active && <Tick width="20" height="20" viewBox="0 0 22 22" fill="var(--indigo9)" />}
|
||||
</Button.UnstyledButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterandSortPopup;
|
||||
|
|
@ -1,49 +1,33 @@
|
|||
import React, { useState } from 'react';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
import Tooltip from 'react-bootstrap/Tooltip';
|
||||
import { getSvgIcon, checkExistingQueryName } from '@/_helpers/appUtils';
|
||||
import { DataSourceTypes } from '../DataSourceManager/SourceComponents';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { checkExistingQueryName } from '@/_helpers/appUtils';
|
||||
import { Confirm } from '../Viewer/Confirm';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useDataQueriesActions, useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
||||
import { useQueryPanelActions, useSelectedQuery, useUnsavedChanges } from '@/_stores/queryPanelStore';
|
||||
import { useQueryPanelActions, useSelectedQuery } from '@/_stores/queryPanelStore';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import Copy from '@/_ui/Icon/solidIcons/Copy';
|
||||
import DataSourceIcon from '../QueryManager/Components/DataSourceIcon';
|
||||
import { isQueryRunnable } from '@/_helpers/utils';
|
||||
|
||||
export const QueryCard = ({
|
||||
dataQuery,
|
||||
setSaveConfirmation,
|
||||
setCancelData,
|
||||
setDraftQuery,
|
||||
darkMode = false,
|
||||
editorRef,
|
||||
}) => {
|
||||
export const QueryCard = ({ dataQuery, darkMode = false, editorRef, appId }) => {
|
||||
const selectedQuery = useSelectedQuery();
|
||||
const isUnsavedChangesAvailable = useUnsavedChanges();
|
||||
const { isDeletingQueryInProcess } = useDataQueriesStore();
|
||||
const { deleteDataQueries, renameQuery } = useDataQueriesActions();
|
||||
const { setSelectedQuery, setSelectedDataSource, setUnSavedChanges } = useQueryPanelActions();
|
||||
const { deleteDataQueries, renameQuery, duplicateQuery } = useDataQueriesActions();
|
||||
const { setSelectedQuery, setPreviewData } = useQueryPanelActions();
|
||||
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
|
||||
|
||||
const { isVersionReleased } = useAppVersionStore(
|
||||
(state) => ({
|
||||
isVersionReleased: state.isVersionReleased,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
||||
const [renamingQuery, setRenamingQuery] = useState(false);
|
||||
|
||||
const getSourceMetaData = (dataSource) => {
|
||||
if (dataSource?.plugin_id) {
|
||||
return dataSource.plugin?.manifest_file?.data.source;
|
||||
}
|
||||
|
||||
return DataSourceTypes.find((source) => source.kind === dataSource.kind);
|
||||
};
|
||||
|
||||
const sourceMeta = getSourceMetaData(dataQuery);
|
||||
const iconFile = dataQuery?.plugin?.iconFile?.data || dataQuery?.plugin?.icon_file?.data;
|
||||
const icon = getSvgIcon(sourceMeta?.kind.toLowerCase(), 20, 20, iconFile);
|
||||
|
||||
let isSeletedQuery = false;
|
||||
if (selectedQuery) {
|
||||
isSeletedQuery = dataQuery.id === selectedQuery.id;
|
||||
|
|
@ -54,24 +38,14 @@ export const QueryCard = ({
|
|||
setShowDeleteConfirmation(true);
|
||||
};
|
||||
|
||||
const cancelDeleteDataQuery = () => {
|
||||
setShowDeleteConfirmation(false);
|
||||
};
|
||||
|
||||
const updateQueryName = (selectedQuery, newName) => {
|
||||
const { id, name } = selectedQuery;
|
||||
const { name } = selectedQuery;
|
||||
if (name === newName) {
|
||||
return setRenamingQuery(false);
|
||||
}
|
||||
const isNewQueryNameAlreadyExists = checkExistingQueryName(newName);
|
||||
if (newName && !isNewQueryNameAlreadyExists) {
|
||||
if (id === 'draftQuery') {
|
||||
toast.success('Query Name Updated');
|
||||
setDraftQuery((query) => ({ ...query, name: newName }));
|
||||
setSelectedQuery('draftQuery', { ...dataQuery, name: newName });
|
||||
} else {
|
||||
renameQuery(dataQuery?.id, newName, editorRef);
|
||||
}
|
||||
renameQuery(dataQuery?.id, newName, editorRef);
|
||||
setRenamingQuery(false);
|
||||
} else {
|
||||
if (isNewQueryNameAlreadyExists) {
|
||||
|
|
@ -83,36 +57,24 @@ export const QueryCard = ({
|
|||
|
||||
const executeDataQueryDeletion = () => {
|
||||
setShowDeleteConfirmation(false);
|
||||
if (dataQuery?.id === 'draftQuery') {
|
||||
toast.success('Query Deleted');
|
||||
setDraftQuery(null);
|
||||
setSelectedQuery(null);
|
||||
setUnSavedChanges(false);
|
||||
setSelectedDataSource(null);
|
||||
return;
|
||||
}
|
||||
deleteDataQueries(dataQuery?.id, editorRef);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={'row query-row' + (isSeletedQuery ? ' query-row-selected' : '')}
|
||||
className={'row query-row pe-2' + (isSeletedQuery ? ' query-row-selected' : '')}
|
||||
key={dataQuery.id}
|
||||
onClick={() => {
|
||||
if (selectedQuery?.id === dataQuery?.id) return;
|
||||
const stateToBeUpdated = { editingQuery: true, selectedQuery: dataQuery, draftQuery: null };
|
||||
if (isUnsavedChangesAvailable) {
|
||||
setSaveConfirmation(true);
|
||||
setCancelData(stateToBeUpdated);
|
||||
} else {
|
||||
setSelectedQuery(dataQuery?.id);
|
||||
setDraftQuery(null);
|
||||
}
|
||||
setSelectedQuery(dataQuery?.id);
|
||||
setPreviewData(null);
|
||||
}}
|
||||
role="button"
|
||||
>
|
||||
<div className="col-auto query-icon d-flex">{icon}</div>
|
||||
<div className="col-auto query-icon d-flex">
|
||||
<DataSourceIcon source={dataQuery} height={16} />
|
||||
</div>
|
||||
<div className="col query-row-query-name">
|
||||
{renamingQuery ? (
|
||||
<input
|
||||
|
|
@ -123,6 +85,11 @@ export const QueryCard = ({
|
|||
type="text"
|
||||
defaultValue={dataQuery.name}
|
||||
autoFocus={true}
|
||||
onKeyDown={({ target, key }) => {
|
||||
if (key === 'Enter') {
|
||||
updateQueryName(selectedQuery, target.value);
|
||||
}
|
||||
}}
|
||||
onBlur={({ target }) => {
|
||||
updateQueryName(selectedQuery, target.value);
|
||||
}}
|
||||
|
|
@ -135,7 +102,15 @@ export const QueryCard = ({
|
|||
overlay={<Tooltip id="button-tooltip">{dataQuery.name}</Tooltip>}
|
||||
>
|
||||
<div className="query-name" data-cy={`list-query-${dataQuery.name.toLowerCase()}`}>
|
||||
{dataQuery.name}
|
||||
<span
|
||||
className="text-truncate"
|
||||
data-tooltip-id="query-card-name-tooltip"
|
||||
data-tooltip-content={dataQuery.name}
|
||||
>
|
||||
{dataQuery.name}
|
||||
</span>{' '}
|
||||
<Tooltip id="query-card-name-tooltip" className="tooltip query-manager-tooltip" />
|
||||
{!isQueryRunnable(dataQuery) && <small className="mx-2 text-secondary">Draft</small>}
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
)}
|
||||
|
|
@ -147,7 +122,7 @@ export const QueryCard = ({
|
|||
className={`col-auto ${renamingQuery && 'display-none'} rename-query`}
|
||||
onClick={() => setRenamingQuery(true)}
|
||||
>
|
||||
<span className="d-flex">
|
||||
<span className="d-flex" data-tooltip-id="query-card-btn-tooltip" data-tooltip-content="Rename query">
|
||||
<svg
|
||||
data-cy={`edit-query-${dataQuery.name.toLowerCase()}`}
|
||||
width="auto"
|
||||
|
|
@ -165,13 +140,23 @@ export const QueryCard = ({
|
|||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div className={`col-auto rename-query`} onClick={() => duplicateQuery(dataQuery?.id, appId)}>
|
||||
<span className="d-flex" data-tooltip-id="query-card-btn-tooltip" data-tooltip-content="Duplicate query">
|
||||
<Copy height={16} width={16} viewBox="0 5 20 20" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-auto">
|
||||
{isDeletingQueryInProcess ? (
|
||||
<div className="px-2">
|
||||
<div className="text-center spinner-border spinner-border-sm" role="status"></div>
|
||||
</div>
|
||||
) : (
|
||||
<span className="delete-query" onClick={deleteDataQuery} disabled={dataQuery?.id === 'draftQuery'}>
|
||||
<span
|
||||
className="delete-query"
|
||||
onClick={deleteDataQuery}
|
||||
data-tooltip-id="query-card-btn-tooltip"
|
||||
data-tooltip-content="Delete query"
|
||||
>
|
||||
<span className="d-flex">
|
||||
<svg
|
||||
data-cy={`delete-query-${dataQuery.name.toLowerCase()}`}
|
||||
|
|
@ -192,19 +177,18 @@ export const QueryCard = ({
|
|||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Tooltip id="query-card-btn-tooltip" className="tooltip" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showDeleteConfirmation ? (
|
||||
<Confirm
|
||||
show={showDeleteConfirmation}
|
||||
message={'Do you really want to delete this query?'}
|
||||
confirmButtonLoading={isDeletingQueryInProcess}
|
||||
onConfirm={executeDataQueryDeletion}
|
||||
onCancel={cancelDeleteDataQuery}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
) : null}
|
||||
<Confirm
|
||||
show={showDeleteConfirmation}
|
||||
message={'Do you really want to delete this query?'}
|
||||
confirmButtonLoading={isDeletingQueryInProcess}
|
||||
onConfirm={executeDataQueryDeletion}
|
||||
onCancel={() => setShowDeleteConfirmation(false)}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,99 +1,157 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { SearchBox } from '@/_components/SearchBox';
|
||||
import Minimize from '@/_ui/Icon/solidIcons/Minimize';
|
||||
import Search from '@/_ui/Icon/solidIcons/Search';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import EmptyQueriesIllustration from '@assets/images/icons/no-queries-added.svg';
|
||||
import { QueryCard } from './QueryCard';
|
||||
import Fuse from 'fuse.js';
|
||||
import cx from 'classnames';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { useDataQueriesStore, useDataQueries } from '@/_stores/dataQueriesStore';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import FilterandSortPopup from './FilterandSortPopup';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import Plus from '@/_ui/Icon/solidIcons/Plus';
|
||||
import useShowPopover from '@/_hooks/useShowPopover';
|
||||
import DataSourceSelect from '../QueryManager/Components/DataSourceSelect';
|
||||
import { OverlayTrigger, Popover } from 'react-bootstrap';
|
||||
import FolderEmpty from '@/_ui/Icon/solidIcons/FolderEmpty';
|
||||
|
||||
export const QueryDataPane = ({
|
||||
setSaveConfirmation,
|
||||
setCancelData,
|
||||
draftQuery,
|
||||
handleAddNewQuery,
|
||||
setDraftQuery,
|
||||
darkMode,
|
||||
fetchDataQueries,
|
||||
editorRef,
|
||||
}) => {
|
||||
export const QueryDataPane = ({ darkMode, fetchDataQueries, editorRef, appId, toggleQueryEditor }) => {
|
||||
const { t } = useTranslation();
|
||||
const { loadingDataQueries } = useDataQueriesStore();
|
||||
const dataQueries = useDataQueries();
|
||||
const [filteredQueries, setFilteredQueries] = useState(dataQueries);
|
||||
const { isVersionReleased } = useAppVersionStore(
|
||||
(state) => ({
|
||||
isVersionReleased: state.isVersionReleased,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
useEffect(() => {
|
||||
setFilteredQueries(dataQueries);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(dataQueries)]);
|
||||
const [showSearchBox, setShowSearchBox] = useState(false);
|
||||
const searchBoxRef = useRef(null);
|
||||
const [dataSourcesForFilters, setDataSourcesForFilters] = useState([]);
|
||||
const [searchTermForFilters, setSearchTermForFilters] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
// Create a copy of the dataQueries array to perform filtering without modifying the original data.
|
||||
let filteredDataQueries = [...dataQueries];
|
||||
|
||||
// Filter the dataQueries based on the selected data sources (dataSourcesForFilters).
|
||||
if (!isEmpty(dataSourcesForFilters)) {
|
||||
const excludedDataSources = ['runjs', 'runpy'];
|
||||
filteredDataQueries = dataQueries.filter((query) => {
|
||||
const queryDSId = excludedDataSources.includes(query.data_source_id) ? null : query.data_source_id;
|
||||
return dataSourcesForFilters.some((source) => source.id == queryDSId && source.kind === query.kind);
|
||||
});
|
||||
}
|
||||
|
||||
// Apply additional filtering based on the search term (searchTermForFilters).
|
||||
filterQueries(searchTermForFilters, filteredDataQueries);
|
||||
|
||||
const filterQueries = useCallback(
|
||||
(value) => {
|
||||
if (value) {
|
||||
const fuse = new Fuse(dataQueries, { keys: ['name'] });
|
||||
const results = fuse.search(value);
|
||||
let filterDataQueries = [];
|
||||
results.every((result) => {
|
||||
if (result.item.name === value) {
|
||||
filterDataQueries = [];
|
||||
filterDataQueries.push(result.item);
|
||||
return false;
|
||||
}
|
||||
filterDataQueries.push(result.item);
|
||||
return true;
|
||||
});
|
||||
setFilteredQueries(filterDataQueries);
|
||||
} else {
|
||||
setFilteredQueries(dataQueries);
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[JSON.stringify(dataQueries)]
|
||||
);
|
||||
}, [JSON.stringify(dataQueries), dataSourcesForFilters, searchTermForFilters]);
|
||||
|
||||
const handleFilterDatasourcesChange = (source) => {
|
||||
const { id, kind } = source;
|
||||
setDataSourcesForFilters((dataSourcesForFilters) => {
|
||||
const exists = dataSourcesForFilters.some((item) => item.id === id && item.kind === kind);
|
||||
return exists
|
||||
? dataSourcesForFilters.filter((item) => item.id !== id || item.kind !== kind)
|
||||
: [...dataSourcesForFilters, source];
|
||||
});
|
||||
};
|
||||
|
||||
const filterQueries = (value, queries) => {
|
||||
if (value) {
|
||||
const fuse = new Fuse(queries, { keys: ['name'] });
|
||||
const results = fuse.search(value);
|
||||
let filterDataQueries = [];
|
||||
results.every((result) => {
|
||||
if (result.item.name === value) {
|
||||
filterDataQueries = [];
|
||||
filterDataQueries.push(result.item);
|
||||
return false;
|
||||
}
|
||||
filterDataQueries.push(result.item);
|
||||
return true;
|
||||
});
|
||||
setFilteredQueries(filterDataQueries);
|
||||
} else {
|
||||
setFilteredQueries(queries);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
showSearchBox && searchBoxRef.current.focus();
|
||||
}, [showSearchBox]);
|
||||
|
||||
return (
|
||||
<div className="data-pane">
|
||||
<div className={`queries-container ${darkMode && 'theme-dark'}`}>
|
||||
<div className={`queries-container ${darkMode && 'theme-dark'} d-flex flex-column h-100`}>
|
||||
<div className="queries-header row d-flex align-items-center justify-content-between">
|
||||
<div className="col-auto">
|
||||
<div className={`queries-search ${darkMode && 'theme-dark'}`}>
|
||||
<div className="col-auto d-flex">
|
||||
<button
|
||||
onClick={toggleQueryEditor}
|
||||
className="btn-query-panel-header"
|
||||
data-tooltip-id="tooltip-for-query-panel-header-btn"
|
||||
data-tooltip-content="Hide query panel"
|
||||
>
|
||||
<Minimize width="14" height="14" viewBox="0 0 18 20" stroke="var(--slate12)" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
showSearchBox && setSearchTermForFilters('');
|
||||
setShowSearchBox((showSearchBox) => !showSearchBox);
|
||||
}}
|
||||
className={cx('btn-query-panel-header mx-1', {
|
||||
active: showSearchBox,
|
||||
})}
|
||||
data-tooltip-id="tooltip-for-query-panel-header-btn"
|
||||
data-tooltip-content="Open quick search"
|
||||
data-cy="query-search-button"
|
||||
>
|
||||
<Search width="14" height="14" fill="var(--slate12)" />
|
||||
</button>
|
||||
<FilterandSortPopup
|
||||
onFilterDatasourcesChange={handleFilterDatasourcesChange}
|
||||
selectedDataSources={dataSourcesForFilters}
|
||||
clearSelectedDataSources={() => setDataSourcesForFilters([])}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
<Tooltip id="tooltip-for-query-panel-header-btn" className="tooltip" />
|
||||
</div>
|
||||
<AddDataSourceButton darkMode={darkMode} disabled={isEmpty(dataQueries)} />
|
||||
</div>
|
||||
<div
|
||||
className={cx('queries-header row d-flex align-items-center justify-content-between', {
|
||||
'd-none': !showSearchBox,
|
||||
})}
|
||||
>
|
||||
<div className="col-auto w-100">
|
||||
<div className={`queries-search d-flex ${darkMode && 'theme-dark'}`}>
|
||||
<SearchBox
|
||||
ref={searchBoxRef}
|
||||
dataCy={`query-manager`}
|
||||
width="100%"
|
||||
onSubmit={filterQueries}
|
||||
initialValue={searchTermForFilters}
|
||||
callBack={(val) => {
|
||||
setSearchTermForFilters(val.target.value);
|
||||
}}
|
||||
onClearCallback={() => setSearchTermForFilters('')}
|
||||
placeholder={t('globals.search', 'Search')}
|
||||
customClass="query-manager-search-box-wrapper"
|
||||
customClass="query-manager-search-box-wrapper flex-grow-1"
|
||||
showClearButton
|
||||
/>
|
||||
<ButtonSolid
|
||||
size="sm"
|
||||
variant="ghostBlue"
|
||||
className="ms-1"
|
||||
onClick={() => {
|
||||
setSearchTermForFilters('');
|
||||
setShowSearchBox(false);
|
||||
}}
|
||||
data-cy={`query-search-close-button`}
|
||||
>
|
||||
Close
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
data-cy={`button-add-new-queries`}
|
||||
className={cx(`col-auto d-flex align-items-center py-1 rounded default-secondary-button`, {
|
||||
disabled: isVersionReleased,
|
||||
'theme-dark': darkMode,
|
||||
})}
|
||||
onClick={handleAddNewQuery}
|
||||
data-tooltip-id="tooltip-for-add-query"
|
||||
data-tooltip-content="Add new query"
|
||||
>
|
||||
<span className={` d-flex query-manager-btn-svg-wrapper align-items-center query-icon-wrapper`}>
|
||||
<svg width="auto" height="auto" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8 15.25C7.71667 15.25 7.47917 15.1542 7.2875 14.9625C7.09583 14.7708 7 14.5333 7 14.25V9H1.75C1.46667 9 1.22917 8.90417 1.0375 8.7125C0.845833 8.52083 0.75 8.28333 0.75 8C0.75 7.71667 0.845833 7.47917 1.0375 7.2875C1.22917 7.09583 1.46667 7 1.75 7H7V1.75C7 1.46667 7.09583 1.22917 7.2875 1.0375C7.47917 0.845833 7.71667 0.75 8 0.75C8.28333 0.75 8.52083 0.845833 8.7125 1.0375C8.90417 1.22917 9 1.46667 9 1.75V7H14.25C14.5333 7 14.7708 7.09583 14.9625 7.2875C15.1542 7.47917 15.25 7.71667 15.25 8C15.25 8.28333 15.1542 8.52083 14.9625 8.7125C14.7708 8.90417 14.5333 9 14.25 9H9V14.25C9 14.5333 8.90417 14.7708 8.7125 14.9625C8.52083 15.1542 8.28333 15.25 8 15.25Z"
|
||||
fill="#3E63DD"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span className="query-manager-btn-name">Add</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{loadingDataQueries ? (
|
||||
|
|
@ -102,41 +160,26 @@ export const QueryDataPane = ({
|
|||
<Skeleton height={'36px'} className="skeleton" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="query-list">
|
||||
<div
|
||||
className={`query-list tj-scrollbar overflow-auto ${
|
||||
filteredQueries.length === 0 ? 'flex-grow-1 align-items-center justify-content-center' : ''
|
||||
}`}
|
||||
>
|
||||
<div>
|
||||
{draftQuery !== null ? (
|
||||
<QueryCard
|
||||
key={draftQuery.id}
|
||||
dataQuery={draftQuery}
|
||||
setSaveConfirmation={setSaveConfirmation}
|
||||
setCancelData={setCancelData}
|
||||
setDraftQuery={setDraftQuery}
|
||||
fetchDataQueries={fetchDataQueries}
|
||||
darkMode={darkMode}
|
||||
editorRef={editorRef}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{filteredQueries.map((query) => (
|
||||
<QueryCard
|
||||
key={query.id}
|
||||
dataQuery={query}
|
||||
setSaveConfirmation={setSaveConfirmation}
|
||||
setCancelData={setCancelData}
|
||||
setDraftQuery={setDraftQuery}
|
||||
fetchDataQueries={fetchDataQueries}
|
||||
darkMode={darkMode}
|
||||
editorRef={editorRef}
|
||||
appId={appId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{filteredQueries.length === 0 && draftQuery === null && (
|
||||
{filteredQueries.length === 0 && (
|
||||
<div className=" d-flex flex-column align-items-center justify-content-start">
|
||||
<EmptyQueriesIllustration />
|
||||
<span data-cy="no-query-message" className="mute-text pt-3">
|
||||
{dataQueries.length === 0 ? 'No queries added' : 'No queries found'}
|
||||
</span>
|
||||
{filteredQueries.length === 0 ? <EmptyDataSource /> : ''}
|
||||
<br />
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -146,3 +189,67 @@ export const QueryDataPane = ({
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const EmptyDataSource = () => (
|
||||
<div>
|
||||
<div className="text-center">
|
||||
<span
|
||||
className="rounded mb-3 bg-slate3 d-flex justify-content-center align-items-center"
|
||||
style={{ width: '32px', height: '32px' }}
|
||||
>
|
||||
<FolderEmpty style={{ height: '16px' }} />
|
||||
</span>
|
||||
</div>
|
||||
<span>No queries have been added. </span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const AddDataSourceButton = ({ darkMode, disabled }) => {
|
||||
const [showMenu, setShowMenu] = useShowPopover(false, '#query-add-ds-popover', '#query-add-ds-popover-btn');
|
||||
const selectRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
if (showMenu) {
|
||||
selectRef.current.focus();
|
||||
}
|
||||
}, [showMenu]);
|
||||
|
||||
return (
|
||||
<OverlayTrigger
|
||||
show={showMenu && !disabled}
|
||||
placement="right-end"
|
||||
arrowOffsetTop={90}
|
||||
arrowOffsetLeft={90}
|
||||
overlay={
|
||||
<Popover
|
||||
key={'page.i'}
|
||||
id="query-add-ds-popover"
|
||||
className={`${darkMode && 'popover-dark-themed dark-theme tj-dark-mode'}`}
|
||||
style={{ width: '244px', maxWidth: '246px' }}
|
||||
>
|
||||
<DataSourceSelect selectRef={selectRef} closePopup={() => setShowMenu(false)} />
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<span className="col-auto" id="query-add-ds-popover-btn">
|
||||
<ButtonSolid
|
||||
size="sm"
|
||||
variant="tertiary"
|
||||
disabled={disabled}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
setShowMenu((show) => !show);
|
||||
}}
|
||||
className="px-1 pe-3 ps-2 gap-0"
|
||||
data-cy={`show-ds-popover-button`}
|
||||
>
|
||||
<Plus style={{ height: '16px' }} />
|
||||
Add
|
||||
</ButtonSolid>
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ import React, { useState, useRef, useCallback, useEffect } from 'react';
|
|||
import { useEventListener } from '@/_hooks/use-event-listener';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { QueryDataPane } from './QueryDataPane';
|
||||
import { Confirm } from '../Viewer/Confirm';
|
||||
import QueryManager from '../QueryManager/QueryManager';
|
||||
|
||||
import useWindowResize from '@/_hooks/useWindowResize';
|
||||
import { useQueryPanelActions, useUnsavedChanges, useSelectedQuery } from '@/_stores/queryPanelStore';
|
||||
import { useDataQueries } from '@/_stores/dataQueriesStore';
|
||||
import { useQueryPanelStore, useQueryPanelActions } from '@/_stores/queryPanelStore';
|
||||
import { useDataQueriesStore, useDataQueries } from '@/_stores/dataQueriesStore';
|
||||
import Maximize from '@/_ui/Icon/solidIcons/Maximize';
|
||||
import { cloneDeep, isEmpty, isEqual } from 'lodash';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
|
||||
const QueryPanel = ({
|
||||
dataQueriesChanged,
|
||||
|
|
@ -17,14 +19,11 @@ const QueryPanel = ({
|
|||
allComponents,
|
||||
appId,
|
||||
appDefinition,
|
||||
dataSourceModalHandler,
|
||||
editorRef,
|
||||
onQueryPaneDragging,
|
||||
handleQueryPaneExpanding,
|
||||
}) => {
|
||||
const { setSelectedQuery, updateQueryPanelHeight, setUnSavedChanges, setSelectedDataSource } = useQueryPanelActions();
|
||||
const isUnsavedQueriesAvailable = useUnsavedChanges();
|
||||
const selectedQuery = useSelectedQuery();
|
||||
const { updateQueryPanelHeight } = useQueryPanelActions();
|
||||
const dataQueries = useDataQueries();
|
||||
const queryManagerPreferences = useRef(JSON.parse(localStorage.getItem('queryManagerPreferences')) ?? {});
|
||||
const queryPaneRef = useRef(null);
|
||||
|
|
@ -36,28 +35,38 @@ const QueryPanel = ({
|
|||
: queryManagerPreferences.current?.queryPanelHeight ?? 70
|
||||
);
|
||||
const [isTopOfQueryPanel, setTopOfQueryPanel] = useState(false);
|
||||
const [showSaveConfirmation, setSaveConfirmation] = useState(false);
|
||||
const [queryCancelData, setCancelData] = useState({});
|
||||
const [draftQuery, setDraftQuery] = useState(null);
|
||||
const [editingQuery, setEditingQuery] = useState(dataQueries.length > 0);
|
||||
const [windowSize, isWindowResizing] = useWindowResize();
|
||||
|
||||
useEffect(() => {
|
||||
if (!editingQuery && selectedQuery !== null && selectedQuery?.id !== 'draftQuery') {
|
||||
setEditingQuery(true);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedQuery?.id, editingQuery]);
|
||||
const queryPanelStoreListner = useQueryPanelStore.subscribe(({ selectedQuery }, prevState) => {
|
||||
if (isEmpty(prevState?.selectedQuery) || isEmpty(selectedQuery)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevState?.selectedQuery?.id !== selectedQuery.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
//removing updated_at since this value changes whenever the data is updated in the BE
|
||||
const formattedQuery = cloneDeep(selectedQuery);
|
||||
delete formattedQuery.updated_at;
|
||||
|
||||
const formattedPrevQuery = cloneDeep(prevState?.selectedQuery || {});
|
||||
delete formattedPrevQuery.updated_at;
|
||||
|
||||
if (!isEqual(formattedQuery, formattedPrevQuery)) {
|
||||
useDataQueriesStore.getState().actions.saveData(selectedQuery);
|
||||
}
|
||||
});
|
||||
|
||||
return queryPanelStoreListner;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
handleQueryPaneExpanding(isExpanded);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isExpanded]);
|
||||
|
||||
useEffect(() => {
|
||||
setEditingQuery(dataQueries.length > 0);
|
||||
}, [dataQueries.length]);
|
||||
|
||||
useEffect(() => {
|
||||
onQueryPaneDragging(isDragging);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -73,14 +82,6 @@ const QueryPanel = ({
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [windowSize.height, isExpanded, isWindowResizing]);
|
||||
|
||||
const createDraftQuery = useCallback((queryDetails, source) => {
|
||||
setSelectedQuery(queryDetails.id, queryDetails);
|
||||
setDraftQuery(queryDetails);
|
||||
setSelectedDataSource(source);
|
||||
setEditingQuery(false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const onMouseUp = () => {
|
||||
setDragging(false);
|
||||
|
||||
|
|
@ -127,27 +128,6 @@ const QueryPanel = ({
|
|||
useEventListener('mousemove', onMouseMove);
|
||||
useEventListener('mouseup', onMouseUp);
|
||||
|
||||
const handleAddNewQuery = useCallback(() => {
|
||||
const stateToBeUpdated = {
|
||||
selectedDataSource: null,
|
||||
selectedQuery: null,
|
||||
editingQuery: false,
|
||||
isSourceSelected: false,
|
||||
draftQuery: null,
|
||||
};
|
||||
|
||||
if (isUnsavedQueriesAvailable) {
|
||||
setSaveConfirmation(true);
|
||||
setCancelData(stateToBeUpdated);
|
||||
} else {
|
||||
setSelectedDataSource(null);
|
||||
setSelectedQuery(null);
|
||||
setDraftQuery(null);
|
||||
setEditingQuery(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isUnsavedQueriesAvailable]);
|
||||
|
||||
const toggleQueryEditor = useCallback(() => {
|
||||
queryManagerPreferences.current = { ...queryManagerPreferences.current, isExpanded: !isExpanded };
|
||||
localStorage.setItem('queryManagerPreferences', JSON.stringify(queryManagerPreferences.current));
|
||||
|
|
@ -161,85 +141,47 @@ const QueryPanel = ({
|
|||
}, [isExpanded]);
|
||||
|
||||
const updateDataQueries = useCallback(() => {
|
||||
setEditingQuery(true);
|
||||
setDraftQuery(null);
|
||||
dataQueriesChanged();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const updateDraftQueryName = useCallback(
|
||||
(newName) => {
|
||||
setDraftQuery((query) => ({ ...query, name: newName }));
|
||||
setSelectedQuery(draftQuery.id, { ...draftQuery, name: newName });
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[draftQuery]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Confirm
|
||||
show={showSaveConfirmation}
|
||||
message={`Query ${selectedQuery?.name} has unsaved changes. Are you sure you want to discard changes ?`}
|
||||
onConfirm={() => {
|
||||
setSaveConfirmation(false);
|
||||
setDraftQuery(null);
|
||||
setSelectedQuery(queryCancelData?.selectedQuery?.id ?? null);
|
||||
setSelectedDataSource(queryCancelData?.selectedDataSource ?? null);
|
||||
setUnSavedChanges(false);
|
||||
if (queryCancelData.hasOwnProperty('editingQuery')) {
|
||||
setEditingQuery(queryCancelData.editingQuery);
|
||||
}
|
||||
}}
|
||||
onCancel={() => {
|
||||
setSaveConfirmation(false);
|
||||
}}
|
||||
confirmButtonText="Discard changes"
|
||||
cancelButtonText="Continue editing"
|
||||
callCancelFnOnConfirm={false}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
<div
|
||||
className="query-pane"
|
||||
style={{
|
||||
height: 40,
|
||||
background: '#fff',
|
||||
padding: '8px 16px',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<h5 className="mb-0 font-weight-500 cursor-pointer" onClick={toggleQueryEditor}>
|
||||
QUERIES
|
||||
</h5>
|
||||
<span
|
||||
<div
|
||||
style={{ width: '288px', padding: '5px 12px' }}
|
||||
className="d-flex justify-content- border-end align-items-center"
|
||||
role="button"
|
||||
onClick={toggleQueryEditor}
|
||||
className="cursor-pointer m-1 d-flex"
|
||||
data-tooltip-id="tooltip-for-show-query-editor"
|
||||
data-tooltip-content="Show query editor"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8.00013 6.18288C7.94457 6.18288 7.88624 6.17177 7.82513 6.14954C7.76402 6.12732 7.70569 6.08843 7.65013 6.03288L5.3668 3.74954C5.2668 3.64954 5.2168 3.52732 5.2168 3.38288C5.2168 3.23843 5.2668 3.11621 5.3668 3.01621C5.4668 2.91621 5.58346 2.86621 5.7168 2.86621C5.85013 2.86621 5.9668 2.91621 6.0668 3.01621L8.00013 4.94954L9.93346 3.01621C10.0335 2.91621 10.1529 2.86621 10.2918 2.86621C10.4307 2.86621 10.5501 2.91621 10.6501 3.01621C10.7501 3.11621 10.8001 3.23566 10.8001 3.37454C10.8001 3.51343 10.7501 3.63288 10.6501 3.73288L8.35013 6.03288C8.29457 6.08843 8.23902 6.12732 8.18346 6.14954C8.12791 6.17177 8.0668 6.18288 8.00013 6.18288ZM5.3668 12.9662C5.2668 12.8662 5.2168 12.7468 5.2168 12.6079C5.2168 12.469 5.2668 12.3495 5.3668 12.2495L7.65013 9.96621C7.70569 9.91065 7.76402 9.87177 7.82513 9.84954C7.88624 9.82732 7.94457 9.81621 8.00013 9.81621C8.0668 9.81621 8.12791 9.82732 8.18346 9.84954C8.23902 9.87177 8.29457 9.91065 8.35013 9.96621L10.6501 12.2662C10.7501 12.3662 10.8001 12.4829 10.8001 12.6162C10.8001 12.7495 10.7501 12.8662 10.6501 12.9662C10.5501 13.0662 10.4279 13.1162 10.2835 13.1162C10.139 13.1162 10.0168 13.0662 9.9168 12.9662L8.00013 11.0495L6.08346 12.9662C5.98346 13.0662 5.86402 13.1162 5.72513 13.1162C5.58624 13.1162 5.4668 13.0662 5.3668 12.9662Z"
|
||||
fill="#121212"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5.3668 5.43327C5.2668 5.33327 5.2168 5.21105 5.2168 5.0666C5.2168 4.92216 5.2668 4.79994 5.3668 4.69994L7.65013 2.4166C7.70569 2.36105 7.76124 2.32216 7.8168 2.29993C7.87235 2.27771 7.93346 2.2666 8.00013 2.2666C8.05569 2.2666 8.11402 2.27771 8.17513 2.29993C8.23624 2.32216 8.29457 2.36105 8.35013 2.4166L10.6335 4.69994C10.7335 4.79994 10.7835 4.92216 10.7835 5.0666C10.7835 5.21105 10.7335 5.33327 10.6335 5.43327C10.5335 5.53327 10.4112 5.58327 10.2668 5.58327C10.1224 5.58327 10.0001 5.53327 9.90013 5.43327L8.00013 3.53327L6.10013 5.43327C6.00013 5.53327 5.87791 5.58327 5.73346 5.58327C5.58902 5.58327 5.4668 5.53327 5.3668 5.43327V5.43327ZM8.00013 13.7999C7.94457 13.7999 7.88624 13.7888 7.82513 13.7666C7.76402 13.7444 7.70569 13.7055 7.65013 13.6499L5.3668 11.3666C5.2668 11.2666 5.2168 11.1444 5.2168 10.9999C5.2168 10.8555 5.2668 10.7333 5.3668 10.6333C5.4668 10.5333 5.58902 10.4833 5.73346 10.4833C5.87791 10.4833 6.00013 10.5333 6.10013 10.6333L8.00013 12.5333L9.90013 10.6333C10.0001 10.5333 10.1224 10.4833 10.2668 10.4833C10.4112 10.4833 10.5335 10.5333 10.6335 10.6333C10.7335 10.7333 10.7835 10.8555 10.7835 10.9999C10.7835 11.1444 10.7335 11.2666 10.6335 11.3666L8.35013 13.6499C8.29457 13.7055 8.23902 13.7444 8.18346 13.7666C8.12791 13.7888 8.0668 13.7999 8.00013 13.7999V13.7999Z"
|
||||
fill="#576574"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</span>
|
||||
<ButtonSolid
|
||||
variant="ghostBlack"
|
||||
size="sm"
|
||||
className="gap-0 p-2 me-2"
|
||||
data-tooltip-id="tooltip-for-query-panel-footer-btn"
|
||||
data-tooltip-content="Show query panel"
|
||||
>
|
||||
<Maximize stroke="var(--slate9)" style={{ height: '14px', width: '14px' }} viewBox={null} />
|
||||
</ButtonSolid>
|
||||
<h5 className="mb-0 font-weight-500 cursor-pointer" onClick={toggleQueryEditor}>
|
||||
Query Manager
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref={queryPaneRef}
|
||||
onMouseDown={onMouseDown}
|
||||
className="query-pane"
|
||||
id="query-manager"
|
||||
style={{
|
||||
height: `calc(100% - ${isExpanded ? height : 100}%)`,
|
||||
cursor: isDragging || isTopOfQueryPanel ? 'row-resize' : 'default',
|
||||
|
|
@ -247,42 +189,32 @@ const QueryPanel = ({
|
|||
>
|
||||
<div className="row main-row">
|
||||
<QueryDataPane
|
||||
showSaveConfirmation={showSaveConfirmation}
|
||||
setSaveConfirmation={setSaveConfirmation}
|
||||
setCancelData={setCancelData}
|
||||
draftQuery={draftQuery}
|
||||
handleAddNewQuery={handleAddNewQuery}
|
||||
setDraftQuery={setDraftQuery}
|
||||
fetchDataQueries={fetchDataQueries}
|
||||
darkMode={darkMode}
|
||||
editorRef={editorRef}
|
||||
appId={appId}
|
||||
toggleQueryEditor={toggleQueryEditor}
|
||||
/>
|
||||
<div className="query-definition-pane-wrapper">
|
||||
<div className="query-definition-pane">
|
||||
<div>
|
||||
<QueryManager
|
||||
addNewQueryAndDeselectSelectedQuery={handleAddNewQuery}
|
||||
toggleQueryEditor={toggleQueryEditor}
|
||||
dataQueries={dataQueries}
|
||||
mode={editingQuery ? 'edit' : 'create'}
|
||||
dataQueriesChanged={updateDataQueries}
|
||||
appId={appId}
|
||||
darkMode={darkMode}
|
||||
apps={apps}
|
||||
allComponents={allComponents}
|
||||
dataSourceModalHandler={dataSourceModalHandler}
|
||||
appDefinition={appDefinition}
|
||||
editorRef={editorRef}
|
||||
createDraftQuery={createDraftQuery}
|
||||
isUnsavedQueriesAvailable={isUnsavedQueriesAvailable}
|
||||
updateDraftQueryName={updateDraftQueryName}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip id="tooltip-for-show-query-editor" className="tooltip" />
|
||||
<Tooltip id="tooltip-for-query-panel-footer-btn" className="tooltip" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
stripTrailingSlash,
|
||||
getSubpath,
|
||||
excludeWorkspaceIdFromURL,
|
||||
isQueryRunnable,
|
||||
redirectToDashboard,
|
||||
getWorkspaceId,
|
||||
} from '@/_helpers/utils';
|
||||
|
|
@ -184,7 +185,7 @@ class ViewerComponent extends React.Component {
|
|||
|
||||
runQueries = (data_queries) => {
|
||||
data_queries.forEach((query) => {
|
||||
if (query.options.runOnPageLoad) {
|
||||
if (query.options.runOnPageLoad && isQueryRunnable(query)) {
|
||||
runQuery(this, query.id, query.name, undefined, 'view');
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export function Confirm({
|
|||
}, [show]);
|
||||
|
||||
const handleClose = () => {
|
||||
onCancel();
|
||||
onCancel && onCancel();
|
||||
setShow(false);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,11 @@ const DynamicForm = ({
|
|||
optionsChanged,
|
||||
queryName,
|
||||
computeSelectStyles = false,
|
||||
onBlur,
|
||||
layout = 'vertical',
|
||||
}) => {
|
||||
const [computedProps, setComputedProps] = React.useState({});
|
||||
const isHorizontalLayout = layout === 'horizontal';
|
||||
const currentState = useCurrentState();
|
||||
|
||||
const { isEditorActive } = useEditorStore(
|
||||
|
|
@ -184,7 +187,8 @@ const DynamicForm = ({
|
|||
value: options?.[key]?.value,
|
||||
...(type === 'textarea' && { rows: rows }),
|
||||
...(helpText && { helpText }),
|
||||
onChange: (e) => optionchanged(key, e.target.value),
|
||||
onChange: (e) => optionchanged(key, e.target.value, true), //shouldNotAutoSave is true because autosave should occur during onBlur, not after each character change (in optionchanged).
|
||||
onblur: () => onBlur(),
|
||||
isGDS,
|
||||
workspaceVariables,
|
||||
};
|
||||
|
|
@ -356,56 +360,80 @@ const DynamicForm = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className={`${isHorizontalLayout ? '' : 'row'}`}>
|
||||
{Object.keys(obj).map((key) => {
|
||||
const { label, type, encrypted, className } = obj[key];
|
||||
const Element = getElement(type);
|
||||
const isSpecificComponent = ['tooljetdb-operations'].includes(type);
|
||||
|
||||
return (
|
||||
<div className={cx('my-2', { 'col-md-12': !className, [className]: !!className })} key={key}>
|
||||
<div className="d-flex align-items-center">
|
||||
{label && (
|
||||
<label
|
||||
className="form-label"
|
||||
data-cy={`label-${String(label).toLocaleLowerCase().replace(/\s+/g, '-')}`}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
{(type === 'password' || encrypted) && selectedDataSource?.id && (
|
||||
<div className="mx-1 col">
|
||||
<ButtonSolid
|
||||
className="datasource-edit-btn mb-2"
|
||||
type="a"
|
||||
variant="tertiary"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={(event) => handleEncryptedFieldsToggle(event, key)}
|
||||
<div
|
||||
className={cx('my-2', {
|
||||
'col-md-12': !className && !isHorizontalLayout,
|
||||
[className]: !!className,
|
||||
'd-flex': isHorizontalLayout,
|
||||
'dynamic-form-row': isHorizontalLayout,
|
||||
})}
|
||||
key={key}
|
||||
>
|
||||
{!isSpecificComponent && (
|
||||
<div
|
||||
className={cx('d-flex', {
|
||||
'form-label': isHorizontalLayout,
|
||||
'align-items-center': !isHorizontalLayout,
|
||||
})}
|
||||
>
|
||||
{label && (
|
||||
<label
|
||||
className="form-label"
|
||||
data-cy={`label-${String(label).toLocaleLowerCase().replace(/\s+/g, '-')}`}
|
||||
>
|
||||
{computedProps?.[key]?.['disabled'] ? 'Edit' : 'Cancel'}
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
)}
|
||||
{(type === 'password' || encrypted) && (
|
||||
<div className="col-auto mb-2">
|
||||
<small className="text-green">
|
||||
<img
|
||||
className="mx-2 encrypted-icon"
|
||||
src="assets/images/icons/padlock.svg"
|
||||
width="12"
|
||||
height="12"
|
||||
/>
|
||||
Encrypted
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
{(type === 'password' || encrypted) && selectedDataSource?.id && (
|
||||
<div className="mx-1 col">
|
||||
<ButtonSolid
|
||||
className="datasource-edit-btn mb-2"
|
||||
type="a"
|
||||
variant="tertiary"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={(event) => handleEncryptedFieldsToggle(event, key)}
|
||||
>
|
||||
{computedProps?.[key]?.['disabled'] ? 'Edit' : 'Cancel'}
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
)}
|
||||
{(type === 'password' || encrypted) && (
|
||||
<div className="col-auto mb-2">
|
||||
<small className="text-green">
|
||||
<img
|
||||
className="mx-2 encrypted-icon"
|
||||
src="assets/images/icons/padlock.svg"
|
||||
width="12"
|
||||
height="12"
|
||||
/>
|
||||
Encrypted
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={cx({
|
||||
'flex-grow-1': isHorizontalLayout && !isSpecificComponent,
|
||||
'w-100': isHorizontalLayout && type !== 'codehinter',
|
||||
})}
|
||||
>
|
||||
<Element
|
||||
{...getElementProps(obj[key])}
|
||||
{...computedProps[key]}
|
||||
data-cy={`${String(label).toLocaleLowerCase().replace(/\s+/g, '-')}-text-field`}
|
||||
customWrap={true} //to be removed after whole ui is same
|
||||
isHorizontalLayout={isHorizontalLayout}
|
||||
/>
|
||||
</div>
|
||||
<Element
|
||||
{...getElementProps(obj[key])}
|
||||
{...computedProps[key]}
|
||||
data-cy={`${String(label).toLocaleLowerCase().replace(/\s+/g, '-')}-text-field`}
|
||||
customWrap={true} //to be removed after whole ui is same
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
@ -421,17 +449,19 @@ const DynamicForm = ({
|
|||
const selector = options?.[flipComponentDropdown?.key]?.value || options?.[flipComponentDropdown?.key];
|
||||
return (
|
||||
<>
|
||||
<div className="row">
|
||||
<div className={`${isHorizontalLayout ? '' : 'row'}`}>
|
||||
{flipComponentDropdown.commonFields && getLayout(flipComponentDropdown.commonFields)}
|
||||
<div
|
||||
className={cx('my-2', {
|
||||
'col-md-12': !flipComponentDropdown.className,
|
||||
'col-md-12': !flipComponentDropdown.className && !isHorizontalLayout,
|
||||
'd-flex': isHorizontalLayout,
|
||||
'dynamic-form-row': isHorizontalLayout,
|
||||
[flipComponentDropdown.className]: !!flipComponentDropdown.className,
|
||||
})}
|
||||
>
|
||||
{flipComponentDropdown.label && (
|
||||
{(flipComponentDropdown.label || isHorizontalLayout) && (
|
||||
<label
|
||||
className="form-label"
|
||||
className={cx('form-label')}
|
||||
data-cy={`${String(flipComponentDropdown.label)
|
||||
.toLocaleLowerCase()
|
||||
.replace(/\s+/g, '-')}-dropdown-label`}
|
||||
|
|
@ -439,7 +469,7 @@ const DynamicForm = ({
|
|||
{flipComponentDropdown.label}
|
||||
</label>
|
||||
)}
|
||||
<div data-cy={'query-select-dropdown'}>
|
||||
<div data-cy={'query-select-dropdown'} className={cx({ 'flex-grow-1': isHorizontalLayout })}>
|
||||
<Select
|
||||
{...getElementProps(flipComponentDropdown)}
|
||||
styles={computeSelectStyles ? computeSelectStyles('100%') : {}}
|
||||
|
|
|
|||
|
|
@ -1,80 +1,93 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, forwardRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cx from 'classnames';
|
||||
import useDebounce from '@/_hooks/useDebounce';
|
||||
import { useMounted } from '@/_hooks/use-mount';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
|
||||
export function SearchBox({
|
||||
width = '200px',
|
||||
onSubmit,
|
||||
className,
|
||||
debounceDelay = 300,
|
||||
darkMode = false,
|
||||
placeholder = 'Search',
|
||||
customClass = '',
|
||||
dataCy = '',
|
||||
callBack,
|
||||
onClearCallback,
|
||||
autoFocus = false,
|
||||
}) {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const debouncedSearchTerm = useDebounce(searchText, debounceDelay);
|
||||
const [isFocused, setFocussed] = useState(false);
|
||||
export const SearchBox = forwardRef(
|
||||
(
|
||||
{
|
||||
width = '200px',
|
||||
onSubmit,
|
||||
className,
|
||||
debounceDelay = 300,
|
||||
darkMode = false,
|
||||
placeholder = 'Search',
|
||||
customClass = '',
|
||||
dataCy = '',
|
||||
callBack,
|
||||
onClearCallback,
|
||||
autoFocus = false,
|
||||
showClearButton,
|
||||
initialValue = '',
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const debouncedSearchTerm = useDebounce(searchText, debounceDelay);
|
||||
const [isFocused, setFocussed] = useState(false);
|
||||
|
||||
const handleChange = (e) => {
|
||||
setSearchText(e.target.value);
|
||||
callBack?.(e);
|
||||
};
|
||||
const handleChange = (e) => {
|
||||
setSearchText(e.target.value);
|
||||
callBack?.(e);
|
||||
};
|
||||
|
||||
const clearSearchText = () => {
|
||||
setSearchText('');
|
||||
onClearCallback?.();
|
||||
};
|
||||
const clearSearchText = () => {
|
||||
setSearchText('');
|
||||
onClearCallback?.();
|
||||
};
|
||||
|
||||
const mounted = useMounted();
|
||||
const mounted = useMounted();
|
||||
|
||||
useEffect(() => {
|
||||
if (mounted) {
|
||||
onSubmit?.(debouncedSearchTerm);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [debouncedSearchTerm, onSubmit]);
|
||||
useEffect(() => {
|
||||
if (mounted) {
|
||||
onSubmit?.(debouncedSearchTerm);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [debouncedSearchTerm, onSubmit]);
|
||||
|
||||
return (
|
||||
<div className={`search-box-wrapper ${customClass}`}>
|
||||
<div className="input-icon">
|
||||
{!isFocused && (
|
||||
<span className="input-icon-addon">
|
||||
<SolidIcon name="search" width="14" />
|
||||
</span>
|
||||
)}
|
||||
<input
|
||||
style={{ width }}
|
||||
type="text"
|
||||
value={searchText}
|
||||
onChange={handleChange}
|
||||
className={cx('form-control', {
|
||||
'dark-theme-placeholder': darkMode,
|
||||
[className]: !!className,
|
||||
})}
|
||||
placeholder={placeholder}
|
||||
onFocus={() => setFocussed(true)}
|
||||
onBlur={() => setFocussed(false)}
|
||||
data-cy={`${dataCy}-search-bar`}
|
||||
autoFocus={autoFocus}
|
||||
/>
|
||||
{isFocused && (
|
||||
<span className="input-icon-addon end" onMouseDown={clearSearchText}>
|
||||
<div className="d-flex tj-common-search-input-clear-icon" title="clear">
|
||||
<SolidIcon name="remove" />
|
||||
</div>
|
||||
</span>
|
||||
)}
|
||||
useEffect(() => {
|
||||
initialValue !== undefined && setSearchText(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
return (
|
||||
<div className={`search-box-wrapper ${customClass}`}>
|
||||
<div className="input-icon">
|
||||
{!isFocused && (
|
||||
<span className="input-icon-addon">
|
||||
<SolidIcon name="search" width="14" />
|
||||
</span>
|
||||
)}
|
||||
<input
|
||||
style={{ width }}
|
||||
type="text"
|
||||
value={searchText}
|
||||
onChange={handleChange}
|
||||
className={cx('form-control', {
|
||||
'dark-theme-placeholder': darkMode,
|
||||
[className]: !!className,
|
||||
})}
|
||||
placeholder={placeholder}
|
||||
onFocus={() => setFocussed(true)}
|
||||
onBlur={() => setFocussed(false)}
|
||||
data-cy={`${dataCy}-search-bar`}
|
||||
autoFocus={autoFocus}
|
||||
ref={ref}
|
||||
/>
|
||||
{(isFocused || showClearButton) && (
|
||||
<span className="input-icon-addon end" onMouseDown={clearSearchText}>
|
||||
<div className="d-flex tj-common-search-input-clear-icon" title="clear">
|
||||
<SolidIcon name="remove" />
|
||||
</div>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
SearchBox.propTypes = {
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
debounceDelay: PropTypes.number,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
computeComponentName,
|
||||
generateAppActions,
|
||||
loadPyodide,
|
||||
isQueryRunnable,
|
||||
} from '@/_helpers/utils';
|
||||
import { dataqueryService } from '@/_services';
|
||||
import _ from 'lodash';
|
||||
|
|
@ -585,7 +586,6 @@ export async function onEvent(_ref, eventName, options, mode = 'edit') {
|
|||
let _self = _ref;
|
||||
|
||||
const { customVariables } = options;
|
||||
|
||||
if (eventName === 'onPageLoad') {
|
||||
await executeActionsForEventId(_ref, 'onPageLoad', { definition: { events: [options] } }, mode, customVariables);
|
||||
}
|
||||
|
|
@ -879,7 +879,7 @@ export function previewQuery(_ref, query, calledFromQuery = false, parameters =
|
|||
case 'Created':
|
||||
case 'Accepted':
|
||||
case 'No Content': {
|
||||
toast(`Query completed.`, {
|
||||
toast(`Query ${'(' + query.name + ') ' || ''}completed.`, {
|
||||
icon: '🚀',
|
||||
});
|
||||
break;
|
||||
|
|
@ -958,7 +958,7 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode =
|
|||
getCurrentState()
|
||||
);
|
||||
} else {
|
||||
queryExecutionPromise = dataqueryService.run(queryId, options);
|
||||
queryExecutionPromise = dataqueryService.run(queryId, options, query?.options);
|
||||
}
|
||||
|
||||
queryExecutionPromise
|
||||
|
|
@ -1600,7 +1600,7 @@ export const checkExistingQueryName = (newName) =>
|
|||
|
||||
export const runQueries = (queries, _ref) => {
|
||||
queries.forEach((query) => {
|
||||
if (query.options.runOnPageLoad) {
|
||||
if (query.options.runOnPageLoad && isQueryRunnable(query)) {
|
||||
runQuery(_ref, query.id, query.name);
|
||||
}
|
||||
});
|
||||
|
|
@ -1611,14 +1611,14 @@ export const computeQueryState = (queries, _ref) => {
|
|||
queries.forEach((query) => {
|
||||
if (query.plugin?.plugin_id) {
|
||||
queryState[query.name] = {
|
||||
...query.plugin.manifest_file.data.source.exposedVariables,
|
||||
...query.plugin.manifest_file.data?.source?.exposedVariables,
|
||||
kind: query.plugin.manifest_file.data.source.kind,
|
||||
...getCurrentState().queries[query.name],
|
||||
};
|
||||
} else {
|
||||
queryState[query.name] = {
|
||||
...DataSourceTypes.find((source) => source.kind === query.kind).exposedVariables,
|
||||
kind: DataSourceTypes.find((source) => source.kind === query.kind).kind,
|
||||
...DataSourceTypes.find((source) => source.kind === query.kind)?.exposedVariables,
|
||||
kind: DataSourceTypes.find((source) => source.kind === query.kind)?.kind,
|
||||
...getCurrentState()?.queries[query.name],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable no-useless-escape */
|
||||
import moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
import _, { isEmpty } from 'lodash';
|
||||
import axios from 'axios';
|
||||
import JSON5 from 'json5';
|
||||
import { previewQuery, executeAction } from '@/_helpers/appUtils';
|
||||
|
|
@ -9,6 +9,7 @@ import { authenticationService } from '@/_services/authentication.service';
|
|||
|
||||
import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
||||
import { getCurrentState } from '@/_stores/currentStateStore';
|
||||
import { staticDataSources } from '@/Editor/QueryManager/constants';
|
||||
|
||||
export function findProp(obj, prop, defval) {
|
||||
if (typeof defval === 'undefined') defval = null;
|
||||
|
|
@ -549,7 +550,14 @@ export const generateAppActions = (_ref, queryId, mode, isPreview = false) => {
|
|||
? Object.entries(_ref.state.appDefinition.pages[currentPageId]?.components)
|
||||
: {};
|
||||
const runQuery = (queryName = '', parameters) => {
|
||||
const query = useDataQueriesStore.getState().dataQueries.find((query) => query.name === queryName);
|
||||
const query = useDataQueriesStore.getState().dataQueries.find((query) => {
|
||||
const isFound = query.name === queryName;
|
||||
if (isPreview) {
|
||||
return isFound;
|
||||
} else {
|
||||
return isFound && isQueryRunnable(query);
|
||||
}
|
||||
});
|
||||
|
||||
const processedParams = {};
|
||||
if (_.isEmpty(query) || queryId === query?.id) {
|
||||
|
|
@ -963,6 +971,15 @@ export const handleHttpErrorMessages = ({ statusCode, error }, feature_name) =>
|
|||
|
||||
export const defaultAppEnvironments = [{ name: 'production', isDefault: true, priority: 3 }];
|
||||
|
||||
/** Check if the query is connected to a DS. */
|
||||
export const isQueryRunnable = (query) => {
|
||||
if (staticDataSources.find((source) => query.kind === source.kind)) {
|
||||
return true;
|
||||
}
|
||||
//TODO: both view api and creat/update apis return dataSourceId in two format 1) camelCase 2) snakeCase. Need to unify it.
|
||||
return !!(query?.data_source_id || query?.dataSourceId || !isEmpty(query?.plugins));
|
||||
};
|
||||
|
||||
export const redirectToDashboard = () => {
|
||||
const subpath = getSubpath();
|
||||
window.location = `${subpath ? `${subpath}` : ''}/${getWorkspaceId()}`;
|
||||
|
|
|
|||
27
frontend/src/_hooks/useShowPopover.jsx
Normal file
27
frontend/src/_hooks/useShowPopover.jsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default (_show, selector, triggerSelector) => {
|
||||
const [show, setShow] = useState(_show);
|
||||
|
||||
const handleClickOutside = (event) => {
|
||||
if (triggerSelector && event.target.closest(triggerSelector) !== null) {
|
||||
return;
|
||||
}
|
||||
if (show && event.target.closest(selector) === null) {
|
||||
setShow(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
} else {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
}
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [show]);
|
||||
|
||||
return [show, setShow];
|
||||
};
|
||||
|
|
@ -9,6 +9,7 @@ export const dataqueryService = {
|
|||
del,
|
||||
preview,
|
||||
changeQueryDataSource,
|
||||
updateStatus,
|
||||
};
|
||||
|
||||
function getAll(appVersionId) {
|
||||
|
|
@ -42,13 +43,23 @@ function update(id, name, options) {
|
|||
return fetch(`${config.apiUrl}/data_queries/${id}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function updateStatus(id, status) {
|
||||
const body = {
|
||||
status,
|
||||
};
|
||||
|
||||
const requestOptions = { method: 'PUT', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) };
|
||||
return fetch(`${config.apiUrl}/data_queries/${id}/status`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function del(id) {
|
||||
const requestOptions = { method: 'DELETE', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/data_queries/${id}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function run(queryId, options) {
|
||||
function run(queryId, resolvedOptions, options) {
|
||||
const body = {
|
||||
resolvedOptions: resolvedOptions,
|
||||
options: options,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { create, zustandDevTools } from './utils';
|
|||
|
||||
const initialState = {
|
||||
editingVersion: null,
|
||||
isSaving: false,
|
||||
appId: null,
|
||||
};
|
||||
|
||||
export const useAppDataStore = create(
|
||||
|
|
@ -10,6 +12,8 @@ export const useAppDataStore = create(
|
|||
...initialState,
|
||||
actions: {
|
||||
updateEditingVersion: (version) => set(() => ({ editingVersion: version })),
|
||||
setIsSaving: (isSaving) => set(() => ({ isSaving })),
|
||||
setAppId: (appId) => set(() => ({ appId })),
|
||||
},
|
||||
}),
|
||||
{ name: 'App Data Store' }
|
||||
|
|
@ -17,4 +21,5 @@ export const useAppDataStore = create(
|
|||
);
|
||||
|
||||
export const useEditingVersion = () => useAppDataStore((state) => state.editingVersion);
|
||||
export const useIsSaving = () => useAppDataStore((state) => state.isSaving);
|
||||
export const useUpdateEditingVersion = () => useAppDataStore((state) => state.actions);
|
||||
|
|
|
|||
|
|
@ -1,157 +1,273 @@
|
|||
import { create, zustandDevTools } from './utils';
|
||||
import { getDefaultOptions } from './storeHelper';
|
||||
import { dataqueryService } from '@/_services';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
import { useQueryPanelStore } from '@/_stores/queryPanelStore';
|
||||
import { runQueries, computeQueryState } from '@/_helpers/appUtils';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { runQueries } from '@/_helpers/appUtils';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
const initialState = {
|
||||
dataQueries: [],
|
||||
sortBy: 'updated_at',
|
||||
sortOrder: 'desc',
|
||||
loadingDataQueries: true,
|
||||
isDeletingQueryInProcess: false,
|
||||
/** TODO: Below two params are primarily used only for websocket invocation post update. Can be removed onece websocket logic is revamped */
|
||||
isCreatingQueryInProcess: false,
|
||||
isUpdatingQueryInProcess: false,
|
||||
};
|
||||
|
||||
export const useDataQueriesStore = create(
|
||||
zustandDevTools(
|
||||
(set, get) => ({
|
||||
(set) => ({
|
||||
...initialState,
|
||||
actions: {
|
||||
// TODO: Remove editor state while changing currentState
|
||||
fetchDataQueries: async (appId, selectFirstQuery = false, runQueriesOnAppLoad = false, editorRef) => {
|
||||
set({ loadingDataQueries: true });
|
||||
await dataqueryService.getAll(appId).then((data) => {
|
||||
set({
|
||||
dataQueries: data.data_queries,
|
||||
loadingDataQueries: false,
|
||||
});
|
||||
// Runs query on loading application
|
||||
if (runQueriesOnAppLoad) runQueries(data.data_queries, editorRef);
|
||||
// Compute query state to be added in the current state
|
||||
computeQueryState(data.data_queries, editorRef);
|
||||
const { actions, selectedQuery } = useQueryPanelStore.getState();
|
||||
if (selectFirstQuery || selectedQuery?.id === 'draftQuery') {
|
||||
actions.setSelectedQuery(data.data_queries[0]?.id, data.data_queries[0]);
|
||||
} else if (selectedQuery?.id) {
|
||||
const query = data.data_queries.find((query) => query.id === selectedQuery?.id);
|
||||
actions.setSelectedQuery(query?.id);
|
||||
}
|
||||
});
|
||||
const data = await dataqueryService.getAll(appId);
|
||||
set((state) => ({
|
||||
dataQueries: sortByAttribute(data.data_queries, state.sortBy, state.sortOrder),
|
||||
loadingDataQueries: false,
|
||||
}));
|
||||
// Runs query on loading application
|
||||
if (runQueriesOnAppLoad) runQueries(data.data_queries, editorRef);
|
||||
// Compute query state to be added in the current state
|
||||
const { actions, selectedQuery } = useQueryPanelStore.getState();
|
||||
if (selectFirstQuery) {
|
||||
actions.setSelectedQuery(data.data_queries[0]?.id, data.data_queries[0]);
|
||||
} else if (selectedQuery?.id) {
|
||||
const query = data.data_queries.find((query) => query.id === selectedQuery?.id);
|
||||
actions.setSelectedQuery(query?.id);
|
||||
}
|
||||
},
|
||||
setDataQueries: (dataQueries) => set({ dataQueries }),
|
||||
deleteDataQueries: (queryId, editorRef) => {
|
||||
deleteDataQueries: (queryId) => {
|
||||
set({ isDeletingQueryInProcess: true });
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
dataqueryService
|
||||
.del(queryId)
|
||||
.then(() => {
|
||||
toast.success('Query Deleted');
|
||||
set({
|
||||
isDeletingQueryInProcess: false,
|
||||
});
|
||||
const { actions, selectedQuery } = useQueryPanelStore.getState();
|
||||
if (queryId === selectedQuery?.id) {
|
||||
actions.setUnSavedChanges(false);
|
||||
actions.setSelectedQuery(null);
|
||||
const { actions } = useQueryPanelStore.getState();
|
||||
const { dataQueries } = useDataQueriesStore.getState();
|
||||
const newSelectedQuery = dataQueries.find((query) => query.id !== queryId);
|
||||
actions.setSelectedQuery(newSelectedQuery?.id || null);
|
||||
if (!newSelectedQuery?.id) {
|
||||
actions.setSelectedDataSource(null);
|
||||
}
|
||||
get().actions.fetchDataQueries(
|
||||
useAppVersionStore.getState().editingVersion?.id,
|
||||
selectedQuery?.id === queryId,
|
||||
false,
|
||||
editorRef
|
||||
);
|
||||
set((state) => ({
|
||||
isDeletingQueryInProcess: false,
|
||||
dataQueries: state.dataQueries.filter((query) => query.id !== queryId),
|
||||
}));
|
||||
})
|
||||
.catch(({ error }) => {
|
||||
.catch(() => {
|
||||
set({
|
||||
isDeletingQueryInProcess: false,
|
||||
});
|
||||
toast.error(error);
|
||||
});
|
||||
})
|
||||
.finally(() => useAppDataStore.getState().actions.setIsSaving(false));
|
||||
},
|
||||
updateDataQuery: (options, shouldRunQuery) => {
|
||||
updateDataQuery: (options) => {
|
||||
set({ isUpdatingQueryInProcess: true });
|
||||
const { actions, selectedQuery } = useQueryPanelStore.getState();
|
||||
const { name, id, kind } = selectedQuery;
|
||||
dataqueryService
|
||||
.update(id, name, options)
|
||||
.then((data) => {
|
||||
const updatedData = { ...data, kind, options };
|
||||
actions.setUnSavedChanges(false);
|
||||
localStorage.removeItem('transformation');
|
||||
toast.success('Query Saved');
|
||||
set((state) => ({
|
||||
isUpdatingQueryInProcess: false,
|
||||
dataQueries: state.dataQueries.map((query) => {
|
||||
if (query.id === data.id) return updatedData;
|
||||
return query;
|
||||
}),
|
||||
}));
|
||||
if (shouldRunQuery) actions.setQueryToBeRun(updatedData);
|
||||
})
|
||||
.catch(({ error }) => {
|
||||
actions.setUnSavedChanges(false);
|
||||
toast.error(error);
|
||||
set({
|
||||
isUpdatingQueryInProcess: false,
|
||||
});
|
||||
});
|
||||
set((state) => ({
|
||||
isUpdatingQueryInProcess: false,
|
||||
dataQueries: state.dataQueries.map((query) => {
|
||||
if (query.id === selectedQuery.id) {
|
||||
return {
|
||||
...query,
|
||||
options: { ...options },
|
||||
};
|
||||
}
|
||||
return query;
|
||||
}),
|
||||
}));
|
||||
actions.setSelectedQuery(selectedQuery.id);
|
||||
},
|
||||
createDataQuery: (appId, appVersionId, options, shouldRunQuery) => {
|
||||
// createDataQuery: (appId, appVersionId, options, kind, name, selectedDataSource, shouldRunQuery) => {
|
||||
createDataQuery: (selectedDataSource, shouldRunQuery) => {
|
||||
const appVersionId = useAppVersionStore.getState().editingVersion?.id;
|
||||
const appId = useAppDataStore.getState().appId;
|
||||
const { options, name } = getDefaultOptions(selectedDataSource);
|
||||
const kind = selectedDataSource.kind;
|
||||
set({ isCreatingQueryInProcess: true });
|
||||
const { actions, selectedQuery, selectedDataSource } = useQueryPanelStore.getState();
|
||||
const { name, kind } = selectedQuery;
|
||||
const dataSourceId = selectedDataSource.id === 'null' ? null : selectedDataSource.id;
|
||||
const { actions, selectedQuery } = useQueryPanelStore.getState();
|
||||
const dataSourceId = selectedDataSource?.id !== 'null' ? selectedDataSource?.id : null;
|
||||
const pluginId = selectedDataSource.pluginId || selectedDataSource.plugin_id;
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
const { dataQueries } = useDataQueriesStore.getState();
|
||||
const currDataQueries = [...dataQueries];
|
||||
const tempId = uuidv4();
|
||||
set(() => ({
|
||||
dataQueries: [
|
||||
{
|
||||
...selectedQuery,
|
||||
data_source_id: dataSourceId,
|
||||
app_version_id: appVersionId,
|
||||
options,
|
||||
name,
|
||||
kind,
|
||||
id: tempId,
|
||||
plugin: selectedDataSource.plugin,
|
||||
},
|
||||
...currDataQueries,
|
||||
],
|
||||
}));
|
||||
actions.setSelectedQuery(tempId);
|
||||
dataqueryService
|
||||
.create(appId, appVersionId, name, kind, options, dataSourceId, pluginId)
|
||||
.then((data) => {
|
||||
const query = { ...data, kind, options };
|
||||
actions.setUnSavedChanges(false);
|
||||
toast.success('Query Added');
|
||||
set((state) => ({
|
||||
isCreatingQueryInProcess: false,
|
||||
dataQueries: [query, ...state.dataQueries],
|
||||
dataQueries: state.dataQueries.map((query) => {
|
||||
if (query.id === tempId) {
|
||||
return { ...query, ...data, data_source_id: dataSourceId };
|
||||
}
|
||||
return query;
|
||||
}),
|
||||
}));
|
||||
if (shouldRunQuery) actions.setQueryToBeRun(query);
|
||||
actions.setSelectedQuery(data.id, data);
|
||||
actions.setNameInputFocussed(true);
|
||||
if (shouldRunQuery) actions.setQueryToBeRun(data);
|
||||
})
|
||||
.catch(({ error }) => {
|
||||
actions.setUnSavedChanges(false);
|
||||
toast.error(error);
|
||||
set({
|
||||
.catch((error) => {
|
||||
set((state) => ({
|
||||
isCreatingQueryInProcess: false,
|
||||
});
|
||||
});
|
||||
dataQueries: state.dataQueries.filter((query) => query.id !== tempId),
|
||||
}));
|
||||
actions.setSelectedQuery(null);
|
||||
toast.error(`Failed to create query: ${error.message}`);
|
||||
})
|
||||
.finally(() => useAppDataStore.getState().actions.setIsSaving(false));
|
||||
},
|
||||
renameQuery: (id, newName, editorRef) => {
|
||||
renameQuery: (id, newName) => {
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
/**
|
||||
* Seting name to store before api call for instant UI update and better UX.
|
||||
* Name is again set to state post api call to handle if renaming fails in backend.
|
||||
* */
|
||||
set((state) => ({
|
||||
dataQueries: state.dataQueries.map((query) => (query.id === id ? { ...query, name: newName } : query)),
|
||||
}));
|
||||
dataqueryService
|
||||
.update(id, newName)
|
||||
.then(() => {
|
||||
toast.success('Query Name Updated');
|
||||
get().actions.fetchDataQueries(useAppVersionStore.getState().editingVersion?.id, false, false, editorRef);
|
||||
.then((data) => {
|
||||
set((state) => ({
|
||||
dataQueries: state.dataQueries.map((query) => {
|
||||
if (query.id === id) {
|
||||
return { ...query, name: newName, updated_at: data.updated_at };
|
||||
}
|
||||
return query;
|
||||
}),
|
||||
}));
|
||||
useQueryPanelStore.getState().actions.setSelectedQuery(id);
|
||||
})
|
||||
.catch(({ error }) => {
|
||||
toast.error(error);
|
||||
});
|
||||
.finally(() => useAppDataStore.getState().actions.setIsSaving(false));
|
||||
},
|
||||
changeDataQuery: (newDataSource) => {
|
||||
const { selectedQuery } = useQueryPanelStore.getState();
|
||||
set({
|
||||
isUpdatingQueryInProcess: true,
|
||||
});
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
dataqueryService
|
||||
.changeQueryDataSource(selectedQuery?.id, newDataSource.id)
|
||||
.then(() => {
|
||||
set((state) => ({
|
||||
isUpdatingQueryInProcess: false,
|
||||
dataQueries: state.dataQueries.map((query) => {
|
||||
if (query?.id === selectedQuery?.id) {
|
||||
return { ...query, dataSourceId: newDataSource?.id, data_source_id: newDataSource?.id };
|
||||
}
|
||||
return query;
|
||||
}),
|
||||
}));
|
||||
useQueryPanelStore.getState().actions.setSelectedQuery(selectedQuery.id);
|
||||
useQueryPanelStore.getState().actions.setSelectedDataSource(newDataSource);
|
||||
})
|
||||
.catch(() => {
|
||||
set({
|
||||
isUpdatingQueryInProcess: false,
|
||||
});
|
||||
toast.success('Data source changed');
|
||||
})
|
||||
.finally(() => useAppDataStore.getState().actions.setIsSaving(false));
|
||||
},
|
||||
duplicateQuery: (id, appId) => {
|
||||
set({ isCreatingQueryInProcess: true });
|
||||
const { actions } = useQueryPanelStore.getState();
|
||||
const { dataQueries } = useDataQueriesStore.getState();
|
||||
const queryToClone = { ...dataQueries.find((query) => query.id === id) };
|
||||
let newName = queryToClone.name + '_copy';
|
||||
const names = dataQueries.map(({ name }) => name);
|
||||
let count = 0;
|
||||
while (names.includes(newName)) {
|
||||
count++;
|
||||
newName = queryToClone.name + '_copy' + count.toString();
|
||||
}
|
||||
queryToClone.name = newName;
|
||||
delete queryToClone.id;
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
dataqueryService
|
||||
.create(
|
||||
appId,
|
||||
queryToClone.app_version_id,
|
||||
queryToClone.name,
|
||||
queryToClone.kind,
|
||||
queryToClone.options,
|
||||
queryToClone.data_source_id,
|
||||
queryToClone.pluginId
|
||||
)
|
||||
.then((data) => {
|
||||
set((state) => ({
|
||||
isCreatingQueryInProcess: false,
|
||||
dataQueries: [{ ...data, data_source_id: queryToClone.data_source_id }, ...state.dataQueries],
|
||||
}));
|
||||
actions.setSelectedQuery(data.id, { ...data, data_source_id: queryToClone.data_source_id });
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(error);
|
||||
console.error('error', error);
|
||||
set({
|
||||
isCreatingQueryInProcess: false,
|
||||
});
|
||||
})
|
||||
.finally(() => useAppDataStore.getState().actions.setIsSaving(false));
|
||||
},
|
||||
saveData: debounce((newValues) => {
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
set({ isUpdatingQueryInProcess: true });
|
||||
dataqueryService
|
||||
.update(newValues?.id, newValues?.name, newValues?.options)
|
||||
.then((data) => {
|
||||
localStorage.removeItem('transformation');
|
||||
set((state) => ({
|
||||
dataQueries: state.dataQueries.map((query) => {
|
||||
if (query.id === newValues?.id) {
|
||||
return { ...query, updated_at: data.updated_at };
|
||||
}
|
||||
return query;
|
||||
}),
|
||||
isUpdatingQueryInProcess: false,
|
||||
}));
|
||||
})
|
||||
.catch(() => {
|
||||
set({
|
||||
isUpdatingQueryInProcess: false,
|
||||
});
|
||||
});
|
||||
})
|
||||
.finally(() => useAppDataStore.getState().actions.setIsSaving(false));
|
||||
}, 500),
|
||||
sortDataQueries: (sortBy, sortOrder) => {
|
||||
set(({ dataQueries, sortOrder: currSortOrder }) => {
|
||||
const newSortOrder = sortOrder ? sortOrder : currSortOrder === 'asc' ? 'desc' : 'asc';
|
||||
return {
|
||||
sortBy,
|
||||
sortOrder: newSortOrder,
|
||||
dataQueries: sortByAttribute(dataQueries, sortBy, newSortOrder),
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
|
@ -159,6 +275,15 @@ export const useDataQueriesStore = create(
|
|||
)
|
||||
);
|
||||
|
||||
const sortByAttribute = (data, sortBy, order) => {
|
||||
if (order === 'asc') {
|
||||
return data.sort((a, b) => (a[sortBy] > b[sortBy] ? 1 : -1));
|
||||
}
|
||||
if (order === 'desc') {
|
||||
return data.sort((a, b) => (a[sortBy] < b[sortBy] ? 1 : -1));
|
||||
}
|
||||
};
|
||||
|
||||
export const useDataQueries = () => useDataQueriesStore((state) => state.dataQueries);
|
||||
export const useDataQueriesActions = () => useDataQueriesStore((state) => state.actions);
|
||||
export const useQueryCreationLoading = () => useDataQueriesStore((state) => state.isCreatingQueryInProcess);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { cloneDeep } from 'lodash';
|
||||
import { create, zustandDevTools } from './utils';
|
||||
|
||||
import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
||||
|
|
@ -7,10 +8,11 @@ const initialState = {
|
|||
queryPanelHeight: queryManagerPreferences?.isExpanded ? queryManagerPreferences?.queryPanelHeight : 95 ?? 70,
|
||||
selectedQuery: null,
|
||||
selectedDataSource: null,
|
||||
isUnsavedChangesAvailable: false,
|
||||
queryToBeRun: null,
|
||||
previewLoading: false,
|
||||
queryPreviewData: null,
|
||||
showCreateQuery: false,
|
||||
nameInputFocussed: false,
|
||||
};
|
||||
|
||||
export const useQueryPanelStore = create(
|
||||
|
|
@ -19,22 +21,21 @@ export const useQueryPanelStore = create(
|
|||
...initialState,
|
||||
actions: {
|
||||
updateQueryPanelHeight: (newHeight) => set(() => ({ queryPanelHeight: newHeight })),
|
||||
setSelectedQuery: (queryId, dataQuery = {}) => {
|
||||
setSelectedQuery: (queryId) => {
|
||||
set(() => {
|
||||
if (queryId === null) {
|
||||
return { selectedQuery: null };
|
||||
} else if (queryId === 'draftQuery') {
|
||||
return { selectedQuery: dataQuery };
|
||||
}
|
||||
const query = useDataQueriesStore.getState().dataQueries.find((query) => query.id === queryId);
|
||||
return { selectedQuery: query ? query : null };
|
||||
return { selectedQuery: query };
|
||||
});
|
||||
},
|
||||
setSelectedDataSource: (dataSource = null) => set({ selectedDataSource: dataSource }),
|
||||
setUnSavedChanges: (value) => set({ isUnsavedChangesAvailable: value }),
|
||||
setQueryToBeRun: (query) => set({ queryToBeRun: query }),
|
||||
setPreviewLoading: (status) => set({ previewLoading: status }),
|
||||
setPreviewData: (data) => set({ queryPreviewData: data }),
|
||||
setShowCreateQuery: (showCreateQuery) => set({ showCreateQuery }),
|
||||
setNameInputFocussed: (nameInputFocussed) => set({ nameInputFocussed }),
|
||||
},
|
||||
}),
|
||||
{ name: 'Query Panel Store' }
|
||||
|
|
@ -44,8 +45,11 @@ export const useQueryPanelStore = create(
|
|||
export const usePanelHeight = () => useQueryPanelStore((state) => state.queryPanelHeight);
|
||||
export const useSelectedQuery = () => useQueryPanelStore((state) => state.selectedQuery);
|
||||
export const useSelectedDataSource = () => useQueryPanelStore((state) => state.selectedDataSource);
|
||||
export const useUnsavedChanges = () => useQueryPanelStore((state) => state.isUnsavedChangesAvailable);
|
||||
export const useQueryToBeRun = () => useQueryPanelStore((state) => state.queryToBeRun);
|
||||
export const usePreviewLoading = () => useQueryPanelStore((state) => state.previewLoading);
|
||||
export const usePreviewData = () => useQueryPanelStore((state) => state.queryPreviewData);
|
||||
export const useQueryPanelActions = () => useQueryPanelStore((state) => state.actions);
|
||||
export const useShowCreateQuery = () =>
|
||||
useQueryPanelStore((state) => [state.showCreateQuery, state.actions.setShowCreateQuery]);
|
||||
export const useNameInputFocussed = () =>
|
||||
useQueryPanelStore((state) => [state.nameInputFocussed, state.actions.setNameInputFocussed]);
|
||||
|
|
|
|||
55
frontend/src/_stores/storeHelper.js
Normal file
55
frontend/src/_stores/storeHelper.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { schemaUnavailableOptions } from '@/Editor/QueryManager/constants';
|
||||
import { allOperations } from '@tooljet/plugins/client';
|
||||
import { capitalize } from 'lodash';
|
||||
import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
||||
|
||||
export const getDefaultOptions = (source) => {
|
||||
const isSchemaUnavailable = Object.keys(schemaUnavailableOptions).includes(source.kind);
|
||||
let options = {};
|
||||
|
||||
if (isSchemaUnavailable) {
|
||||
options = {
|
||||
...{ ...schemaUnavailableOptions[source.kind] },
|
||||
...(source?.kind != 'runjs' && {
|
||||
transformationLanguage: 'javascript',
|
||||
enableTransformation: false,
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
const selectedSourceDefault =
|
||||
source?.plugin?.operationsFile?.data?.defaults ?? allOperations[capitalize(source.kind)]?.defaults;
|
||||
if (selectedSourceDefault) {
|
||||
options = {
|
||||
...{ ...selectedSourceDefault },
|
||||
...(source?.kind != 'runjs' && {
|
||||
transformationLanguage: 'javascript',
|
||||
enableTransformation: false,
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
options = {
|
||||
...(source?.kind != 'runjs' && {
|
||||
transformationLanguage: 'javascript',
|
||||
enableTransformation: false,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { options, name: computeQueryName(source.kind) };
|
||||
};
|
||||
|
||||
const computeQueryName = (kind) => {
|
||||
const dataQueries = useDataQueriesStore.getState().dataQueries;
|
||||
const currentQueriesForKind = dataQueries.filter((query) => query.kind === kind);
|
||||
let currentNumber = currentQueriesForKind.length + 1;
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const newName = `${kind}${currentNumber}`;
|
||||
if (dataQueries.find((query) => query.name === newName) === undefined) {
|
||||
return newName;
|
||||
}
|
||||
currentNumber += 1;
|
||||
}
|
||||
};
|
||||
|
|
@ -148,15 +148,46 @@ $primary-light: unquote("rgb(#{$primary-rgb-darker})");
|
|||
.color-light-gray-c3c3c3{
|
||||
color: #c3c3c3;
|
||||
}
|
||||
.color-light-indigo-09 {
|
||||
color: $color-light-indigo-09;
|
||||
}
|
||||
|
||||
.bg-color-primary {
|
||||
background-color: $primary !important;
|
||||
}
|
||||
|
||||
.bg-slate3{
|
||||
.color-slate9 {
|
||||
color: var(--slate9) !important;
|
||||
}
|
||||
|
||||
.color-indigo9 {
|
||||
color: var(--indigo9) !important;
|
||||
}
|
||||
|
||||
.color-slate11 {
|
||||
color: var(--slate11) !important;
|
||||
}
|
||||
|
||||
.color-slate12 {
|
||||
color: var(--slate12) !important;
|
||||
}
|
||||
|
||||
.bg-slate2 {
|
||||
background-color: var(--slate2) !important;
|
||||
}
|
||||
|
||||
.border-slate3 {
|
||||
border-top: 1px solid var(--slate3) !important;
|
||||
}
|
||||
|
||||
.border-slate3-top {
|
||||
border-top: 1px solid var(--slate3) !important;
|
||||
}
|
||||
|
||||
.bg-slate3 {
|
||||
background-color: var(--slate3) !important;
|
||||
}
|
||||
|
||||
.color-slate12{
|
||||
color: var(--slate12) !important;
|
||||
.bg-slate6 {
|
||||
background-color: var(--slate6) !important;
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ $btn-dark-color: #FFFFFF;
|
|||
transition: all 0.3s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
@if $bg !=none {
|
||||
@if $bg !=none {
|
||||
background-color: darken($bg, 10%);
|
||||
}
|
||||
}
|
||||
|
|
@ -229,6 +229,22 @@ $btn-dark-color: #FFFFFF;
|
|||
}
|
||||
}
|
||||
|
||||
.notification-dot {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background-color: var(--indigo9);
|
||||
border-radius: 50%;
|
||||
border: 3px solid var(--base);
|
||||
}
|
||||
|
||||
.query-manager-sort-filter-popup {
|
||||
width: 215px !important;
|
||||
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03) !important;
|
||||
}
|
||||
|
||||
.page-icons {
|
||||
position: relative;
|
||||
left: 1rem;
|
||||
|
|
@ -267,7 +283,7 @@ $btn-dark-color: #FFFFFF;
|
|||
}
|
||||
|
||||
.page-handler-input {
|
||||
border-radius: $base-border-radius !important;
|
||||
border-radius: $base-border-radius !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -329,7 +345,7 @@ $btn-dark-color: #FFFFFF;
|
|||
|
||||
|
||||
#popover-change-scope {
|
||||
border: 1px solid rgba(101, 109, 119, 0.16);
|
||||
border: 1px solid rgba(101, 109, 119, 0.16);
|
||||
box-shadow: 0px 3px 2px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,10 +44,10 @@ $border-radius: 4px;
|
|||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.query-pane::-webkit-scrollbar {
|
||||
width: 0;
|
||||
background: transparent;
|
||||
}
|
||||
// .query-pane::-webkit-scrollbar {
|
||||
// width: 0;
|
||||
// background: transparent;
|
||||
// }
|
||||
|
||||
.query-pane {
|
||||
z-index: 1;
|
||||
|
|
@ -101,9 +101,8 @@ $border-radius: 4px;
|
|||
display: none;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-right: 9.33px;
|
||||
margin-left: 2px;
|
||||
width: 44px;
|
||||
width: 66px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
|
|
@ -124,15 +123,6 @@ $border-radius: 4px;
|
|||
|
||||
.query-icon {
|
||||
margin: auto 8px auto 12px;
|
||||
width: 21.33px;
|
||||
height: 21.33px;
|
||||
padding: 1.33px;
|
||||
|
||||
svg {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.delete-query,
|
||||
|
|
@ -303,7 +293,7 @@ $border-radius: 4px;
|
|||
gap: 12px;
|
||||
|
||||
.queries-search {
|
||||
width: 172px !important;
|
||||
// width: 172px !important;
|
||||
height: 28px !important;
|
||||
|
||||
.query-manager-search-box-wrapper {
|
||||
|
|
@ -339,7 +329,7 @@ $border-radius: 4px;
|
|||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
border: 0;
|
||||
border: 1px solid var(--slate-07, #D7DBDF);
|
||||
color: $color-light-slate-12;
|
||||
padding-left: 12px !important;
|
||||
|
||||
|
|
@ -418,16 +408,9 @@ $border-radius: 4px;
|
|||
}
|
||||
}
|
||||
|
||||
.query-list::-webkit-scrollbar {
|
||||
width: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.query-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
gap: 2px;
|
||||
width: 100%;
|
||||
|
|
@ -512,11 +495,15 @@ $border-radius: 4px;
|
|||
}
|
||||
|
||||
.delete-field-option {
|
||||
max-height: 32px;
|
||||
height: 34px;
|
||||
flex: 0 0 28px;
|
||||
background: #ffffff;
|
||||
max-width: 28px !important;
|
||||
width: 28px;
|
||||
width: 34px;
|
||||
}
|
||||
|
||||
.delete-field-option-dark {
|
||||
background-color: #272822 !important;
|
||||
border-color: #272822 !important ;
|
||||
}
|
||||
|
||||
.code-hinter.codehinter-default-input {
|
||||
|
|
@ -640,7 +627,7 @@ $border-radius: 4px;
|
|||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
// gap: 4px;
|
||||
|
||||
.list-group-item {
|
||||
border: none !important;
|
||||
|
|
@ -648,10 +635,11 @@ $border-radius: 4px;
|
|||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
padding: 0 !important;
|
||||
height: 28px;
|
||||
height: 32px;
|
||||
color: $color-light-slate-11;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #dee2e6 !important;
|
||||
|
||||
span {
|
||||
padding: 6px 8px;
|
||||
|
|
@ -662,7 +650,6 @@ $border-radius: 4px;
|
|||
.list-group-item:hover {
|
||||
color: $color-light-slate-12 !important;
|
||||
background-color: $color-light-slate-03 !important;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.list-group-item+.list-group-item.active {
|
||||
|
|
@ -709,7 +696,7 @@ $border-radius: 4px;
|
|||
}
|
||||
|
||||
.query-preview-list-group {
|
||||
width: 100%;
|
||||
// width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
|
|
@ -720,14 +707,15 @@ $border-radius: 4px;
|
|||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
padding: 0 !important;
|
||||
height: 28px;
|
||||
height: 24px;
|
||||
color: $color-light-slate-11;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
padding: 6px 8px;
|
||||
// padding: 6px 8px;
|
||||
padding: 2px 0px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
|
@ -748,11 +736,15 @@ $border-radius: 4px;
|
|||
|
||||
.list-group-item.active {
|
||||
background-color: transparent !important;
|
||||
color: $color-light-indigo-09 !important;
|
||||
// color: $color-light-indigo-09 !important;
|
||||
z-index: inherit !important;
|
||||
border-bottom: 2px solid $color-light-indigo-09 !important;
|
||||
// border-bottom: 2px solid $color-light-indigo-09 !important;
|
||||
border-radius: 0;
|
||||
transition-delay: 5ms;
|
||||
|
||||
span {
|
||||
background: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.list-group-item.active:hover {
|
||||
|
|
@ -1108,14 +1100,14 @@ $border-radius: 4px;
|
|||
|
||||
.preview-default-container {
|
||||
user-select: text;
|
||||
background-color: #F8F9FA;
|
||||
background-color: #FBFCFD;
|
||||
border: 0 0 6px 6px;
|
||||
height: 52px,
|
||||
}
|
||||
|
||||
.tab-pane.active {
|
||||
div {
|
||||
background-color: #F8F9FA !important;
|
||||
background-color: #FBFCFD !important;
|
||||
border-radius: 0 0 6px 6px !important;
|
||||
}
|
||||
|
||||
|
|
@ -1123,6 +1115,10 @@ $border-radius: 4px;
|
|||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-section-header {
|
||||
background-color: var(--slate2);
|
||||
}
|
||||
}
|
||||
|
||||
.query-manager-border-color {
|
||||
|
|
@ -1131,39 +1127,55 @@ $border-radius: 4px;
|
|||
|
||||
.query-details {
|
||||
.form-label {
|
||||
color: $color-light-slate-12 !important;
|
||||
color: var(--slate9) !important;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
margin-bottom: 0 !important;
|
||||
width: 100px;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.preview-data-container {
|
||||
min-height: 65px;
|
||||
}
|
||||
}
|
||||
|
||||
.rest-methods-url {
|
||||
.code-hinter.codehinter-default-input {
|
||||
border-style: solid !important;
|
||||
border-color: $color-light-slate-07 !important;
|
||||
border-radius: 0 6px 6px 0 !important;
|
||||
.rest-methods-url {
|
||||
|
||||
.url-input-group {
|
||||
.code-hinter.codehinter-default-input {
|
||||
border-radius: 0 6px 6px 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
box-shadow: 0px 0px 0px 2px #C6D4F9 !important;
|
||||
border: 1px solid #3E63DD !important;
|
||||
background-color: #F8FAFF;
|
||||
position: relative;
|
||||
z-index: 1 !important;
|
||||
.code-hinter.codehinter-default-input {
|
||||
border-style: solid !important;
|
||||
border-color: var(--slate7) !important;
|
||||
// border-radius: 0 6px 6px 0 !important;
|
||||
border-radius: 6px !important;
|
||||
|
||||
&:focus-within {
|
||||
box-shadow: 0px 0px 0px 2px #C6D4F9 !important;
|
||||
border: 1px solid #3E63DD !important;
|
||||
background-color: #F8FAFF;
|
||||
position: relative;
|
||||
z-index: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror.CodeMirror-wrap {
|
||||
background-color: transparent !important;
|
||||
|
||||
.cm-variable,
|
||||
.cm-comment {
|
||||
font-size: 12px !important;
|
||||
color: $color-light-slate-12 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror.CodeMirror-wrap {
|
||||
background-color: transparent !important;
|
||||
|
||||
.cm-variable,
|
||||
.cm-comment {
|
||||
font-size: 12px !important;
|
||||
color: $color-light-slate-12 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rest-api-methods-select-element-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
@ -1243,7 +1255,7 @@ $border-radius: 4px;
|
|||
|
||||
.inspector-add-button {
|
||||
background-color: transparent;
|
||||
font-weight: 400 !important;
|
||||
font-weight: bold;
|
||||
font-size: 12px !important;
|
||||
color: $color-light-indigo-09 !important;
|
||||
|
||||
|
|
@ -1290,6 +1302,16 @@ $border-radius: 4px;
|
|||
.CodeMirror-gutters {
|
||||
background-color: $color-light-slate-02;
|
||||
}
|
||||
|
||||
.CodeMirror-sizer {
|
||||
margin-left: 29px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.runpy-editor{
|
||||
.CodeMirror-sizer {
|
||||
margin-left: 29px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.code-hinter,
|
||||
|
|
@ -1445,7 +1467,6 @@ $border-radius: 4px;
|
|||
|
||||
.query-details {
|
||||
.form-label {
|
||||
color: #f4f6fa !important;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
|
|
@ -1595,7 +1616,6 @@ $border-radius: 4px;
|
|||
.rest-methods-url {
|
||||
.code-hinter.codehinter-default-input {
|
||||
border: solid inherit !important;
|
||||
border-radius: 0 6px 6px 0 !important;
|
||||
|
||||
.CodeMirror.cm-s-monokai.CodeMirror-wrap {
|
||||
background-color: transparent !important;
|
||||
|
|
@ -1848,3 +1868,14 @@ $border-radius: 4px;
|
|||
color: var(--slate11);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.query-manager-tooltip {
|
||||
max-width: 800px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.query-manager-ds-select-tooltip {
|
||||
max-width: 230px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
|
@ -165,6 +165,7 @@
|
|||
src: url('/assets/fonts/ibm-plex-sans-v19-latin/ibm-plex-sans-v19-latin-700italic.woff2') format('woff2');
|
||||
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
|
||||
// variables
|
||||
$border-radius: 4px;
|
||||
|
||||
|
|
@ -302,7 +303,7 @@ button {
|
|||
|
||||
.resizer-select,
|
||||
.resizer-active {
|
||||
border: solid 1px $primary !important;
|
||||
border: solid 1px $primary !important;
|
||||
|
||||
.top-right,
|
||||
.top-left,
|
||||
|
|
@ -332,7 +333,8 @@ button {
|
|||
|
||||
.datasource-picker {
|
||||
margin-bottom: 24px;
|
||||
padding: 0 32px;
|
||||
width: 475px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.header-query-datasource-card-container {
|
||||
|
|
@ -786,7 +788,7 @@ button {
|
|||
|
||||
.list-group.list-group-transparent.dark .all-apps-link,
|
||||
.list-group-item-action.dark.active {
|
||||
background-color: $dark-background !important;
|
||||
background-color: $dark-background !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1463,7 +1465,7 @@ button {
|
|||
.select-search-dark input {
|
||||
width: 224px !important;
|
||||
height: 32px !important;
|
||||
border-radius: $border-radius !important;
|
||||
border-radius: $border-radius !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1474,7 +1476,7 @@ button {
|
|||
.select-search__value input,
|
||||
.select-search-dark input {
|
||||
height: 32px !important;
|
||||
border-radius: $border-radius !important;
|
||||
border-radius: $border-radius !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1535,7 +1537,7 @@ button {
|
|||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
border-radius: $border-radius !important;
|
||||
border-radius: $border-radius !important;
|
||||
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
|
|
@ -3407,6 +3409,7 @@ input:focus-visible {
|
|||
|
||||
.query-pane {
|
||||
background-color: #1f2936 !important;
|
||||
border-top: 2px solid var(--slate4) !important;
|
||||
}
|
||||
|
||||
.input-icon .input-icon-addon img {
|
||||
|
|
@ -3861,7 +3864,7 @@ input[type="text"] {
|
|||
|
||||
.nav-tabs .nav-link.active {
|
||||
font-weight: 400 !important;
|
||||
color: $primary !important;
|
||||
color: $primary !important;
|
||||
}
|
||||
|
||||
.empty {
|
||||
|
|
@ -4376,7 +4379,7 @@ input[type="text"] {
|
|||
|
||||
.tabs-inspector.dark {
|
||||
.nav-link.active {
|
||||
border-bottom: 1px solid $primary !important;
|
||||
border-bottom: 1px solid $primary !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4611,7 +4614,7 @@ input[type="text"] {
|
|||
}
|
||||
|
||||
input {
|
||||
border-radius: $border-radius !important;
|
||||
border-radius: $border-radius !important;
|
||||
padding-left: 1.75rem !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -4779,11 +4782,11 @@ input[type="text"] {
|
|||
}
|
||||
|
||||
.modal-content.home-modal-component.dark {
|
||||
background-color: $bg-dark-light !important;
|
||||
color: $white !important;
|
||||
background-color: $bg-dark-light !important;
|
||||
color: $white !important;
|
||||
|
||||
.modal-header {
|
||||
background-color: $bg-dark-light !important;
|
||||
background-color: $bg-dark-light !important;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
|
|
@ -4791,22 +4794,22 @@ input[type="text"] {
|
|||
}
|
||||
|
||||
.form-control {
|
||||
border-color: $border-grey-dark !important;
|
||||
border-color: $border-grey-dark !important;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: $bg-dark-light !important;
|
||||
background-color: $bg-dark-light !important;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
background-color: $bg-dark !important;
|
||||
color: $white !important;
|
||||
border-color: $border-grey-dark !important;
|
||||
background-color: $bg-dark !important;
|
||||
color: $white !important;
|
||||
border-color: $border-grey-dark !important;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: $white !important;
|
||||
color: $white !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5101,7 +5104,7 @@ div#driver-page-overlay {
|
|||
}
|
||||
|
||||
.dark-theme-walkthrough#driver-popover-item {
|
||||
background-color: $bg-dark-light !important;
|
||||
background-color: $bg-dark-light !important;
|
||||
border-color: rgba(101, 109, 119, 0.16) !important;
|
||||
|
||||
.driver-popover-title {
|
||||
|
|
@ -5109,7 +5112,7 @@ div#driver-page-overlay {
|
|||
}
|
||||
|
||||
.driver-popover-tip {
|
||||
border-color: transparent transparent transparent $bg-dark-light !important;
|
||||
border-color: transparent transparent transparent $bg-dark-light !important;
|
||||
}
|
||||
|
||||
.driver-popover-description {
|
||||
|
|
@ -5141,7 +5144,7 @@ div#driver-page-overlay {
|
|||
|
||||
.driver-next-btn,
|
||||
.driver-prev-btn {
|
||||
color: $primary !important;
|
||||
color: $primary !important;
|
||||
}
|
||||
|
||||
.driver-disabled {
|
||||
|
|
@ -5167,11 +5170,11 @@ div#driver-page-overlay {
|
|||
color: #d9dcde !important;
|
||||
}
|
||||
|
||||
.popover-header {
|
||||
background-color: var(--slate2);
|
||||
color: var(--slate11);
|
||||
border-bottom-color: var
|
||||
}
|
||||
.popover-header {
|
||||
background-color: var(--slate2);
|
||||
color: var(--slate11);
|
||||
border-bottom-color: var
|
||||
}
|
||||
}
|
||||
|
||||
.toast-dark-mode {
|
||||
|
|
@ -5200,8 +5203,12 @@ div#driver-page-overlay {
|
|||
color: $light-gray;
|
||||
}
|
||||
|
||||
.dynamic-form-row {
|
||||
margin-top: 20px !important;
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
|
||||
#transformation-popover-container {
|
||||
margin-left: 80px !important;
|
||||
margin-bottom: -2px !important;
|
||||
}
|
||||
|
||||
|
|
@ -5596,7 +5603,7 @@ div#driver-page-overlay {
|
|||
}
|
||||
|
||||
.selected-node {
|
||||
border-color: $primary-light !important;
|
||||
border-color: $primary-light !important;
|
||||
}
|
||||
|
||||
.json-tree-icon-container .selected-node>svg:first-child {
|
||||
|
|
@ -5687,7 +5694,7 @@ div#driver-page-overlay {
|
|||
}
|
||||
|
||||
.selected-node {
|
||||
border-color: $primary-light !important;
|
||||
border-color: $primary-light !important;
|
||||
}
|
||||
|
||||
.selected-node .group-object-container .badge {
|
||||
|
|
@ -5954,7 +5961,7 @@ div#driver-page-overlay {
|
|||
|
||||
//Kanban board
|
||||
.kanban-container.dark-themed {
|
||||
background-color: $bg-dark-light !important;
|
||||
background-color: $bg-dark-light !important;
|
||||
|
||||
.kanban-column {
|
||||
.card-header {
|
||||
|
|
@ -6000,7 +6007,7 @@ div#driver-page-overlay {
|
|||
}
|
||||
|
||||
.dnd-card.card.card-dark {
|
||||
background-color: $bg-dark !important;
|
||||
background-color: $bg-dark !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7427,6 +7434,12 @@ tbody {
|
|||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
opacity: .65;
|
||||
}
|
||||
|
||||
.query-run-svg {
|
||||
padding: 4px 2.67px;
|
||||
}
|
||||
|
|
@ -10817,25 +10830,27 @@ tbody {
|
|||
}
|
||||
}
|
||||
}
|
||||
.generate-cell-value-component-div-wrapper{
|
||||
|
||||
|
||||
.generate-cell-value-component-div-wrapper {
|
||||
|
||||
.form-control-plaintext:focus-visible {
|
||||
outline-color: #dadcde;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
.form-control-plaintext:hover {
|
||||
outline-color: #dadcde;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
.dark-theme{
|
||||
.generate-cell-value-component-div-wrapper{
|
||||
|
||||
|
||||
.dark-theme {
|
||||
.generate-cell-value-component-div-wrapper {
|
||||
|
||||
.form-control-plaintext:focus-visible {
|
||||
filter: invert(-1);
|
||||
}
|
||||
|
||||
|
||||
.form-control-plaintext:hover {
|
||||
filter: invert(-1);
|
||||
}
|
||||
|
|
@ -10938,6 +10953,89 @@ tbody {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.query-rename-input {
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
box-shadow: 0px 0px 0px 2px #C6D4F9;
|
||||
border: 1px solid var(--light-indigo-09, #3E63DD);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-query-panel-header {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
||||
&.active {
|
||||
background-color: var(--slate5) !important;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--slate4) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tj-scrollbar {
|
||||
|
||||
::-webkit-scrollbar,
|
||||
&::-webkit-scrollbar {
|
||||
width: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb,
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border: 4px solid var(--base);
|
||||
border-radius: 8px;
|
||||
background-color: var(--slate4) !important;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track,
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: var(--base);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.form-check>.form-check-input:not(:checked) {
|
||||
background-color: var(--base);
|
||||
border-color: var(--slate7);
|
||||
}
|
||||
|
||||
/*
|
||||
* remove this once whole app is migrated to new styles. use only `theme-dark` class everywhere.
|
||||
* This is added since some of the pages are in old theme and making changes to `theme-dark` styles can break UI style somewhere else
|
||||
*/
|
||||
.tj-dark-mode {
|
||||
background-color: var(--base) !important;
|
||||
color: var(--base-black) !important;
|
||||
}
|
||||
|
||||
.tj-list-btn {
|
||||
border-radius: 6px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--slate4);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--slate5);
|
||||
}
|
||||
}
|
||||
|
||||
.tj-list-option {
|
||||
&.active {
|
||||
background-color: var(--indigo2);
|
||||
}
|
||||
}
|
||||
|
||||
.runjs-parameter-badge {
|
||||
max-width: 140px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export const ButtonBase = function ButtonBase(props) {
|
|||
fill,
|
||||
iconCustomClass,
|
||||
iconWidth,
|
||||
iconViewBox,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
|
|
@ -41,7 +42,15 @@ export const ButtonBase = function ButtonBase(props) {
|
|||
>
|
||||
{!isLoading && leftIcon && (
|
||||
<span className="tj-btn-left-icon">
|
||||
{<SolidIcon fill={fill} className={iconCustomClass} name={leftIcon} width={iconWidth} />}
|
||||
{
|
||||
<SolidIcon
|
||||
fill={fill}
|
||||
className={iconCustomClass}
|
||||
name={leftIcon}
|
||||
width={iconWidth}
|
||||
viewBox={iconViewBox}
|
||||
/>
|
||||
}
|
||||
</span>
|
||||
)}
|
||||
{isLoading ? (
|
||||
|
|
|
|||
|
|
@ -114,26 +114,25 @@
|
|||
}
|
||||
|
||||
.tj-tertiary-btn {
|
||||
background: var(--base);
|
||||
background: transparent;
|
||||
border: 1px solid var(--slate7);
|
||||
color: var(--slate12);
|
||||
|
||||
&:hover {
|
||||
background: var(--slate8);
|
||||
background: var(--slate4);
|
||||
color: var(--slate11);
|
||||
border: 1px solid var(--slate8);
|
||||
background: var(--base);
|
||||
border: 1px solid var(--slate7);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--base);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border: 1px solid var(--slate12);
|
||||
color: var(--slate12);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
background: var(--base);
|
||||
background: transparent;
|
||||
color: var(--slate11);
|
||||
outline: 1px solid var(--slate8);
|
||||
box-shadow: 0px 0px 0px 4px var(--slate6);
|
||||
|
|
@ -149,6 +148,7 @@
|
|||
|
||||
&:hover {
|
||||
color: var(--indigo10);
|
||||
background-color: var(--indigo4);
|
||||
}
|
||||
|
||||
&:active {
|
||||
|
|
@ -162,6 +162,9 @@
|
|||
box-shadow: 0px 0px 0px 4px var(--indigo6);
|
||||
outline: none;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0px 0px 0px 4px var(--indigo6);
|
||||
}
|
||||
}
|
||||
|
||||
.tj-ghost-black-btn {
|
||||
|
|
@ -171,6 +174,7 @@
|
|||
|
||||
&:hover {
|
||||
color: var(--slate11);
|
||||
background: var(--slate4, #ECEEF0);
|
||||
}
|
||||
|
||||
&:active {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import React from 'react';
|
||||
import { CodeHinter } from '@/Editor/CodeBuilder/CodeHinter';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import AddRectangle from '@/_ui/Icon/bulkIcons/AddRectangle';
|
||||
|
||||
export default ({ options, addNewKeyValuePair, removeKeyValuePair, keyValuePairValueChanged }) => {
|
||||
return (
|
||||
<div>
|
||||
{options.map((option, index) => {
|
||||
return (
|
||||
<div className="d-flex gap-2" key={index}>
|
||||
<div className="d-flex gap-2 mb-2" key={index}>
|
||||
<div className="d-flex justify-content-between gap-2 w-100">
|
||||
<div className="w-100">
|
||||
<CodeHinter
|
||||
|
|
@ -27,15 +29,27 @@ export default ({ options, addNewKeyValuePair, removeKeyValuePair, keyValuePairV
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" className="btn btn-primary" onClick={() => removeKeyValuePair(index)}>
|
||||
<img src="assets/images/icons/trash-light.svg" className="h-3" />
|
||||
</button>
|
||||
<ButtonSolid variant="ghostBlue" size="sm" className="py-3" onClick={() => removeKeyValuePair(index)}>
|
||||
{/* <img src="assets/images/icons/trash-light.svg" className="h-3" /> */}
|
||||
<svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.72386 0.884665C3.97391 0.634616 4.31304 0.494141 4.66667 0.494141H7.33333C7.68696 0.494141 8.02609 0.634616 8.27614 0.884665C8.52619 1.13471 8.66667 1.47385 8.66667 1.82747V3.16081H10.6589C10.6636 3.16076 10.6683 3.16076 10.673 3.16081H11.3333C11.7015 3.16081 12 3.45928 12 3.82747C12 4.19566 11.7015 4.49414 11.3333 4.49414H11.2801L10.6664 11.858C10.6585 12.3774 10.4488 12.8738 10.0809 13.2417C9.70581 13.6168 9.1971 13.8275 8.66667 13.8275H3.33333C2.8029 13.8275 2.29419 13.6168 1.91912 13.2417C1.55125 12.8738 1.34148 12.3774 1.33357 11.858L0.719911 4.49414H0.666667C0.298477 4.49414 0 4.19566 0 3.82747C0 3.45928 0.298477 3.16081 0.666667 3.16081H1.32702C1.33174 3.16076 1.33644 3.16076 1.34113 3.16081H3.33333V1.82747C3.33333 1.47385 3.47381 1.13471 3.72386 0.884665ZM2.05787 4.49414L2.66436 11.7721C2.6659 11.7905 2.66667 11.809 2.66667 11.8275C2.66667 12.0043 2.7369 12.1739 2.86193 12.2989C2.98695 12.4239 3.15652 12.4941 3.33333 12.4941H8.66667C8.84348 12.4941 9.01305 12.4239 9.13807 12.2989C9.2631 12.1739 9.33333 12.0043 9.33333 11.8275C9.33333 11.809 9.3341 11.7905 9.33564 11.7721L9.94213 4.49414H2.05787ZM7.33333 3.16081H4.66667V1.82747H7.33333V3.16081ZM4.19526 7.63221C3.93491 7.37186 3.93491 6.94975 4.19526 6.6894C4.45561 6.42905 4.87772 6.42905 5.13807 6.6894L6 7.55133L6.86193 6.6894C7.12228 6.42905 7.54439 6.42905 7.80474 6.6894C8.06509 6.94975 8.06509 7.37186 7.80474 7.63221L6.94281 8.49414L7.80474 9.35607C8.06509 9.61642 8.06509 10.0385 7.80474 10.2989C7.54439 10.5592 7.12228 10.5592 6.86193 10.2989L6 9.43695L5.13807 10.2989C4.87772 10.5592 4.45561 10.5592 4.19526 10.2989C3.93491 10.0385 3.93491 9.61642 4.19526 9.35607L5.05719 8.49414L4.19526 7.63221Z"
|
||||
fill="#E54D2E"
|
||||
/>
|
||||
</svg>
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<button type="button" className="btn btn-primary" onClick={addNewKeyValuePair}>
|
||||
{/* <ButtonSolid variant="ghostBlue" size="sm" onClick={addNewKeyValuePair}>
|
||||
+ Add header
|
||||
</button>
|
||||
</ButtonSolid> */}
|
||||
<ButtonSolid variant="ghostBlue" size="sm" onClick={addNewKeyValuePair}>
|
||||
<AddRectangle width="15" fill="#3E63DD" opacity="1" secondaryFill="#ffffff" />
|
||||
Add header
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ const SolidIcon = (props) => {
|
|||
const { name, ...restProps } = props;
|
||||
return <Icon {...restProps} name={name} />;
|
||||
};
|
||||
|
||||
export default SolidIcon;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import React from 'react';
|
||||
|
||||
const AddRectangle = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => (
|
||||
const AddRectangle = ({
|
||||
fill = '#C1C8CD',
|
||||
width = '25',
|
||||
className = '',
|
||||
viewBox = '0 0 25 25',
|
||||
opacity = '0.4',
|
||||
secondaryFill = '#121212',
|
||||
}) => (
|
||||
<svg
|
||||
width={width}
|
||||
className={className}
|
||||
|
|
@ -10,7 +17,7 @@ const AddRectangle = ({ fill = '#C1C8CD', width = '25', className = '', viewBox
|
|||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
opacity="0.4"
|
||||
opacity={opacity}
|
||||
d="M6 2H18C20.2091 2 22 3.79086 22 6V18C22 20.2091 20.2091 22 18 22H6C3.79086 22 2 20.2091 2 18V6C2 3.79086 3.79086 2 6 2Z"
|
||||
fill={fill}
|
||||
/>
|
||||
|
|
@ -18,7 +25,7 @@ const AddRectangle = ({ fill = '#C1C8CD', width = '25', className = '', viewBox
|
|||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12.75 8C12.75 7.58579 12.4142 7.25 12 7.25C11.5858 7.25 11.25 7.58579 11.25 8V11.25H8C7.58579 11.25 7.25 11.5858 7.25 12C7.25 12.4142 7.58579 12.75 8 12.75H11.25V16C11.25 16.4142 11.5858 16.75 12 16.75C12.4142 16.75 12.75 16.4142 12.75 16V12.75H16C16.4142 12.75 16.75 12.4142 16.75 12C16.75 11.5858 16.4142 11.25 16 11.25H12.75V8Z"
|
||||
fill="#121212"
|
||||
fill={secondaryFill}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
const ArrowLeft = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => (
|
||||
const ArrowLeft = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25', tailOpacity = '0.4' }) => (
|
||||
<svg
|
||||
width={width}
|
||||
height={width}
|
||||
|
|
@ -10,7 +10,7 @@ const ArrowLeft = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '
|
|||
className={className}
|
||||
>
|
||||
<path
|
||||
opacity="0.4"
|
||||
opacity={tailOpacity}
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.46967 11.4697C5.17678 11.7626 5.17678 12.2374 5.46967 12.5303L9.46967 16.5303C9.76256 16.8232 10.2374 16.8232 10.5303 16.5303C10.8232 16.2374 10.8232 15.7626 10.5303 15.4697L7.81066 12.75L18 12.75C18.4142 12.75 18.75 12.4142 18.75 12C18.75 11.5858 18.4142 11.25 18 11.25L7.81066 11.25L10.5303 8.53033C10.8232 8.23744 10.8232 7.76256 10.5303 7.46967C10.2374 7.17678 9.76256 7.17678 9.46967 7.46967L5.46967 11.4697Z"
|
||||
|
|
|
|||
31
frontend/src/_ui/Icon/solidIcons/FolderEmpty.jsx
Normal file
31
frontend/src/_ui/Icon/solidIcons/FolderEmpty.jsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import React from 'react';
|
||||
|
||||
const FolderEmpty = ({ width = '16', className = '', viewBox = '0 0 16 16', style = {} }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={width}
|
||||
height={width}
|
||||
viewBox={viewBox}
|
||||
fill="none"
|
||||
className={className}
|
||||
style={style}
|
||||
>
|
||||
<path
|
||||
opacity="0.4"
|
||||
d="M13.6823 5.33398H2.31431C1.46545 5.33398 0.832714 6.11667 1.01057 6.94669L2.2133 12.5594C2.47677 13.7889 3.56334 14.6673 4.82077 14.6673H11.1759C12.4333 14.6673 13.5199 13.7889 13.7834 12.5594L14.9861 6.94669C15.1639 6.11667 14.5312 5.33398 13.6823 5.33398Z"
|
||||
fill="#889096"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M6.23238 8.23238C6.42765 8.03712 6.74423 8.03712 6.93949 8.23238L8.00015 9.29304L9.06081 8.23238C9.25607 8.03712 9.57266 8.03712 9.76792 8.23238C9.96318 8.42765 9.96318 8.74423 9.76792 8.93949L8.70726 10.0002L9.76792 11.0608C9.96318 11.2561 9.96318 11.5727 9.76792 11.7679C9.57266 11.9632 9.25607 11.9632 9.06081 11.7679L8.00015 10.7073L6.93949 11.7679C6.74423 11.9632 6.42765 11.9632 6.23238 11.7679C6.03712 11.5727 6.03712 11.2561 6.23238 11.0608L7.29304 10.0002L6.23238 8.93949C6.03712 8.74423 6.03712 8.42765 6.23238 8.23238Z"
|
||||
fill="#889096"
|
||||
/>
|
||||
<path
|
||||
d="M13.3346 5.33398V4.66732C13.3346 3.56275 12.4392 2.66732 11.3346 2.66732H9.4663C9.02093 2.66732 8.58831 2.51866 8.237 2.24492L7.61005 1.75638C7.25874 1.48264 6.82612 1.33398 6.38075 1.33398H4.66797C3.5634 1.33398 2.66797 2.22941 2.66797 3.33398V5.33398H13.3346Z"
|
||||
fill="#889096"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default FolderEmpty;
|
||||
17
frontend/src/_ui/Icon/solidIcons/Maximize.jsx
Normal file
17
frontend/src/_ui/Icon/solidIcons/Maximize.jsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import React from 'react';
|
||||
|
||||
function Maximize({ stroke = '#C1C8CD', width = '25', viewBox = '0 0 25 25', style = {} }) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={width} fill="none" viewBox={viewBox} style={style}>
|
||||
<path
|
||||
stroke={stroke}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M8.5 1H13m0 0v4.5M13 1L1 13m0 0V8.5M1 13h4.5"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default Maximize;
|
||||
17
frontend/src/_ui/Icon/solidIcons/Minimize.jsx
Normal file
17
frontend/src/_ui/Icon/solidIcons/Minimize.jsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import React from 'react';
|
||||
|
||||
function Minimize({ stroke = '#C1C8CD', width = '25', viewBox = '0 0 25 25' }) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={width} fill="none" viewBox={viewBox}>
|
||||
<path
|
||||
stroke={stroke}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M17 1l-7 7m-9 9l7-7m2-2h5m-5 0V3m-2 7v5m0-5H3"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default Minimize;
|
||||
|
|
@ -1,18 +1,9 @@
|
|||
import React from 'react';
|
||||
|
||||
const Play = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => (
|
||||
<svg
|
||||
width={width}
|
||||
height={width}
|
||||
viewBox={viewBox}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={width} viewBox={viewBox} fill="none">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M17.4611 14.5255L9.49228 19.0792C8.15896 19.8411 6.5 18.8783 6.5 17.3427V12.7891V8.23542C6.5 6.69978 8.15896 5.73704 9.49228 6.49894L17.4611 11.0526C18.8048 11.8204 18.8048 13.7578 17.4611 14.5255Z"
|
||||
d="M11.9044 8.54526L4.81454 12.5966C3.62831 13.2744 2.15234 12.4179 2.15234 11.0517V2.949C2.15234 1.58275 3.62831 0.726218 4.81454 1.40407L11.9044 5.45539C13.0998 6.13849 13.0998 7.86217 11.9044 8.54526Z"
|
||||
fill={fill}
|
||||
/>
|
||||
</svg>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
const Plus = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25', dataCy = '' }) => (
|
||||
const Plus = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25', dataCy = '', style }) => (
|
||||
<svg
|
||||
width={width}
|
||||
height={width}
|
||||
|
|
@ -9,6 +9,7 @@ const Plus = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 2
|
|||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
data-cy={dataCy}
|
||||
style={style}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
const Search = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => (
|
||||
const Search = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25', style }) => (
|
||||
<svg
|
||||
width={width}
|
||||
height={width}
|
||||
|
|
@ -8,6 +8,7 @@ const Search = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0
|
|||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
style={style}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
const Trash = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => (
|
||||
const Trash = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25', style }) => (
|
||||
<svg
|
||||
width={width}
|
||||
height={width}
|
||||
|
|
@ -8,6 +8,7 @@ const Trash = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0
|
|||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
style={style}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
|
|
|
|||
|
|
@ -115,6 +115,8 @@ import Lock from './Lock.jsx';
|
|||
import Mail from './Mail.jsx';
|
||||
import Logs from './Logs.jsx';
|
||||
import Marketplace from './Marketplace.jsx';
|
||||
import Minimize from './Minimize.jsx';
|
||||
import Maximize from './Maximize.jsx';
|
||||
import PlusRectangle from './PlusRectangle.jsx';
|
||||
|
||||
const Icon = (props) => {
|
||||
|
|
@ -351,6 +353,10 @@ const Icon = (props) => {
|
|||
return <Mail {...props} />;
|
||||
case 'marketplace':
|
||||
return <Marketplace {...props} />;
|
||||
case 'minimize':
|
||||
return <Minimize {...props} />;
|
||||
case 'maximize':
|
||||
return <Maximize {...props} />;
|
||||
default:
|
||||
return <Apps {...props} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ const Button = ({
|
|||
};
|
||||
|
||||
const Content = ({ title = null, iconSrc = null, direction = 'left', dataCy }) => {
|
||||
const icon = !iconSrc ? (
|
||||
const Icon = !iconSrc ? (
|
||||
''
|
||||
) : (
|
||||
<img
|
||||
|
|
@ -52,7 +52,7 @@ const Content = ({ title = null, iconSrc = null, direction = 'left', dataCy }) =
|
|||
.replace(/\s+/g, '-')}-option-icon`}
|
||||
/>
|
||||
);
|
||||
const btnTitle = !title ? (
|
||||
const BtnTitle = !title ? (
|
||||
''
|
||||
) : typeof title === 'function' ? (
|
||||
title()
|
||||
|
|
@ -66,7 +66,19 @@ const Content = ({ title = null, iconSrc = null, direction = 'left', dataCy }) =
|
|||
{title}
|
||||
</span>
|
||||
);
|
||||
const content = direction === 'left' ? [icon, btnTitle] : [btnTitle, icon];
|
||||
|
||||
const content =
|
||||
direction === 'left' ? (
|
||||
<>
|
||||
{Icon}
|
||||
{BtnTitle}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{BtnTitle}
|
||||
{Icon}
|
||||
</>
|
||||
);
|
||||
|
||||
return content;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default function styles(darkMode, width = 224, height = 32, styles = {})
|
|||
}),
|
||||
control: (provided, state) => ({
|
||||
...provided,
|
||||
border: state.isDisabled && darkMode ? 'none' : styles.border ?? '1px solid hsl(0, 0%, 80%)',
|
||||
border: state.isDisabled && darkMode ? 'none' : styles.border ?? '1px solid var(--slate7)',
|
||||
boxShadow: 'none',
|
||||
'&:hover': {
|
||||
backgroundColor: darkMode ? '' : '#F8F9FA',
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@
|
|||
"description": "Enter max keys",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins col-6"
|
||||
"className": "codehinter-plugins"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Offset",
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -125,7 +124,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -156,7 +154,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -187,7 +184,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -218,7 +214,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -240,7 +235,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -271,7 +265,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -313,7 +306,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -353,7 +345,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -393,7 +384,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -433,7 +423,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -473,7 +462,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -513,7 +501,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -544,7 +531,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -575,7 +561,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -606,7 +591,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
@ -637,7 +621,6 @@
|
|||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"description": "Enter collection",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "Enter collection"
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@
|
|||
"description": "Enter max keys",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins col-6"
|
||||
"className": "codehinter-plugins"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Offset",
|
||||
|
|
@ -162,7 +162,7 @@
|
|||
"description": "Enter offset",
|
||||
"width": "320px",
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins col-6"
|
||||
"className": "codehinter-plugins"
|
||||
},
|
||||
"NextContinuationToken": {
|
||||
"label": "Next Continuation Token",
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
2.12.0
|
||||
2.13.0
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { EntityManager } from 'typeorm';
|
|||
import { DataSource } from 'src/entities/data_source.entity';
|
||||
import { DataSourceScopes, DataSourceTypes } from 'src/helpers/data_source.constants';
|
||||
import { App } from 'src/entities/app.entity';
|
||||
import { isEmpty } from 'class-validator';
|
||||
|
||||
@Controller('data_queries')
|
||||
export class DataQueriesController {
|
||||
|
|
@ -127,7 +128,12 @@ export class DataQueriesController {
|
|||
appVersionId,
|
||||
manager
|
||||
);
|
||||
return decamelizeKeys(dataQuery);
|
||||
|
||||
const decamelizedQuery = decamelizeKeys({ ...dataQuery, kind });
|
||||
|
||||
decamelizedQuery['options'] = dataQuery.options;
|
||||
|
||||
return decamelizedQuery;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +150,9 @@ export class DataQueriesController {
|
|||
}
|
||||
|
||||
const result = await this.dataQueriesService.update(dataQueryId, name, options);
|
||||
return decamelizeKeys(result);
|
||||
const decamelizedQuery = decamelizeKeys({ ...dataQuery, ...result });
|
||||
decamelizedQuery['options'] = result.options;
|
||||
return decamelizedQuery;
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
|
@ -169,7 +177,7 @@ export class DataQueriesController {
|
|||
@Param('environmentId') environmentId,
|
||||
@Body() updateDataQueryDto: UpdateDataQueryDto
|
||||
) {
|
||||
const { options } = updateDataQueryDto;
|
||||
const { options, resolvedOptions } = updateDataQueryDto;
|
||||
|
||||
const dataQuery = await this.dataQueriesService.findOne(dataQueryId);
|
||||
|
||||
|
|
@ -179,12 +187,17 @@ export class DataQueriesController {
|
|||
if (!ability.can('runQuery', dataQuery.app)) {
|
||||
throw new ForbiddenException('you do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
if (ability.can('updateQuery', dataQuery.app) && !isEmpty(options)) {
|
||||
await this.dataQueriesService.update(dataQueryId, dataQuery.name, options);
|
||||
dataQuery['options'] = options;
|
||||
}
|
||||
}
|
||||
|
||||
let result = {};
|
||||
|
||||
try {
|
||||
result = await this.dataQueriesService.runQuery(user, dataQuery, options, environmentId);
|
||||
result = await this.dataQueriesService.runQuery(user, dataQuery, resolvedOptions, environmentId);
|
||||
} catch (error) {
|
||||
if (error.constructor.name === 'QueryError') {
|
||||
result = {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@ export class CreateDataQueryDto {
|
|||
|
||||
@IsObject()
|
||||
options: object;
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
resolvedOptions: object;
|
||||
}
|
||||
|
||||
export class UpdateDataQueryDto extends PartialType(CreateDataQueryDto) {}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue