feat: unified response view component (#37897)
## Description Common component for response tab view. Fixes #37759 ## Automation /ok-to-test tags="@tag.All" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/12242471397> > Commit: 5263092079ad514d6c949b48d8510536ebeeadac > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12242471397&attempt=2" target="_blank">Cypress dashboard</a>. > Tags: `@tag.All` > Spec: > <hr>Mon, 09 Dec 2024 21:00:53 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [x] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a new `Response` component to handle and display action responses in the plugin action editor. - Streamlined response handling by consolidating components and updating props for better performance and user experience. - **Bug Fixes** - Enhanced error handling and response display logic to improve clarity and user interaction. - **Documentation** - Updated import paths and component references to reflect the new structure and naming conventions. - **Tests** - Modified Cypress tests to utilize the new `runQueryAndVerifyResponseViews` method for improved readability and maintainability. - Updated tests to replace direct interactions with UI elements by using methods from the `BottomTabs` module for selecting response types. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
parent
ad5e25f12e
commit
e754e48e1f
|
|
@ -126,7 +126,7 @@ describe.skip(
|
|||
dataSourceName,
|
||||
spreadSheetName,
|
||||
);
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(0, GSHEET_DATA[0].uniq_id);
|
||||
dataSources.AssertQueryTableResponse(1, "ホーンビィ 2014 カタログ"); // Asserting other language
|
||||
dataSources.AssertQueryTableResponse(2, "₹, $, €, ¥, £"); // Asserting different symbols
|
||||
|
|
@ -135,7 +135,7 @@ describe.skip(
|
|||
// Update query to fetch only 1 column and verify
|
||||
gsheetHelper.SelectMultiDropDownValue("Columns", "product_name");
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(0, GSHEET_DATA[0].product_name);
|
||||
|
||||
//Remove column filter and add Sort By Ascending and verify
|
||||
|
|
@ -145,7 +145,7 @@ describe.skip(
|
|||
directInput: false,
|
||||
inputFieldName: "Sort By",
|
||||
});
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"5afbaf65680c9f378af5b3a3ae22427e",
|
||||
|
|
@ -161,7 +161,7 @@ describe.skip(
|
|||
dataSources.ClearSortByOption(); //clearing previous sort option
|
||||
dataSources.EnterSortByValues("price", "Descending");
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
1,
|
||||
"ホーンビー ゲージ ウェスタン エクスプレス デジタル トレイン セット (eLink および TTS ロコ トレイン セット付き)",
|
||||
|
|
@ -181,7 +181,7 @@ describe.skip(
|
|||
dataSources._nestedWhereClauseValue(0),
|
||||
);
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(8);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 8 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"87bbb472ef9d90dcef140a551665c929",
|
||||
|
|
@ -199,7 +199,7 @@ describe.skip(
|
|||
inputFieldName: "Cell range",
|
||||
});
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(4);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 4 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"eac7efa5dbd3d667f26eb3d3ab504464",
|
||||
|
|
@ -246,7 +246,7 @@ describe.skip(
|
|||
0,
|
||||
true,
|
||||
);
|
||||
dataSources.RunQueryNVerifyResponseViews(10);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 10 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"eac7efa5dbd3d667f26eb3d3ab504464",
|
||||
|
|
@ -281,7 +281,7 @@ describe.skip(
|
|||
true,
|
||||
); // Converting the field to dropdown
|
||||
dataSources.ValidateNSelectDropdown("Sheet name", "", "Sheet1");
|
||||
dataSources.RunQueryNVerifyResponseViews(10);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 10 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"eac7efa5dbd3d667f26eb3d3ab504464",
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ describe.skip(
|
|||
dataSources.ValidateNSelectDropdown("Entity", "Sheet Row(s)");
|
||||
dataSources.ValidateNSelectDropdown("Spreadsheet", "", spreadSheetName);
|
||||
dataSources.ValidateNSelectDropdown("Sheet name", "", "Sheet1");
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryResponseHeaders(columnHeaders);
|
||||
dataSources.AssertQueryTableResponse(0, GSHEET_DATA[0].uniq_id);
|
||||
dataSources.AssertQueryTableResponse(1, "ホーンビィ 2014 カタログ"); // Asserting other language
|
||||
|
|
@ -104,7 +104,7 @@ describe.skip(
|
|||
dataSources.ValidateNSelectDropdown("Entity", "Sheet Row(s)");
|
||||
dataSources.ValidateNSelectDropdown("Spreadsheet", "", spreadSheetName);
|
||||
dataSources.ValidateNSelectDropdown("Sheet name", "", "Sheet1");
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryResponseHeaders(columnHeaders);
|
||||
dataSources.AssertQueryTableResponse(0, GSHEET_DATA[0].uniq_id);
|
||||
dataSources.AssertQueryTableResponse(1, "ホーンビィ 2014 カタログ"); // Asserting other language
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ describe.skip(
|
|||
dataSourceName.readNWrite,
|
||||
spreadSheetName,
|
||||
);
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(0, GSHEET_DATA[0].uniq_id);
|
||||
dataSources.AssertQueryTableResponse(1, "ホーンビィ 2014 カタログ"); // Asserting other language
|
||||
dataSources.AssertQueryTableResponse(2, "₹, $, €, ¥, £"); // Asserting different symbols
|
||||
|
|
@ -136,7 +136,7 @@ describe.skip(
|
|||
// Update query to fetch only 1 column and verify
|
||||
gsheetHelper.SelectMultiDropDownValue("Columns", "product_name");
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(0, GSHEET_DATA[0].product_name);
|
||||
//Remove column filter and add Sort By Ascending and verify
|
||||
gsheetHelper.SelectMultiDropDownValue("Columns", "product_name"); //unselect the Columns dd value
|
||||
|
|
@ -145,7 +145,7 @@ describe.skip(
|
|||
directInput: false,
|
||||
inputFieldName: "Sort By",
|
||||
});
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"5afbaf65680c9f378af5b3a3ae22427e",
|
||||
|
|
@ -160,7 +160,7 @@ describe.skip(
|
|||
dataSources.ClearSortByOption(); //clearing previous sort option
|
||||
dataSources.EnterSortByValues("price", "Descending");
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
1,
|
||||
"ホーンビー ゲージ ウェスタン エクスプレス デジタル トレイン セット (eLink および TTS ロコ トレイン セット付き)",
|
||||
|
|
@ -179,7 +179,7 @@ describe.skip(
|
|||
dataSources._nestedWhereClauseValue(0),
|
||||
);
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(8);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 8 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"87bbb472ef9d90dcef140a551665c929",
|
||||
|
|
@ -196,7 +196,7 @@ describe.skip(
|
|||
inputFieldName: "Cell range",
|
||||
});
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(4);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 4 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"eac7efa5dbd3d667f26eb3d3ab504464",
|
||||
|
|
@ -242,7 +242,7 @@ describe.skip(
|
|||
0,
|
||||
true,
|
||||
);
|
||||
dataSources.RunQueryNVerifyResponseViews(10);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 10 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"eac7efa5dbd3d667f26eb3d3ab504464",
|
||||
|
|
@ -275,7 +275,7 @@ describe.skip(
|
|||
true,
|
||||
); // Converting the field to dropdown
|
||||
dataSources.ValidateNSelectDropdown("Sheet name", "", "Sheet1");
|
||||
dataSources.RunQueryNVerifyResponseViews(10);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 10 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"eac7efa5dbd3d667f26eb3d3ab504464",
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ describe.skip(
|
|||
dataSourceName.readOnly,
|
||||
spreadSheetName,
|
||||
);
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(0, GSHEET_DATA[0].uniq_id);
|
||||
dataSources.AssertQueryTableResponse(1, "ホーンビィ 2014 カタログ"); // Asserting other language
|
||||
dataSources.AssertQueryTableResponse(2, "₹, $, €, ¥, £"); // Asserting different symbols
|
||||
|
|
@ -144,7 +144,7 @@ describe.skip(
|
|||
// Update query to fetch only 1 column and verify
|
||||
gsheetHelper.SelectMultiDropDownValue("Columns", "product_name");
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(0, GSHEET_DATA[0].product_name);
|
||||
//Remove column filter and add Sort By Ascending and verify
|
||||
gsheetHelper.SelectMultiDropDownValue("Columns", "product_name"); //unselect the Columns dd value
|
||||
|
|
@ -153,7 +153,7 @@ describe.skip(
|
|||
directInput: false,
|
||||
inputFieldName: "Sort By",
|
||||
});
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"5afbaf65680c9f378af5b3a3ae22427e",
|
||||
|
|
@ -168,7 +168,7 @@ describe.skip(
|
|||
dataSources.ClearSortByOption(); //clearing previous sort option
|
||||
dataSources.EnterSortByValues("price", "Descending");
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
1,
|
||||
"ホーンビー ゲージ ウェスタン エクスプレス デジタル トレイン セット (eLink および TTS ロコ トレイン セット付き)",
|
||||
|
|
@ -187,7 +187,7 @@ describe.skip(
|
|||
dataSources._nestedWhereClauseValue(0),
|
||||
);
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(8);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 8 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"87bbb472ef9d90dcef140a551665c929",
|
||||
|
|
@ -204,7 +204,7 @@ describe.skip(
|
|||
inputFieldName: "Cell range",
|
||||
});
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(4);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 4 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"eac7efa5dbd3d667f26eb3d3ab504464",
|
||||
|
|
@ -224,7 +224,7 @@ describe.skip(
|
|||
0,
|
||||
true,
|
||||
);
|
||||
dataSources.RunQueryNVerifyResponseViews(10);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 10 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"eac7efa5dbd3d667f26eb3d3ab504464",
|
||||
|
|
@ -257,7 +257,7 @@ describe.skip(
|
|||
true,
|
||||
); // Converting the field to dropdown
|
||||
dataSources.ValidateNSelectDropdown("Sheet name", "", "Sheet1");
|
||||
dataSources.RunQueryNVerifyResponseViews(10);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 10 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"eac7efa5dbd3d667f26eb3d3ab504464",
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ describe(
|
|||
dataSourceName,
|
||||
spreadSheetName,
|
||||
);
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(0, GSHEET_DATA[0].uniq_id);
|
||||
dataSources.AssertQueryTableResponse(1, "ホーンビィ 2014 カタログ"); // Asserting other language
|
||||
dataSources.AssertQueryTableResponse(2, "₹, $, €, ¥, £"); // Asserting different symbols
|
||||
|
|
@ -123,7 +123,7 @@ describe(
|
|||
// Update query to fetch only 1 column and verify
|
||||
gsheetHelper.SelectMultiDropDownValue("Columns", "product_name");
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(0, GSHEET_DATA[0].product_name);
|
||||
|
||||
//Remove column filter and add Sort By Ascending and verify
|
||||
|
|
@ -133,7 +133,7 @@ describe(
|
|||
directInput: false,
|
||||
inputFieldName: "Sort By",
|
||||
});
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"5afbaf65680c9f378af5b3a3ae22427e",
|
||||
|
|
@ -149,7 +149,7 @@ describe(
|
|||
dataSources.ClearSortByOption(); //clearing previous sort option
|
||||
dataSources.EnterSortByValues("price", "Descending");
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(GSHEET_DATA.length);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: GSHEET_DATA.length });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
1,
|
||||
"ホーンビー ゲージ ウェスタン エクスプレス デジタル トレイン セット (eLink および TTS ロコ トレイン セット付き)",
|
||||
|
|
@ -169,7 +169,7 @@ describe(
|
|||
dataSources._nestedWhereClauseValue(0),
|
||||
);
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(8);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 8 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"87bbb472ef9d90dcef140a551665c929",
|
||||
|
|
@ -187,7 +187,7 @@ describe(
|
|||
inputFieldName: "Cell range",
|
||||
});
|
||||
dataSources.RunQuery();
|
||||
dataSources.RunQueryNVerifyResponseViews(4);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 4 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"eac7efa5dbd3d667f26eb3d3ab504464",
|
||||
|
|
@ -237,7 +237,7 @@ describe(
|
|||
0,
|
||||
true,
|
||||
);
|
||||
dataSources.RunQueryNVerifyResponseViews(10);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 10 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"eac7efa5dbd3d667f26eb3d3ab504464",
|
||||
|
|
@ -272,7 +272,7 @@ describe(
|
|||
true,
|
||||
); // Converting the field to dropdown
|
||||
dataSources.ValidateNSelectDropdown("Sheet name", "", "Sheet1");
|
||||
dataSources.RunQueryNVerifyResponseViews(10);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 10 });
|
||||
dataSources.AssertQueryTableResponse(
|
||||
0,
|
||||
"eac7efa5dbd3d667f26eb3d3ab504464",
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ describe.skip(
|
|||
dataSourceName,
|
||||
spreadSheetName,
|
||||
);
|
||||
dataSources.RunQueryNVerifyResponseViews(10);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 10 });
|
||||
|
||||
// Adding suggested widgets and verify
|
||||
dataSources.AddSuggestedWidget(Widgets.Table);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ describe(
|
|||
dataSourceName,
|
||||
spreadSheetName,
|
||||
);
|
||||
dataSources.RunQueryNVerifyResponseViews(10);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 10 });
|
||||
|
||||
// Adding suggested widgets and verify
|
||||
dataSources.AddSuggestedWidget(Widgets.Table);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import EditorNavigation, {
|
|||
EntityType,
|
||||
} from "../../../../support/Pages/EditorNavigation";
|
||||
|
||||
import BottomTabs from "../../../../support/Pages/IDE/BottomTabs";
|
||||
|
||||
describe(
|
||||
"Test Create Api and Bind to List widget",
|
||||
{ tags: ["@tag.Binding"] },
|
||||
|
|
@ -24,7 +26,7 @@ describe(
|
|||
it("1. Test_Add users api and execute api", function () {
|
||||
apiPage.CreateAndFillApi(this.dataSet.userApi + "/mock-api?records=10");
|
||||
cy.RunAPI();
|
||||
cy.get(apiLocators.jsonResponseTab).click();
|
||||
BottomTabs.response.selectResponseResponseTypeFromMenu("JSON");
|
||||
cy.get(apiLocators.responseBody)
|
||||
.contains("name")
|
||||
.siblings("span")
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const apiwidget = require("../../../../locators/apiWidgetslocator.json");
|
|||
const testdata = require("../../../../fixtures/testdata.json");
|
||||
import apiLocators from "../../../../locators/ApiEditor";
|
||||
import * as _ from "../../../../support/Objects/ObjectsCore";
|
||||
import BottomTabs from "../../../../support/Pages/IDE/BottomTabs";
|
||||
|
||||
describe(
|
||||
"Bind a button and Api usecase",
|
||||
|
|
@ -28,7 +29,7 @@ describe(
|
|||
.click({ force: true })
|
||||
.type("{{Button1.text", { parseSpecialCharSequences: true });
|
||||
cy.RunAPI();
|
||||
cy.get(apiLocators.jsonResponseTab).click();
|
||||
BottomTabs.response.selectResponseResponseTypeFromMenu("JSON");
|
||||
cy.get(apiLocators.responseBody)
|
||||
.contains("name")
|
||||
.siblings("span")
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ describe(
|
|||
_.dataManager.dsValues[_.dataManager.defaultEnviorment].mockApiUrl,
|
||||
);
|
||||
_.apiPage.RunAPI();
|
||||
BottomTabs.response.switchResponseType("JSON");
|
||||
BottomTabs.response.selectResponseResponseTypeFromMenu("JSON");
|
||||
_.apiPage.ReadApiResponsebyKey("name");
|
||||
cy.get("@apiResp").then((value) => {
|
||||
valueToTest = value;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
agHelper,
|
||||
deployMode,
|
||||
} from "../../../../support/Objects/ObjectsCore";
|
||||
import BottomTabs from "../../../../support/Pages/IDE/BottomTabs";
|
||||
|
||||
describe(
|
||||
"Test Create Api and Bind to Table widget V2",
|
||||
|
|
@ -21,7 +22,7 @@ describe(
|
|||
it("1. Test_Add users api and execute api", function () {
|
||||
cy.createAndFillApi(this.dataSet.userApi, "/mock-api?records=100");
|
||||
cy.RunAPI();
|
||||
cy.get(apiPage.jsonResponseTab).click();
|
||||
BottomTabs.response.selectResponseResponseTypeFromMenu("JSON");
|
||||
cy.get(apiPage.responseBody)
|
||||
.contains("name")
|
||||
.siblings("span")
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
agHelper,
|
||||
deployMode,
|
||||
} from "../../../../support/Objects/ObjectsCore";
|
||||
import BottomTabs from "../../../../support/Pages/IDE/BottomTabs";
|
||||
|
||||
describe(
|
||||
"Test Create Api and Bind to Table widget",
|
||||
|
|
@ -22,7 +23,7 @@ describe(
|
|||
it("1. Test_Add users api and execute api", function () {
|
||||
apiPage.CreateAndFillApi(this.dataSet.userApi + "/mock-api?records=10");
|
||||
cy.RunAPI();
|
||||
cy.get(apiLocators.jsonResponseTab).click();
|
||||
BottomTabs.response.selectResponseResponseTypeFromMenu("JSON");
|
||||
cy.get(apiLocators.responseBody)
|
||||
.contains("name")
|
||||
.siblings("span")
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
|
||||
const testdata = require("../../../../fixtures/testdata.json");
|
||||
import { agHelper, apiPage } from "../../../../support/Objects/ObjectsCore";
|
||||
import apiEditor from "../../../../locators/ApiEditor";
|
||||
import BottomTabs from "../../../../support/Pages/IDE/BottomTabs";
|
||||
const testUrl1 =
|
||||
"http://host.docker.internal:5001/v1/dynamicrecords/getstudents";
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ describe(
|
|||
apiPage.CreateAndFillApi(testUrl1, "TableTestAPI");
|
||||
agHelper.AssertAutoSave();
|
||||
apiPage.RunAPI();
|
||||
cy.get(apiEditor.tableResponseTab).should("exist");
|
||||
BottomTabs.response.validateTypeInMenu("TABLE", "exist");
|
||||
cy.DeleteAPI();
|
||||
});
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ describe(
|
|||
);
|
||||
agHelper.AssertAutoSave();
|
||||
apiPage.RunAPI();
|
||||
cy.get(apiEditor.tableResponseTab).should("not.exist");
|
||||
BottomTabs.response.validateTypeInMenu("TABLE", "not.exist");
|
||||
cy.DeleteAPI();
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import {
|
|||
PageLeftPane,
|
||||
} from "../../../../support/Pages/EditorNavigation";
|
||||
|
||||
import BottomTabs from "../../../../support/Pages/IDE/BottomTabs";
|
||||
|
||||
const testUrl1 =
|
||||
"http://host.docker.internal:5001/v1/dynamicrecords/generaterecords?records=10";
|
||||
const testUrl2 =
|
||||
|
|
@ -52,11 +54,11 @@ describe(
|
|||
cy.RunAPI();
|
||||
apiPage.CreateAndFillApi(testUrl2);
|
||||
cy.RunAPI();
|
||||
cy.get(ApiEditor.jsonResponseTab).click();
|
||||
BottomTabs.response.selectResponseResponseTypeFromMenu("JSON");
|
||||
dataSources.AssertBindDataVisible();
|
||||
cy.get(ApiEditor.rawResponseTab).click();
|
||||
BottomTabs.response.selectResponseResponseTypeFromMenu("RAW");
|
||||
dataSources.AssertBindDataVisible();
|
||||
cy.get(ApiEditor.tableResponseTab).click();
|
||||
BottomTabs.response.selectResponseResponseTypeFromMenu("TABLE");
|
||||
dataSources.AssertBindDataVisible();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ describe(
|
|||
);
|
||||
dataSources.RunQuery();
|
||||
dataSources.EnterQuery(selectQuery);
|
||||
dataSources.RunQueryNVerifyResponseViews();
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 1, operator: "gte" });
|
||||
dataSources.ToggleUsePreparedStatement(true);
|
||||
query = `ALTER TABLE ${guid} ADD (raw_data RAW(16), maintenance_interval INTERVAL YEAR(3) TO MONTH);`;
|
||||
dataSources.EnterQuery(query);
|
||||
|
|
@ -272,7 +272,7 @@ describe(
|
|||
dataSources.EnterQuery(query);
|
||||
dataSources.RunQuery();
|
||||
dataSources.EnterQuery(selectQuery);
|
||||
dataSources.RunQueryNVerifyResponseViews(2);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 2 });
|
||||
query = `INSERT ALL
|
||||
INTO ${guid} (
|
||||
aircraft_id,
|
||||
|
|
@ -334,10 +334,10 @@ describe(
|
|||
dataSources.EnterQuery(query);
|
||||
dataSources.RunQuery();
|
||||
dataSources.EnterQuery(selectQuery);
|
||||
dataSources.RunQueryNVerifyResponseViews(4);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 4 });
|
||||
selectQuery = selectQuery + ` and aircraft_id IN (1, 6)`;
|
||||
dataSources.EnterQuery(selectQuery);
|
||||
dataSources.RunQueryNVerifyResponseViews(2);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 2 });
|
||||
dataSources.AddSuggestedWidget(Widgets.Table);
|
||||
deployMode.DeployApp(locators._widgetInDeployed(draggableWidgets.TABLE));
|
||||
table.WaitUntilTableLoad(0, 0, "v2");
|
||||
|
|
@ -375,7 +375,7 @@ WHERE aircraft_type = 'Passenger Plane'`;
|
|||
dataSources.RunQuery();
|
||||
selectQuery = selectQuery + ` or aircraft_type = 'Passenger Plane'`;
|
||||
dataSources.EnterQuery(selectQuery);
|
||||
dataSources.RunQueryNVerifyResponseViews(3);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 3 });
|
||||
dataSources.AddSuggestedWidget(
|
||||
Widgets.Table,
|
||||
dataSources._addSuggestedExisting,
|
||||
|
|
@ -404,7 +404,7 @@ WHERE aircraft_type = 'Passenger Plane'`;
|
|||
dataSources.RunQuery();
|
||||
selectQuery = `SELECT * FROM ${guid}`;
|
||||
dataSources.EnterQuery(selectQuery);
|
||||
dataSources.RunQueryNVerifyResponseViews(2);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 2 });
|
||||
dataSources.AddSuggestedWidget(
|
||||
Widgets.Table,
|
||||
dataSources._addSuggestedExisting,
|
||||
|
|
@ -439,7 +439,7 @@ WHERE aircraft_type = 'Passenger Plane'`;
|
|||
toastToValidate: "copied to page",
|
||||
});
|
||||
agHelper.GetNAssertContains(locators._queryName, "Query1Copy");
|
||||
dataSources.RunQueryNVerifyResponseViews(2);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 2 });
|
||||
PageList.AddNewPage();
|
||||
EditorNavigation.SelectEntityByName("Page1", EntityType.Page);
|
||||
agHelper.ActionContextMenuWithInPane({
|
||||
|
|
@ -449,7 +449,7 @@ WHERE aircraft_type = 'Passenger Plane'`;
|
|||
});
|
||||
agHelper.WaitUntilAllToastsDisappear();
|
||||
agHelper.GetNAssertContains(locators._queryName, "Query1Copy");
|
||||
dataSources.RunQueryNVerifyResponseViews(2);
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 2 });
|
||||
agHelper.ActionContextMenuWithInPane({
|
||||
action: "Delete",
|
||||
entityType: entityItems.Query,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ describe(
|
|||
);
|
||||
|
||||
apiPage.RunAPI();
|
||||
BottomTabs.response.switchResponseType("JSON");
|
||||
BottomTabs.response.selectResponseResponseTypeFromMenu("JSON");
|
||||
|
||||
apiPage.CreateAndFillApi(
|
||||
"http://host.docker.internal:5001/v1/favqs/qotd",
|
||||
|
|
@ -65,7 +65,7 @@ describe(
|
|||
);
|
||||
apiPage.EnterHeader("dependency", "{{RandomUser.data}}"); //via Params tab
|
||||
apiPage.RunAPI();
|
||||
BottomTabs.response.switchResponseType("JSON");
|
||||
BottomTabs.response.selectResponseResponseTypeFromMenu("JSON");
|
||||
|
||||
apiPage.CreateAndFillApi(
|
||||
"http://host.docker.internal:5001/v1/boredapi/activity",
|
||||
|
|
@ -74,7 +74,7 @@ describe(
|
|||
);
|
||||
apiPage.EnterHeader("dependency", "{{InspiringQuotes.data.data}}");
|
||||
apiPage.RunAPI();
|
||||
BottomTabs.response.switchResponseType("JSON");
|
||||
BottomTabs.response.selectResponseResponseTypeFromMenu("JSON");
|
||||
|
||||
apiPage.CreateAndFillApi(
|
||||
"http://host.docker.internal:5001/v1/genderize/sampledata",
|
||||
|
|
@ -83,7 +83,7 @@ describe(
|
|||
);
|
||||
apiPage.EnterParams("name", "{{RandomUser.data[0].name}}"); //via Params tab
|
||||
apiPage.RunAPI();
|
||||
BottomTabs.response.switchResponseType("JSON");
|
||||
BottomTabs.response.selectResponseResponseTypeFromMenu("JSON");
|
||||
|
||||
//Adding dependency in right order matters!
|
||||
EditorNavigation.SelectEntityByName("Image1", EntityType.Widget);
|
||||
|
|
@ -163,7 +163,7 @@ describe(
|
|||
value: "{{RandomUser.data[0].name}}",
|
||||
}); // verifies Bug 10055
|
||||
apiPage.RunAPI();
|
||||
BottomTabs.response.switchResponseType("JSON");
|
||||
BottomTabs.response.selectResponseResponseTypeFromMenu("JSON");
|
||||
|
||||
deployMode.DeployApp(
|
||||
locators._widgetInDeployed("textwidget"),
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ describe(
|
|||
dataSources.CreateQueryAfterDSSaved("select * from users limit 10");
|
||||
dataSources.RunQuery({ toValidateResponse: false });
|
||||
cy.wait(500);
|
||||
cy.get("[data-testid=t--query-error]").contains(
|
||||
cy.get(dataSources._queryError).contains(
|
||||
"[Missing username for authentication., Missing hostname., Missing password for authentication.]",
|
||||
);
|
||||
agHelper.ActionContextMenuWithInPane({
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ describe(
|
|||
it("2. Run a Select query & Add Suggested widget - Table", () => {
|
||||
query = `Select * from Simpsons;`;
|
||||
dataSources.CreateQueryFromOverlay(dsName, query, "selectSimpsons"); //Creating query from EE overlay
|
||||
dataSources.RunQueryNVerifyResponseViews(10); //Could be 99 in CI, to check aft init load script is working
|
||||
dataSources.runQueryAndVerifyResponseViews({ count: 10 }); //Could be 99 in CI, to check aft init load script is working
|
||||
|
||||
dataSources.AddSuggestedWidget(Widgets.Table);
|
||||
agHelper.GetNClick(propPane._deleteWidget);
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@ export default {
|
|||
bodyTypeSelected: "[data-testid=\"t--api-body-tab-switch\"] .rc-select-selection-item",
|
||||
bodyTab: "Body",
|
||||
headersTab: "Header",
|
||||
jsonResponseTab: "[data-value='JSON']",
|
||||
tableResponseTab: "[data-value='TABLE']",
|
||||
rawResponseTab: "[data-value='RAW']",
|
||||
httpDropDownOptions: ".rc-select-item",
|
||||
codeEditorWrapper: ".t--code-editor-wrapper",
|
||||
apiSearchHint: ".datasource-hint",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
"queryKey": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.key\\.0",
|
||||
"queryValue": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.value\\.0",
|
||||
"formEncoded": ".t--actionConfiguration\\.bodyFormData\\[0\\]\\.key\\.0",
|
||||
"responseStatus": "//div[@id='root']",
|
||||
"responseText": ".CodeMirror-line > [role='presentation']",
|
||||
"createApiOnSideBar": "button:contains('Create new API')",
|
||||
"saveButton": "button:contains('Save')",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const apiwidget = require("../locators/apiWidgetslocator.json");
|
|||
const explorer = require("../locators/explorerlocators.json");
|
||||
import { ObjectsRegistry } from "./Objects/Registry";
|
||||
import { PluginActionForm } from "./Pages/PluginActionForm";
|
||||
import BottomTabs from "./Pages/IDE/BottomTabs";
|
||||
|
||||
let agHelper = ObjectsRegistry.AggregateHelper;
|
||||
let dataSources = ObjectsRegistry.DataSources;
|
||||
|
|
@ -38,8 +39,7 @@ Cypress.Commands.add("enterDatasource", (datasource) => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add("ResponseStatusCheck", (statusCode) => {
|
||||
cy.xpath(apiwidget.responseStatus).should("be.visible");
|
||||
cy.xpath(apiwidget.responseStatus).contains(statusCode);
|
||||
BottomTabs.response.validateResponseStatus(statusCode);
|
||||
});
|
||||
|
||||
Cypress.Commands.add("ResponseCheck", () => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
import * as _ from "../Objects/ObjectsCore";
|
||||
import ApiEditor from "../../locators/ApiEditor";
|
||||
import { PluginActionForm } from "./PluginActionForm";
|
||||
import BottomTabs from "./IDE/BottomTabs";
|
||||
|
||||
type RightPaneTabs = "datasources" | "connections";
|
||||
|
||||
|
|
@ -82,7 +83,7 @@ export class ApiPage {
|
|||
"input[name='confirmBeforeExecute'][type='checkbox']";
|
||||
private _paginationTypeLabels = ".t--apiFormPaginationType label";
|
||||
_saveAsDS = ".t--store-as-datasource";
|
||||
_responseStatus = ".t--response-status-code";
|
||||
|
||||
public _responseTabHeader = "[data-testid=t--tab-HEADERS_TAB]";
|
||||
public _headersTabContent = ".t--headers-tab";
|
||||
public _autoGeneratedHeaderInfoIcon = (key: string) =>
|
||||
|
|
@ -438,8 +439,7 @@ export class ApiPage {
|
|||
}
|
||||
|
||||
ResponseStatusCheck(statusCode: string) {
|
||||
this.agHelper.AssertElementVisibility(this._responseStatus);
|
||||
this.agHelper.GetNAssertContains(this._responseStatus, statusCode);
|
||||
BottomTabs.response.validateResponseStatus(statusCode);
|
||||
}
|
||||
|
||||
public SelectPaginationTypeViaIndex(index: number) {
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ export class DataSources {
|
|||
"']";
|
||||
_refreshIcon = "button .bp3-icon-refresh";
|
||||
_addIcon = "button .bp3-icon-add";
|
||||
_queryError = "[data-testid='t--query-error']";
|
||||
_queryError = "[data-testid='t--response-error']";
|
||||
_queryEditorTabs = (responseType: string) =>
|
||||
"//button[@role='tab' or @role='tablist']//span[text()='" +
|
||||
responseType +
|
||||
|
|
@ -1142,25 +1142,6 @@ export class DataSources {
|
|||
this.assertHelper.AssertNetworkStatus("@saveAction", 200);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
public RunQueryNVerifyResponseViews(
|
||||
expectedRecordsCount = 1,
|
||||
tableCheck = true,
|
||||
) {
|
||||
this.RunQuery();
|
||||
if (tableCheck) {
|
||||
this.agHelper.AssertElementVisibility(
|
||||
BottomTabs.response.getResponseTypeSelector("TABLE"),
|
||||
);
|
||||
this.agHelper.AssertElementVisibility(
|
||||
BottomTabs.response.getResponseTypeSelector("JSON"),
|
||||
);
|
||||
this.agHelper.AssertElementVisibility(
|
||||
BottomTabs.response.getResponseTypeSelector("RAW"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public runQueryAndVerifyResponseViews({
|
||||
count = 1,
|
||||
operator = "eq",
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export class DebuggerHelper {
|
|||
_logsGroup: "[data-testid='t--log-filter']",
|
||||
_logGroupOption: (option: string) =>
|
||||
`[data-testid='t--log-filter-${option}']`,
|
||||
_downStreamLogMessage: ".t--debugger-log-downstream-message",
|
||||
_downStreamLogMessage: "[data-testid='t--response-error']",
|
||||
};
|
||||
|
||||
OpenDebugger() {
|
||||
|
|
|
|||
|
|
@ -8,29 +8,17 @@ interface ValidationParams {
|
|||
class Response {
|
||||
public locators = {
|
||||
responseTab: "[data-testid='t--tab-RESPONSE_TAB']",
|
||||
responseDataContainer: "[data-testid='t--query-response-data-container']",
|
||||
responseTypeMenuTrigger: "[data-testid='t--query-response-type-trigger']",
|
||||
responseRecordCount: "[data-testid='t--query-response-record-count']",
|
||||
|
||||
/** @deprecated */
|
||||
responseType(type: string): string {
|
||||
return `//div[@data-testid='t--response-tab-segmented-control']//span[text()='${type}']`;
|
||||
},
|
||||
responseDataContainer: "[data-testid='t--response-data-container']",
|
||||
responseTypeMenuTrigger: "[data-testid='t--response-type-trigger']",
|
||||
responseRecordCount: "[data-testid='t--response-record-count']",
|
||||
responseStatusInfo: "[data-testid='t--response-status-info']",
|
||||
responseStatusInfoTooltip: "#t--response-tooltip",
|
||||
|
||||
responseTypeMenuItem(type: string) {
|
||||
return `[data-testid="t--query-response-type-menu-item"][data-value="${type}"]`;
|
||||
return `[data-testid="t--response-type-menu-item"][data-value="${type}"]`;
|
||||
},
|
||||
};
|
||||
|
||||
/** @deprecated: method will be deleted when segmented control in response pane is replaced */
|
||||
public getResponseTypeSelector = this.locators.responseType;
|
||||
|
||||
/** @deprecated: method will be deleted when segmented control in response pane is replaced */
|
||||
public switchResponseType(type: string): void {
|
||||
this.switchToResponseTab();
|
||||
cy.xpath(this.locators.responseType(type)).click({ force: true });
|
||||
}
|
||||
|
||||
public switchToResponseTab(): void {
|
||||
cy.get(this.locators.responseTab).click({ force: true });
|
||||
}
|
||||
|
|
@ -50,6 +38,21 @@ class Response {
|
|||
cy.get(this.locators.responseTypeMenuTrigger).realClick();
|
||||
}
|
||||
|
||||
public validateTypeInMenu(type: string, assertion: string): void {
|
||||
this.switchToResponseTab();
|
||||
this.openResponseTypeMenu();
|
||||
cy.get(this.locators.responseTypeMenuItem(type)).should(assertion);
|
||||
this.closeResponseTypeMenu();
|
||||
}
|
||||
|
||||
public validateResponseStatus(status: string): void {
|
||||
cy.get(this.locators.responseStatusInfo).trigger("mouseover");
|
||||
cy.get(this.locators.responseStatusInfoTooltip).should(
|
||||
"include.text",
|
||||
status,
|
||||
);
|
||||
}
|
||||
|
||||
public validateRecordCount({
|
||||
count,
|
||||
operator = "eq",
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@
|
|||
"object-hash": "^3.0.0",
|
||||
"openai": "^4.64.0",
|
||||
"path-to-regexp": "^6.3.0",
|
||||
"pluralize": "^8.0.0",
|
||||
"popper.js": "^1.15.0",
|
||||
"prismjs": "^1.27.0",
|
||||
"proxy-memoize": "^1.2.0",
|
||||
|
|
@ -271,6 +272,7 @@
|
|||
"@types/node-fetch": "^2.6.11",
|
||||
"@types/node-forge": "^0.10.0",
|
||||
"@types/object-hash": "^2.2.1",
|
||||
"@types/pluralize": "^0.0.33",
|
||||
"@types/prismjs": "^1.16.1",
|
||||
"@types/react": "^17.0.2",
|
||||
"@types/react-custom-scrollbars": "^4.0.7",
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
import React, { useMemo } from "react";
|
||||
import ReactJson from "react-json-view";
|
||||
import { isEmpty, noop } from "lodash";
|
||||
import styled from "styled-components";
|
||||
import { Callout, Flex } from "@appsmith/ads";
|
||||
import {
|
||||
JsonWrapper,
|
||||
reactJsonProps,
|
||||
} from "components/editorComponents/Debugger/ErrorLogs/components/LogCollapseData";
|
||||
import type { ActionResponse } from "api/ActionAPI";
|
||||
import type { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
import type { SourceEntity } from "entities/AppsmithConsole";
|
||||
import ApiResponseMeta from "./ApiResponseMeta";
|
||||
import ActionExecutionInProgressView from "components/editorComponents/ActionExecutionInProgressView";
|
||||
import LogAdditionalInfo from "components/editorComponents/Debugger/ErrorLogs/components/LogAdditionalInfo";
|
||||
import LogHelper from "components/editorComponents/Debugger/ErrorLogs/components/LogHelper";
|
||||
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
||||
import { type Action } from "entities/Action";
|
||||
import { hasFailed } from "../utils";
|
||||
import { getUpdateTimestamp } from "components/editorComponents/Debugger/ErrorLogs/ErrorLogItem";
|
||||
import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils";
|
||||
import ApiFormatSegmentedResponse from "./ApiFormatSegmentedResponse";
|
||||
import { NoResponse } from "./NoResponse";
|
||||
|
||||
const HelpSection = styled.div`
|
||||
padding-bottom: 5px;
|
||||
padding-top: 10px;
|
||||
`;
|
||||
|
||||
const ResponseDataContainer = styled.div`
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& .CodeEditorTarget {
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ResponseTabErrorContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 16px;
|
||||
gap: 8px;
|
||||
height: fit-content;
|
||||
background: var(--ads-v2-color-bg-error);
|
||||
border-bottom: 1px solid var(--ads-v2-color-border);
|
||||
`;
|
||||
|
||||
export const ResponseTabErrorContent = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
`;
|
||||
|
||||
export const ResponseTabErrorDefaultMessage = styled.div`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
export const apiReactJsonProps = { ...reactJsonProps, collapsed: 0 };
|
||||
|
||||
export function ApiResponse(props: {
|
||||
action: Action;
|
||||
actionResponse?: ActionResponse;
|
||||
isRunning: boolean;
|
||||
isRunDisabled: boolean;
|
||||
theme: EditorTheme;
|
||||
onRunClick: () => void;
|
||||
responseTabHeight: number;
|
||||
}) {
|
||||
const { id, name } = props.action;
|
||||
const actionSource: SourceEntity = useMemo(
|
||||
() => ({
|
||||
type: ENTITY_TYPE.ACTION,
|
||||
name,
|
||||
id,
|
||||
}),
|
||||
[name, id],
|
||||
);
|
||||
|
||||
if (props.isRunning) {
|
||||
return (
|
||||
<Flex h="100%" w="100%">
|
||||
<ActionExecutionInProgressView actionType="API" theme={props.theme} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
if (!props.actionResponse) {
|
||||
return (
|
||||
<Flex h="100%" w="100%">
|
||||
<NoResponse
|
||||
isRunDisabled={props.isRunDisabled}
|
||||
isRunning={props.isRunning}
|
||||
onRunClick={props.onRunClick}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
const { messages, pluginErrorDetails, request } = props.actionResponse;
|
||||
|
||||
const runHasFailed = hasFailed(props.actionResponse);
|
||||
const requestWithTimestamp = getUpdateTimestamp(request);
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column" h="100%" w="100%">
|
||||
<ApiResponseMeta
|
||||
actionName={name}
|
||||
actionResponse={props.actionResponse}
|
||||
/>
|
||||
{Array.isArray(messages) && messages.length > 0 && (
|
||||
<HelpSection>
|
||||
{messages.map((message, i) => (
|
||||
<Callout key={i} kind="warning">
|
||||
{message}
|
||||
</Callout>
|
||||
))}
|
||||
</HelpSection>
|
||||
)}
|
||||
{runHasFailed && !props.isRunning ? (
|
||||
<ResponseTabErrorContainer>
|
||||
<ResponseTabErrorContent>
|
||||
<ResponseTabErrorDefaultMessage>
|
||||
Your API failed to execute
|
||||
{pluginErrorDetails && ":"}
|
||||
</ResponseTabErrorDefaultMessage>
|
||||
{pluginErrorDetails && (
|
||||
<>
|
||||
<div className="t--debugger-log-downstream-message">
|
||||
{pluginErrorDetails.downstreamErrorMessage}
|
||||
</div>
|
||||
{pluginErrorDetails.downstreamErrorCode && (
|
||||
<LogAdditionalInfo
|
||||
text={pluginErrorDetails.downstreamErrorCode}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<LogHelper
|
||||
logType={LOG_TYPE.ACTION_EXECUTION_ERROR}
|
||||
name="PluginExecutionError"
|
||||
pluginErrorDetails={pluginErrorDetails}
|
||||
source={actionSource}
|
||||
/>
|
||||
</ResponseTabErrorContent>
|
||||
{requestWithTimestamp && (
|
||||
<JsonWrapper className="t--debugger-log-state" onClick={noop}>
|
||||
<ReactJson src={requestWithTimestamp} {...apiReactJsonProps} />
|
||||
</JsonWrapper>
|
||||
)}
|
||||
</ResponseTabErrorContainer>
|
||||
) : (
|
||||
<ResponseDataContainer>
|
||||
{isEmpty(props.actionResponse.statusCode) ? (
|
||||
<NoResponse
|
||||
isRunDisabled={props.isRunDisabled}
|
||||
isRunning={props.isRunning}
|
||||
onRunClick={props.onRunClick}
|
||||
/>
|
||||
) : (
|
||||
<ApiFormatSegmentedResponse
|
||||
actionId={id}
|
||||
actionResponse={props.actionResponse}
|
||||
responseTabHeight={props.responseTabHeight}
|
||||
/>
|
||||
)}
|
||||
</ResponseDataContainer>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,480 +0,0 @@
|
|||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import ReactJson from "react-json-view";
|
||||
import {
|
||||
apiReactJsonProps,
|
||||
ResponseTabErrorContainer,
|
||||
ResponseTabErrorContent,
|
||||
ResponseTabErrorDefaultMessage,
|
||||
} from "../ApiResponse";
|
||||
import { ResponseFormatTabs } from "../ResponseFormatTabs";
|
||||
import { NoResponse } from "../NoResponse";
|
||||
import LogAdditionalInfo from "components/editorComponents/Debugger/ErrorLogs/components/LogAdditionalInfo";
|
||||
import LogHelper from "components/editorComponents/Debugger/ErrorLogs/components/LogHelper";
|
||||
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
||||
import { JsonWrapper } from "components/editorComponents/Debugger/ErrorLogs/components/LogCollapseData";
|
||||
import {
|
||||
Callout,
|
||||
Menu,
|
||||
MenuContent,
|
||||
MenuGroup,
|
||||
MenuGroupName,
|
||||
MenuItem,
|
||||
MenuTrigger,
|
||||
Text,
|
||||
Tooltip,
|
||||
type CalloutLinkProps,
|
||||
} from "@appsmith/ads";
|
||||
|
||||
import { DEBUGGER_TAB_KEYS } from "components/editorComponents/Debugger/constants";
|
||||
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
|
||||
import { setActionResponseDisplayFormat } from "actions/pluginActionActions";
|
||||
import { getUpdateTimestamp } from "components/editorComponents/Debugger/ErrorLogs/ErrorLogItem";
|
||||
import type { SourceEntity } from "entities/AppsmithConsole";
|
||||
import type { Action } from "entities/Action";
|
||||
import { getActionData } from "ee/selectors/entitiesSelector";
|
||||
import { actionResponseDisplayDataFormats } from "pages/Editor/utils";
|
||||
import { getErrorAsString } from "sagas/ActionExecution/errorUtils";
|
||||
import { isString } from "lodash";
|
||||
import ActionExecutionInProgressView from "components/editorComponents/ActionExecutionInProgressView";
|
||||
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
import BindDataButton from "../BindDataButton";
|
||||
import {
|
||||
getPluginActionDebuggerState,
|
||||
openPluginActionSettings,
|
||||
setPluginActionEditorSelectedTab,
|
||||
} from "../../../../store";
|
||||
import {
|
||||
createMessage,
|
||||
PREPARED_STATEMENT_WARNING,
|
||||
} from "ee/constants/messages";
|
||||
import { EDITOR_TABS } from "constants/QueryEditorConstants";
|
||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||
|
||||
import * as Styled from "./styles";
|
||||
import { useBoolean, useEventCallback } from "usehooks-ts";
|
||||
import { RESPONSE_TABLE_HEIGHT_OFFSET } from "./constants";
|
||||
import { scrollbarWidth } from "utils/helpers";
|
||||
|
||||
interface Props {
|
||||
actionSource: SourceEntity;
|
||||
isRunDisabled?: boolean;
|
||||
isRunning: boolean;
|
||||
onRunClick: () => void;
|
||||
currentActionConfig: Action;
|
||||
runErrorMessage?: string;
|
||||
actionName: string;
|
||||
}
|
||||
|
||||
export const QueryResponseTab = (props: Props) => {
|
||||
const {
|
||||
actionName,
|
||||
actionSource,
|
||||
currentActionConfig,
|
||||
isRunDisabled = false,
|
||||
isRunning,
|
||||
onRunClick,
|
||||
runErrorMessage,
|
||||
} = props;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { toggle: toggleContentTypeMenuOpen, value: isContentTypeMenuOpen } =
|
||||
useBoolean(false);
|
||||
|
||||
const {
|
||||
setFalse: setIsNotHovered,
|
||||
setTrue: setIsHovered,
|
||||
value: isDataContainerHovered,
|
||||
} = useBoolean(false);
|
||||
|
||||
const isContentTypeSelectorVisible =
|
||||
isDataContainerHovered || isContentTypeMenuOpen;
|
||||
|
||||
const isActionRedesignEnabled = useFeatureFlag(
|
||||
FEATURE_FLAG.release_actions_redesign_enabled,
|
||||
);
|
||||
|
||||
const actionResponse = useSelector((state) =>
|
||||
getActionData(state, currentActionConfig.id),
|
||||
);
|
||||
const { responseTabHeight } = useSelector(getPluginActionDebuggerState);
|
||||
|
||||
const { responseDataTypes, responseDisplayFormat } =
|
||||
actionResponseDisplayDataFormats(actionResponse);
|
||||
|
||||
const scrollbarOffset = scrollbarWidth();
|
||||
|
||||
let output: Record<string, unknown>[] | string = "";
|
||||
let errorMessage = runErrorMessage;
|
||||
let hintMessages: Array<string> = [];
|
||||
let showPreparedStatementWarning = false;
|
||||
|
||||
// Query is executed even once during the session, show the response data.
|
||||
if (actionResponse) {
|
||||
if (!actionResponse.isExecutionSuccess) {
|
||||
// Pass the error to be shown in the error tab
|
||||
errorMessage = actionResponse.readableError
|
||||
? getErrorAsString(actionResponse.readableError)
|
||||
: getErrorAsString(actionResponse.body);
|
||||
} else if (isString(actionResponse.body)) {
|
||||
//reset error.
|
||||
errorMessage = "";
|
||||
try {
|
||||
// Try to parse response as JSON array to be displayed in the Response tab
|
||||
output = JSON.parse(actionResponse.body);
|
||||
} catch (e) {
|
||||
// In case the string is not a JSON, wrap it in a response object
|
||||
output = [
|
||||
{
|
||||
response: actionResponse.body,
|
||||
},
|
||||
];
|
||||
}
|
||||
} else {
|
||||
//reset error.
|
||||
errorMessage = "";
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
output = actionResponse.body as any;
|
||||
}
|
||||
|
||||
if (actionResponse.messages && actionResponse.messages.length) {
|
||||
//reset error.
|
||||
errorMessage = "";
|
||||
hintMessages = actionResponse.messages;
|
||||
}
|
||||
|
||||
const { actionConfiguration } = currentActionConfig;
|
||||
const hasPluginSpecifiedTemplates =
|
||||
actionConfiguration?.pluginSpecifiedTemplates?.[0]?.value === true;
|
||||
// oracle have different key for prepared statements
|
||||
const hasPreparedStatement =
|
||||
actionConfiguration?.formData?.preparedStatement?.data === true;
|
||||
|
||||
if (errorMessage && (hasPluginSpecifiedTemplates || hasPreparedStatement)) {
|
||||
showPreparedStatementWarning = true;
|
||||
}
|
||||
}
|
||||
|
||||
const recordCount = output?.length ?? 1;
|
||||
|
||||
const responseBodyTabs =
|
||||
responseDataTypes &&
|
||||
responseDataTypes.map((dataType, index) => {
|
||||
return {
|
||||
index: index,
|
||||
key: dataType.key,
|
||||
title: dataType.title,
|
||||
panelComponent: (
|
||||
<ResponseFormatTabs
|
||||
data={output}
|
||||
responseType={dataType.key}
|
||||
tableBodyHeight={responseTabHeight}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const contentTypeOptions =
|
||||
responseBodyTabs &&
|
||||
responseBodyTabs.map((item) => {
|
||||
return { value: item.key, label: item.title };
|
||||
});
|
||||
|
||||
const [firstContentTypeOption] = contentTypeOptions;
|
||||
const [selectedContentType, setSelectedContentType] = useState(
|
||||
firstContentTypeOption?.value,
|
||||
);
|
||||
|
||||
const currentContentType =
|
||||
selectedContentType || firstContentTypeOption?.value;
|
||||
|
||||
const responseState =
|
||||
actionResponse && getUpdateTimestamp(actionResponse.request);
|
||||
|
||||
const selectedTabIndex =
|
||||
responseDataTypes &&
|
||||
responseDataTypes.findIndex(
|
||||
(dataType) => dataType.title === responseDisplayFormat?.title,
|
||||
);
|
||||
|
||||
const onResponseTabSelect = (tabKey: string) => {
|
||||
if (tabKey === DEBUGGER_TAB_KEYS.ERROR_TAB) {
|
||||
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
||||
source: "QUERY_PANE",
|
||||
});
|
||||
}
|
||||
|
||||
dispatch(
|
||||
setActionResponseDisplayFormat({
|
||||
id: currentActionConfig?.id || "",
|
||||
field: "responseDisplayFormat",
|
||||
value: tabKey,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const handleRunClick = useEventCallback(() => {
|
||||
onRunClick();
|
||||
|
||||
AnalyticsUtil.logEvent("RESPONSE_TAB_RUN_ACTION_CLICK", {
|
||||
source: "QUERY_PANE",
|
||||
});
|
||||
});
|
||||
|
||||
const navigateToSettings = useCallback(() => {
|
||||
if (isActionRedesignEnabled) {
|
||||
dispatch(openPluginActionSettings(true));
|
||||
} else {
|
||||
dispatch(setPluginActionEditorSelectedTab(EDITOR_TABS.SETTINGS));
|
||||
}
|
||||
}, [dispatch, isActionRedesignEnabled]);
|
||||
|
||||
const preparedStatementCalloutLinks: CalloutLinkProps[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
onClick: navigateToSettings,
|
||||
children: createMessage(PREPARED_STATEMENT_WARNING.LINK),
|
||||
},
|
||||
],
|
||||
[navigateToSettings],
|
||||
);
|
||||
|
||||
const queryTooltipContent = useMemo(() => {
|
||||
if (actionResponse) {
|
||||
const messages = [
|
||||
[
|
||||
"duration",
|
||||
"Time to run",
|
||||
`${(Number(actionResponse.duration) / 1000).toFixed(1)}s`,
|
||||
],
|
||||
];
|
||||
|
||||
if (actionResponse.size) {
|
||||
messages.push([
|
||||
"size",
|
||||
"Response size",
|
||||
`${(Number(actionResponse.size) / 1000).toFixed(1)}kb`,
|
||||
]);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{messages.map(([key, title, message]) => (
|
||||
<div key={key}>{`${title}: ${message}`}</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [actionResponse]);
|
||||
|
||||
const handleContentTypeChange = useEventCallback((e?: Event) => {
|
||||
if (e?.target && e.target instanceof HTMLElement) {
|
||||
const { value } = e.target.dataset;
|
||||
|
||||
if (typeof value === "string") {
|
||||
setSelectedContentType(value);
|
||||
onResponseTabSelect(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const handleJsonWrapperClick = useEventCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement>) => e.stopPropagation(),
|
||||
);
|
||||
|
||||
if (isRunning) {
|
||||
return (
|
||||
<Styled.LoadingContainer>
|
||||
<ActionExecutionInProgressView
|
||||
actionType="query"
|
||||
theme={EditorTheme.LIGHT}
|
||||
/>
|
||||
</Styled.LoadingContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (!output && !errorMessage) {
|
||||
return (
|
||||
<NoResponse
|
||||
isRunDisabled={isRunDisabled}
|
||||
isRunning={isRunning}
|
||||
onRunClick={handleRunClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Styled.Root>
|
||||
{errorMessage && (
|
||||
<div>
|
||||
<Styled.StatusBar>
|
||||
<Styled.StatusBarInfo>
|
||||
<Styled.StatusBarText $isBold kind="code">
|
||||
{`${actionName}.run():`}
|
||||
</Styled.StatusBarText>
|
||||
<Styled.StatusBarText $isError kind="code">
|
||||
Error
|
||||
</Styled.StatusBarText>
|
||||
</Styled.StatusBarInfo>
|
||||
</Styled.StatusBar>
|
||||
<ResponseTabErrorContainer>
|
||||
<ResponseTabErrorContent>
|
||||
<ResponseTabErrorDefaultMessage>
|
||||
Your query failed to execute
|
||||
{actionResponse &&
|
||||
(actionResponse.pluginErrorDetails || actionResponse.body) &&
|
||||
":"}
|
||||
</ResponseTabErrorDefaultMessage>
|
||||
{actionResponse &&
|
||||
(actionResponse.pluginErrorDetails ? (
|
||||
<>
|
||||
<div data-testid="t--query-error">
|
||||
{actionResponse.pluginErrorDetails
|
||||
.downstreamErrorMessage ||
|
||||
actionResponse.pluginErrorDetails.appsmithErrorMessage}
|
||||
</div>
|
||||
{actionResponse.pluginErrorDetails.downstreamErrorCode && (
|
||||
<LogAdditionalInfo
|
||||
text={
|
||||
actionResponse.pluginErrorDetails.downstreamErrorCode
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
actionResponse.body && (
|
||||
<div data-testid="t--query-error">
|
||||
{actionResponse.body}
|
||||
</div>
|
||||
)
|
||||
))}
|
||||
<LogHelper
|
||||
logType={LOG_TYPE.ACTION_EXECUTION_ERROR}
|
||||
name="PluginExecutionError"
|
||||
pluginErrorDetails={
|
||||
actionResponse && actionResponse.pluginErrorDetails
|
||||
}
|
||||
source={actionSource}
|
||||
/>
|
||||
</ResponseTabErrorContent>
|
||||
{actionResponse && actionResponse.request && (
|
||||
<JsonWrapper
|
||||
className="t--debugger-log-state"
|
||||
onClick={handleJsonWrapperClick}
|
||||
>
|
||||
<ReactJson src={responseState} {...apiReactJsonProps} />
|
||||
</JsonWrapper>
|
||||
)}
|
||||
</ResponseTabErrorContainer>
|
||||
</div>
|
||||
)}
|
||||
{showPreparedStatementWarning && (
|
||||
<Callout
|
||||
data-testid="t--prepared-statement-warning"
|
||||
kind="warning"
|
||||
links={preparedStatementCalloutLinks}
|
||||
>
|
||||
{createMessage(PREPARED_STATEMENT_WARNING.MESSAGE)}
|
||||
</Callout>
|
||||
)}
|
||||
{hintMessages && hintMessages.length > 0 && (
|
||||
<Styled.HelpSection>
|
||||
{hintMessages.map((msg, index) => (
|
||||
<Callout key={index} kind="warning">
|
||||
{msg}
|
||||
</Callout>
|
||||
))}
|
||||
</Styled.HelpSection>
|
||||
)}
|
||||
|
||||
{currentActionConfig &&
|
||||
output &&
|
||||
responseBodyTabs &&
|
||||
responseBodyTabs.length > 0 &&
|
||||
selectedTabIndex !== -1 && (
|
||||
<Styled.DataContainer
|
||||
$height={responseTabHeight}
|
||||
data-testid="t--query-response-data-container"
|
||||
onMouseEnter={setIsHovered}
|
||||
onMouseLeave={setIsNotHovered}
|
||||
>
|
||||
<Styled.StatusBar>
|
||||
<Tooltip
|
||||
content={queryTooltipContent}
|
||||
isDisabled={!queryTooltipContent}
|
||||
placement="bottom"
|
||||
>
|
||||
<Styled.StatusBarInfo>
|
||||
<Styled.StatusBarText $hasTooltip $isBold kind="code">
|
||||
{`${actionName}.run():`}
|
||||
</Styled.StatusBarText>
|
||||
<Styled.StatusBarText
|
||||
$hasTooltip
|
||||
data-testid="t--query-response-record-count"
|
||||
kind="code"
|
||||
>{`${recordCount} record${recordCount > 1 ? "s" : ""}`}</Styled.StatusBarText>
|
||||
</Styled.StatusBarInfo>
|
||||
</Tooltip>
|
||||
<BindDataButton
|
||||
actionName={actionName || currentActionConfig.name}
|
||||
hasResponse={!!actionResponse}
|
||||
suggestedWidgets={actionResponse?.suggestedWidgets}
|
||||
/>
|
||||
</Styled.StatusBar>
|
||||
|
||||
<Styled.Response>
|
||||
<ResponseFormatTabs
|
||||
data={output}
|
||||
responseType={currentContentType}
|
||||
tableBodyHeight={
|
||||
responseTabHeight +
|
||||
RESPONSE_TABLE_HEIGHT_OFFSET -
|
||||
scrollbarOffset
|
||||
}
|
||||
/>
|
||||
</Styled.Response>
|
||||
<Menu onOpenChange={toggleContentTypeMenuOpen}>
|
||||
<MenuTrigger>
|
||||
<Styled.Fab
|
||||
$isVisible={isContentTypeSelectorVisible}
|
||||
aria-label={`Change response format. Current format: ${currentContentType}`}
|
||||
data-testid="t--query-response-type-trigger"
|
||||
endIcon={
|
||||
isContentTypeMenuOpen
|
||||
? "arrow-up-s-line"
|
||||
: "arrow-down-s-line"
|
||||
}
|
||||
kind="secondary"
|
||||
startIcon={`content-type-${currentContentType.toLocaleLowerCase()}`}
|
||||
>
|
||||
{currentContentType}
|
||||
</Styled.Fab>
|
||||
</MenuTrigger>
|
||||
<MenuContent loop>
|
||||
<MenuGroupName asChild>
|
||||
<Text kind="body-s">View as</Text>
|
||||
</MenuGroupName>
|
||||
<MenuGroup>
|
||||
{contentTypeOptions.map(({ label, value }) => (
|
||||
<MenuItem
|
||||
data-testid="t--query-response-type-menu-item"
|
||||
data-value={value}
|
||||
key={value}
|
||||
onSelect={handleContentTypeChange}
|
||||
startIcon={`content-type-${value.toLocaleLowerCase()}`}
|
||||
>
|
||||
{label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuGroup>
|
||||
</MenuContent>
|
||||
</Menu>
|
||||
</Styled.DataContainer>
|
||||
)}
|
||||
</Styled.Root>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
/** Offset needed to make table height match response tab height */
|
||||
export const RESPONSE_TABLE_HEIGHT_OFFSET = 14;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { QueryResponseTab as default } from "./QueryResponseTab";
|
||||
|
|
@ -2,25 +2,19 @@ import React from "react";
|
|||
import { render } from "@testing-library/react";
|
||||
import { Provider } from "react-redux";
|
||||
import configureStore from "redux-mock-store";
|
||||
import QueryResponseTab from "./QueryResponseTab";
|
||||
import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils";
|
||||
import type { Action } from "entities/Action";
|
||||
import { Response } from "./Response";
|
||||
import { PluginType, type Action } from "entities/Action";
|
||||
import { lightTheme } from "selectors/themeSelectors";
|
||||
import { ThemeProvider } from "styled-components";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
import { getIDETestState } from "test/factories/AppIDEFactoryUtils";
|
||||
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
import type { ActionResponse } from "api/ActionAPI";
|
||||
|
||||
// Mock store
|
||||
const mockStore = configureStore([]);
|
||||
|
||||
const defaultProps = {
|
||||
actionName: "Test Action",
|
||||
actionSource: {
|
||||
name: "test source",
|
||||
id: "test-source-id",
|
||||
type: ENTITY_TYPE.ACTION,
|
||||
},
|
||||
currentActionConfig: {
|
||||
action: {
|
||||
id: "test-action-id",
|
||||
name: "Test Action",
|
||||
actionConfiguration: { pluginSpecifiedTemplates: [{ value: true }] },
|
||||
|
|
@ -28,12 +22,14 @@ const defaultProps = {
|
|||
} as Action,
|
||||
isRunning: false,
|
||||
onRunClick: jest.fn(),
|
||||
runErrorMessage: "",
|
||||
theme: EditorTheme.LIGHT,
|
||||
isRunDisabled: false,
|
||||
responseTabHeight: 200,
|
||||
};
|
||||
|
||||
const storeData = getIDETestState({});
|
||||
|
||||
describe("QueryResponseTab", () => {
|
||||
describe("Response", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let store: any;
|
||||
|
||||
|
|
@ -47,7 +43,7 @@ describe("QueryResponseTab", () => {
|
|||
<Provider store={store}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<Router>
|
||||
<QueryResponseTab {...defaultProps} />
|
||||
<Response {...defaultProps} />
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
|
|
@ -64,7 +60,7 @@ describe("QueryResponseTab", () => {
|
|||
<Provider store={store}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<Router>
|
||||
<QueryResponseTab {...defaultProps} isRunning />
|
||||
<Response {...defaultProps} isRunning />
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
|
|
@ -101,7 +97,7 @@ describe("QueryResponseTab", () => {
|
|||
<Provider store={store}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<Router>
|
||||
<QueryResponseTab {...defaultProps} />
|
||||
<Response {...defaultProps} />
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
|
|
@ -134,11 +130,23 @@ describe("QueryResponseTab", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const props = {
|
||||
...defaultProps,
|
||||
action: {
|
||||
...defaultProps.action,
|
||||
pluginType: PluginType.DB,
|
||||
} as Action,
|
||||
actionResponse: {
|
||||
isExecutionSuccess: false,
|
||||
readableError: "ERROR: relation 'userssss' does not exist Position: 15",
|
||||
} as ActionResponse,
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<Router>
|
||||
<QueryResponseTab {...defaultProps} />
|
||||
<Response {...props} />
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
|
|
@ -173,17 +181,22 @@ describe("QueryResponseTab", () => {
|
|||
|
||||
const props = {
|
||||
...defaultProps,
|
||||
currentActionConfig: {
|
||||
...defaultProps.currentActionConfig,
|
||||
action: {
|
||||
...defaultProps.action,
|
||||
pluginType: PluginType.DB,
|
||||
actionConfiguration: { pluginSpecifiedTemplates: [{ value: false }] },
|
||||
} as Action,
|
||||
actionResponse: {
|
||||
isExecutionSuccess: false,
|
||||
readableError: "ERROR: relation 'userssss' does not exist Position: 15",
|
||||
} as ActionResponse,
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<Router>
|
||||
<QueryResponseTab {...props} />
|
||||
<Response {...props} />
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
import React, { useMemo } from "react";
|
||||
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useBoolean, useEventCallback } from "usehooks-ts";
|
||||
import pluralize from "pluralize";
|
||||
|
||||
import { Callout, Tooltip, type CalloutLinkProps } from "@appsmith/ads";
|
||||
|
||||
import type { ActionResponse } from "api/ActionAPI";
|
||||
import ActionExecutionInProgressView from "components/editorComponents/ActionExecutionInProgressView";
|
||||
import type { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
import { PluginType, type Action } from "entities/Action";
|
||||
|
||||
import { setActionResponseDisplayFormat } from "actions/pluginActionActions";
|
||||
import { actionResponseDisplayDataFormats } from "pages/Editor/utils";
|
||||
import { scrollbarWidth } from "utils/helpers";
|
||||
|
||||
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||
import {
|
||||
openPluginActionSettings,
|
||||
setPluginActionEditorSelectedTab,
|
||||
} from "PluginActionEditor/store";
|
||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||
|
||||
import { getUpdateTimestamp } from "components/editorComponents/Debugger/ErrorLogs/ErrorLogItem";
|
||||
import { EDITOR_TABS } from "constants/QueryEditorConstants";
|
||||
import {
|
||||
createMessage,
|
||||
PREPARED_STATEMENT_WARNING,
|
||||
} from "ee/constants/messages";
|
||||
import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils";
|
||||
import type { SourceEntity } from "entities/AppsmithConsole";
|
||||
|
||||
import BindDataButton from "../BindDataButton";
|
||||
import { NoResponse } from "../NoResponse";
|
||||
import { ResponseFormatTabs } from "../ResponseFormatTabs";
|
||||
import { ContentTypeSelector, ErrorView } from "./components";
|
||||
import {
|
||||
API_REACT_JSON_PROPS,
|
||||
REACT_JSON_PROPS,
|
||||
RESPONSE_TABLE_HEIGHT_OFFSET,
|
||||
} from "./constants";
|
||||
|
||||
import * as Styled from "./styles";
|
||||
import { checkForPreparedStatement, parseActionResponse } from "./utils";
|
||||
|
||||
interface ResponseProps {
|
||||
action: Action;
|
||||
actionResponse?: ActionResponse;
|
||||
isRunDisabled: boolean;
|
||||
isRunning: boolean;
|
||||
onRunClick: () => void;
|
||||
responseTabHeight: number;
|
||||
theme: EditorTheme;
|
||||
}
|
||||
|
||||
export function Response(props: ResponseProps) {
|
||||
const isActionRedesignEnabled = useFeatureFlag(
|
||||
FEATURE_FLAG.release_actions_redesign_enabled,
|
||||
);
|
||||
|
||||
const {
|
||||
action,
|
||||
actionResponse,
|
||||
isRunDisabled,
|
||||
isRunning,
|
||||
onRunClick,
|
||||
responseTabHeight,
|
||||
theme,
|
||||
} = props;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const scrollbarOffset = scrollbarWidth();
|
||||
|
||||
const {
|
||||
setFalse: setIsNotHovered,
|
||||
setTrue: setIsHovered,
|
||||
value: isDataContainerHovered,
|
||||
} = useBoolean(false);
|
||||
|
||||
const { errorMessage, hintMessages, response } =
|
||||
parseActionResponse(actionResponse);
|
||||
|
||||
const recordCount = response?.length ?? 1;
|
||||
|
||||
const { responseDataTypes, responseDisplayFormat } =
|
||||
actionResponseDisplayDataFormats(actionResponse);
|
||||
|
||||
const { contentTypeOptions, currentContentType } = useMemo(() => {
|
||||
const contentTypeOptions = responseDataTypes.map(({ key, title }) => ({
|
||||
value: key,
|
||||
label: title,
|
||||
}));
|
||||
|
||||
const [firstContentTypeOption] = contentTypeOptions;
|
||||
const currentContentType =
|
||||
responseDisplayFormat.value || firstContentTypeOption?.value;
|
||||
|
||||
return { currentContentType, contentTypeOptions };
|
||||
}, [responseDisplayFormat, responseDataTypes]);
|
||||
|
||||
const showRecordCount =
|
||||
action.pluginType === PluginType.DB ||
|
||||
actionResponse?.dataTypes?.some(({ dataType }) => dataType === "TABLE");
|
||||
|
||||
const tooltipContent = useMemo(() => {
|
||||
if (actionResponse) {
|
||||
const messages = [
|
||||
[
|
||||
"duration",
|
||||
"Time to run",
|
||||
`${(Number(actionResponse.duration) / 1000).toFixed(1)}s`,
|
||||
],
|
||||
];
|
||||
|
||||
if (actionResponse.size) {
|
||||
messages.push([
|
||||
"size",
|
||||
"Response size",
|
||||
`${(Number(actionResponse.size) / 1000).toFixed(1)}kb`,
|
||||
]);
|
||||
}
|
||||
|
||||
if (actionResponse.statusCode) {
|
||||
messages.push(["statusCode", "Status", actionResponse.statusCode]);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{messages.map(([key, title, message]) => (
|
||||
<div key={key}>{`${title}: ${message}`}</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [actionResponse]);
|
||||
|
||||
const showPreparedStatementWarning = Boolean(
|
||||
checkForPreparedStatement(action) && errorMessage,
|
||||
);
|
||||
|
||||
const actionSource: SourceEntity = useMemo(
|
||||
() => ({
|
||||
type: ENTITY_TYPE.ACTION,
|
||||
name: action.name,
|
||||
id: action.id,
|
||||
}),
|
||||
[action.name, action.id],
|
||||
);
|
||||
|
||||
const updateTimestamp = getUpdateTimestamp(actionResponse?.request);
|
||||
const reactJsonParams =
|
||||
action.pluginType === PluginType.API
|
||||
? API_REACT_JSON_PROPS
|
||||
: REACT_JSON_PROPS;
|
||||
|
||||
const preparedStatementCalloutLinks: CalloutLinkProps[] = useMemo(() => {
|
||||
const navigateToSettings = () => {
|
||||
if (isActionRedesignEnabled) {
|
||||
dispatch(openPluginActionSettings(true));
|
||||
} else {
|
||||
dispatch(setPluginActionEditorSelectedTab(EDITOR_TABS.SETTINGS));
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
onClick: navigateToSettings,
|
||||
children: createMessage(PREPARED_STATEMENT_WARNING.LINK),
|
||||
},
|
||||
];
|
||||
}, [dispatch, isActionRedesignEnabled]);
|
||||
|
||||
const handleContentTypeChange = useEventCallback((e?: Event) => {
|
||||
if (e?.target && e.target instanceof HTMLElement) {
|
||||
const { value } = e.target.dataset;
|
||||
|
||||
if (typeof value === "string") {
|
||||
dispatch(
|
||||
setActionResponseDisplayFormat({
|
||||
id: action?.id || "",
|
||||
field: "responseDisplayFormat",
|
||||
value,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const handleJsonWrapperClick = useEventCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement>) => e.stopPropagation(),
|
||||
);
|
||||
|
||||
if (isRunning) {
|
||||
return (
|
||||
<Styled.LoadingContainer>
|
||||
<ActionExecutionInProgressView actionType="query" theme={theme} />
|
||||
</Styled.LoadingContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (!response && !errorMessage) {
|
||||
return (
|
||||
<NoResponse
|
||||
isRunDisabled={isRunDisabled}
|
||||
isRunning={isRunning}
|
||||
onRunClick={onRunClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Styled.Root>
|
||||
{errorMessage && (
|
||||
<ErrorView
|
||||
action={action}
|
||||
actionResponse={actionResponse}
|
||||
actionSource={actionSource}
|
||||
handleJsonWrapperClick={handleJsonWrapperClick}
|
||||
reactJsonParams={reactJsonParams}
|
||||
tooltipContent={tooltipContent}
|
||||
updateTimestamp={updateTimestamp}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showPreparedStatementWarning && (
|
||||
<Callout
|
||||
data-testid="t--prepared-statement-warning"
|
||||
kind="warning"
|
||||
links={preparedStatementCalloutLinks}
|
||||
>
|
||||
{createMessage(PREPARED_STATEMENT_WARNING.MESSAGE)}
|
||||
</Callout>
|
||||
)}
|
||||
|
||||
{hintMessages && hintMessages.length > 0 && (
|
||||
<Styled.HelpSection>
|
||||
{hintMessages.map((msg, index) => (
|
||||
<Callout key={index} kind="warning">
|
||||
{msg}
|
||||
</Callout>
|
||||
))}
|
||||
</Styled.HelpSection>
|
||||
)}
|
||||
|
||||
{response && !!currentContentType && (
|
||||
<Styled.DataContainer
|
||||
$height={responseTabHeight}
|
||||
data-testid="t--response-data-container"
|
||||
onMouseEnter={setIsHovered}
|
||||
onMouseLeave={setIsNotHovered}
|
||||
>
|
||||
<Styled.StatusBar>
|
||||
<Tooltip
|
||||
content={tooltipContent}
|
||||
id="t--response-tooltip"
|
||||
isDisabled={!tooltipContent}
|
||||
placement="bottom"
|
||||
>
|
||||
<Styled.StatusBarInfo data-testid="t--response-status-info">
|
||||
<Styled.StatusBarText
|
||||
$hasTooltip={!!tooltipContent}
|
||||
$isBold
|
||||
kind="code"
|
||||
>
|
||||
{`${action.name}.run()${showRecordCount ? ":" : ""}`}
|
||||
</Styled.StatusBarText>
|
||||
{showRecordCount && (
|
||||
<Styled.StatusBarText
|
||||
$hasTooltip={!!tooltipContent}
|
||||
data-testid="t--response-record-count"
|
||||
kind="code"
|
||||
>{`${recordCount} ${pluralize("record", recordCount)}`}</Styled.StatusBarText>
|
||||
)}
|
||||
</Styled.StatusBarInfo>
|
||||
</Tooltip>
|
||||
<BindDataButton
|
||||
actionName={action.name}
|
||||
hasResponse={!!actionResponse}
|
||||
suggestedWidgets={actionResponse?.suggestedWidgets}
|
||||
/>
|
||||
</Styled.StatusBar>
|
||||
|
||||
<Styled.Response>
|
||||
<ResponseFormatTabs
|
||||
data={response}
|
||||
responseType={currentContentType}
|
||||
tableBodyHeight={
|
||||
responseTabHeight +
|
||||
RESPONSE_TABLE_HEIGHT_OFFSET -
|
||||
scrollbarOffset
|
||||
}
|
||||
/>
|
||||
</Styled.Response>
|
||||
<ContentTypeSelector
|
||||
contentTypeOptions={contentTypeOptions}
|
||||
currentContentType={currentContentType}
|
||||
handleContentTypeChange={handleContentTypeChange}
|
||||
isHovered={isDataContainerHovered}
|
||||
/>
|
||||
</Styled.DataContainer>
|
||||
)}
|
||||
</Styled.Root>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import React from "react";
|
||||
import {
|
||||
Menu,
|
||||
MenuTrigger,
|
||||
MenuContent,
|
||||
MenuGroupName,
|
||||
MenuGroup,
|
||||
MenuItem,
|
||||
Text,
|
||||
} from "@appsmith/ads";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
|
||||
import * as Styled from "./styles";
|
||||
|
||||
interface ContentTypeSelectorProps {
|
||||
currentContentType: string;
|
||||
contentTypeOptions: Array<{ label: string; value: string }>;
|
||||
isHovered: boolean;
|
||||
handleContentTypeChange: (e?: Event) => void;
|
||||
}
|
||||
export const ContentTypeSelector = (props: ContentTypeSelectorProps) => {
|
||||
const {
|
||||
contentTypeOptions,
|
||||
currentContentType,
|
||||
handleContentTypeChange,
|
||||
isHovered,
|
||||
} = props;
|
||||
|
||||
const { toggle: toggleContentTypeMenuOpen, value: isOpen } =
|
||||
useBoolean(false);
|
||||
|
||||
const isVisible = isHovered || isOpen;
|
||||
|
||||
return (
|
||||
<Menu onOpenChange={toggleContentTypeMenuOpen}>
|
||||
<MenuTrigger>
|
||||
<Styled.Fab
|
||||
$isVisible={isVisible}
|
||||
aria-label={`Change response format. Current format: ${currentContentType}`}
|
||||
data-testid="t--response-type-trigger"
|
||||
endIcon={isOpen ? "arrow-up-s-line" : "arrow-down-s-line"}
|
||||
kind="secondary"
|
||||
startIcon={`content-type-${currentContentType.toLocaleLowerCase()}`}
|
||||
>
|
||||
{currentContentType}
|
||||
</Styled.Fab>
|
||||
</MenuTrigger>
|
||||
<MenuContent loop>
|
||||
<MenuGroupName asChild>
|
||||
<Text kind="body-s">View as</Text>
|
||||
</MenuGroupName>
|
||||
<MenuGroup>
|
||||
{contentTypeOptions.map(({ label, value }) => (
|
||||
<MenuItem
|
||||
data-testid="t--response-type-menu-item"
|
||||
data-value={value}
|
||||
key={value}
|
||||
onSelect={handleContentTypeChange}
|
||||
startIcon={`content-type-${value.toLocaleLowerCase()}`}
|
||||
>
|
||||
{label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuGroup>
|
||||
</MenuContent>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { ContentTypeSelector } from "./ContentTypeSelector";
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { Button } from "@appsmith/ads";
|
||||
import styled from "styled-components";
|
||||
import { TAB_BAR_HEIGHT } from "../../../constants";
|
||||
|
||||
export const Fab = styled(Button)<{ $isVisible: boolean }>`
|
||||
&& {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: calc(${TAB_BAR_HEIGHT}px + 20px);
|
||||
box-shadow: 0px 1px 20px 0px rgba(76, 86, 100, 0.11);
|
||||
z-index: var(--ads-v2-z-index-3);
|
||||
opacity: ${({ $isVisible }) => ($isVisible ? 1 : 0)};
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
`;
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
import React from "react";
|
||||
import ReactJson from "react-json-view";
|
||||
|
||||
import { JsonWrapper } from "components/editorComponents/CodeEditor/PeekOverlayPopup/JsonWrapper";
|
||||
import LogAdditionalInfo from "components/editorComponents/Debugger/ErrorLogs/components/LogAdditionalInfo";
|
||||
import LogHelper from "components/editorComponents/Debugger/ErrorLogs/components/LogHelper";
|
||||
|
||||
import { Tooltip } from "@appsmith/ads";
|
||||
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
||||
import { PluginType, type Action } from "entities/Action";
|
||||
import type { ActionResponse } from "api/ActionAPI";
|
||||
import type { SourceEntity } from "entities/AppsmithConsole";
|
||||
|
||||
import type { API_REACT_JSON_PROPS, REACT_JSON_PROPS } from "../../constants";
|
||||
import * as Styled from "../../styles";
|
||||
|
||||
interface ErrorViewProps {
|
||||
action: Action;
|
||||
actionResponse?: ActionResponse;
|
||||
actionSource: SourceEntity;
|
||||
reactJsonParams: typeof API_REACT_JSON_PROPS | typeof REACT_JSON_PROPS;
|
||||
tooltipContent: JSX.Element | null;
|
||||
updateTimestamp: unknown;
|
||||
handleJsonWrapperClick: (
|
||||
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const ErrorView: React.FC<ErrorViewProps> = ({
|
||||
action,
|
||||
actionResponse,
|
||||
actionSource,
|
||||
handleJsonWrapperClick,
|
||||
reactJsonParams,
|
||||
tooltipContent,
|
||||
updateTimestamp,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<Styled.StatusBar>
|
||||
<Tooltip
|
||||
content={tooltipContent}
|
||||
id="t--response-tooltip"
|
||||
isDisabled={!tooltipContent}
|
||||
placement="bottom"
|
||||
>
|
||||
<Styled.StatusBarInfo data-testid="t--response-status-info">
|
||||
<Styled.StatusBarText
|
||||
$hasTooltip={!!tooltipContent}
|
||||
$isBold
|
||||
kind="code"
|
||||
>
|
||||
{`${action.name}.run():`}
|
||||
</Styled.StatusBarText>
|
||||
<Styled.StatusBarText
|
||||
$hasTooltip={!!tooltipContent}
|
||||
$isError
|
||||
kind="code"
|
||||
>
|
||||
Error
|
||||
</Styled.StatusBarText>
|
||||
</Styled.StatusBarInfo>
|
||||
</Tooltip>
|
||||
</Styled.StatusBar>
|
||||
<Styled.ErrorContainer>
|
||||
<Styled.ErrorContent>
|
||||
<Styled.ErrorDefaultMessage>
|
||||
Request has failed to execute
|
||||
{actionResponse &&
|
||||
(actionResponse.pluginErrorDetails || actionResponse.body) &&
|
||||
":"}
|
||||
</Styled.ErrorDefaultMessage>
|
||||
{actionResponse &&
|
||||
(actionResponse.pluginErrorDetails ? (
|
||||
<>
|
||||
<div data-testid="t--response-error">
|
||||
{actionResponse.pluginErrorDetails.downstreamErrorMessage ||
|
||||
actionResponse.pluginErrorDetails.appsmithErrorMessage}
|
||||
</div>
|
||||
{actionResponse.pluginErrorDetails.downstreamErrorCode && (
|
||||
<LogAdditionalInfo
|
||||
text={actionResponse.pluginErrorDetails.downstreamErrorCode}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
actionResponse.body &&
|
||||
action.pluginType === PluginType.DB && (
|
||||
<div data-testid="t--response-error">{actionResponse.body}</div>
|
||||
)
|
||||
))}
|
||||
<LogHelper
|
||||
logType={LOG_TYPE.ACTION_EXECUTION_ERROR}
|
||||
name="PluginExecutionError"
|
||||
pluginErrorDetails={
|
||||
actionResponse && actionResponse.pluginErrorDetails
|
||||
}
|
||||
source={actionSource}
|
||||
/>
|
||||
</Styled.ErrorContent>
|
||||
{actionResponse && actionResponse.request && (
|
||||
<JsonWrapper
|
||||
className="t--debugger-log-state"
|
||||
onClick={handleJsonWrapperClick}
|
||||
>
|
||||
<ReactJson src={updateTimestamp} {...reactJsonParams} />
|
||||
</JsonWrapper>
|
||||
)}
|
||||
</Styled.ErrorContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { ErrorView } from "./ErrorView";
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./ContentTypeSelector";
|
||||
export * from "./ErrorView";
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/** Offset needed to make table height match response tab height */
|
||||
export const RESPONSE_TABLE_HEIGHT_OFFSET = 14;
|
||||
|
||||
export const REACT_JSON_PROPS = {
|
||||
name: null,
|
||||
enableClipboard: false,
|
||||
displayObjectSize: false,
|
||||
displayDataTypes: false,
|
||||
style: {
|
||||
fontFamily: "var(--ads-v2-font-family)",
|
||||
fontSize: "11px",
|
||||
fontWeight: "400",
|
||||
letterSpacing: "-0.195px",
|
||||
lineHeight: "13px",
|
||||
},
|
||||
collapsed: 1,
|
||||
};
|
||||
|
||||
export const API_REACT_JSON_PROPS = { ...REACT_JSON_PROPS, collapsed: 0 };
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export { Response } from "./Response";
|
||||
export {
|
||||
ErrorContainer as ResponseErrorContainer,
|
||||
ErrorContent as ResponseErrorContent,
|
||||
} from "./styles";
|
||||
|
|
@ -72,3 +72,25 @@ export const StatusBarText = styled(Text)<StatusBarTextProps>`
|
|||
${({ $isBold }) => $isBold && `font-weight: 700;`}
|
||||
${({ $isError }) => $isError && `color: var(--ads-v2-color-fg-on-error);`}
|
||||
`;
|
||||
|
||||
export const ErrorContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 16px;
|
||||
gap: 8px;
|
||||
height: fit-content;
|
||||
background: var(--ads-v2-color-bg-error);
|
||||
border-bottom: 1px solid var(--ads-v2-color-border);
|
||||
`;
|
||||
|
||||
export const ErrorContent = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
`;
|
||||
|
||||
export const ErrorDefaultMessage = styled.div`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { PluginType, type Action } from "entities/Action";
|
||||
|
||||
export function checkForPreparedStatement(action: Action) {
|
||||
const { actionConfiguration } = action;
|
||||
|
||||
if (PluginType.DB !== action.pluginType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (actionConfiguration?.pluginSpecifiedTemplates?.[0]?.value === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const preparedStatement = actionConfiguration?.formData?.preparedStatement;
|
||||
|
||||
if (
|
||||
preparedStatement &&
|
||||
typeof preparedStatement === "object" &&
|
||||
"data" in preparedStatement &&
|
||||
preparedStatement.data === true
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { parseActionResponse } from "./parseActionResponse";
|
||||
export { checkForPreparedStatement } from "./checkForPreparedStatement";
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import type { ActionResponse } from "api/ActionAPI";
|
||||
import { getErrorAsString } from "sagas/ActionExecution/errorUtils";
|
||||
import { isString } from "lodash";
|
||||
|
||||
export function parseActionResponse(actionResponse?: ActionResponse) {
|
||||
let response: Array<Record<string, unknown>> | string = "";
|
||||
let errorMessage = "";
|
||||
let hintMessages: Array<string> = [];
|
||||
|
||||
if (actionResponse) {
|
||||
if (!actionResponse.isExecutionSuccess) {
|
||||
errorMessage = actionResponse.readableError
|
||||
? getErrorAsString(actionResponse.readableError)
|
||||
: getErrorAsString(actionResponse.body);
|
||||
} else if (isString(actionResponse.body)) {
|
||||
errorMessage = "";
|
||||
try {
|
||||
response = JSON.parse(actionResponse.body);
|
||||
} catch (e) {
|
||||
response = [{ response: actionResponse.body }];
|
||||
}
|
||||
} else {
|
||||
errorMessage = "";
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
response = actionResponse.body as any;
|
||||
}
|
||||
|
||||
if (actionResponse.messages && actionResponse.messages.length) {
|
||||
errorMessage = "";
|
||||
hintMessages = actionResponse.messages;
|
||||
}
|
||||
}
|
||||
|
||||
return { response, errorMessage, hintMessages };
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@ import {
|
|||
import ErrorLogs from "components/editorComponents/Debugger/Errors";
|
||||
import DebuggerLogs from "components/editorComponents/Debugger/DebuggerLogs";
|
||||
import { PluginType } from "entities/Action";
|
||||
import { ApiResponse } from "PluginActionEditor/components/PluginActionResponse/components/ApiResponse";
|
||||
import { ApiResponseHeaders } from "PluginActionEditor/components/PluginActionResponse/components/ApiResponseHeaders";
|
||||
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
import { getErrorCount } from "selectors/debuggerSelectors";
|
||||
|
|
@ -26,15 +25,13 @@ import {
|
|||
import { doesPluginRequireDatasource } from "ee/entities/Engine/actionHelpers";
|
||||
import useShowSchema from "PluginActionEditor/components/PluginActionResponse/hooks/useShowSchema";
|
||||
import { Datasource } from "PluginActionEditor/components/PluginActionResponse/components/DatasourceTab";
|
||||
import QueryResponseTab from "PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab";
|
||||
import type { SourceEntity } from "entities/AppsmithConsole";
|
||||
import { ENTITY_TYPE as SOURCE_ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils";
|
||||
import {
|
||||
useBlockExecution,
|
||||
useHandleRunClick,
|
||||
useAnalyticsOnRunClick,
|
||||
} from "PluginActionEditor/hooks";
|
||||
import useDebuggerTriggerClick from "components/editorComponents/Debugger/hooks/useDebuggerTriggerClick";
|
||||
import { Response } from "PluginActionEditor/components/PluginActionResponse/components/Response";
|
||||
|
||||
function usePluginActionResponseTabs() {
|
||||
const { action, actionResponse, datasource, plugin } =
|
||||
|
|
@ -81,7 +78,7 @@ function usePluginActionResponseTabs() {
|
|||
key: DEBUGGER_TAB_KEYS.RESPONSE_TAB,
|
||||
title: createMessage(DEBUGGER_RESPONSE),
|
||||
panelComponent: (
|
||||
<ApiResponse
|
||||
<Response
|
||||
action={action}
|
||||
actionResponse={actionResponse}
|
||||
isRunDisabled={blockExecution}
|
||||
|
|
@ -117,12 +114,6 @@ function usePluginActionResponseTabs() {
|
|||
PluginType.INTERNAL,
|
||||
].includes(plugin.type)
|
||||
) {
|
||||
const actionSource: SourceEntity = {
|
||||
type: SOURCE_ENTITY_TYPE.ACTION,
|
||||
name: action.name,
|
||||
id: action.id,
|
||||
};
|
||||
|
||||
if (showSchema) {
|
||||
tabs.push({
|
||||
key: DEBUGGER_TAB_KEYS.DATASOURCE_TAB,
|
||||
|
|
@ -141,14 +132,14 @@ function usePluginActionResponseTabs() {
|
|||
key: DEBUGGER_TAB_KEYS.RESPONSE_TAB,
|
||||
title: createMessage(DEBUGGER_RESPONSE),
|
||||
panelComponent: (
|
||||
<QueryResponseTab
|
||||
actionName={action.name}
|
||||
actionSource={actionSource}
|
||||
currentActionConfig={action}
|
||||
<Response
|
||||
action={action}
|
||||
actionResponse={actionResponse}
|
||||
isRunDisabled={blockExecution}
|
||||
isRunning={isRunning}
|
||||
onRunClick={onRunClick}
|
||||
runErrorMessage={""} // TODO
|
||||
responseTabHeight={responseTabHeight}
|
||||
theme={EditorTheme.LIGHT}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import { getIDEViewMode } from "selectors/ideSelectors";
|
|||
import { EditorViewMode } from "ee/entities/IDE/constants";
|
||||
import useDebuggerTriggerClick from "./Debugger/hooks/useDebuggerTriggerClick";
|
||||
import { IDEBottomView, ViewHideBehaviour } from "IDE";
|
||||
import { ApiResponse } from "PluginActionEditor/components/PluginActionResponse/components/ApiResponse";
|
||||
import { Response } from "PluginActionEditor/components/PluginActionResponse/components/Response";
|
||||
import { ApiResponseHeaders } from "PluginActionEditor/components/PluginActionResponse/components/ApiResponseHeaders";
|
||||
|
||||
interface Props {
|
||||
|
|
@ -96,7 +96,7 @@ function ApiResponseView(props: Props) {
|
|||
key: DEBUGGER_TAB_KEYS.RESPONSE_TAB,
|
||||
title: createMessage(DEBUGGER_RESPONSE),
|
||||
panelComponent: (
|
||||
<ApiResponse
|
||||
<Response
|
||||
action={currentActionConfig}
|
||||
actionResponse={actionResponse}
|
||||
isRunDisabled={isRunDisabled}
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ import { getJSResponseViewState, JSResponseState } from "./utils";
|
|||
import { getFilteredErrors } from "selectors/debuggerSelectors";
|
||||
import { NoResponse } from "PluginActionEditor/components/PluginActionResponse/components/NoResponse";
|
||||
import {
|
||||
ResponseTabErrorContainer,
|
||||
ResponseTabErrorContent,
|
||||
} from "PluginActionEditor/components/PluginActionResponse/components/ApiResponse";
|
||||
ResponseErrorContainer,
|
||||
ResponseErrorContent,
|
||||
} from "PluginActionEditor/components/PluginActionResponse/components/Response";
|
||||
import LogHelper from "./Debugger/ErrorLogs/components/LogHelper";
|
||||
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
||||
import type { Log, SourceEntity } from "entities/AppsmithConsole";
|
||||
|
|
@ -198,8 +198,8 @@ function JSResponseView(props: Props) {
|
|||
{localExecutionAllowed &&
|
||||
(hasExecutionParseErrors ||
|
||||
(hasJSObjectParseError && errorMessage)) && (
|
||||
<ResponseTabErrorContainer>
|
||||
<ResponseTabErrorContent>
|
||||
<ResponseErrorContainer>
|
||||
<ResponseErrorContent>
|
||||
<div className="t--js-response-parse-error-call-out">
|
||||
{errorMessage}
|
||||
</div>
|
||||
|
|
@ -209,8 +209,8 @@ function JSResponseView(props: Props) {
|
|||
name={errorType}
|
||||
source={actionSource}
|
||||
/>
|
||||
</ResponseTabErrorContent>
|
||||
</ResponseTabErrorContainer>
|
||||
</ResponseErrorContent>
|
||||
</ResponseErrorContainer>
|
||||
)}
|
||||
<ResponseTabWrapper
|
||||
className={errors.length && localExecutionAllowed ? "disable" : ""}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { Datasource } from "PluginActionEditor/components/PluginActionResponse/c
|
|||
import type { ActionResponse } from "api/ActionAPI";
|
||||
import type { SourceEntity } from "entities/AppsmithConsole";
|
||||
import type { Action } from "entities/Action";
|
||||
import QueryResponseTab from "PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab";
|
||||
import { Response } from "PluginActionEditor/components/PluginActionResponse/components/Response";
|
||||
import {
|
||||
getDatasource,
|
||||
getDatasourceStructureById,
|
||||
|
|
@ -33,6 +33,7 @@ import { actionResponseDisplayDataFormats } from "../utils";
|
|||
import { getIDEViewMode } from "selectors/ideSelectors";
|
||||
import { EditorViewMode } from "ee/entities/IDE/constants";
|
||||
import { IDEBottomView, ViewHideBehaviour } from "IDE";
|
||||
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
|
||||
interface QueryDebuggerTabsProps {
|
||||
actionSource: SourceEntity;
|
||||
|
|
@ -49,12 +50,10 @@ interface QueryDebuggerTabsProps {
|
|||
function QueryDebuggerTabs({
|
||||
actionName,
|
||||
actionResponse,
|
||||
actionSource,
|
||||
currentActionConfig,
|
||||
isRunDisabled = false,
|
||||
isRunning,
|
||||
onRunClick,
|
||||
runErrorMessage,
|
||||
showSchema,
|
||||
}: QueryDebuggerTabsProps) {
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -202,14 +201,14 @@ function QueryDebuggerTabs({
|
|||
key: DEBUGGER_TAB_KEYS.RESPONSE_TAB,
|
||||
title: createMessage(DEBUGGER_RESPONSE),
|
||||
panelComponent: (
|
||||
<QueryResponseTab
|
||||
actionName={actionName}
|
||||
actionSource={actionSource}
|
||||
currentActionConfig={currentActionConfig}
|
||||
<Response
|
||||
action={currentActionConfig}
|
||||
actionResponse={actionResponse}
|
||||
isRunDisabled={isRunDisabled}
|
||||
isRunning={isRunning}
|
||||
onRunClick={onRunClick}
|
||||
runErrorMessage={runErrorMessage}
|
||||
responseTabHeight={responseTabHeight}
|
||||
theme={EditorTheme.LIGHT}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10771,6 +10771,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/pluralize@npm:^0.0.33":
|
||||
version: 0.0.33
|
||||
resolution: "@types/pluralize@npm:0.0.33"
|
||||
checksum: 282d42dc0187e5e0912f9f36ee0f5615bfd273a08d40afe5bf5881cb28daf1977abe10564543032aa0f42352ebba739ff3d86bf5562ac4691c6d1761fcc7cf39
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/prettier@npm:^2.1.5":
|
||||
version: 2.7.0
|
||||
resolution: "@types/prettier@npm:2.7.0"
|
||||
|
|
@ -12754,6 +12761,7 @@ __metadata:
|
|||
"@types/node-fetch": ^2.6.11
|
||||
"@types/node-forge": ^0.10.0
|
||||
"@types/object-hash": ^2.2.1
|
||||
"@types/pluralize": ^0.0.33
|
||||
"@types/prismjs": ^1.16.1
|
||||
"@types/react": ^17.0.2
|
||||
"@types/react-custom-scrollbars": ^4.0.7
|
||||
|
|
@ -12893,6 +12901,7 @@ __metadata:
|
|||
path-to-regexp: ^6.3.0
|
||||
pg: ^8.11.3
|
||||
plop: ^3.1.1
|
||||
pluralize: ^8.0.0
|
||||
popper.js: ^1.15.0
|
||||
postcss: 8.4.31
|
||||
postcss-at-rules-variables: ^0.3.0
|
||||
|
|
@ -26664,6 +26673,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pluralize@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "pluralize@npm:8.0.0"
|
||||
checksum: 08931d4a6a4a5561a7f94f67a31c17e6632cb21e459ab3ff4f6f629d9a822984cf8afef2311d2005fbea5d7ef26016ebb090db008e2d8bce39d0a9a9d218736e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pngjs@npm:^3.4.0":
|
||||
version: 3.4.0
|
||||
resolution: "pngjs@npm:3.4.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user