Skip to content

Commit 12e7ee0

Browse files
author
Rishabh Rathod
authored
Add s3 support for generate CRUD (#6264)
* Add s3 support for generate CRUD - Dropdown enhancement to open options on initial load - Hide column selection option for s3 * Refactor the prop name * Add useS3BucketList hook WIP * Dropdown enchancement & Fix small issues * Add fetch all sheets query * Add Query to get all S3 buckets * Fix dropdown open issue * Remove defaultIsOpen prop from dropdown * Resolve comments - Remove debugger - mockSheetUrl -> getSheetUrl * Add S3 cypress test * Fix cypress test yml config * Fix generate page cypress test
1 parent 094b778 commit 12e7ee0

File tree

18 files changed

+680
-329
lines changed

18 files changed

+680
-329
lines changed

.github/workflows/client-test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,8 @@ jobs:
371371
CYPRESS_TESTPASSWORD1: ${{ secrets.CYPRESS_TESTPASSWORD1 }}
372372
CYPRESS_TESTUSERNAME2: ${{ secrets.CYPRESS_TESTUSERNAME2 }}
373373
CYPRESS_TESTPASSWORD2: ${{ secrets.CYPRESS_TESTPASSWORD1 }}
374+
CYPRESS_S3_ACCESS_KEY: ${{ secrets.CYPRESS_S3_ACCESS_KEY }}
375+
CYPRESS_S3_SECRET_KEY: ${{ secrets.CYPRESS_S3_SECRET_KEY }}
374376
APPSMITH_DISABLE_TELEMETRY: true
375377
APPSMITH_GOOGLE_MAPS_API_KEY: ${{ secrets.APPSMITH_GOOGLE_MAPS_API_KEY }}
376378
POSTGRES_PASSWORD: postgres

.github/workflows/external-client-test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,8 @@ jobs:
354354
CYPRESS_TESTPASSWORD1: ${{ secrets.CYPRESS_TESTPASSWORD1 }}
355355
CYPRESS_TESTUSERNAME2: ${{ secrets.CYPRESS_TESTUSERNAME2 }}
356356
CYPRESS_TESTPASSWORD2: ${{ secrets.CYPRESS_TESTPASSWORD1 }}
357+
CYPRESS_S3_ACCESS_KEY: ${{ secrets.CYPRESS_S3_ACCESS_KEY }}
358+
CYPRESS_S3_SECRET_KEY: ${{ secrets.CYPRESS_S3_SECRET_KEY }}
357359
APPSMITH_DISABLE_TELEMETRY: true
358360
APPSMITH_GOOGLE_MAPS_API_KEY: ${{ secrets.APPSMITH_GOOGLE_MAPS_API_KEY }}
359361
POSTGRES_PASSWORD: postgres

app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/GenerateCRUD/GenerateCRUDPage_Spec.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ import homePage from "../../../../locators/HomePage.json";
44
import datasource from "../../../../locators/DatasourcesEditor.json";
55

66
describe("Generate New CRUD Page Inside from entity explorer", function() {
7+
let datasourceName;
8+
79
before(() => {
810
cy.startRoutesForDatasource();
911
cy.createPostgresDatasource();
12+
13+
cy.get("@createDatasource").then((httpResponse) => {
14+
datasourceName = httpResponse.response.body.data.name;
15+
});
1016
// TODO
1117
// 1. Add INVALID credential for a datasource and test the invalid datasource structure flow.
1218
// 2. Add 2 supported datasource and 1 not supported datasource with a fixed name to search.
@@ -27,7 +33,7 @@ describe("Generate New CRUD Page Inside from entity explorer", function() {
2733
cy.get(generatePage.selectDatasourceDropdown).click();
2834

2935
cy.get(generatePage.datasourceDropdownOption)
30-
.first()
36+
.contains(datasourceName)
3137
.click();
3238

3339
cy.wait("@getDatasourceStructure").should(
@@ -81,7 +87,8 @@ describe("Generate New CRUD Page Inside from entity explorer", function() {
8187
cy.fillPostgresDatasourceForm();
8288

8389
cy.generateUUID().then((UUID) => {
84-
cy.renameDatasource(`PostgresSQL CRUD Demo ${UUID}`);
90+
datasourceName = `PostgresSQL CRUD Demo ${UUID}`;
91+
cy.renameDatasource(datasourceName);
8592
});
8693

8794
cy.startRoutesForDatasource();
@@ -124,10 +131,16 @@ describe("Generate New CRUD Page Inside from entity explorer", function() {
124131
cy.get(pages.integrationActiveTab)
125132
.should("be.visible")
126133
.click({ force: true });
134+
cy.wait(1000);
127135

128-
cy.get(generatePage.datasourceCardGeneratePageBtn)
129-
.first()
130-
.click();
136+
cy.get(datasource.datasourceCard)
137+
.contains(datasourceName)
138+
.scrollIntoView()
139+
.should("be.visible")
140+
.closest(datasource.datasourceCard)
141+
.within(() => {
142+
cy.get(datasource.datasourceCardGeneratePageBtn).click();
143+
});
131144

132145
cy.wait("@getDatasourceStructure").should(
133146
"have.nested.property",
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const pages = require("../../../../locators/Pages.json");
2+
const generatePage = require("../../../../locators/GeneratePage.json");
3+
4+
describe("Generate New CRUD Page Inside from entity explorer", function() {
5+
let datasourceName;
6+
before(() => {
7+
cy.startRoutesForDatasource();
8+
cy.createAmazonS3Datasource();
9+
10+
cy.get("@createDatasource").then((httpResponse) => {
11+
datasourceName = httpResponse.response.body.data.name;
12+
});
13+
});
14+
15+
it("Add new Page and generate CRUD template using existing supported datasource", function() {
16+
cy.get(pages.AddPage)
17+
.first()
18+
.click();
19+
cy.wait("@createPage").should(
20+
"have.nested.property",
21+
"response.body.responseMeta.status",
22+
201,
23+
);
24+
25+
cy.get(generatePage.generateCRUDPageActionCard).click();
26+
27+
cy.get(generatePage.selectDatasourceDropdown).click();
28+
29+
cy.get(generatePage.datasourceDropdownOption)
30+
.contains(datasourceName)
31+
.click();
32+
33+
// fetch bucket
34+
cy.wait("@datasourceQuery").should(
35+
"have.nested.property",
36+
"response.body.responseMeta.status",
37+
200,
38+
);
39+
40+
cy.get(generatePage.selectTableDropdown).click();
41+
42+
cy.get(generatePage.dropdownOption)
43+
.contains("assets-test.appsmith.com")
44+
.scrollIntoView()
45+
.should("be.visible")
46+
.click();
47+
// skip optional search column selection.
48+
cy.get(generatePage.generatePageFormSubmitBtn).click();
49+
50+
cy.wait("@replaceLayoutWithCRUDPage").should(
51+
"have.nested.property",
52+
"response.body.responseMeta.status",
53+
201,
54+
);
55+
cy.wait("@getActions");
56+
cy.wait("@postExecute").should(
57+
"have.nested.property",
58+
"response.body.responseMeta.status",
59+
200,
60+
);
61+
});
62+
});

app/client/cypress/locators/DatasourcesEditor.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"activeDatasourceList": ".t--active-datasource-list",
1717
"datasourceCard": ".t--datasource",
1818
"datasourceCardMenu": ".t--datasource-menu-option",
19+
"datasourceCardGeneratePageBtn": ".t--generate-template",
1920
"datasourceMenuOptionEdit": "t--datasource-option-edit",
2021
"datasourceMenuOptionDelete":"t--datasource-option-delete",
2122
"editDatasource": ".t--edit-datasource",
@@ -30,7 +31,7 @@
3031
"MsSQL": ".t--plugin-name:contains('MsSQL')",
3132
"Firestore": ".t--plugin-name:contains('Firestore')",
3233
"Redshift": ".t--plugin-name:contains('Redshift')",
33-
"AmazonS3": ".t--plugin-name:contains('Amazon S3')",
34+
"AmazonS3": ".t--plugin-name:contains('S3')",
3435
"authType": "[data-cy=authType]",
3536
"OAuth2": "//div[contains(@class,'option') and text()='OAuth 2.0']",
3637
"accessTokenUrl": "[data-cy='authentication.accessTokenUrl'] input",

app/client/cypress/locators/GeneratePage.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,5 @@
66
"selectTableDropdown":"[data-cy=t--table-dropdown]",
77
"dropdownOption": ".bp3-popover-content .t--dropdown-option",
88
"selectSearchColumnDropdown":"[data-cy=t--searchColumn-dropdown]",
9-
"generatePageFormSubmitBtn":"[data-cy=t--generate-page-form-submit]",
10-
"datasourceCardGeneratePageBtn": ".t--generate-template"
9+
"generatePageFormSubmitBtn":"[data-cy=t--generate-page-form-submit]"
1110
}

app/client/cypress/support/commands.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2464,6 +2464,9 @@ Cypress.Commands.add("startServerAndRoutes", () => {
24642464
cy.route("GET", "/api/v1/datasources/*/structure?ignoreCache=*").as(
24652465
"getDatasourceStructure",
24662466
);
2467+
cy.route("PUT", "/api/v1/datasources/datasource-query/*").as(
2468+
"datasourceQuery",
2469+
);
24672470

24682471
cy.route("PUT", "/api/v1/pages/crud-page/*").as("replaceLayoutWithCRUDPage");
24692472
cy.route("POST", "/api/v1/pages/crud-page").as("generateCRUDPage");
@@ -2683,3 +2686,19 @@ Cypress.Commands.add("renameDatasource", (datasourceName) => {
26832686
Cypress.Commands.add("skipGenerateCRUDPage", () => {
26842687
cy.get(generatePage.buildFromScratchActionCard).click();
26852688
});
2689+
2690+
Cypress.Commands.add("fillAmazonS3DatasourceForm", () => {
2691+
cy.get(datasourceEditor.projectID).type(Cypress.env("S3_ACCESS_KEY"));
2692+
cy.get(datasourceEditor.serviceAccCredential)
2693+
.clear()
2694+
.type(Cypress.env("S3_SECRET_KEY"));
2695+
});
2696+
2697+
Cypress.Commands.add("createAmazonS3Datasource", () => {
2698+
cy.NavigateToDatasourceEditor();
2699+
cy.get(datasourceEditor.AmazonS3).click();
2700+
2701+
cy.fillAmazonS3DatasourceForm();
2702+
2703+
cy.testSaveDatasource();
2704+
});

app/client/src/actions/datasourceActions.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -199,20 +199,20 @@ export const getOAuthAccessToken = (datasourceId: string) => {
199199
};
200200
};
201201

202-
export type executeDatasourceQuerySuccessPayload = {
202+
export type executeDatasourceQuerySuccessPayload<T> = {
203203
responseMeta: ResponseMeta;
204204
data: {
205-
body: Array<{ id: string; name: string }>;
205+
body: T;
206206
headers: Record<string, string[]>;
207207
statusCode: string;
208208
isExecutionSuccess: boolean;
209209
};
210210
};
211211
type errorPayload = unknown;
212212

213-
export type executeDatasourceQueryReduxAction = ReduxActionWithCallbacks<
213+
export type executeDatasourceQueryReduxAction<T> = ReduxActionWithCallbacks<
214214
executeDatasourceQueryRequest,
215-
executeDatasourceQuerySuccessPayload,
215+
executeDatasourceQuerySuccessPayload<T>,
216216
errorPayload
217217
>;
218218

@@ -222,9 +222,11 @@ export const executeDatasourceQuery = ({
222222
payload,
223223
}: {
224224
onErrorCallback?: (payload: errorPayload) => void;
225-
onSuccessCallback?: (payload: executeDatasourceQuerySuccessPayload) => void;
225+
onSuccessCallback?: (
226+
payload: executeDatasourceQuerySuccessPayload<any>,
227+
) => void;
226228
payload: executeDatasourceQueryRequest;
227-
}): executeDatasourceQueryReduxAction => {
229+
}): executeDatasourceQueryReduxAction<any> => {
228230
return {
229231
type: ReduxActionTypes.EXECUTE_DATASOURCE_QUERY_INIT,
230232
payload,

app/client/src/api/DatasourcesApi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export interface EmbeddedRestDatasourceRequest {
2525
pluginId: string;
2626
}
2727

28-
type executeQueryData = Array<{ key: string; value?: string }>;
28+
type executeQueryData = Array<{ key?: string; value?: string }>;
2929

3030
export interface executeDatasourceQueryRequest {
3131
datasourceId: string;

app/client/src/components/ads/Dropdown.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export type DropdownProps = CommonComponentProps &
6666
renderOption?: RenderOption;
6767
isLoading?: boolean;
6868
errorMsg?: string; // If errorMsg is defined, we show dropDown's error state with the message.
69+
helperText?: string;
6970
};
7071
export interface DefaultDropDownValueNodeProps {
7172
selected: DropdownOption;
@@ -309,14 +310,20 @@ const SelectedIcon = styled(Icon)`
309310
const ErrorMsg = styled.span`
310311
${(props) => getTypographyByKey(props, "p3")};
311312
color: ${Colors.POMEGRANATE2};
312-
margin: 6px 0px 10px;
313+
margin-top: 8px;
313314
`;
314315

315316
const ErrorLabel = styled.span`
316317
${(props) => getTypographyByKey(props, "p1")};
317318
color: ${Colors.POMEGRANATE2};
318319
`;
319320

321+
const HelperText = styled.span`
322+
${(props) => getTypographyByKey(props, "p3")};
323+
color: ${Colors.GRAY};
324+
margin-top: 8px;
325+
`;
326+
320327
function DefaultDropDownValueNode({
321328
errorMsg,
322329
renderNode,
@@ -455,12 +462,20 @@ export default function Dropdown(props: DropdownProps) {
455462
SelectedValueNode = DefaultDropDownValueNode,
456463
renderOption,
457464
errorMsg = "",
465+
helperText = "",
458466
} = { ...props };
459467
const [isOpen, setIsOpen] = useState<boolean>(false);
460468
const [selected, setSelected] = useState<DropdownOption>(props.selected);
461469

470+
const closeIfOpen = () => {
471+
if (isOpen) {
472+
setIsOpen(false);
473+
}
474+
};
475+
462476
useEffect(() => {
463477
setSelected(props.selected);
478+
closeIfOpen();
464479
}, [props.selected]);
465480

466481
const optionClickHandler = useCallback(
@@ -474,7 +489,7 @@ export default function Dropdown(props: DropdownProps) {
474489
);
475490

476491
const disabled = props.disabled || isLoading || !!errorMsg;
477-
const downIconColor = errorMsg ? Colors.POMEGRANATE2 : "";
492+
const downIconColor = errorMsg ? Colors.POMEGRANATE2 : Colors.DARK_GRAY;
478493

479494
const dropdownTrigger = props.dropdownTriggerIcon ? (
480495
<DropdownTriggerWrapper
@@ -516,6 +531,9 @@ export default function Dropdown(props: DropdownProps) {
516531
)}
517532
</Selected>
518533
{errorMsg && <ErrorMsg>{errorMsg}</ErrorMsg>}
534+
{helperText && !isOpen && !errorMsg && (
535+
<HelperText>{helperText}</HelperText>
536+
)}
519537
</DropdownSelect>
520538
);
521539
return (

0 commit comments

Comments
 (0)