diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index 3aac744840..0c7628411f 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -40,6 +40,9 @@ export interface QueryConfig { export type ActionCreateUpdateResponse = ApiResponse & { id: string; jsonPathKeys: Record; + datasource: { + id?: string; + }; }; export type PaginationField = "PREV" | "NEXT"; diff --git a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/TableOrSpreadsheetDropdown/index.tsx b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/TableOrSpreadsheetDropdown/index.tsx index dd9e5c15a3..2aa090d5d3 100644 --- a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/TableOrSpreadsheetDropdown/index.tsx +++ b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/TableOrSpreadsheetDropdown/index.tsx @@ -34,7 +34,9 @@ function TableOrSpreadsheetDropdown() { isLoading={isLoading} isValid={!error} onSelect={(value: string, selectedOption: DefaultOptionType) => { - const option = options.find((d) => d.id === selectedOption.key); + const option = options.find( + (d: DefaultOptionType) => d.id === selectedOption.key, + ); if (option) { onSelect(value, option); diff --git a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/TableOrSpreadsheetDropdown/useTableOrSpreadsheet.tsx b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/TableOrSpreadsheetDropdown/useTableOrSpreadsheet.tsx index b2b592211c..3b323493a4 100644 --- a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/TableOrSpreadsheetDropdown/useTableOrSpreadsheet.tsx +++ b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/TableOrSpreadsheetDropdown/useTableOrSpreadsheet.tsx @@ -24,6 +24,7 @@ import type { DropdownOptionType } from "../../types"; import { getisOneClickBindingConnectingForWidget } from "selectors/oneClickBindingSelectors"; import AnalyticsUtil from "utils/AnalyticsUtil"; import { getWidget } from "sagas/selectors"; +import type { DatasourceStructure } from "entities/Datasource"; export function useTableOrSpreadsheet() { const dispatch = useDispatch(); @@ -34,8 +35,8 @@ export function useTableOrSpreadsheet() { const widget = useSelector((state: AppState) => getWidget(state, widgetId)); - const datasourceStructure = useSelector( - getDatasourceStructureById(config.datasource), + const datasourceStructure: DatasourceStructure = useSelector((state) => + getDatasourceStructureById(state, config.datasource), ); const isDatasourceLoading = useSelector(getDatasourceLoading); diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index d09de3594a..6ff1bcd8bd 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -9,6 +9,7 @@ import { import { all, call, + fork, put, race, select, @@ -16,7 +17,7 @@ import { takeEvery, takeLatest, } from "redux-saga/effects"; -import type { Datasource } from "entities/Datasource"; +import type { Datasource, DatasourceStructure } from "entities/Datasource"; import type { ActionCreateUpdateResponse } from "api/ActionAPI"; import ActionAPI from "api/ActionAPI"; import type { ApiResponse } from "api/ApiResponses"; @@ -68,6 +69,7 @@ import { getActions, getCurrentPageNameByActionId, getDatasource, + getDatasourceStructureById, getDatasources, getEditorConfig, getPageNameByPageId, @@ -136,6 +138,7 @@ import { import { DEFAULT_GRAPHQL_ACTION_CONFIG } from "constants/ApiEditorConstants/GraphQLEditorConstants"; import { DEFAULT_API_ACTION_CONFIG } from "constants/ApiEditorConstants/ApiEditorConstants"; import { createNewApiName, createNewQueryName } from "utils/AppsmithUtils"; +import { fetchDatasourceStructure } from "actions/datasourceActions"; export function* createDefaultActionPayload( pageId: string, @@ -256,6 +259,9 @@ export function* createActionSaga( const newAction = response.data; // @ts-expect-error: type mismatch ActionCreateUpdateResponse vs Action yield put(createActionSuccess(newAction)); + + // we fork to prevent the call from blocking + yield fork(fetchActionDatasourceStructure, newAction); } } catch (error) { yield put({ @@ -265,6 +271,19 @@ export function* createActionSaga( } } +function* fetchActionDatasourceStructure(action: ActionCreateUpdateResponse) { + if (action.datasource?.id) { + const doesDatasourceStructureAlreadyExist: DatasourceStructure = + yield select(getDatasourceStructureById, action.datasource.id); + if (doesDatasourceStructureAlreadyExist) { + return; + } + yield put(fetchDatasourceStructure(action.datasource.id, true)); + } else { + return; + } +} + export function* fetchActionsSaga( action: EvaluationReduxAction, ) { @@ -632,14 +651,12 @@ function* copyActionSaga( // checking if there is existing datasource to be added to the action payload const existingDatasource = datasources.find( - // @ts-expect-error: datasource not present on ActionCreateUpdateResponse (d: Datasource) => d.id === response.data.datasource.id, ); let payload = response.data; if (existingDatasource) { - // @ts-expect-error: datasource not present on ActionCreateUpdateResponse payload = { ...payload, datasource: existingDatasource }; } diff --git a/app/client/src/sagas/DatasourcesSagas.ts b/app/client/src/sagas/DatasourcesSagas.ts index 01a2d0be47..bfc9bfe75a 100644 --- a/app/client/src/sagas/DatasourcesSagas.ts +++ b/app/client/src/sagas/DatasourcesSagas.ts @@ -2,6 +2,7 @@ import { all, call, + fork, put, select, take, @@ -42,6 +43,8 @@ import { getPlugin, getEditorConfig, getPluginByPackageName, + getDatasourcesUsedInApplicationByActions, + getDatasourceStructureById, } from "selectors/entitiesSelector"; import { addMockDatasourceToWorkspace } from "actions/datasourceActions"; import type { @@ -64,6 +67,7 @@ import type { CreateDatasourceConfig } from "api/DatasourcesApi"; import DatasourcesApi from "api/DatasourcesApi"; import type { Datasource, + DatasourceStructure, MockDatasource, TokenResponse, } from "entities/Datasource"; @@ -169,6 +173,32 @@ function* fetchDatasourcesSaga( } } +function* handleFetchDatasourceStructureOnLoad() { + try { + // we fork to prevent the call from blocking + yield fork(fetchDatasourceStructureOnLoad); + } catch (error) {} +} + +function* fetchDatasourceStructureOnLoad() { + try { + // get datasources of all actions used in the the application + const datasourcesUsedInApplication: Datasource[] = yield select( + getDatasourcesUsedInApplicationByActions, + ); + + for (const datasource of datasourcesUsedInApplication) { + // it is very unlikely for this to happen, but it does not hurt to check. + const doesDatasourceStructureAlreadyExist: DatasourceStructure = + yield select(getDatasourceStructureById, datasource.id); + if (doesDatasourceStructureAlreadyExist) { + continue; + } + yield put(fetchDatasourceStructure(datasource.id, true)); + } + } catch (error) {} +} + function* fetchMockDatasourcesSaga() { try { const response: ApiResponse = yield DatasourcesApi.fetchMockDatasources(); @@ -1798,5 +1828,9 @@ export function* watchDatasourcesSagas() { ReduxActionTypes.ADD_AND_FETCH_MOCK_DATASOURCE_STRUCTURE_INIT, addAndFetchDatasourceStructureSaga, ), + takeEvery( + ReduxActionTypes.FETCH_DATASOURCES_SUCCESS, + handleFetchDatasourceStructureOnLoad, + ), ]); } diff --git a/app/client/src/selectors/entitiesSelector.ts b/app/client/src/selectors/entitiesSelector.ts index b856e3b758..a6d354a464 100644 --- a/app/client/src/selectors/entitiesSelector.ts +++ b/app/client/src/selectors/entitiesSelector.ts @@ -12,6 +12,7 @@ import type { } from "entities/Datasource"; import { isEmbeddedRestDatasource } from "entities/Datasource"; import type { Action } from "entities/Action"; +import { isStoredDatasource } from "entities/Action"; import { PluginType } from "entities/Action"; import { find, get, sortBy } from "lodash"; import ImageAlt from "assets/images/placeholder-image.svg"; @@ -41,6 +42,7 @@ import recommendedLibraries from "pages/Editor/Explorer/Libraries/recommendedLib import type { TJSLibrary } from "workers/common/JSLibrary"; import { getEntityNameAndPropertyPath } from "@appsmith/workers/Evaluation/evaluationUtils"; import { getFormValues } from "redux-form"; +import { TEMP_DATASOURCE_ID } from "constants/Datasource"; export const getEntities = (state: AppState): AppState["entities"] => state.entities; @@ -59,15 +61,16 @@ export const getDatasourcesStructure = ( return state.entities.datasources.structure; }; -export const getDatasourceStructureById = - (id: string) => - (state: AppState): DatasourceStructure => { - return state.entities.datasources.structure[id]; - }; +export const getDatasourceStructureById = ( + state: AppState, + id: string, +): DatasourceStructure => { + return state.entities.datasources.structure[id]; +}; export const getDatasourceTableColumns = (datasourceId: string, tableName: string) => (state: AppState) => { - const structure = getDatasourceStructureById(datasourceId)(state); + const structure = getDatasourceStructureById(state, datasourceId); if (structure) { const table = structure.tables?.find((d) => d.name === tableName); @@ -77,7 +80,7 @@ export const getDatasourceTableColumns = }; export const getDatasourceTablePrimaryColumn = (datasourceId: string, tableName: string) => (state: AppState) => { - const structure = getDatasourceStructureById(datasourceId)(state); + const structure = getDatasourceStructureById(state, datasourceId); if (structure) { const table = structure.tables?.find((d) => d.name === tableName); @@ -1086,3 +1089,27 @@ export const getDatasourceScopeValue = ( )?.label; return label; }; + +export const getDatasourcesUsedInApplicationByActions = ( + state: AppState, +): Datasource[] => { + const actions = getActions(state); + const datasources = getDatasources(state); + const datasourceIdsUsedInCurrentApplication = actions.reduce( + (acc, action: ActionData) => { + if ( + isStoredDatasource(action.config.datasource) && + action.config.datasource.id + ) { + acc.add(action.config.datasource.id); + } + return acc; + }, + new Set(), + ); + return datasources.filter( + (ds) => + datasourceIdsUsedInCurrentApplication.has(ds.id) && + ds.id !== TEMP_DATASOURCE_ID, + ); +};