From e754e48e1f973b87f8ee328ea02f1598bb8c1cd3 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 10 Dec 2024 10:06:57 +0300 Subject: [PATCH] feat: unified response view component (#37897) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Common component for response tab view. Fixes #37759 ## Automation /ok-to-test tags="@tag.All" ### :mag: Cypress test results > [!TIP] > 🟒 🟒 🟒 All cypress tests have passed! πŸŽ‰ πŸŽ‰ πŸŽ‰ > Workflow run: > Commit: 5263092079ad514d6c949b48d8510536ebeeadac > Cypress dashboard. > Tags: `@tag.All` > Spec: >
Mon, 09 Dec 2024 21:00:53 UTC ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [x] No ## 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. --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../cypress/e2e/GSheet/AllAccess_Spec.ts | 16 +- .../cypress/e2e/GSheet/GsheetMisc_Spec.ts | 4 +- .../e2e/GSheet/ReadNWrite_Access_Spec.ts | 16 +- .../e2e/GSheet/ReadOnly_Access_Spec.ts | 16 +- .../e2e/GSheet/SelectedSheet_Access_Spec.ts | 16 +- .../GSheet/WidgetBinding_AllAccess_Spec.ts | 2 +- .../WidgetBinding_SelectedAccess_Spec.ts | 2 +- .../Binding/API_with_List_Widget_spec.js | 4 +- .../Binding/Button_with_API_spec.js | 3 +- .../Binding/JSObjectToListWidget_Spec.ts | 2 +- .../ClientSide/Binding/TableV2_Api_spec.js | 3 +- .../ClientSide/Binding/Table_Api_spec.js | 3 +- .../ApiTests/API_Response_View_spec.js | 6 +- .../ServerSide/ApiTests/API_Search_spec.js | 8 +- .../ServerSide/Datasources/Oracle_Spec.ts | 16 +- .../OnLoadTests/OnLoadActions_Spec.ts | 10 +- .../QueryPane/EmptyDataSource_spec.js | 2 +- .../Sanity/Datasources/MsSQL_Basic_Spec.ts | 2 +- app/client/cypress/locators/ApiEditor.js | 3 - .../cypress/locators/apiWidgetslocator.json | 1 - app/client/cypress/support/ApiCommands.js | 4 +- app/client/cypress/support/Pages/ApiPage.ts | 6 +- .../cypress/support/Pages/DataSources.ts | 21 +- .../cypress/support/Pages/DebuggerHelper.ts | 2 +- .../support/Pages/IDE/BottomTabs/Response.ts | 39 +- app/client/package.json | 2 + .../components/ApiResponse.tsx | 175 ------- .../QueryResponseTab/QueryResponseTab.tsx | 480 ------------------ .../components/QueryResponseTab/constants.ts | 2 - .../components/QueryResponseTab/index.ts | 1 - .../Response.test.tsx} | 53 +- .../components/Response/Response.tsx | 307 +++++++++++ .../ContentTypeSelector.tsx | 68 +++ .../components/ContentTypeSelector/index.ts | 1 + .../components/ContentTypeSelector/styles.ts | 15 + .../components/ErrorView/ErrorView.tsx | 112 ++++ .../Response/components/ErrorView/index.ts | 1 + .../components/Response/components/index.ts | 2 + .../components/Response/constants.ts | 19 + .../components/Response/index.ts | 5 + .../{QueryResponseTab => Response}/styles.ts | 22 + .../utils/checkForPreparedStatement.ts | 26 + .../components/Response/utils/index.ts | 2 + .../Response/utils/parseActionResponse.ts | 36 ++ .../hooks/usePluginActionResponseTabs.tsx | 23 +- .../editorComponents/ApiResponseView.tsx | 4 +- .../editorComponents/JSResponseView.tsx | 14 +- .../Editor/QueryEditor/QueryDebuggerTabs.tsx | 15 +- app/client/yarn.lock | 16 + 49 files changed, 787 insertions(+), 821 deletions(-) delete mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/ApiResponse.tsx delete mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/QueryResponseTab.tsx delete mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/constants.ts delete mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/index.ts rename app/client/src/PluginActionEditor/components/PluginActionResponse/components/{QueryResponseTab.test.tsx => Response/Response.test.tsx} (80%) create mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/Response.tsx create mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ContentTypeSelector/ContentTypeSelector.tsx create mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ContentTypeSelector/index.ts create mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ContentTypeSelector/styles.ts create mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ErrorView/ErrorView.tsx create mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ErrorView/index.ts create mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/index.ts create mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/constants.ts create mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/index.ts rename app/client/src/PluginActionEditor/components/PluginActionResponse/components/{QueryResponseTab => Response}/styles.ts (80%) create mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/utils/checkForPreparedStatement.ts create mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/utils/index.ts create mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/utils/parseActionResponse.ts diff --git a/app/client/cypress/e2e/GSheet/AllAccess_Spec.ts b/app/client/cypress/e2e/GSheet/AllAccess_Spec.ts index 5414dad27d..e8a07a8f75 100644 --- a/app/client/cypress/e2e/GSheet/AllAccess_Spec.ts +++ b/app/client/cypress/e2e/GSheet/AllAccess_Spec.ts @@ -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", diff --git a/app/client/cypress/e2e/GSheet/GsheetMisc_Spec.ts b/app/client/cypress/e2e/GSheet/GsheetMisc_Spec.ts index 30e61e78ae..94507df74f 100644 --- a/app/client/cypress/e2e/GSheet/GsheetMisc_Spec.ts +++ b/app/client/cypress/e2e/GSheet/GsheetMisc_Spec.ts @@ -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 diff --git a/app/client/cypress/e2e/GSheet/ReadNWrite_Access_Spec.ts b/app/client/cypress/e2e/GSheet/ReadNWrite_Access_Spec.ts index 006a67e42e..38339d976e 100644 --- a/app/client/cypress/e2e/GSheet/ReadNWrite_Access_Spec.ts +++ b/app/client/cypress/e2e/GSheet/ReadNWrite_Access_Spec.ts @@ -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", diff --git a/app/client/cypress/e2e/GSheet/ReadOnly_Access_Spec.ts b/app/client/cypress/e2e/GSheet/ReadOnly_Access_Spec.ts index 987a767a15..0a5b542d42 100644 --- a/app/client/cypress/e2e/GSheet/ReadOnly_Access_Spec.ts +++ b/app/client/cypress/e2e/GSheet/ReadOnly_Access_Spec.ts @@ -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", diff --git a/app/client/cypress/e2e/GSheet/SelectedSheet_Access_Spec.ts b/app/client/cypress/e2e/GSheet/SelectedSheet_Access_Spec.ts index 50028ae77c..791d9a2cb2 100644 --- a/app/client/cypress/e2e/GSheet/SelectedSheet_Access_Spec.ts +++ b/app/client/cypress/e2e/GSheet/SelectedSheet_Access_Spec.ts @@ -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", diff --git a/app/client/cypress/e2e/GSheet/WidgetBinding_AllAccess_Spec.ts b/app/client/cypress/e2e/GSheet/WidgetBinding_AllAccess_Spec.ts index 43576a8b15..fb0625cbd3 100644 --- a/app/client/cypress/e2e/GSheet/WidgetBinding_AllAccess_Spec.ts +++ b/app/client/cypress/e2e/GSheet/WidgetBinding_AllAccess_Spec.ts @@ -57,7 +57,7 @@ describe.skip( dataSourceName, spreadSheetName, ); - dataSources.RunQueryNVerifyResponseViews(10); + dataSources.runQueryAndVerifyResponseViews({ count: 10 }); // Adding suggested widgets and verify dataSources.AddSuggestedWidget(Widgets.Table); diff --git a/app/client/cypress/e2e/GSheet/WidgetBinding_SelectedAccess_Spec.ts b/app/client/cypress/e2e/GSheet/WidgetBinding_SelectedAccess_Spec.ts index 7a768d834a..c9bfcf3afd 100644 --- a/app/client/cypress/e2e/GSheet/WidgetBinding_SelectedAccess_Spec.ts +++ b/app/client/cypress/e2e/GSheet/WidgetBinding_SelectedAccess_Spec.ts @@ -57,7 +57,7 @@ describe( dataSourceName, spreadSheetName, ); - dataSources.RunQueryNVerifyResponseViews(10); + dataSources.runQueryAndVerifyResponseViews({ count: 10 }); // Adding suggested widgets and verify dataSources.AddSuggestedWidget(Widgets.Table); diff --git a/app/client/cypress/e2e/Regression/ClientSide/Binding/API_with_List_Widget_spec.js b/app/client/cypress/e2e/Regression/ClientSide/Binding/API_with_List_Widget_spec.js index 2cecc97309..36f1138a97 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Binding/API_with_List_Widget_spec.js +++ b/app/client/cypress/e2e/Regression/ClientSide/Binding/API_with_List_Widget_spec.js @@ -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") diff --git a/app/client/cypress/e2e/Regression/ClientSide/Binding/Button_with_API_spec.js b/app/client/cypress/e2e/Regression/ClientSide/Binding/Button_with_API_spec.js index 58bd1b4fb6..dddaf07781 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Binding/Button_with_API_spec.js +++ b/app/client/cypress/e2e/Regression/ClientSide/Binding/Button_with_API_spec.js @@ -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") diff --git a/app/client/cypress/e2e/Regression/ClientSide/Binding/JSObjectToListWidget_Spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Binding/JSObjectToListWidget_Spec.ts index 17d085543a..4864c15720 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Binding/JSObjectToListWidget_Spec.ts +++ b/app/client/cypress/e2e/Regression/ClientSide/Binding/JSObjectToListWidget_Spec.ts @@ -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; diff --git a/app/client/cypress/e2e/Regression/ClientSide/Binding/TableV2_Api_spec.js b/app/client/cypress/e2e/Regression/ClientSide/Binding/TableV2_Api_spec.js index d9a9e1c773..9ca9d480a5 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Binding/TableV2_Api_spec.js +++ b/app/client/cypress/e2e/Regression/ClientSide/Binding/TableV2_Api_spec.js @@ -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") diff --git a/app/client/cypress/e2e/Regression/ClientSide/Binding/Table_Api_spec.js b/app/client/cypress/e2e/Regression/ClientSide/Binding/Table_Api_spec.js index 595220a6ca..38a52a0f4a 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Binding/Table_Api_spec.js +++ b/app/client/cypress/e2e/Regression/ClientSide/Binding/Table_Api_spec.js @@ -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") diff --git a/app/client/cypress/e2e/Regression/ServerSide/ApiTests/API_Response_View_spec.js b/app/client/cypress/e2e/Regression/ServerSide/ApiTests/API_Response_View_spec.js index 630146e5d5..775b454ea8 100644 --- a/app/client/cypress/e2e/Regression/ServerSide/ApiTests/API_Response_View_spec.js +++ b/app/client/cypress/e2e/Regression/ServerSide/ApiTests/API_Response_View_spec.js @@ -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(); }); }, diff --git a/app/client/cypress/e2e/Regression/ServerSide/ApiTests/API_Search_spec.js b/app/client/cypress/e2e/Regression/ServerSide/ApiTests/API_Search_spec.js index a2e9fc56c0..e343164725 100644 --- a/app/client/cypress/e2e/Regression/ServerSide/ApiTests/API_Search_spec.js +++ b/app/client/cypress/e2e/Regression/ServerSide/ApiTests/API_Search_spec.js @@ -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(); }); diff --git a/app/client/cypress/e2e/Regression/ServerSide/Datasources/Oracle_Spec.ts b/app/client/cypress/e2e/Regression/ServerSide/Datasources/Oracle_Spec.ts index 64eee95960..440174aefb 100644 --- a/app/client/cypress/e2e/Regression/ServerSide/Datasources/Oracle_Spec.ts +++ b/app/client/cypress/e2e/Regression/ServerSide/Datasources/Oracle_Spec.ts @@ -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, diff --git a/app/client/cypress/e2e/Regression/ServerSide/OnLoadTests/OnLoadActions_Spec.ts b/app/client/cypress/e2e/Regression/ServerSide/OnLoadTests/OnLoadActions_Spec.ts index befa58330c..5d2b8c7a69 100644 --- a/app/client/cypress/e2e/Regression/ServerSide/OnLoadTests/OnLoadActions_Spec.ts +++ b/app/client/cypress/e2e/Regression/ServerSide/OnLoadTests/OnLoadActions_Spec.ts @@ -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"), diff --git a/app/client/cypress/e2e/Regression/ServerSide/QueryPane/EmptyDataSource_spec.js b/app/client/cypress/e2e/Regression/ServerSide/QueryPane/EmptyDataSource_spec.js index ae3dd19742..c67f4b340f 100644 --- a/app/client/cypress/e2e/Regression/ServerSide/QueryPane/EmptyDataSource_spec.js +++ b/app/client/cypress/e2e/Regression/ServerSide/QueryPane/EmptyDataSource_spec.js @@ -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({ diff --git a/app/client/cypress/e2e/Sanity/Datasources/MsSQL_Basic_Spec.ts b/app/client/cypress/e2e/Sanity/Datasources/MsSQL_Basic_Spec.ts index aa515c69ab..cd391ed301 100644 --- a/app/client/cypress/e2e/Sanity/Datasources/MsSQL_Basic_Spec.ts +++ b/app/client/cypress/e2e/Sanity/Datasources/MsSQL_Basic_Spec.ts @@ -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); diff --git a/app/client/cypress/locators/ApiEditor.js b/app/client/cypress/locators/ApiEditor.js index a9a3c86f90..a168a5a123 100644 --- a/app/client/cypress/locators/ApiEditor.js +++ b/app/client/cypress/locators/ApiEditor.js @@ -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", diff --git a/app/client/cypress/locators/apiWidgetslocator.json b/app/client/cypress/locators/apiWidgetslocator.json index 5263f33991..da51eeb258 100644 --- a/app/client/cypress/locators/apiWidgetslocator.json +++ b/app/client/cypress/locators/apiWidgetslocator.json @@ -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')", diff --git a/app/client/cypress/support/ApiCommands.js b/app/client/cypress/support/ApiCommands.js index bfced38f73..3b77c7eea3 100644 --- a/app/client/cypress/support/ApiCommands.js +++ b/app/client/cypress/support/ApiCommands.js @@ -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", () => { diff --git a/app/client/cypress/support/Pages/ApiPage.ts b/app/client/cypress/support/Pages/ApiPage.ts index 7af9d454d1..33bfc0db84 100644 --- a/app/client/cypress/support/Pages/ApiPage.ts +++ b/app/client/cypress/support/Pages/ApiPage.ts @@ -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) { diff --git a/app/client/cypress/support/Pages/DataSources.ts b/app/client/cypress/support/Pages/DataSources.ts index 85ad360fb8..83c0012437 100644 --- a/app/client/cypress/support/Pages/DataSources.ts +++ b/app/client/cypress/support/Pages/DataSources.ts @@ -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", diff --git a/app/client/cypress/support/Pages/DebuggerHelper.ts b/app/client/cypress/support/Pages/DebuggerHelper.ts index 728a082d50..ff188d7f01 100644 --- a/app/client/cypress/support/Pages/DebuggerHelper.ts +++ b/app/client/cypress/support/Pages/DebuggerHelper.ts @@ -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() { diff --git a/app/client/cypress/support/Pages/IDE/BottomTabs/Response.ts b/app/client/cypress/support/Pages/IDE/BottomTabs/Response.ts index 485559535a..2e74183375 100644 --- a/app/client/cypress/support/Pages/IDE/BottomTabs/Response.ts +++ b/app/client/cypress/support/Pages/IDE/BottomTabs/Response.ts @@ -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", diff --git a/app/client/package.json b/app/client/package.json index 2387afc4ee..02bd681ec3 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -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", diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/ApiResponse.tsx b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/ApiResponse.tsx deleted file mode 100644 index f5be06f401..0000000000 --- a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/ApiResponse.tsx +++ /dev/null @@ -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 ( - - - - ); - } - - if (!props.actionResponse) { - return ( - - - - ); - } - - const { messages, pluginErrorDetails, request } = props.actionResponse; - - const runHasFailed = hasFailed(props.actionResponse); - const requestWithTimestamp = getUpdateTimestamp(request); - - return ( - - - {Array.isArray(messages) && messages.length > 0 && ( - - {messages.map((message, i) => ( - - {message} - - ))} - - )} - {runHasFailed && !props.isRunning ? ( - - - - Your API failed to execute - {pluginErrorDetails && ":"} - - {pluginErrorDetails && ( - <> -
- {pluginErrorDetails.downstreamErrorMessage} -
- {pluginErrorDetails.downstreamErrorCode && ( - - )} - - )} - -
- {requestWithTimestamp && ( - - - - )} -
- ) : ( - - {isEmpty(props.actionResponse.statusCode) ? ( - - ) : ( - - )} - - )} -
- ); -} diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/QueryResponseTab.tsx b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/QueryResponseTab.tsx deleted file mode 100644 index b080dd85e0..0000000000 --- a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/QueryResponseTab.tsx +++ /dev/null @@ -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 = ""; - let errorMessage = runErrorMessage; - let hintMessages: Array = []; - 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: ( - - ), - }; - }); - - 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]) => ( -
{`${title}: ${message}`}
- ))} - - ); - } - - 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) => e.stopPropagation(), - ); - - if (isRunning) { - return ( - - - - ); - } - - if (!output && !errorMessage) { - return ( - - ); - } - - return ( - - {errorMessage && ( -
- - - - {`${actionName}.run():`} - - - Error - - - - - - - Your query failed to execute - {actionResponse && - (actionResponse.pluginErrorDetails || actionResponse.body) && - ":"} - - {actionResponse && - (actionResponse.pluginErrorDetails ? ( - <> -
- {actionResponse.pluginErrorDetails - .downstreamErrorMessage || - actionResponse.pluginErrorDetails.appsmithErrorMessage} -
- {actionResponse.pluginErrorDetails.downstreamErrorCode && ( - - )} - - ) : ( - actionResponse.body && ( -
- {actionResponse.body} -
- ) - ))} - -
- {actionResponse && actionResponse.request && ( - - - - )} -
-
- )} - {showPreparedStatementWarning && ( - - {createMessage(PREPARED_STATEMENT_WARNING.MESSAGE)} - - )} - {hintMessages && hintMessages.length > 0 && ( - - {hintMessages.map((msg, index) => ( - - {msg} - - ))} - - )} - - {currentActionConfig && - output && - responseBodyTabs && - responseBodyTabs.length > 0 && - selectedTabIndex !== -1 && ( - - - - - - {`${actionName}.run():`} - - {`${recordCount} record${recordCount > 1 ? "s" : ""}`} - - - - - - - - - - - - {currentContentType} - - - - - View as - - - {contentTypeOptions.map(({ label, value }) => ( - - {label} - - ))} - - - - - )} -
- ); -}; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/constants.ts b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/constants.ts deleted file mode 100644 index c8093c4039..0000000000 --- a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** Offset needed to make table height match response tab height */ -export const RESPONSE_TABLE_HEIGHT_OFFSET = 14; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/index.ts b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/index.ts deleted file mode 100644 index 9b719118de..0000000000 --- a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { QueryResponseTab as default } from "./QueryResponseTab"; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab.test.tsx b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/Response.test.tsx similarity index 80% rename from app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab.test.tsx rename to app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/Response.test.tsx index 2ddca2cbc9..a8c0da559e 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab.test.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/Response.test.tsx @@ -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", () => { - + , @@ -64,7 +60,7 @@ describe("QueryResponseTab", () => { - + , @@ -101,7 +97,7 @@ describe("QueryResponseTab", () => { - + , @@ -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( - + , @@ -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( - + , diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/Response.tsx b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/Response.tsx new file mode 100644 index 0000000000..7117b3df01 --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/Response.tsx @@ -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]) => ( +
{`${title}: ${message}`}
+ ))} + + ); + } + + 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) => e.stopPropagation(), + ); + + if (isRunning) { + return ( + + + + ); + } + + if (!response && !errorMessage) { + return ( + + ); + } + + return ( + + {errorMessage && ( + + )} + + {showPreparedStatementWarning && ( + + {createMessage(PREPARED_STATEMENT_WARNING.MESSAGE)} + + )} + + {hintMessages && hintMessages.length > 0 && ( + + {hintMessages.map((msg, index) => ( + + {msg} + + ))} + + )} + + {response && !!currentContentType && ( + + + + + + {`${action.name}.run()${showRecordCount ? ":" : ""}`} + + {showRecordCount && ( + {`${recordCount} ${pluralize("record", recordCount)}`} + )} + + + + + + + + + + + )} + + ); +} diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ContentTypeSelector/ContentTypeSelector.tsx b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ContentTypeSelector/ContentTypeSelector.tsx new file mode 100644 index 0000000000..9ef4fecf0e --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ContentTypeSelector/ContentTypeSelector.tsx @@ -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 ( + + + + {currentContentType} + + + + + View as + + + {contentTypeOptions.map(({ label, value }) => ( + + {label} + + ))} + + + + ); +}; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ContentTypeSelector/index.ts b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ContentTypeSelector/index.ts new file mode 100644 index 0000000000..c0c0417dde --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ContentTypeSelector/index.ts @@ -0,0 +1 @@ +export { ContentTypeSelector } from "./ContentTypeSelector"; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ContentTypeSelector/styles.ts b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ContentTypeSelector/styles.ts new file mode 100644 index 0000000000..79d1308517 --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ContentTypeSelector/styles.ts @@ -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; + } +`; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ErrorView/ErrorView.tsx b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ErrorView/ErrorView.tsx new file mode 100644 index 0000000000..da5fbad98d --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ErrorView/ErrorView.tsx @@ -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, + ) => void; +} + +export const ErrorView: React.FC = ({ + action, + actionResponse, + actionSource, + handleJsonWrapperClick, + reactJsonParams, + tooltipContent, + updateTimestamp, +}) => { + return ( +
+ + + + + {`${action.name}.run():`} + + + Error + + + + + + + + Request has failed to execute + {actionResponse && + (actionResponse.pluginErrorDetails || actionResponse.body) && + ":"} + + {actionResponse && + (actionResponse.pluginErrorDetails ? ( + <> +
+ {actionResponse.pluginErrorDetails.downstreamErrorMessage || + actionResponse.pluginErrorDetails.appsmithErrorMessage} +
+ {actionResponse.pluginErrorDetails.downstreamErrorCode && ( + + )} + + ) : ( + actionResponse.body && + action.pluginType === PluginType.DB && ( +
{actionResponse.body}
+ ) + ))} + +
+ {actionResponse && actionResponse.request && ( + + + + )} +
+
+ ); +}; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ErrorView/index.ts b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ErrorView/index.ts new file mode 100644 index 0000000000..483ffa662a --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/ErrorView/index.ts @@ -0,0 +1 @@ +export { ErrorView } from "./ErrorView"; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/index.ts b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/index.ts new file mode 100644 index 0000000000..9493e34932 --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/components/index.ts @@ -0,0 +1,2 @@ +export * from "./ContentTypeSelector"; +export * from "./ErrorView"; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/constants.ts b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/constants.ts new file mode 100644 index 0000000000..4c2b87ce20 --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/constants.ts @@ -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 }; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/index.ts b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/index.ts new file mode 100644 index 0000000000..3ca8748c6e --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/index.ts @@ -0,0 +1,5 @@ +export { Response } from "./Response"; +export { + ErrorContainer as ResponseErrorContainer, + ErrorContent as ResponseErrorContent, +} from "./styles"; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/styles.ts b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/styles.ts similarity index 80% rename from app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/styles.ts rename to app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/styles.ts index 81089d4679..edb46d9919 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/QueryResponseTab/styles.ts +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/styles.ts @@ -72,3 +72,25 @@ export const StatusBarText = styled(Text)` ${({ $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; +`; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/utils/checkForPreparedStatement.ts b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/utils/checkForPreparedStatement.ts new file mode 100644 index 0000000000..6c3bd71a2e --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/utils/checkForPreparedStatement.ts @@ -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; +} diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/utils/index.ts b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/utils/index.ts new file mode 100644 index 0000000000..9d05b1fc57 --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/utils/index.ts @@ -0,0 +1,2 @@ +export { parseActionResponse } from "./parseActionResponse"; +export { checkForPreparedStatement } from "./checkForPreparedStatement"; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/utils/parseActionResponse.ts b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/utils/parseActionResponse.ts new file mode 100644 index 0000000000..2eddce557c --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Response/utils/parseActionResponse.ts @@ -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> | string = ""; + let errorMessage = ""; + let hintMessages: Array = []; + + 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 }; +} diff --git a/app/client/src/ce/PluginActionEditor/components/PluginActionResponse/hooks/usePluginActionResponseTabs.tsx b/app/client/src/ce/PluginActionEditor/components/PluginActionResponse/hooks/usePluginActionResponseTabs.tsx index cae1fc5953..581c99350b 100644 --- a/app/client/src/ce/PluginActionEditor/components/PluginActionResponse/hooks/usePluginActionResponseTabs.tsx +++ b/app/client/src/ce/PluginActionEditor/components/PluginActionResponse/hooks/usePluginActionResponseTabs.tsx @@ -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: ( - ), }); diff --git a/app/client/src/components/editorComponents/ApiResponseView.tsx b/app/client/src/components/editorComponents/ApiResponseView.tsx index ef01cbd7d9..be02637831 100644 --- a/app/client/src/components/editorComponents/ApiResponseView.tsx +++ b/app/client/src/components/editorComponents/ApiResponseView.tsx @@ -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: ( - - + +
{errorMessage}
@@ -209,8 +209,8 @@ function JSResponseView(props: Props) { name={errorType} source={actionSource} /> -
- + + )} ), }); diff --git a/app/client/yarn.lock b/app/client/yarn.lock index e321f81549..6b67641dc6 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -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"