feat: debugger query pane navigation (#25853)
This commit is contained in:
parent
0fe6145435
commit
fbfb282759
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,3 +30,7 @@ export interface IMatchedSection {
|
||||||
export interface IApiPaneNavigationConfig {
|
export interface IApiPaneNavigationConfig {
|
||||||
tabIndex?: number;
|
tabIndex?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IQueryPaneNavigationConfig {
|
||||||
|
tab: string;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user