feat: debugger query pane navigation (#25853)

This commit is contained in:
akash-codemonk 2023-08-16 17:17:24 +05:30 committed by GitHub
parent 0fe6145435
commit fbfb282759
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 204 additions and 4 deletions

View File

@ -0,0 +1,83 @@
/// <reference types="cypress-tags" />
import {
agHelper,
homePage,
dataSources,
entityExplorer,
entityItems,
debuggerHelper,
} from "../../../../support/Objects/ObjectsCore";
describe("excludeForAirgap", "Query pane navigation", () => {
let ds1Name: string;
let ds2Name: string;
before("Add dsl and create datasource from the", () => {
agHelper.GenerateUUID();
cy.get("@guid").then((uid) => {
homePage.CreateNewWorkspace("Workspace" + uid, true);
homePage.CreateAppInWorkspace("Workspace" + uid, "App" + uid);
});
dataSources.CreateDataSource("S3", true, false);
cy.get("@dsName").then(($dsName) => {
ds1Name = $dsName as unknown as string;
});
dataSources.CreateDataSource("Firestore", true, false);
cy.get("@dsName").then(($dsName) => {
ds2Name = $dsName as unknown as string;
});
});
it("1. Switching between S3 query and firestore query from the debugger", () => {
entityExplorer.CreateNewDsQuery(ds1Name);
agHelper.EnterValue("{{test}}", {
propFieldName:
".t--actionConfiguration\\.formData\\.list\\.sortBy\\.data\\[0\\]\\.column",
directInput: true,
inputFieldName: "",
});
agHelper.UpdateCodeInput(
".t--actionConfiguration\\.formData\\.bucket\\.data",
"test",
);
debuggerHelper.AssertErrorCount(1);
cy.get("@dsName").then(($dsName) => {
ds2Name = $dsName as unknown as string;
});
entityExplorer.CreateNewDsQuery(ds2Name);
agHelper.UpdateCodeInput(
".t--actionConfiguration\\.formData\\.limitDocuments\\.data",
"{{test}}",
);
agHelper.UpdateCodeInput(
".t--actionConfiguration\\.formData\\.path\\.data",
"test",
);
debuggerHelper.AssertErrorCount(2);
debuggerHelper.ClickDebuggerIcon();
debuggerHelper.ClicklogEntityLink();
agHelper.AssertElementVisibility(
".t--actionConfiguration\\.formData\\.limitDocuments\\.data",
);
debuggerHelper.ClicklogEntityLink(true);
agHelper.AssertElementVisibility(
".t--actionConfiguration\\.formData\\.list\\.sortBy\\.data\\[0\\]\\.column",
);
entityExplorer.ActionContextMenuByEntityName({
entityNameinLeftSidebar: "Query1",
entityType: entityItems.Query,
});
entityExplorer.ActionContextMenuByEntityName({
entityNameinLeftSidebar: "Query2",
entityType: entityItems.Query,
});
dataSources.DeleteDSFromEntityExplorer(ds1Name);
dataSources.DeleteDSFromEntityExplorer(ds2Name);
});
});

View File

@ -1395,6 +1395,17 @@ export class AggregateHelper extends ReusableHelper {
//return this.ScrollIntoView(selector, index, timeout).should("be.visible");//to find out why this is failing. //return this.ScrollIntoView(selector, index, timeout).should("be.visible");//to find out why this is failing.
} }
public AssertElementNotVisible(
selector: ElementType,
index = 0,
timeout = 20000,
) {
return this.GetElement(selector, timeout)
.eq(index)
.scrollIntoView()
.should("not.be.visible");
}
public CheckForErrorToast(error: string) { public CheckForErrorToast(error: string) {
cy.get("body").then(($ele) => { cy.get("body").then(($ele) => {
if ($ele.find(this.locator._toastMsg).length) { if ($ele.find(this.locator._toastMsg).length) {

View File

@ -852,6 +852,7 @@ const ActionTypes = {
FETCH_PRODUCT_ALERT_INIT: "FETCH_PRODUCT_ALERT_INIT", FETCH_PRODUCT_ALERT_INIT: "FETCH_PRODUCT_ALERT_INIT",
FETCH_PRODUCT_ALERT_SUCCESS: "FETCH_PRODUCT_ALERT_SUCCESS", FETCH_PRODUCT_ALERT_SUCCESS: "FETCH_PRODUCT_ALERT_SUCCESS",
UPDATE_PRODUCT_ALERT_CONFIG: "UPDATE_PRODUCT_ALERT_CONFIG", UPDATE_PRODUCT_ALERT_CONFIG: "UPDATE_PRODUCT_ALERT_CONFIG",
FORM_EVALUATION_EMPTY_BUFFER: "FORM_EVALUATION_EMPTY_BUFFER",
}; };
export const ReduxActionTypes = { export const ReduxActionTypes = {

View File

@ -5,13 +5,14 @@ import {
getPlugin, getPlugin,
getSettingConfig, getSettingConfig,
} from "selectors/entitiesSelector"; } from "selectors/entitiesSelector";
import { call, delay, select } from "redux-saga/effects"; import { call, delay, put, select } from "redux-saga/effects";
import PaneNavigation from "../PaneNavigation"; import PaneNavigation from "../PaneNavigation";
import type { Plugin } from "api/PluginApi"; import type { Plugin } from "api/PluginApi";
import { getCurrentApplicationId } from "selectors/editorSelectors"; import { getCurrentApplicationId } from "selectors/editorSelectors";
import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers"; import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers";
import history from "utils/history"; import history from "utils/history";
import { NAVIGATION_DELAY } from "../costants"; import { NAVIGATION_DELAY } from "../costants";
import { setFocusableInputField } from "actions/editorContextActions";
export default class ActionPaneNavigation extends PaneNavigation { export default class ActionPaneNavigation extends PaneNavigation {
action!: Action; action!: Action;
@ -61,6 +62,8 @@ export default class ActionPaneNavigation extends PaneNavigation {
if (!url) return; if (!url) return;
history.push(url); history.push(url);
yield delay(NAVIGATION_DELAY); yield delay(NAVIGATION_DELAY);
// Reset context switching field for the id, to allow scrolling to the error field
yield put(setFocusableInputField(id));
} }
*scrollToView(propertyPath: string) { *scrollToView(propertyPath: string) {

View File

@ -0,0 +1,83 @@
import { call, delay, put, race, select, take } from "redux-saga/effects";
import type { EntityInfo, IQueryPaneNavigationConfig } from "../types";
import { ActionPaneNavigation } from "./exports";
import { NAVIGATION_DELAY } from "../costants";
import { setQueryPaneConfigSelectedTabIndex } from "actions/queryPaneActions";
import { EDITOR_TABS } from "constants/QueryEditorConstants";
import { getFormEvaluationState } from "selectors/formSelectors";
import type { FormEvaluationState } from "reducers/evaluationReducers/formEvaluationReducer";
import { isEmpty } from "lodash";
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import { isActionSaving } from "selectors/entitiesSelector";
export default class QueryPaneNavigation extends ActionPaneNavigation {
constructor(entityInfo: EntityInfo) {
super(entityInfo);
this.getConfig = this.getConfig.bind(this);
this.navigate = this.navigate.bind(this);
this.getTab = this.getTab.bind(this);
this.waitForFormUpdate = this.waitForFormUpdate.bind(this);
}
*getConfig() {
let config: IQueryPaneNavigationConfig = {
tab: EDITOR_TABS.QUERY,
};
if (!this.entityInfo.propertyPath) return {};
const tab: string = yield call(this.getTab, this.entityInfo.propertyPath);
config = {
tab,
};
return config;
}
*navigate() {
const config: IQueryPaneNavigationConfig = yield call(this.getConfig);
yield call(this.navigateToUrl);
if (!this.entityInfo.propertyPath) return;
if (config.tab) {
yield put(setQueryPaneConfigSelectedTabIndex(config.tab));
}
yield call(this.waitForFormUpdate);
yield call(this.scrollToView, this.entityInfo.propertyPath);
}
*waitForFormUpdate() {
const formEvaluationState: FormEvaluationState = yield select(
getFormEvaluationState,
);
const isSaving: boolean = yield select(isActionSaving(this.action.id));
if (isEmpty(formEvaluationState[this.action.id]) || isSaving) {
// Wait till the form fields are computed
yield take(ReduxActionTypes.FORM_EVALUATION_EMPTY_BUFFER);
yield delay(NAVIGATION_DELAY);
} else {
yield race({
evaluation: take(ReduxActionTypes.FORM_EVALUATION_EMPTY_BUFFER),
timeout: delay(NAVIGATION_DELAY),
});
}
}
*getTab(propertyPath: string) {
let tab = EDITOR_TABS.QUERY;
const modifiedProperty = propertyPath.replace(
"config",
"actionConfiguration",
);
const inSettingsTab: boolean = yield call(
this.isInSettingsTab,
modifiedProperty,
);
if (inSettingsTab) {
tab = EDITOR_TABS.SETTINGS;
}
return tab;
}
}

View File

@ -1,2 +1,3 @@
export { default as ActionPaneNavigation } from "./ActionPaneNavigation"; export { default as ActionPaneNavigation } from "./ActionPaneNavigation";
export { default as ApiPaneNavigation } from "./ApiPaneNavigation"; export { default as ApiPaneNavigation } from "./ApiPaneNavigation";
export { default as QueryPaneNavigation } from "./QueryPaneNavigation";

View File

@ -2,16 +2,23 @@ import { PluginType, type Action } from "entities/Action";
import type { EntityInfo } from "../types"; import type { EntityInfo } from "../types";
import { getAction } from "selectors/entitiesSelector"; import { getAction } from "selectors/entitiesSelector";
import { select } from "redux-saga/effects"; import { select } from "redux-saga/effects";
import { ActionPaneNavigation, ApiPaneNavigation } from "./exports"; import {
ActionPaneNavigation,
ApiPaneNavigation,
QueryPaneNavigation,
} from "./exports";
export default class ActionPaneNavigationFactory { export default class ActionPaneNavigationFactory {
static *create(entityInfo: EntityInfo) { static *create(entityInfo: EntityInfo) {
const action: Action | undefined = yield select(getAction, entityInfo.id); const action: Action | undefined = yield select(getAction, entityInfo.id);
if (!action) throw Error(`Couldn't find action with id: ${entityInfo.id}`); if (!action) throw Error(`Couldn't find action with id: ${entityInfo.id}`);
switch (action.pluginType) { switch (action.pluginType) {
case PluginType.API: case PluginType.API:
return new ApiPaneNavigation(entityInfo); return new ApiPaneNavigation(entityInfo);
case PluginType.DB:
case PluginType.SAAS:
case PluginType.REMOTE:
return new QueryPaneNavigation(entityInfo);
default: default:
return new ActionPaneNavigation(entityInfo); return new ActionPaneNavigation(entityInfo);
} }

View File

@ -30,3 +30,7 @@ export interface IMatchedSection {
export interface IApiPaneNavigationConfig { export interface IApiPaneNavigationConfig {
tabIndex?: number; tabIndex?: number;
} }
export interface IQueryPaneNavigationConfig {
tab: string;
}

View File

@ -28,6 +28,7 @@ import {
extractQueueOfValuesToBeFetched, extractQueueOfValuesToBeFetched,
} from "./helper"; } from "./helper";
import type { DatasourceConfiguration } from "entities/Datasource"; import type { DatasourceConfiguration } from "entities/Datasource";
import { buffers } from "redux-saga";
export type FormEvalActionPayload = { export type FormEvalActionPayload = {
formId: string; formId: string;
@ -260,9 +261,15 @@ function* fetchDynamicValueSaga(
} }
function* formEvaluationChangeListenerSaga() { function* formEvaluationChangeListenerSaga() {
const buffer = buffers.fixed();
const formEvalChannel: ActionPattern<ReduxAction<FormEvalActionPayload>> = const formEvalChannel: ActionPattern<ReduxAction<FormEvalActionPayload>> =
yield actionChannel(FORM_EVALUATION_REDUX_ACTIONS); yield actionChannel(FORM_EVALUATION_REDUX_ACTIONS, buffer as any);
while (true) { while (true) {
if (buffer.isEmpty()) {
yield put({
type: ReduxActionTypes.FORM_EVALUATION_EMPTY_BUFFER,
});
}
const action: ReduxAction<FormEvalActionPayload> = yield take( const action: ReduxAction<FormEvalActionPayload> = yield take(
formEvalChannel, formEvalChannel,
); );