WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import * as _ from "../../../../support/Objects/ObjectsCore";
import {
agHelper,
dataSources,
assertHelper,
} from "../../../../support/Objects/ObjectsCore";
import { ObjectsRegistry } from "../../../../support/Objects/Registry";

describe(
"Workspace Datasource Settings",
{ tags: ["@tag.Workspace", "@tag.Datasource", "@tag.Sanity"] },
function () {
const locator = ObjectsRegistry.CommonLocators;

beforeEach(() => {
// Start datasource routes
dataSources.StartDataSourceRoutes();
});

it("1. Workspace menu Datasources option should show an empty list and available plugins", function () {
_.homePage.NavigateToHome();
_.agHelper.GenerateUUID();
cy.get("@guid").then((uid) => {
const workspaceId = String(uid);
_.homePage.CreateNewWorkspace(workspaceId);

// Open the workspace menu (triple dot) and click the Datasources option
_.homePage.OpenWorkspaceOptions(workspaceId);
agHelper.GetNClick(locator.workspaceDatasources);

assertHelper.AssertNetworkStatus("@getDataSources", 200);

agHelper.AssertElementVisibility(locator.workspaceDatasourcesPage);

// No datasources should be present yet
cy.get(locator._datasource).should("have.length", 0);

// Workspace datasources main blank state should be visible
agHelper.AssertElementVisibility(locator._dataBlankState);

// Blank state CTA should be visible
agHelper.GetNClick(locator._addDatasourceButtonBlankScreen);

// When blank state CTA is shown, the header + icon should NOT be visible
agHelper.AssertElementAbsence(locator._addDatasourceButton);
});
});

it("1.a Blank state CTA navigation should not show + icon on Connect a datasource page", function () {
_.homePage.NavigateToHome();
_.agHelper.GenerateUUID();
cy.get("@guid").then((uid) => {
const workspaceId = String(uid);
_.homePage.CreateNewWorkspace(workspaceId);

_.homePage.OpenWorkspaceOptions(workspaceId);
agHelper.GetNClick(locator.workspaceDatasources);

assertHelper.AssertNetworkStatus("@getDataSources", 200);

// Click the blank state CTA button (Bring your data)
agHelper.GetNClick(locator._addDatasourceButtonBlankScreen);

// We should be on the Connect a datasource page
cy.location("pathname").should(
"match",
/^\/workspace\/[^/]+\/datasources\/NEW$/,
);

// On Connect a datasource page, the header + icon should NOT be visible
agHelper.AssertElementAbsence(locator._addDatasourceButton);
});
});

it("2. Should allow adding DB and REST API datasources from workspace settings", function () {
_.homePage.NavigateToHome();
_.agHelper.GenerateUUID();
cy.get("@guid").then((uid) => {
const workspaceId = String(uid);
_.homePage.CreateNewWorkspace(workspaceId);

cy.intercept("POST", "/api/v1/datasources/mocks").as(
"addMockDatasource",
);

_.homePage.OpenWorkspaceOptions(workspaceId);
agHelper.GetNClick(locator.workspaceDatasources);

assertHelper.AssertNetworkStatus("@getDataSources", 200);

agHelper.GetNClick(locator._addDatasourceButtonBlankScreen);

cy.contains(locator._mockDatasourceName, "Users")
.should("be.visible")
.click();

assertHelper.AssertNetworkStatus("@addMockDatasource", 200);

cy.location("pathname").should(
"match",
/^\/workspace\/[^/]+\/datasource\/[^/]+$/,
);
cy.contains("View data").should("be.visible");
cy.contains("Configurations").should("be.visible");
cy.contains("button", "Edit").should("be.visible");

agHelper.GetNClick(dataSources._addNewDataSource, 0, true);
agHelper.AssertElementVisibility(locator._newIntegrationsWrapper);

cy.contains(locator._datasourceName, "Authenticated API")
.scrollIntoView()
.should("be.visible")
.click({ force: true });

agHelper.RenameDatasource("Mock API");
agHelper.TypeText("input[name='url']", "https://mock-api.appsmith.com");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Replace plain attribute selector with a locator variable.

The selector "input[name='url']" violates the guideline to use locator variables and avoid attribute selectors. Define this selector in commonLocators.json and reference it via the locator variable.

Based on coding guidelines: "Use locator variables for locators and do not use plain strings" and "Avoid Xpaths, Attributes and CSS path."

Apply this pattern:

-        agHelper.TypeText("input[name='url']", "https://mock-api.appsmith.com");
+        agHelper.TypeText(locator._datasourceUrl, "https://mock-api.appsmith.com");

Add _datasourceUrl: "input[data-testid='t--datasource-url']" to commonLocators.json.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
app/client/cypress/e2e/Regression/ClientSide/Workspace/WorkspaceDatasourceSettings_spec.ts
around line 116, replace the plain attribute selector "input[name='url']" with a
locator variable: add an entry _datasourceUrl:
"input[data-testid='t--datasource-url']" to commonLocators.json and then update
the test to use that locator variable (e.g.,
agHelper.TypeText(commonLocators._datasourceUrl,
"https://mock-api.appsmith.com")). Ensure the new data-testid matches the input
element in the app and run the spec to verify the locator resolves.


agHelper.GetNClick(dataSources._saveDs);
assertHelper.AssertNetworkStatus("@saveDatasource", 201);

cy.contains("button", "Edit").should("be.visible");
cy.get(locator._datasource).contains("Users").should("be.visible");
cy.get(locator._datasource).contains("Mock API").should("be.visible");
cy.get(locator._datasource).should("have.length.at.least", 2);
});
});

it("3. Should support deleting workspace datasources via the context menu", function () {
_.homePage.NavigateToHome();
_.agHelper.GenerateUUID();
cy.get("@guid").then((uid) => {
const workspaceId = String(uid);
_.homePage.CreateNewWorkspace(workspaceId);

cy.intercept("POST", "/api/v1/datasources/mocks").as(
"addMockDatasource",
);

_.homePage.OpenWorkspaceOptions(workspaceId);
agHelper.GetNClick(locator.workspaceDatasources);
assertHelper.AssertNetworkStatus("@getDataSources", 200);

agHelper.GetNClick(locator._addDatasourceButtonBlankScreen);

cy.contains(locator._mockDatasourceName, "Users")
.should("be.visible")
.click();

assertHelper.AssertNetworkStatus("@addMockDatasource", 200);

cy.location("pathname").should(
"match",
/^\/workspace\/[^/]+\/datasource\/[^/]+$/,
);
cy.contains("View data").should("be.visible");
cy.contains("Configurations").should("be.visible");
cy.contains("button", "Edit").should("be.visible");

agHelper.GetNClick(locator._contextMenuTrigger, 0, true);

agHelper.GetNClick(locator._datasourceOptionDelete, 0, true);

cy.contains("span", "Are you sure?")
.should("be.visible")
.click({ force: true });
Comment on lines +163 to +165
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Replace plain element selector with a locator variable.

The selector "span" in cy.contains("span", "Are you sure?") violates the guideline to use locator variables. Define this selector with a data-testid attribute in commonLocators.json.

Based on coding guidelines: "Use locator variables for locators and do not use plain strings."

Apply this pattern:

-        cy.contains("span", "Are you sure?")
-          .should("be.visible")
-          .click({ force: true });
+        agHelper.GetNClick(locator._confirmDeleteButton);

Add _confirmDeleteButton: "[data-testid='t--confirm-delete-button']" to commonLocators.json.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
app/client/cypress/e2e/Regression/ClientSide/Workspace/WorkspaceDatasourceSettings_spec.ts
around lines 163-165, the test uses a plain element selector cy.contains("span",
"Are you sure?"); add a locator variable to commonLocators.json named
_confirmDeleteButton with value "[data-testid='t--confirm-delete-button']" and
update the test to use that locator variable instead of the literal "span" (use
the locator to find the confirm-delete button and then assert visibility and
click).

assertHelper.AssertNetworkStatus("@deleteDatasource", 200);

cy.get(locator._datasource).should("have.length", 0);
cy.contains("Connect a datasource").should("be.visible");
});
});

it("4. Should keep edits when saving changes from the discard dialog", function () {
_.homePage.NavigateToHome();
_.agHelper.GenerateUUID();
cy.get("@guid").then((uid) => {
const workspaceId = String(uid);
_.homePage.CreateNewWorkspace(workspaceId);

cy.intercept("POST", "/api/v1/datasources/mocks").as(
"addMockDatasource",
);

_.homePage.OpenWorkspaceOptions(workspaceId);
agHelper.GetNClick(locator.workspaceDatasources);
assertHelper.AssertNetworkStatus("@getDataSources", 200);

agHelper.GetNClick(locator._addDatasourceButtonBlankScreen);

cy.contains(locator._mockDatasourceName, "Users")
.should("be.visible")
.click();
assertHelper.AssertNetworkStatus("@addMockDatasource", 200);

agHelper.GetNClick(dataSources._addNewDataSource, 0, true);
agHelper.AssertElementVisibility(locator._newIntegrationsWrapper);

cy.contains(locator._datasourceName, "Authenticated API")
.scrollIntoView()
.should("be.visible")
.click({ force: true });

agHelper.RenameDatasource("Mock API");
agHelper.TypeText("input[name='url']", "https://mock-api.appsmith.com");

agHelper.GetNClick(dataSources._saveDs);
assertHelper.AssertNetworkStatus("@saveDatasource", 201);

cy.contains("button", "Edit").should("be.visible").click();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Replace plain element selector with a locator variable.

The selector "button" in cy.contains("button", "Edit") violates the guideline to use locator variables. Use a locator variable with a data-testid attribute.

Based on coding guidelines: "Use locator variables for locators and do not use plain strings."

Apply this pattern:

-        cy.contains("button", "Edit").should("be.visible").click();
+        agHelper.GetNClick(locator._editDatasourceButton);

Add _editDatasourceButton: "[data-testid='t--edit-datasource-button']" to commonLocators.json.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
app/client/cypress/e2e/Regression/ClientSide/Workspace/WorkspaceDatasourceSettings_spec.ts
around line 209, the test uses cy.contains("button", "Edit") which uses a plain
element selector; add a locator variable named _editDatasourceButton with value
"[data-testid='t--edit-datasource-button']" to commonLocators.json, then replace
the cy.contains call with a locator-based call referencing the new
commonLocators._editDatasourceButton (e.g., use
cy.get(commonLocators._editDatasourceButton).should("be.visible").click()) so
the test uses the data-testid locator variable instead of the plain "button"
string.


agHelper.TypeText(
`${locator._headersArray} input[name$='.key']`,
"Content-Type",
);
agHelper.TypeText(
`${locator._headersArray} input[name$='.value']`,
"application/json",
);
Comment on lines +211 to +218
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Replace attribute selectors with data-testid based locator variables.

The composite selectors using input[name$='.key'] and input[name$='.value'] violate the guideline to avoid attribute selectors. Define these with data-testid attributes in commonLocators.json.

Based on coding guidelines: "Avoid Xpaths, Attributes and CSS path" and "Use data-* attributes for selectors."

Apply this pattern:

-        agHelper.TypeText(
-          `${locator._headersArray} input[name$='.key']`,
-          "Content-Type",
-        );
-        agHelper.TypeText(
-          `${locator._headersArray} input[name$='.value']`,
-          "application/json",
-        );
+        agHelper.TypeText(locator._headerKeyInput, "Content-Type");
+        agHelper.TypeText(locator._headerValueInput, "application/json");

Add to commonLocators.json:

"_headerKeyInput": "[data-testid='t--header-key-input']",
"_headerValueInput": "[data-testid='t--header-value-input']"
🤖 Prompt for AI Agents
In
app/client/cypress/e2e/Regression/ClientSide/Workspace/WorkspaceDatasourceSettings_spec.ts
around lines 211 to 218, replace the attribute-based composite selectors
`${locator._headersArray} input[name$='.key']` and `${locator._headersArray}
input[name$='.value']` with the new data-testid based locator variables (e.g.
use `${locator._headersArray} ${locator._headerKeyInput}` and
`${locator._headersArray} ${locator._headerValueInput}`), and add the
corresponding entries to commonLocators.json: "_headerKeyInput":
"[data-testid='t--header-key-input']" and "_headerValueInput":
"[data-testid='t--header-value-input']".


agHelper.GetNClick(locator._cancelEditDatasource);
cy.contains(".ads-v2-modal__content-header", "Are you sure?").should(
"be.visible",
);
Comment on lines +221 to +223
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Replace CSS class selector with a data-testid based locator variable.

The selector .ads-v2-modal__content-header uses a CSS class, which violates the guideline to use data-testid attributes. Define this selector in commonLocators.json with a data-testid.

Based on coding guidelines: "Use data-* attributes for selectors" and "Avoid CSS path."

Apply this pattern:

-        cy.contains(".ads-v2-modal__content-header", "Are you sure?").should(
-          "be.visible",
-        );
+        agHelper.AssertElementVisibility(locator._discardChangesModalHeader);
+        cy.contains(locator._discardChangesModalHeader, "Are you sure?").should("be.visible");

Add _discardChangesModalHeader: "[data-testid='t--modal-header']" to commonLocators.json.

Committable suggestion skipped: line range outside the PR's diff.

agHelper.GetNClick(locator._datasourceModalSave);
assertHelper.AssertNetworkStatus("@updateDatasource", 200);

cy.contains("button", "Edit").should("be.visible");
cy.contains(locator._datasource, "Users").should("be.visible");
cy.contains(locator._datasource, "Mock API").should("be.visible");
});
});
},
);
16 changes: 15 additions & 1 deletion app/client/cypress/locators/commonlocators.json
Original file line number Diff line number Diff line change
Expand Up @@ -246,5 +246,19 @@
"tostifyIcon": ".Toastify--animate-icon > span",
"downloadFileType": "button[class*='t--open-dropdown-Select-file-type'] > span:first-of-type",
"listToggle": "[data-testid='t--list-toggle']",
"showBindingsMenu": "//*[@id='entity-properties-container']"
"showBindingsMenu": "//*[@id='entity-properties-container']",
"workspaceDatasources": "[data-testid='t--workspace-datasources']",
"workspaceDatasourcesPage": "[data-testid='t--workspace-datasources-page']",
"addDatasourceButtonBlankScreen": ".t--add-datasource-button-blank-screen",
"addDatasourceButton": ".t--add-datasource-button",
"datasource": ".t--datasource",
"dataBlankState": ".t--data-blank-state",
"datasourceOptionDelete": ".t--datasource-option-delete",
"cancelEditDatasource": ".t--cancel-edit-datasource",
"datasourceModalSave": ".t--datasource-modal-save",
"headersArray": ".t--headers-array",
"mockDatasourceName": "[data-testid='mockdatasource-name']",
"datasourceName": "[data-testid='datasource-name']",
"contextMenuTrigger": "[data-testid='t--context-menu-trigger']",
"newIntegrationsWrapper": "#new-integrations-wrapper"
}
14 changes: 14 additions & 0 deletions app/client/cypress/support/Objects/CommonLocators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,4 +358,18 @@ export class CommonLocators {
_dropdownActiveOption = ".rc-select-dropdown .rc-select-item-option-active";
_homeIcon = "[data-testid='t--default-home-icon']";
_widget = (widgetName: string) => `.t--widget-${widgetName}`;
workspaceDatasources = "[data-testid='t--workspace-datasources']";
workspaceDatasourcesPage = "[data-testid='t--workspace-datasources-page']";
_addDatasourceButtonBlankScreen = ".t--add-datasource-button-blank-screen";
_addDatasourceButton = ".t--add-datasource-button";
_datasource = ".t--datasource";
_dataBlankState = ".t--data-blank-state";
_datasourceOptionDelete = ".t--datasource-option-delete";
_cancelEditDatasource = ".t--cancel-edit-datasource";
_datasourceModalSave = ".t--datasource-modal-save";
_headersArray = ".t--headers-array";
_mockDatasourceName = "[data-testid='mockdatasource-name']";
_datasourceName = "[data-testid='datasource-name']";
_contextMenuTrigger = "[data-testid='t--context-menu-trigger']";
_newIntegrationsWrapper = "#new-integrations-wrapper";
}
12 changes: 12 additions & 0 deletions app/client/src/ce/actions/workspaceDatasourceActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";

