fix: Disable run button when there are empty fields (#24031)
When an API or SQL query is generated, we disable the run button if no input has been provided to the URL and BODY fields respectively. Fixes #23957 #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change > Please delete options that are not relevant. - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) - Breaking change (fix or feature that would cause existing functionality to not work as expected) - Chore (housekeeping or task changes that don't impact user perception) - This change requires a documentation update > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [x] Manual - [ ] Jest - [ ] Cypress > > #### Test Plan > https://github.com/appsmithorg/TestSmith/issues/2412 > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Test-plan-implementation#speedbreaker-features-to-consider-for-every-change) have been covered - [x] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans/_edit#areas-of-interest) - [x] Test plan has been peer reviewed by project stakeholders and other QA members - [x] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
This commit is contained in:
parent
0e0d19b195
commit
1e3a82522e
|
|
@ -28,7 +28,7 @@ describe("Addwidget from Query and bind with other widgets", function () {
|
|||
.focus()
|
||||
.type("SELECT * FROM configs LIMIT 10;");
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(500);
|
||||
cy.wait(1000);
|
||||
// Mock the response for this test
|
||||
cy.intercept("/api/v1/actions/execute", {
|
||||
fixture: "addWidgetTable-mock",
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ describe("Check Suggested Widgets Feature in Auto Layout", function () {
|
|||
.focus()
|
||||
.type("SELECT * FROM configs LIMIT 10;");
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(500);
|
||||
cy.wait(1000);
|
||||
// Mock the response for this test
|
||||
cy.intercept("/api/v1/actions/execute", {
|
||||
fixture: "addWidgetTable-mock",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import { ObjectsRegistry } from "../../../../support/Objects/Registry";
|
||||
|
||||
const agHelper = ObjectsRegistry.AggregateHelper,
|
||||
ee = ObjectsRegistry.EntityExplorer,
|
||||
apiPage = ObjectsRegistry.ApiPage,
|
||||
dataSources = ObjectsRegistry.DataSources;
|
||||
|
||||
const url = "https://www.google.com";
|
||||
|
||||
describe("Block Action Execution when no field is present", () => {
|
||||
it("1. Ensure API Run button is disabled when no url is present", () => {
|
||||
apiPage.CreateApi("FirstAPI", "GET");
|
||||
apiPage.AssertRunButtonDisability(true);
|
||||
apiPage.EnterURL(url);
|
||||
apiPage.AssertRunButtonDisability(false);
|
||||
});
|
||||
|
||||
it("1. Ensure Run button is disabled when no SQL body field is present", () => {
|
||||
let name: any;
|
||||
dataSources.CreateDataSource("MySql", true, false);
|
||||
cy.get("@dsName").then(($dsName) => {
|
||||
name = $dsName;
|
||||
|
||||
agHelper.Sleep(1000);
|
||||
dataSources.NavigateFromActiveDS(name, true);
|
||||
agHelper.GetNClick(dataSources._templateMenu);
|
||||
dataSources.EnterQuery("SELECT * from users");
|
||||
dataSources.AssertRunButtonDisability(false);
|
||||
dataSources.EnterQuery("");
|
||||
dataSources.AssertRunButtonDisability(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -17,11 +17,9 @@ describe("API Panel Test Functionality ", function () {
|
|||
cy.log("Navigation to API Panel screen successful");
|
||||
cy.generateUUID().then((uid) => {
|
||||
cy.CreateAPI(`FirstAPI_${uid}`);
|
||||
cy.RunAPI();
|
||||
cy.log("Creation of FirstAPI Action successful");
|
||||
cy.NavigateToAPI_Panel();
|
||||
cy.CreateAPI(`SecondAPI_${uid}`);
|
||||
cy.RunAPI();
|
||||
cy.CheckAndUnfoldEntityItem("Queries/JS");
|
||||
cy.log("Creation of SecondAPI Action successful");
|
||||
cy.get(".t--entity-name").contains("FirstAPI");
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ describe("Addwidget from Query and bind with other widgets", function () {
|
|||
.focus()
|
||||
.type("SELECT * FROM configs LIMIT 10;");
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(500);
|
||||
cy.wait(1000);
|
||||
// Mock the response for this test
|
||||
cy.intercept("/api/v1/actions/execute", {
|
||||
fixture: "addWidgetTable-mock",
|
||||
|
|
|
|||
|
|
@ -113,10 +113,20 @@ export class ApiPage {
|
|||
this.CreateApi(apiName, apiVerb, aftDSSaved);
|
||||
this.EnterURL(url);
|
||||
//this.agHelper.Sleep(2000);// Added because api name edit takes some time to reflect in api sidebar after the call passes.
|
||||
cy.get(this._apiRunBtn).should("not.be.disabled");
|
||||
this.AssertRunButtonDisability();
|
||||
if (queryTimeout != 10000) this.SetAPITimeout(queryTimeout);
|
||||
}
|
||||
|
||||
AssertRunButtonDisability(disabled = false) {
|
||||
let query = "";
|
||||
if (disabled) {
|
||||
query = "be.disabled";
|
||||
} else {
|
||||
query = "not.be.disabled";
|
||||
}
|
||||
cy.get(this._apiRunBtn).should(query);
|
||||
}
|
||||
|
||||
EnterURL(url: string) {
|
||||
this.agHelper.EnterValue(url, {
|
||||
propFieldName: this._resourceUrl,
|
||||
|
|
@ -397,7 +407,7 @@ export class ApiPage {
|
|||
this.EnterURL(url);
|
||||
this.agHelper.AssertAutoSave();
|
||||
//this.agHelper.Sleep(2000);// Added because api name edit takes some time to reflect in api sidebar after the call passes.
|
||||
cy.get(this._apiRunBtn).should("not.be.disabled");
|
||||
this.AssertRunButtonDisability();
|
||||
if (queryTimeout != 10000) this.SetAPITimeout(queryTimeout);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -834,6 +834,16 @@ export class DataSources {
|
|||
}
|
||||
}
|
||||
|
||||
AssertRunButtonDisability(disabled = false) {
|
||||
let query = "";
|
||||
if (disabled) {
|
||||
query = "be.disabled";
|
||||
} else {
|
||||
query = "not.be.disabled";
|
||||
}
|
||||
cy.get(this._runQueryBtn).should(query);
|
||||
}
|
||||
|
||||
public ReadQueryTableResponse(index: number, timeout = 100) {
|
||||
//timeout can be sent higher values incase of larger tables
|
||||
this.agHelper.Sleep(timeout); //Settling time for table!
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ import { TEMP_DATASOURCE_ID } from "constants/Datasource";
|
|||
import LazyCodeEditor from "components/editorComponents/LazyCodeEditor";
|
||||
import { getCodeMirrorNamespaceFromEditor } from "utils/getCodeMirrorNamespace";
|
||||
import { isDynamicValue } from "utils/DynamicBindingUtils";
|
||||
import { DEFAULT_DATASOURCE_NAME } from "constants/ApiEditorConstants/ApiEditorConstants";
|
||||
|
||||
type ReduxStateProps = {
|
||||
workspaceId: string;
|
||||
|
|
@ -527,7 +528,7 @@ class EmbeddedDatasourcePathComponent extends React.Component<
|
|||
evaluatedValue={this.handleEvaluatedValue()}
|
||||
focusElementName={`${this.props.actionName}.url`}
|
||||
/>
|
||||
{datasource && datasource.name !== "DEFAULT_REST_DATASOURCE" && (
|
||||
{datasource && datasource.name !== DEFAULT_DATASOURCE_NAME && (
|
||||
<StyledTooltip
|
||||
id="custom-tooltip"
|
||||
width={this.state.highlightedElementWidth}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import {
|
|||
|
||||
const DEFAULT_METHOD_TYPE = HTTP_METHOD.GET;
|
||||
|
||||
export const DEFAULT_DATASOURCE_NAME = "DEFAULT_REST_DATASOURCE";
|
||||
|
||||
export const DEFAULT_API_ACTION_CONFIG: ApiActionConfig = {
|
||||
timeoutInMillisecond: DEFAULT_ACTION_TIMEOUT,
|
||||
encodeParamsToggle: true,
|
||||
|
|
@ -30,7 +32,7 @@ export const DEFAULT_API_ACTION_CONFIG: ApiActionConfig = {
|
|||
export const DEFAULT_CREATE_API_CONFIG = {
|
||||
config: DEFAULT_API_ACTION_CONFIG,
|
||||
datasource: {
|
||||
name: "DEFAULT_REST_DATASOURCE",
|
||||
name: DEFAULT_DATASOURCE_NAME,
|
||||
},
|
||||
eventData: {
|
||||
actionType: "API",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
import { PluginPackageName } from "entities/Action";
|
||||
import { PluginPackageName, PluginName } from "entities/Action";
|
||||
|
||||
export const QUERY_BODY_FIELDS = [
|
||||
"actionConfiguration.body",
|
||||
"actionConfiguration.formData.body.data",
|
||||
];
|
||||
|
||||
export const SQL_DATASOURCES: Array<string> = [
|
||||
PluginName.POSTGRES,
|
||||
PluginName.MS_SQL,
|
||||
PluginName.MY_SQL,
|
||||
PluginName.ORACLE,
|
||||
];
|
||||
|
||||
export const PLUGIN_PACKAGE_DBS = [
|
||||
PluginPackageName.POSTGRES,
|
||||
PluginPackageName.MONGO,
|
||||
|
|
|
|||
|
|
@ -13,11 +13,6 @@ export enum PluginType {
|
|||
REMOTE = "REMOTE",
|
||||
}
|
||||
|
||||
// more can be added subsequently.
|
||||
export enum PluginName {
|
||||
MONGO = "MongoDB",
|
||||
}
|
||||
|
||||
export enum PluginPackageName {
|
||||
POSTGRES = "postgres-plugin",
|
||||
MONGO = "mongo-plugin",
|
||||
|
|
@ -30,6 +25,17 @@ export enum PluginPackageName {
|
|||
ORACLE = "oracle-plugin",
|
||||
}
|
||||
|
||||
// more can be added subsequently.
|
||||
export enum PluginName {
|
||||
MONGO = "MongoDB",
|
||||
POSTGRES = "PostgreSQL",
|
||||
MY_SQL = "MySQL",
|
||||
MS_SQL = "Microsoft SQL Server",
|
||||
GOOGLE_SHEETS = "Google Sheets",
|
||||
FIRESTORE = "Firestore",
|
||||
ORACLE = "Oracle",
|
||||
}
|
||||
|
||||
export enum PaginationType {
|
||||
NONE = "NONE",
|
||||
PAGE_NO = "PAGE_NO",
|
||||
|
|
@ -110,6 +116,7 @@ export const isStoredDatasource = (val: any): val is StoredDatasource => {
|
|||
return true;
|
||||
};
|
||||
export interface StoredDatasource {
|
||||
name?: string;
|
||||
id: string;
|
||||
pluginId?: string;
|
||||
datasourceConfiguration?: { url?: string };
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ import SearchSnippets from "../../common/SearchSnippets";
|
|||
import { DocsLink, openDoc } from "../../../constants/DocumentationLinks";
|
||||
import { getApiPaneConfigSelectedTabIndex } from "selectors/apiPaneSelectors";
|
||||
import { noop } from "lodash";
|
||||
import { DEFAULT_DATASOURCE_NAME } from "constants/ApiEditorConstants/ApiEditorConstants";
|
||||
|
||||
const Form = styled.form`
|
||||
position: relative;
|
||||
|
|
@ -554,6 +555,7 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
|
|||
(state: AppState) => state.entities.actions.map((action) => action.config),
|
||||
equal,
|
||||
);
|
||||
|
||||
const currentActionConfig: Action | undefined = actions.find(
|
||||
(action) => action.id === params.apiId || action.id === params.queryId,
|
||||
);
|
||||
|
|
@ -572,6 +574,20 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
|
|||
getPlugin(state, pluginId ?? ""),
|
||||
);
|
||||
|
||||
// this gets the url of the current action's datasource
|
||||
const actionDatasourceUrl =
|
||||
currentActionConfig?.datasource?.datasourceConfiguration?.url || "";
|
||||
// this gets the name of the current action's datasource
|
||||
const actionDatasourceName = currentActionConfig?.datasource.name || "";
|
||||
|
||||
// if the url is empty and the action's datasource name is the default datasource name (this means the api does not have a datasource attached)
|
||||
// or the user does not have permission,
|
||||
// we block action execution.
|
||||
const blockExecution =
|
||||
(!actionDatasourceUrl &&
|
||||
actionDatasourceName === DEFAULT_DATASOURCE_NAME) ||
|
||||
!isExecutePermitted;
|
||||
|
||||
// Debugger render flag
|
||||
const showDebugger = useSelector(showDebuggerFlag);
|
||||
|
||||
|
|
@ -621,7 +637,7 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
|
|||
/>
|
||||
<Button
|
||||
className="t--apiFormRunBtn"
|
||||
isDisabled={!isExecutePermitted}
|
||||
isDisabled={blockExecution}
|
||||
isLoading={isRunning}
|
||||
onClick={() => {
|
||||
onRunClick();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ import type { InjectedFormProps } from "redux-form";
|
|||
import { Tag } from "@blueprintjs/core";
|
||||
import { isString, noop } from "lodash";
|
||||
import type { Datasource } from "entities/Datasource";
|
||||
import { getPluginImages } from "selectors/entitiesSelector";
|
||||
import {
|
||||
getPluginImages,
|
||||
getPluginNameFromId,
|
||||
} from "selectors/entitiesSelector";
|
||||
import FormControl from "../FormControl";
|
||||
import type { Action, QueryAction, SaaSAction } from "entities/Action";
|
||||
import { SlashCommand } from "entities/Action";
|
||||
|
|
@ -81,7 +84,7 @@ import { getErrorAsString } from "sagas/ActionExecution/errorUtils";
|
|||
import type { UpdateActionPropertyActionPayload } from "actions/pluginActionActions";
|
||||
import Guide from "pages/Editor/GuidedTour/Guide";
|
||||
import { inGuidedTour } from "selectors/onboardingSelectors";
|
||||
import { EDITOR_TABS } from "constants/QueryEditorConstants";
|
||||
import { EDITOR_TABS, SQL_DATASOURCES } from "constants/QueryEditorConstants";
|
||||
import type { FormEvalOutput } from "reducers/evaluationReducers/formEvaluationReducer";
|
||||
import { isValidFormConfig } from "reducers/evaluationReducers/formEvaluationReducer";
|
||||
import {
|
||||
|
|
@ -426,6 +429,19 @@ export function EditorJSONtoForm(props: Props) {
|
|||
userWorkspacePermissions,
|
||||
);
|
||||
|
||||
// get the current action's plugin name
|
||||
const currentActionPluginName = useSelector((state: AppState) =>
|
||||
getPluginNameFromId(state, currentActionConfig?.pluginId || ""),
|
||||
);
|
||||
|
||||
// this gets the url of the current action
|
||||
const actionBody = currentActionConfig?.actionConfiguration?.body || "";
|
||||
|
||||
// if (the body is empty and the action is an sql datasource) or the user does not have permission, block action execution.
|
||||
const blockExecution =
|
||||
(!actionBody && SQL_DATASOURCES.includes(currentActionPluginName)) ||
|
||||
!isExecutePermitted;
|
||||
|
||||
// Query is executed even once during the session, show the response data.
|
||||
if (executedQueryData) {
|
||||
if (!executedQueryData.isExecutionSuccess) {
|
||||
|
|
@ -912,7 +928,7 @@ export function EditorJSONtoForm(props: Props) {
|
|||
<Button
|
||||
className="t--run-query"
|
||||
data-guided-tour-iid="run-query"
|
||||
isDisabled={!isExecutePermitted}
|
||||
isDisabled={blockExecution}
|
||||
isLoading={isRunning}
|
||||
onClick={onRunClick}
|
||||
size="md"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user