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:
Alex 2024-12-10 10:06:57 +03:00 committed by GitHub
parent ad5e25f12e
commit e754e48e1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 787 additions and 821 deletions

View File

@ -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",

View File

@ -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

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -57,7 +57,7 @@ describe.skip(
dataSourceName,
spreadSheetName,
);
dataSources.RunQueryNVerifyResponseViews(10);
dataSources.runQueryAndVerifyResponseViews({ count: 10 });
// Adding suggested widgets and verify
dataSources.AddSuggestedWidget(Widgets.Table);

View File

@ -57,7 +57,7 @@ describe(
dataSourceName,
spreadSheetName,
);
dataSources.RunQueryNVerifyResponseViews(10);
dataSources.runQueryAndVerifyResponseViews({ count: 10 });
// Adding suggested widgets and verify
dataSources.AddSuggestedWidget(Widgets.Table);

View File

@ -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")

View File

@ -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")

View File

@ -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;

View File

@ -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")

View File

@ -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")

View File

@ -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();
});
},

View File

@ -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();
});

View File

@ -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,

View File

@ -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"),

View File

@ -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({

View File

@ -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);

View File

@ -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",

View File

@ -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')",

View File

@ -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", () => {

View File

@ -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) {

View File

@ -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",

View File

@ -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() {

View File

@ -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",

View File

@ -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",

View File

@ -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>
);
}

View File

@ -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>
);
};

View File

@ -1,2 +0,0 @@
/** Offset needed to make table height match response tab height */
export const RESPONSE_TABLE_HEIGHT_OFFSET = 14;

View File

@ -1 +0,0 @@
export { QueryResponseTab as default } from "./QueryResponseTab";

View File

@ -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>,

View File

@ -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>
);
}

View File

@ -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>
);
};

View File

@ -0,0 +1 @@
export { ContentTypeSelector } from "./ContentTypeSelector";

View File

@ -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;
}
`;

View File

@ -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>
);
};

View File

@ -0,0 +1 @@
export { ErrorView } from "./ErrorView";

View File

@ -0,0 +1,2 @@
export * from "./ContentTypeSelector";
export * from "./ErrorView";

View File

@ -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 };

View File

@ -0,0 +1,5 @@
export { Response } from "./Response";
export {
ErrorContainer as ResponseErrorContainer,
ErrorContent as ResponseErrorContent,
} from "./styles";

View File

@ -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;
`;

View File

@ -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;
}

View File

@ -0,0 +1,2 @@
export { parseActionResponse } from "./parseActionResponse";
export { checkForPreparedStatement } from "./checkForPreparedStatement";

View File

@ -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 };
}

View File

@ -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}
/>
),
});

View File

@ -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}

View File

@ -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" : ""}

View File

@ -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}
/>
),
});

View File

@ -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"