export interface InitWorkspaceDatasourcePayload {
workspaceId: string;
}

export const initWorkspaceDatasource = (
payload: InitWorkspaceDatasourcePayload,
) => ({
type: ReduxActionTypes.INITIALIZE_WORKSPACE_DATASOURCE,
payload,
});
5 changes: 5 additions & 0 deletions app/client/src/ce/constants/ReduxActionConstants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,9 @@ const IDEActionTypes = {
INITIALIZE_CURRENT_PAGE: "INITIALIZE_CURRENT_PAGE",
INITIALIZE_EDITOR: "INITIALIZE_EDITOR",
INITIALIZE_EDITOR_SUCCESS: "INITIALIZE_EDITOR_SUCCESS",
INITIALIZE_WORKSPACE_DATASOURCE: "INITIALIZE_WORKSPACE_DATASOURCE",
INITIALIZE_WORKSPACE_DATASOURCE_SUCCESS:
"INITIALIZE_WORKSPACE_DATASOURCE_SUCCESS",
INITIALIZE_PAGE_VIEWER: "INITIALIZE_PAGE_VIEWER",
INITIALIZE_PAGE_VIEWER_SUCCESS: "INITIALIZE_PAGE_VIEWER_SUCCESS",
SET_EXPLORER_PINNED: "SET_EXPLORER_PINNED",
Expand Down Expand Up @@ -478,6 +481,8 @@ const IDEActionTypes = {
const IDEActionErrorTypes = {
SETUP_PAGE_ERROR: "SETUP_PAGE_ERROR",
SETUP_PUBLISHED_PAGE_ERROR: "SETUP_PUBLISHED_PAGE_ERROR",
INITIALIZE_WORKSPACE_DATASOURCE_ERROR:
"INITIALIZE_WORKSPACE_DATASOURCE_ERROR",
};

const ErrorManagementActionTypes = {
Expand Down
14 changes: 14 additions & 0 deletions app/client/src/ce/pages/Applications/WorkspaceMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,20 @@ function WorkspaceMenu({
workspaceId={workspace.id}
workspacePermissions={workspace.userPermissions || []}
/>
{hasManageWorkspacePermissions && (
<CustomMenuItem
className="workspace-menu-item"
data-testid="t--workspace-datasources"
onClick={() =>
getOnSelectAction(DropdownOnSelectActions.REDIRECT, {
path: `/workspace/${workspace.id}/datasources`,
})
}
>
<Icon name="database-2-line" size="md" />
Datasources
</CustomMenuItem>
)}
{canInviteToWorkspace && (
<CustomMenuItem
className="error-menuitem workspace-menu-item"
Expand Down
11 changes: 11 additions & 0 deletions app/client/src/ce/pages/common/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ import {
CUSTOM_WIDGETS_EDITOR_ID_PATH,
CUSTOM_WIDGETS_EDITOR_ID_PATH_CUSTOM,
CUSTOM_WIDGETS_DEPRECATED_EDITOR_ID_PATH,
WORKSPACE_DATASOURCES_URL,
WORKSPACE_DATASOURCE_EDITOR_URL,
} from "constants/routes";
import Navigation from "pages/AppViewer/Navigation";
import type { RouteComponentProps } from "react-router";
import { Header as AppIDEHeader } from "pages/AppIDE/layouts/components/Header";
import WorkspaceDatasourceHeader from "pages/workspace/WorkspaceDatasourceHeader";

export type Props = RouteComponentProps;

Expand All @@ -45,6 +48,14 @@ export const Routes = () => {
exact
path={CUSTOM_WIDGETS_DEPRECATED_EDITOR_ID_PATH}
/>
<Route
component={WorkspaceDatasourceHeader}
path={WORKSPACE_DATASOURCES_URL}
/>
<Route
component={WorkspaceDatasourceHeader}
path={WORKSPACE_DATASOURCE_EDITOR_URL}
/>
<Route component={AppIDEHeader} path={BUILDER_PATH_DEPRECATED} />
<Route component={Navigation} path={VIEWER_PATH_DEPRECATED} />
<Route component={AppIDEHeader} path={BUILDER_PATH} />
Expand Down
12 changes: 12 additions & 0 deletions app/client/src/ce/reducers/uiReducers/editorReducer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { UpdateCanvasPayload } from "actions/pageActions";
export const initialState: EditorReduxState = {
widgetConfigBuilt: false,
initialized: false,
isWorkspaceDatasourceInitialized: false,
loadingStates: {
publishing: false,
publishingError: false,
Expand Down Expand Up @@ -65,6 +66,16 @@ export const handlers = {
[ReduxActionTypes.INITIALIZE_EDITOR_SUCCESS]: (state: EditorReduxState) => {
return { ...state, initialized: true };
},
[ReduxActionTypes.INITIALIZE_WORKSPACE_DATASOURCE]: (
state: EditorReduxState,
) => {
return { ...state, isWorkspaceDatasourceInitialized: false };
},
[ReduxActionTypes.INITIALIZE_WORKSPACE_DATASOURCE_SUCCESS]: (
state: EditorReduxState,
) => {
return { ...state, isWorkspaceDatasourceInitialized: true };
},
[ReduxActionTypes.UPDATE_PAGE_SUCCESS]: (
state: EditorReduxState,
action: ReduxAction<UpdatePageResponse>,
Expand Down Expand Up @@ -318,6 +329,7 @@ const editorReducer = createReducer(initialState, handlers);
export interface EditorReduxState {
widgetConfigBuilt: boolean;
initialized: boolean;
isWorkspaceDatasourceInitialized: boolean;
pageWidgetId?: string;
currentLayoutId?: string;
currentPageName?: string;
Expand Down
Loading
Loading