From 86b2f2b0174e745c7c90ba3d0924bc395f86259a Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Thu, 10 Sep 2020 13:13:17 +0530 Subject: [PATCH] Release (#515) * Add more options to action settings (#473) * Update install.sh * Add few ternjs defs for moment (#508) * default icon selection issue fixed in stories (#495) * Feature: Toggle component (#490) * Toggle component implemented with story * props fixed * lighten and darken colors implemented * removed the unused css properties * removed the used import in story * toggle hover state fixed * storywrapper import updated * Drag And drop widget (#503) * Drag And drop widget * Updated cordinates * Feature: Tooltip component with story (#498) * tooltip component implemented with story * default props fixed * used constant classes names * blueprint classname corrected * Feature: Checkbox component (#475) * checkbox disabled state added and checked UI fixed * checkbox imported corrected in story * checkbox implemented using input and label elements. * align and isChecked props removed * used styled component for span * Feature: Radio group component (#481) * Radio group component with story implemented * radio group implemented using native input and label. * radio component height fixed in story * disabled name in theme corrected for radio * align prop removed * removed duplicate user object (#513) removed id field which is never populated added help modal in applications page and intercom to load in help modal shutdown intercom on signout to clear existing chats Co-authored-by: Nikhil Nandagopal * Feature: Table Widget - multi row selection (#507) * Fix typo (#514) * Update install.sh * selectedRowIndexes changed to selectedRowIndices (#516) Data type of selectedRowIndices changed to array of numbers * Fixing storybook build (#517) * Fixes (#522) - Remove cache fields from settings - Call the event callback method with success false to fix button widget loading issue * Add migration to fix incorrect action IDs for onLoad (#519) * Add migration to fix incorrect action IDs for onLoad * Remove redundant comments * Fix migration to work with published onLoad actions as well * Only update the action ID when correcting onLoad action IDs * Fix migration name for correcting action IDs Co-authored-by: Trisha Anand * Fix migration id for correcting action IDs * Reformat code Co-authored-by: Trisha Anand * deprecated selectedOptionValueArr * Feature/intercom (#521) * removed duplicate user object removed id field which is never populated added help modal in applications page and intercom to load in help modal shutdown intercom on signout to clear existing chats * added analytics to help modal and invite user unescaped bindings before evaluation to prune newlines * updated action confirmation text * added a check to ignore old properties which are no longer dynamic * fixed test case * fix tests Co-authored-by: Nikhil Nandagopal Co-authored-by: Hetu Nandu * Remove run on page load in api pane (#524) * Remove run on page load in api pane * Remove run on page load setting in query pane * Revert removal of run query on page load Co-authored-by: akash-codemonk <67054171+akash-codemonk@users.noreply.github.com> Co-authored-by: Nikhil Nandagopal Co-authored-by: devrk96 Co-authored-by: NandanAnantharamu <67676905+NandanAnantharamu@users.noreply.github.com> Co-authored-by: Nikhil Nandagopal Co-authored-by: vicky-primathon <67091118+vicky-primathon@users.noreply.github.com> Co-authored-by: satbir121 <39981226+satbir121@users.noreply.github.com> Co-authored-by: Shrikant Sharat Kandula Co-authored-by: Trisha Anand --- .../Entity_Explorer_DragAndDropWidget_spec.js | 59 +++++++ .../QueryPane/ConfirmRunAction_spec.js | 49 ++++++ .../cypress/locators/explorerlocators.json | 5 +- app/client/cypress/support/commands.js | 43 +++++ app/client/package.json | 4 +- app/client/src/components/ads/Checkbox.tsx | 102 +++++++++++- app/client/src/components/ads/Radio.tsx | 156 +++++++++++++++++- app/client/src/components/ads/Spinner.tsx | 13 +- app/client/src/components/ads/Toggle.tsx | 143 +++++++++++++++- app/client/src/components/ads/Tooltip.tsx | 59 +++++++ app/client/src/components/ads/common.tsx | 15 ++ .../appsmith/ReactTableComponent.tsx | 4 +- .../designSystems/appsmith/Table.tsx | 8 +- .../designSystems/appsmith/help/HelpModal.tsx | 26 ++- .../components/formControls/SwitchControl.tsx | 2 +- .../src/components/stories/Button.stories.tsx | 8 +- .../components/stories/Checkbox.stories.tsx | 22 +++ .../src/components/stories/Icon.stories.tsx | 19 +-- .../src/components/stories/Menu.stories.tsx | 14 +- .../src/components/stories/Radio.stories.tsx | 56 +++++++ .../src/components/stories/Tabs.stories.tsx | 26 ++- .../src/components/stories/Toggle.stories.tsx | 23 +++ .../components/stories/Tooltip.stories.tsx | 34 ++++ app/client/src/constants/DefaultTheme.tsx | 3 + app/client/src/constants/defs/moment.json | 109 ++++++++++++ app/client/src/constants/userConstants.ts | 5 +- app/client/src/entities/Action/index.ts | 1 + .../src/mockResponses/ActionSettings.tsx | 52 +++++- app/client/src/pages/Applications/index.tsx | 2 + .../src/pages/Editor/ConfirmRunModal.tsx | 8 +- app/client/src/pages/Editor/EditorHeader.tsx | 2 +- .../src/pages/Editor/QueryEditor/Form.tsx | 1 + app/client/src/pages/Editor/index.tsx | 17 -- .../pages/organization/OrgInviteUsersForm.tsx | 2 + .../src/reducers/uiReducers/usersReducer.ts | 4 +- app/client/src/sagas/ActionExecutionSagas.ts | 66 +++++--- app/client/src/utils/AnalyticsUtil.tsx | 16 +- app/client/src/utils/DynamicBindingUtils.ts | 38 +++-- .../utils/autocomplete/EntityDefinitions.ts | 2 +- .../src/utils/autocomplete/TernServer.ts | 3 +- app/client/src/widgets/DropdownWidget.tsx | 3 + app/client/src/widgets/TableWidget.tsx | 45 ++--- app/client/yarn.lock | 104 ++++++------ .../server/migrations/DatabaseChangelog.java | 89 ++++++++++ deploy/install.sh | 14 +- 45 files changed, 1254 insertions(+), 222 deletions(-) create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js create mode 100644 app/client/cypress/integration/Smoke_TestSuite/QueryPane/ConfirmRunAction_spec.js create mode 100644 app/client/src/components/ads/Tooltip.tsx create mode 100644 app/client/src/components/stories/Checkbox.stories.tsx create mode 100644 app/client/src/components/stories/Radio.stories.tsx create mode 100644 app/client/src/components/stories/Toggle.stories.tsx create mode 100644 app/client/src/components/stories/Tooltip.stories.tsx create mode 100644 app/client/src/constants/defs/moment.json diff --git a/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js new file mode 100644 index 0000000000..b9fc71360c --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js @@ -0,0 +1,59 @@ +const testdata = require("../../../fixtures/testdata.json"); +const apiwidget = require("../../../locators/apiWidgetslocator.json"); +const explorer = require("../../../locators/explorerlocators.json"); +const commonlocators = require("../../../locators/commonlocators.json"); +const formWidgetsPage = require("../../../locators/FormWidgets.json"); +const publish = require("../../../locators/publishWidgetspage.json"); + +const pageid = "MyPage"; + +describe("Entity explorer Drag and Drop widgets testcases", function() { + it("Drag and drop form widget and validate", function() { + cy.log("Login Successful"); + cy.get(explorer.addWidget).click(); + cy.get(commonlocators.entityExplorersearch).should("be.visible"); + cy.get(commonlocators.entityExplorersearch) + .clear() + .type("form"); + cy.dragAndDropToCanvas("formwidget"); + /** + * @param{Text} Random Text + * @param{FormWidget}Mouseover + * @param{FormPre Css} Assertion + */ + cy.widgetText( + "FormTest", + formWidgetsPage.formWidget, + formWidgetsPage.formInner, + ); + /** + * @param{Text} Random Colour + */ + cy.testCodeMirror(this.data.colour); + cy.get(formWidgetsPage.formD) + .should("have.css", "background-color") + .and("eq", this.data.rgbValue); + /** + * @param{toggleButton Css} Assert to be checked + */ + cy.togglebar(commonlocators.scrollView); + cy.get(formWidgetsPage.formD) + .scrollTo("bottom") + .should("be.visible"); + cy.get(commonlocators.editPropCrossButton).click(); + cy.get(explorer.closeWidgets).click(); + cy.PublishtheApp(); + cy.get(publish.backToEditor) + .first() + .click(); + cy.SearchEntityandOpen("FormTest"); + cy.get(explorer.property) + .last() + .click({ force: true }); + cy.get(apiwidget.propertyList).then(function($lis) { + expect($lis).to.have.length(2); + expect($lis.eq(0)).to.contain("{{FormTest.isVisible}}"); + expect($lis.eq(1)).to.contain("{{FormTest.data}}"); + }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/QueryPane/ConfirmRunAction_spec.js b/app/client/cypress/integration/Smoke_TestSuite/QueryPane/ConfirmRunAction_spec.js new file mode 100644 index 0000000000..b1e016724a --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/QueryPane/ConfirmRunAction_spec.js @@ -0,0 +1,49 @@ +const queryLocators = require("../../../locators/QueryEditor.json"); +const queryEditor = require("../../../locators/QueryEditor.json"); +let datasourceName; + +describe("Confirm run action", function() { + beforeEach(() => { + cy.createPostgresDatasource(); + cy.get("@createDatasource").then(httpResponse => { + datasourceName = httpResponse.response.body.data.name; + }); + }); + + it("Confirm run action", () => { + cy.NavigateToQueryEditor(); + + cy.get(".t--datasource-name") + .contains(datasourceName) + .click(); + cy.get(queryLocators.templateMenu).click(); + cy.get(".CodeMirror textarea") + .first() + .focus() + .type("select * from configs"); + cy.get("li:contains('Settings')").click({ force: true }); + cy.get("[data-cy=confirmBeforeExecute]") + .find(".bp3-switch") + .click(); + + cy.get(queryEditor.runQuery).click(); + cy.get(".bp3-dialog") + .find(".bp3-button") + .contains("Confirm") + .click(); + cy.wait("@postExecute").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + + cy.get(queryEditor.deleteQuery).click(); + cy.wait("@deleteAction").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + + cy.deletePostgresDatasource(datasourceName); + }); +}); diff --git a/app/client/cypress/locators/explorerlocators.json b/app/client/cypress/locators/explorerlocators.json index 3a74403c37..a416dfa58d 100644 --- a/app/client/cypress/locators/explorerlocators.json +++ b/app/client/cypress/locators/explorerlocators.json @@ -16,5 +16,8 @@ "property": ".language-appsmith-binding", "editNameField": ".editing input", "editEntityField": ".bp3-editable-text-input", - "entity":".t--entity-name" + "entity":".t--entity-name", + "addWidget":".widgets .t--entity-add-btn", + "dropHere":".appsmith_widget_0", + "closeWidgets":".t--close-widgets-sidebar" } \ No newline at end of file diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index 272139d441..7125e79cbd 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -34,6 +34,15 @@ Cypress.Commands.add("createOrg", orgName => { ); }); +Cypress.Commands.add( + "dragTo", + { prevSubject: "element" }, + (subject, targetEl) => { + cy.wrap(subject).trigger("dragstart"); + cy.get(targetEl).trigger("drop"); + }, +); + Cypress.Commands.add("navigateToOrgSettings", orgName => { cy.get(homePage.orgList.concat(orgName).concat(")")) .scrollIntoView() @@ -1307,6 +1316,30 @@ Cypress.Commands.add("fillPostgresDatasourceForm", () => { ); }); +Cypress.Commands.add("createPostgresDatasource", () => { + cy.NavigateToDatasourceEditor(); + cy.get(datasourceEditor.PostgreSQL).click(); + + cy.getPluginFormsAndCreateDatasource(); + + cy.fillPostgresDatasourceForm(); + + cy.testSaveDatasource(); +}); + +Cypress.Commands.add("deletePostgresDatasource", datasourceName => { + cy.NavigateToDatasourceEditor(); + cy.get(".t--entity-name:contains(PostgreSQL)").click(); + cy.get(`.t--entity-name:contains(${datasourceName})`).click(); + + cy.get(".t--delete-datasource").click(); + cy.wait("@deleteDatasource").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); +}); + Cypress.Commands.add("runQuery", () => { cy.get(queryEditor.runQuery).click(); cy.wait("@postExecute").should( @@ -1363,6 +1396,16 @@ Cypress.Commands.add("runAndDeleteQuery", () => { ); }); +Cypress.Commands.add("dragAndDropToCanvas", widgetType => { + const selector = `.t--widget-card-draggable-${widgetType}`; + cy.get(selector) + .trigger("mousedown", { button: 0 }, { force: true }) + .trigger("mousemove", 300, -300, { force: true }); + cy.get(explorer.dropHere) + .click() + .trigger("mouseup", { force: true }); +}); + Cypress.Commands.add("openPropertyPane", widgetType => { const selector = `.t--draggable-${widgetType}`; cy.get(selector) diff --git a/app/client/package.json b/app/client/package.json index 54319e0891..6ad9d2f312 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -16,8 +16,8 @@ "@craco/craco": "^5.6.1", "@manaflair/redux-batch": "^1.0.0", "@optimizely/optimizely-sdk": "^4.0.0", - "@sentry/react": "^5.22.2", - "@sentry/tracing": "^5.22.2", + "@sentry/react": "^5.22.3", + "@sentry/tracing": "^5.22.3", "@sentry/webpack-plugin": "^1.12.1", "@types/chance": "^1.0.7", "@types/lodash": "^4.14.120", diff --git a/app/client/src/components/ads/Checkbox.tsx b/app/client/src/components/ads/Checkbox.tsx index 7f7325bdee..8221668fa6 100644 --- a/app/client/src/components/ads/Checkbox.tsx +++ b/app/client/src/components/ads/Checkbox.tsx @@ -1,14 +1,100 @@ import { CommonComponentProps } from "./common"; +import React, { useState } from "react"; +import styled from "styled-components"; type CheckboxProps = CommonComponentProps & { label: string; - isChecked: boolean; - onCheckChange: (isChecked: boolean) => void; - isLoading: boolean; - align: "left" | "right"; - cypressSelector?: string; + onCheckChange?: (isChecked: boolean) => void; }; -export default function Checkbox(props: CheckboxProps) { - return null; -} +const Checkmark = styled.span<{ + disabled?: boolean; + isChecked?: boolean; +}>` + position: absolute; + top: 1px; + left: 0; + width: ${props => props.theme.spaces[8]}px; + height: ${props => props.theme.spaces[8]}px; + background-color: ${props => + props.isChecked + ? props.disabled + ? props.theme.colors.blackShades[3] + : props.theme.colors.info.main + : "transparent"}; + border: 2px solid + ${props => + props.isChecked + ? props.disabled + ? props.theme.colors.blackShades[3] + : props.theme.colors.info.main + : props.theme.colors.blackShades[4]}; + + &::after { + content: ""; + position: absolute; + display: none; + top: 0px; + left: 4px; + width: 6px; + height: 11px; + border: solid + ${props => + props.disabled ? "#565656" : props.theme.colors.blackShades[9]}; + border-width: 0 2px 2px 0; + transform: rotate(45deg); + } +`; + +const StyledCheckbox = styled.label<{ + disabled?: boolean; +}>` + position: relative; + display: block; + width: 100%; + cursor: ${props => (props.disabled ? "not-allowed" : "pointer")}; + font-weight: ${props => props.theme.typography.p1.fontWeight}; + font-size: ${props => props.theme.typography.p1.fontSize}px; + line-height: ${props => props.theme.typography.p1.lineHeight}px; + letter-spacing: ${props => props.theme.typography.p1.letterSpacing}px; + color: ${props => props.theme.colors.blackShades[7]}; + padding-left: ${props => props.theme.spaces[12] - 2}px; + + input { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; + } + + input:checked ~ ${Checkmark}:after { + display: block; + } +`; + +const Checkbox = (props: CheckboxProps) => { + const [checked, setChecked] = useState(false); + + const onChangeHandler = (checked: boolean) => { + setChecked(checked); + props.onCheckChange && props.onCheckChange(checked); + }; + + return ( + + {props.label} + ) => + onChangeHandler(e.target.checked) + } + /> + + + ); +}; + +export default Checkbox; diff --git a/app/client/src/components/ads/Radio.tsx b/app/client/src/components/ads/Radio.tsx index e66b8693f1..7486b67c82 100644 --- a/app/client/src/components/ads/Radio.tsx +++ b/app/client/src/components/ads/Radio.tsx @@ -1,12 +1,154 @@ import { CommonComponentProps } from "./common"; +import React, { useState, useEffect } from "react"; +import styled from "styled-components"; -type RadioProps = CommonComponentProps & { - align?: "horizontal" | "vertical" | "column" | "row"; - columns?: number; - rows?: number; - value?: string; +type OptionProps = { + label: string; + value: string; + disabled?: boolean; + onSelect?: (value: string) => void; }; -export default function Radio(props: RadioProps) { - return null; +type RadioProps = CommonComponentProps & { + columns?: number; + rows?: number; + defaultValue: string; + onSelect?: (value: string) => void; + options: OptionProps[]; +}; + +const RadioGroup = styled.div<{ + rows?: number; +}>` + display: flex; + flex-wrap: wrap; + ${props => + props.rows && props.rows > 0 + ? ` + flex-direction: column; + height: 100%; + ` + : null}; +`; + +const Radio = styled.label<{ + disabled?: boolean; + columns?: number; + rows?: number; +}>` + display: block; + position: relative; + padding-left: ${props => props.theme.spaces[12] - 2}px; + cursor: ${props => (props.disabled ? "not-allowed" : "pointer")}; + font-size: ${props => props.theme.typography.p1.fontSize}px; + font-weight: ${props => props.theme.typography.p1.fontWeight}; + line-height: ${props => props.theme.typography.p1.lineHeight}px; + letter-spacing: ${props => props.theme.typography.p1.letterSpacing}px; + color: ${props => props.theme.colors.blackShades[9]}; + ${props => + props.rows && props.rows > 0 + ? `flex-basis: calc(100% / ${props.rows})` + : null}; + ${props => + props.columns && props.columns > 0 + ? ` + flex-basis: calc(100% / ${props.columns}); + margin-bottom: ${props.theme.spaces[11] + 1}px; + ` + : null}; + + input { + position: absolute; + opacity: 0; + cursor: pointer; + } + + .checkbox { + position: absolute; + top: 0; + left: 0; + width: ${props => props.theme.spaces[8]}px; + height: ${props => props.theme.spaces[8]}px; + background-color: transparent; + border: ${props => props.theme.spaces[1] - 2}px solid + ${props => props.theme.colors.blackShades[4]}; + border-radius: 50%; + margin-top: ${props => props.theme.spaces[0]}px; + } + + .checkbox:after { + content: ""; + position: absolute; + display: none; + } + + input:checked ~ .checkbox:after { + display: block; + } + + input:disabled ~ .checkbox:after { + background-color: ${props => props.theme.colors.radio.disabled}; + } + + .checkbox:after { + content: ""; + position: absolute; + width: ${props => props.theme.spaces[4]}px; + height: ${props => props.theme.spaces[4]}px; + ${props => + props.disabled + ? `background-color: ${props.theme.colors.radio.disabled}` + : `background-color: ${props.theme.colors.info.main};`}; + top: ${props => props.theme.spaces[1] - 2}px; + left: ${props => props.theme.spaces[1] - 2}px; + border-radius: 50%; + } +`; + +export default function RadioComponent(props: RadioProps) { + const [selected, setSelected] = useState(props.defaultValue); + + useEffect(() => { + if (props.rows && props.columns && props.rows > 0 && props.columns > 0) { + console.error( + "Please pass either rows prop or column prop but not both.", + ); + } + }, [props]); + + useEffect(() => { + setSelected(props.defaultValue); + }, [props.defaultValue]); + + const onChangeHandler = (value: string) => { + setSelected(value); + props.onSelect && props.onSelect(value); + }; + + return ( + onChangeHandler(e.target.value)} + > + {props.options.map((option: OptionProps, index: number) => ( + + {option.label} + option.onSelect && option.onSelect(e.target.value)} + checked={selected === option.value} + name="radio" + /> + + + ))} + + ); } diff --git a/app/client/src/components/ads/Spinner.tsx b/app/client/src/components/ads/Spinner.tsx index 9adc4101ad..2f40c640a1 100644 --- a/app/client/src/components/ads/Spinner.tsx +++ b/app/client/src/components/ads/Spinner.tsx @@ -1,6 +1,7 @@ import React from "react"; import styled, { keyframes } from "styled-components"; import { sizeHandler, IconSize } from "./Icon"; +import { Classes } from "./common"; const rotate = keyframes` 100% { @@ -23,14 +24,14 @@ const dash = keyframes` } `; -const SvgContainer = styled("svg")` +const SvgContainer = styled.svg` animation: ${rotate} 2s linear infinite; width: ${props => sizeHandler(props.size)}px; height: ${props => sizeHandler(props.size)}px; `; -const SvgCircle = styled("circle")` - stroke: white; +const SvgCircle = styled.circle` + stroke: ${props => props.theme.colors.blackShades[9]}; stroke-linecap: round; animation: ${dash} 1.5s ease-in-out infinite; stroke-width: ${props => props.theme.spaces[1]}px; @@ -46,7 +47,11 @@ Spinner.defaultProp = { export default function Spinner(props: SpinnerProp) { return ( - + ); diff --git a/app/client/src/components/ads/Toggle.tsx b/app/client/src/components/ads/Toggle.tsx index 2146ba2179..1b1dc1de23 100644 --- a/app/client/src/components/ads/Toggle.tsx +++ b/app/client/src/components/ads/Toggle.tsx @@ -1,10 +1,149 @@ -import { CommonComponentProps } from "./common"; +import { CommonComponentProps, Classes, lighten, darken } from "./common"; +import React, { useState, useEffect } from "react"; +import styled from "styled-components"; +import Spinner from "./Spinner"; type ToggleProps = CommonComponentProps & { onToggle: (value: boolean) => void; value: boolean; }; +const StyledToggle = styled.label<{ + isLoading?: boolean; + disabled?: boolean; + value: boolean; +}>` + position: relative; + display: block; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + background-color: ${props => + props.isLoading + ? props.theme.colors.blackShades[3] + : props.theme.colors.blackShades[4]}; + transition: 0.4s; + width: 46px; + height: 23px; + border-radius: 92px; + } + + ${props => + props.isLoading + ? `.toggle-spinner { + position: absolute; + top: 3px; + left: 17px; + } + .slider:before { + display: none; + }` + : `.slider:before { + position: absolute; + content: ""; + height: 19px; + width: 19px; + top: 2px; + left: 2px; + background-color: ${ + props.disabled && !props.value + ? lighten(props.theme.colors.tertiary.dark, 16) + : props.theme.colors.blackShades[9] + }; + box-shadow: ${ + props.value + ? "1px 0px 3px rgba(0, 0, 0, 0.16)" + : "-1px 0px 3px rgba(0, 0, 0, 0.16)" + }; + opacity: ${props.value ? 1 : 0.9}; + transition: .4s; + border-radius: 50%; + }`} + + && input:hover + .slider:before { + opacity: 1; + } + + input:focus + .slider:before { + ${props => (props.value ? "opacity: 0.6" : "opacity: 0.7")}; + } + + input:disabled + .slider:before { + ${props => (props.value ? "opacity: 0.24" : "opacity: 1")}; + } + + input:checked + .slider:before { + transform: translateX(23px); + } + + input:checked + .slider { + background-color: ${props => props.theme.colors.info.main}; + } + + input:hover + .slider, + input:focus + .slider { + background-color: ${props => + props.value + ? lighten(props.theme.colors.info.main, 12) + : lighten(props.theme.colors.blackShades[3], 16)}; + } + + input:disabled + .slider { + cursor: not-allowed; + background-color: ${props => + props.value && !props.isLoading + ? darken(props.theme.colors.info.darker, 20) + : props.theme.colors.tertiary.dark}; + } + + .${Classes.SPINNER} { + circle { + stroke: ${props => props.theme.colors.blackShades[6]}; + } + } +`; + export default function Toggle(props: ToggleProps) { - return null; + const [value, setValue] = useState(false); + + useEffect(() => { + setValue(props.value); + }, [props.value]); + + const onChangeHandler = (value: boolean) => { + setValue(value); + props.onToggle && props.onToggle(value); + }; + + return ( + + ) => + onChangeHandler(e.target.checked) + } + /> + + {props.isLoading ? ( +
+ +
+ ) : null} +
+ ); } diff --git a/app/client/src/components/ads/Tooltip.tsx b/app/client/src/components/ads/Tooltip.tsx new file mode 100644 index 0000000000..1a021f7b8d --- /dev/null +++ b/app/client/src/components/ads/Tooltip.tsx @@ -0,0 +1,59 @@ +import React from "react"; +import { CommonComponentProps } from "./common"; +import styled from "styled-components"; +import { Position, Tooltip, Classes } from "@blueprintjs/core"; +import { Classes as CsClasses } from "./common"; + +type Variant = "dark" | "light"; + +type TooltipProps = CommonComponentProps & { + content: JSX.Element | string; + position?: Position; + children: JSX.Element; + variant?: Variant; +}; + +const TooltipWrapper = styled.div<{ variant?: Variant }>` + .${Classes.TOOLTIP} .${Classes.POPOVER_CONTENT} { + padding: 10px 12px; + border-radius: 0px; + background-color: ${props => + props.variant === "dark" + ? props.theme.colors.blackShades[0] + : props.theme.colors.blackShades[8]}; + } + div.${Classes.POPOVER_ARROW} { + display: block; + } + .${Classes.TOOLTIP} { + box-shadow: 0px 12px 20px rgba(0, 0, 0, 0.35);a + } + .${Classes.TOOLTIP} .${CsClasses.BP3_POPOVER_ARROW_BORDER}, + &&&& .${Classes.TOOLTIP} .${CsClasses.BP3_POPOVER_ARROW_FILL} { + fill: ${props => + props.variant === "dark" + ? props.theme.colors.blackShades[0] + : props.theme.colors.blackShades[8]}; + } +`; + +const TooltipComponent = (props: TooltipProps) => { + return ( + + + {props.children} + + + ); +}; + +TooltipComponent.defaultProps = { + position: Position.TOP, + variant: "dark", +}; + +export default TooltipComponent; diff --git a/app/client/src/components/ads/common.tsx b/app/client/src/components/ads/common.tsx index 4f779bebb0..a0e3f44750 100644 --- a/app/client/src/components/ads/common.tsx +++ b/app/client/src/components/ads/common.tsx @@ -1,4 +1,5 @@ import { Theme } from "constants/DefaultTheme"; +import tinycolor from "tinycolor2"; import styled from "styled-components"; export interface CommonComponentProps { @@ -14,6 +15,9 @@ export type ThemeProp = { export enum Classes { ICON = "cs-icon", TEXT = "cs-text", + BP3_POPOVER_ARROW_BORDER = "bp3-popover-arrow-border", + BP3_POPOVER_ARROW_FILL = "bp3-popover-arrow-fill", + SPINNER = "cs-spinner", } export const hexToRgb = ( @@ -42,6 +46,17 @@ export const hexToRgba = (color: string, alpha: number) => { return `rgba(${value.r}, ${value.g}, ${value.b}, ${alpha});`; }; +export const lighten = (color: string, amount: number) => { + return tinycolor(color) + .lighten(amount) + .toString(); +}; + +export const darken = (color: string, amount: number) => { + return tinycolor(color) + .darken(amount) + .toString(); +}; export const StoryWrapper = styled.div` background: #1a191c; height: 700px; diff --git a/app/client/src/components/designSystems/appsmith/ReactTableComponent.tsx b/app/client/src/components/designSystems/appsmith/ReactTableComponent.tsx index 87055ee567..7ec12d1fc7 100644 --- a/app/client/src/components/designSystems/appsmith/ReactTableComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/ReactTableComponent.tsx @@ -54,7 +54,7 @@ interface ReactTableComponentProps { serverSidePaginationEnabled: boolean; columnActions?: ColumnAction[]; selectedRowIndex: number; - selectedRowIndexes: number[]; + selectedRowIndices: number[]; multiRowSelection?: boolean; hiddenColumns?: string[]; columnNameMap?: { [key: string]: string }; @@ -301,7 +301,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => { }} serverSidePaginationEnabled={props.serverSidePaginationEnabled} selectedRowIndex={props.selectedRowIndex} - selectedRowIndexes={props.selectedRowIndexes} + selectedRowIndices={props.selectedRowIndices} disableDrag={() => { props.disableDrag(true); }} diff --git a/app/client/src/components/designSystems/appsmith/Table.tsx b/app/client/src/components/designSystems/appsmith/Table.tsx index 91ac503b64..824b33f5e3 100644 --- a/app/client/src/components/designSystems/appsmith/Table.tsx +++ b/app/client/src/components/designSystems/appsmith/Table.tsx @@ -49,7 +49,7 @@ interface TableProps { prevPageClick: () => void; serverSidePaginationEnabled: boolean; selectedRowIndex: number; - selectedRowIndexes: number[]; + selectedRowIndices: number[]; disableDrag: () => void; enableDrag: () => void; searchTableData: (searchKey: any) => void; @@ -111,7 +111,7 @@ export const Table = (props: TableProps) => { } const subPage = page.slice(startIndex, endIndex); const selectedRowIndex = props.selectedRowIndex; - const selectedRowIndexes = props.selectedRowIndexes; + const selectedRowIndices = props.selectedRowIndices; const tableSizes = TABLE_SIZES[props.compactMode || CompactModeTypes.DEFAULT]; /* Subtracting 9px to handling widget padding */ return ( @@ -205,7 +205,7 @@ export const Table = (props: TableProps) => { "tr" + `${ row.index === selectedRowIndex || - selectedRowIndexes.includes(row.index) + selectedRowIndices.includes(row.index) ? " selected-row" : "" }` @@ -215,7 +215,7 @@ export const Table = (props: TableProps) => { props.selectTableRow( row, row.index === selectedRowIndex || - selectedRowIndexes.includes(row.index), + selectedRowIndices.includes(row.index), ); }} key={rowIndex} diff --git a/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx b/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx index 710a6bcd01..ccace89270 100644 --- a/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx +++ b/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx @@ -13,8 +13,11 @@ import { getAppsmithConfigs } from "configs"; import { LayersContext } from "constants/Layers"; import { connect } from "react-redux"; import { AppState } from "reducers"; +import { getCurrentUser } from "selectors/usersSelectors"; +import { User } from "constants/userConstants"; +import AnalyticsUtil from "utils/AnalyticsUtil"; -const { algolia } = getAppsmithConfigs(); +const { algolia, cloudHosting, intercomAppID } = getAppsmithConfigs(); const HelpButton = styled.button<{ highlight: boolean; layer: number; @@ -54,10 +57,29 @@ const HelpIcon = HelpIcons.HELP_ICON; type Props = { isHelpModalOpen: boolean; dispatch: any; + user?: User; + page: string; }; class HelpModal extends React.Component { static contextType = LayersContext; + + componentDidMount() { + const { user } = this.props; + if (cloudHosting && intercomAppID && window.Intercom) { + window.Intercom("boot", { + // eslint-disable-next-line @typescript-eslint/camelcase + app_id: intercomAppID, + // eslint-disable-next-line @typescript-eslint/camelcase + user_id: user?.username, + // eslint-disable-next-line @typescript-eslint/camelcase + custom_launcher_selector: "#intercom-trigger", + name: user?.name, + email: user?.email, + }); + } + } + render() { const { dispatch, isHelpModalOpen } = this.props; const layers = this.context; @@ -91,6 +113,7 @@ class HelpModal extends React.Component { highlight={!isHelpModalOpen} layer={layers.help} onClick={() => { + AnalyticsUtil.logEvent("OPEN_HELP", { page: this.props.page }); dispatch(setHelpModalVisibility(!isHelpModalOpen)); }} > @@ -104,6 +127,7 @@ class HelpModal extends React.Component { const mapStateToProps = (state: AppState) => ({ isHelpModalOpen: getHelpModalOpen(state), + user: getCurrentUser(state), }); export default connect(mapStateToProps)(HelpModal); diff --git a/app/client/src/components/formControls/SwitchControl.tsx b/app/client/src/components/formControls/SwitchControl.tsx index e9e2cae7d5..0946f856e2 100644 --- a/app/client/src/components/formControls/SwitchControl.tsx +++ b/app/client/src/components/formControls/SwitchControl.tsx @@ -33,7 +33,7 @@ export class SwitchField extends React.Component { return (
- + {label} {isRequired && "*"} diff --git a/app/client/src/components/stories/Button.stories.tsx b/app/client/src/components/stories/Button.stories.tsx index 22bcb3b19a..4f6a7f7931 100644 --- a/app/client/src/components/stories/Button.stories.tsx +++ b/app/client/src/components/stories/Button.stories.tsx @@ -3,7 +3,7 @@ import Button, { Size, Category, Variant } from "components/ads/Button"; import { withKnobs, select, boolean, text } from "@storybook/addon-knobs"; import { withDesign } from "storybook-addon-designs"; import { StoryWrapper } from "components/ads/common"; -import { IconCollection } from "components/ads/Icon"; +import { IconCollection, IconName } from "components/ads/Icon"; export default { title: "Button", @@ -17,7 +17,11 @@ export const withDynamicProps = () => ( size={select("size", Object.values(Size), Size.large)} category={select("category", Object.values(Category), Category.primary)} variant={select("variant", Object.values(Variant), Variant.info)} - icon={select("Icon name", IconCollection, undefined)} + icon={select( + "Icon name", + ["Select icon" as IconName, ...IconCollection], + "Select icon" as IconName, + )} isLoading={boolean("Loading", false)} disabled={boolean("Disabled", false)} text={text("text", "Get")} diff --git a/app/client/src/components/stories/Checkbox.stories.tsx b/app/client/src/components/stories/Checkbox.stories.tsx new file mode 100644 index 0000000000..befd978777 --- /dev/null +++ b/app/client/src/components/stories/Checkbox.stories.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { withKnobs, boolean, text } from "@storybook/addon-knobs"; +import { withDesign } from "storybook-addon-designs"; +import { action } from "@storybook/addon-actions"; +import Checkbox from "components/ads/Checkbox"; +import { StoryWrapper } from "components/ads/common"; + +export default { + title: "Checkbox", + component: Checkbox, + decorators: [withKnobs, withDesign], +}; + +export const CustomCheckbox = () => ( + + + +); diff --git a/app/client/src/components/stories/Icon.stories.tsx b/app/client/src/components/stories/Icon.stories.tsx index 90b1b5be7e..303738ed15 100644 --- a/app/client/src/components/stories/Icon.stories.tsx +++ b/app/client/src/components/stories/Icon.stories.tsx @@ -28,18 +28,7 @@ export const ButtonIcon = () => ( export const BordelessIcon = () => ( @@ -48,11 +37,7 @@ export const BordelessIcon = () => ( export const AppIconVariant = () => ( { W} disabled={boolean("First option disabled", false)} @@ -112,7 +116,11 @@ export const MenuStory = () => { {boolean("First menu item divider", false) ? : null} W} /> diff --git a/app/client/src/components/stories/Radio.stories.tsx b/app/client/src/components/stories/Radio.stories.tsx new file mode 100644 index 0000000000..ff0d9480b5 --- /dev/null +++ b/app/client/src/components/stories/Radio.stories.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { + withKnobs, + select, + boolean, + text, + number, +} from "@storybook/addon-knobs"; +import { withDesign } from "storybook-addon-designs"; +import { StoryWrapper } from "components/ads/common"; +import RadioComponent from "components/ads/Radio"; +import { action } from "@storybook/addon-actions"; + +export default { + title: "Radio", + component: RadioComponent, + decorators: [withKnobs, withDesign], +}; + +export const Radio = () => ( + +
+ +
+
+); diff --git a/app/client/src/components/stories/Tabs.stories.tsx b/app/client/src/components/stories/Tabs.stories.tsx index 6b7e97a9c4..17eb511ef8 100644 --- a/app/client/src/components/stories/Tabs.stories.tsx +++ b/app/client/src/components/stories/Tabs.stories.tsx @@ -2,7 +2,7 @@ import React from "react"; import { TabComponent, TabProp } from "components/ads/Tabs"; import { select, text, withKnobs } from "@storybook/addon-knobs"; import { withDesign } from "storybook-addon-designs"; -import { IconCollection } from "components/ads/Icon"; +import { IconCollection, IconName } from "components/ads/Icon"; import { StoryWrapper } from "components/ads/common"; export default { @@ -87,13 +87,29 @@ const TabStory = (props: any) => { export const Tabs = () => ( ); diff --git a/app/client/src/components/stories/Toggle.stories.tsx b/app/client/src/components/stories/Toggle.stories.tsx new file mode 100644 index 0000000000..7a0d8ade16 --- /dev/null +++ b/app/client/src/components/stories/Toggle.stories.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { withKnobs, boolean } from "@storybook/addon-knobs"; +import { withDesign } from "storybook-addon-designs"; +import { action } from "@storybook/addon-actions"; +import Toggle from "components/ads/Toggle"; +import { StoryWrapper } from "components/ads/common"; + +export default { + title: "Toggle", + component: Toggle, + decorators: [withKnobs, withDesign], +}; + +export const CustomToggle = () => ( + + + +); diff --git a/app/client/src/components/stories/Tooltip.stories.tsx b/app/client/src/components/stories/Tooltip.stories.tsx new file mode 100644 index 0000000000..c5cddb6cc3 --- /dev/null +++ b/app/client/src/components/stories/Tooltip.stories.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { select, withKnobs } from "@storybook/addon-knobs"; +import { withDesign } from "storybook-addon-designs"; +import { Position } from "@blueprintjs/core"; +import TooltipComponent from "components/ads/Tooltip"; +import { StoryWrapper } from "components/ads/common"; +import Text, { TextType } from "components/ads/Text"; +import Button from "components/ads/Button"; + +export default { + title: "Tooltip", + component: TooltipComponent, + decorators: [withKnobs, withDesign], +}; + +export const MenuStory = () => ( + +
+ + This is a tooltip + + } + variant={select("variant", ["dark", "light"], "dark")} + > + + Hover to show tooltip + + +
+
+); diff --git a/app/client/src/constants/DefaultTheme.tsx b/app/client/src/constants/DefaultTheme.tsx index 2d110ce332..32471b2d9f 100644 --- a/app/client/src/constants/DefaultTheme.tsx +++ b/app/client/src/constants/DefaultTheme.tsx @@ -572,6 +572,9 @@ export const theme: Theme = { darker: "#2B1A1D", darkest: "#462F32", }, + radio: { + disabled: "#565656", + }, primaryOld: Colors.GREEN, primaryDarker: Colors.JUNGLE_GREEN, primaryDarkest: Colors.JUNGLE_GREEN_DARKER, diff --git a/app/client/src/constants/defs/moment.json b/app/client/src/constants/defs/moment.json new file mode 100644 index 0000000000..94c32a003b --- /dev/null +++ b/app/client/src/constants/defs/moment.json @@ -0,0 +1,109 @@ +{ + "!name": "moment", + "moment": { + "!type": "fn(inp?: MomentInput, format?: MomentFormatSpecification, strict?: boolean) -> Moment", + "!url": "https://momentjs.com/docs/#/parsing/", + "!doc": "Returns a wrapper for the Date object", + "now": { + "!type": "fn() -> number", + "!doc": "Returns unix time in milliseconds." + } + }, + "Moment": { + "isValid": { + "!type": "fn() -> bool", + "!url": "https://momentjs.com/docs/#/parsing/is-valid/", + "!doc": "Check whether the Moment considers the date invalid" + }, + "add": { + "!type": "fn(amount?: DurationInputArg1, unit?: DurationInputArg2)", + "!url": "https://momentjs.com/docs/#/manipulating/add/", + "!doc": "Mutates the original moment by adding time." + }, + "format": { + "!type": "fn(format?: string) -> string", + "!url": "https://momentjs.com/docs/#/displaying/format/", + "!doc": "Takes a string of tokens and replaces them with their corresponding values" + }, + "isAfter": { + "!type": "fn(inp?: MomentInput, granularity?: ?) -> bool", + "!url": "https://momentjs.com/docs/#/query/is-after/", + "!doc": "Check if a moment is after another moment." + }, + "isSame": { + "!type": "fn(inp?: MomentInput, granularity?: ?) -> bool", + "!url": "https://momentjs.com/docs/#/query/is-same/", + "!doc": "Check if a moment is the same as another moment." + }, + "isBefore": { + "!type": "fn(inp?: MomentInput, granularity?: ?) -> bool", + "!url": "https://momentjs.com/docs/#/query/is-before/", + "!doc": "Check if a moment is before another moment" + }, + "fromNow": { + "!type": "fn(withoutSuffix?: bool) -> string", + "!url": "https://momentjs.com/docs/#/displaying/fromnow/", + "!doc": "Get relative time" + }, + "clone": { + "!type": "fn() -> Moment", + "!url": "https://momentjs.com/docs/#/parsing/moment-clone/", + "!doc": "Returns the clone of a moment," + }, + "year": { + "!type": "fn(y: number) -> Moment", + "!url": "https://momentjs.com/docs/#/get-set/year/", + "!doc": "Gets or sets the year" + }, + "month": { + "!type": "fn() -> number", + "!url": "https://momentjs.com/docs/#/get-set/month/", + "!doc": "Gets or sets the month" + }, + "day": { + "!type": "fn() -> number", + "!url": "https://momentjs.com/docs/#/get-set/day/", + "!doc": "Gets or sets the day of the week." + }, + "date": { + "!type": "fn() -> number", + "!url": "https://momentjs.com/docs/#/get-set/date/", + "!doc": "Gets or sets the day of the month." + }, + "hour": { + "!type": "fn() -> number", + "!url": "https://momentjs.com/docs/#/get-set/hour/", + "!doc": "Gets or sets the hour." + }, + "minute": { + "!type": "fn() -> number", + "!url": "https://momentjs.com/docs/#/get-set/minute/", + "!doc": "Gets or sets the minutes." + }, + "second": { + "!type": "fn([s])", + "!url": "https://momentjs.com/docs/#/get-set/second/", + "!doc": "Gets or sets the seconds." + }, + "millisecond": { + "!type": "fn([ms])", + "!url": "https://momentjs.com/docs/#/get-set/millisecond/", + "!doc": "Gets or sets the milliseconds." + }, + "get": { + "!type": "fn(unit: ?) -> number", + "!url": "https://momentjs.com/docs/#/get-set/get/", + "!doc": "String getter" + }, + "set": { + "!type": "fn(unit: ?, value: number) -> Moment", + "!url": "https://momentjs.com/docs/#/get-set/set/", + "!doc": "Generic setter, accepting unit as first argument, and value as second" + }, + "toDate": { + "!type": "fn()", + "!url": "https://momentjs.com/docs/#/displaying/as-javascript-date/", + "!doc": "Get a copy of the native Date object that Moment.js wraps" + } + } +} \ No newline at end of file diff --git a/app/client/src/constants/userConstants.ts b/app/client/src/constants/userConstants.ts index ea0e91914d..1eb644e959 100644 --- a/app/client/src/constants/userConstants.ts +++ b/app/client/src/constants/userConstants.ts @@ -1,12 +1,15 @@ export const ANONYMOUS_USERNAME = "anonymousUser"; +type Gender = "MALE" | "FEMALE"; + export type User = { - id: string; email: string; currentOrganizationId: string; organizationIds: string[]; applications: UserApplication[]; username: string; + name: string; + gender: Gender; }; export interface UserApplication { diff --git a/app/client/src/entities/Action/index.ts b/app/client/src/entities/Action/index.ts index 73f19713fe..c913d8283b 100644 --- a/app/client/src/entities/Action/index.ts +++ b/app/client/src/entities/Action/index.ts @@ -71,6 +71,7 @@ export interface Action { providerId?: string; provider?: ActionProvider; documentation?: { text: string }; + confirmBeforeExecute?: boolean; } export interface RestAction extends Action { diff --git a/app/client/src/mockResponses/ActionSettings.tsx b/app/client/src/mockResponses/ActionSettings.tsx index 036ec9cdad..2a01006e46 100644 --- a/app/client/src/mockResponses/ActionSettings.tsx +++ b/app/client/src/mockResponses/ActionSettings.tsx @@ -9,12 +9,29 @@ export const queryActionSettingsConfig = [ controlType: "SWITCH", info: "Will refresh data everytime page is reloaded", }, + { + label: "Request confirmation before running query", + configProperty: "confirmBeforeExecute", + controlType: "SWITCH", + info: "Ask confirmation from the user everytime before refreshing data", + }, // { - // label: "Request confirmation before running query", - // configProperty: "requestConfirmation", + // label: "Cache response", + // configProperty: "shouldCacheResponse", // controlType: "SWITCH", - // info: "Ask confirmation from the user everytime before refreshing data", // }, + // { + // label: "Cache timeout (in milliseconds)", + // configProperty: "cacheTimeout", + // controlType: "INPUT_TEXT", + // dataType: "NUMBER", + // }, + { + label: "Query Timeout (in milliseconds)", + configProperty: "actionConfiguration.timeoutInMillisecond", + controlType: "INPUT_TEXT", + dataType: "NUMBER", + }, ], }, ]; @@ -24,18 +41,35 @@ export const apiActionSettingsConfig = [ sectionName: "", id: 1, children: [ + // { + // label: "Run api on Page load", + // configProperty: "executeOnLoad", + // controlType: "SWITCH", + // info: "Will refresh data everytime page is reloaded", + // }, { - label: "Run api on Page load", - configProperty: "executeOnLoad", + label: "Request confirmation before running api", + configProperty: "confirmBeforeExecute", controlType: "SWITCH", - info: "Will refresh data everytime page is reloaded", + info: "Ask confirmation from the user everytime before refreshing data", }, // { - // label: "Request confirmation before running api", - // configProperty: "requestConfirmation", + // label: "Cache response", + // configProperty: "shouldCacheResponse", // controlType: "SWITCH", - // info: "Ask confirmation from the user everytime before refreshing data", // }, + // { + // label: "Cache timeout (in milliseconds)", + // configProperty: "cacheTimeout", + // controlType: "INPUT_TEXT", + // dataType: "NUMBER", + // }, + { + label: "Api Timeout (in milliseconds)", + configProperty: "actionConfiguration.timeoutInMillisecond", + controlType: "INPUT_TEXT", + dataType: "NUMBER", + }, ], }, ]; diff --git a/app/client/src/pages/Applications/index.tsx b/app/client/src/pages/Applications/index.tsx index 9858a0fb03..e0ead7b216 100644 --- a/app/client/src/pages/Applications/index.tsx +++ b/app/client/src/pages/Applications/index.tsx @@ -39,6 +39,7 @@ import { import { Directions } from "utils/helpers"; import { HeaderIcons } from "icons/HeaderIcons"; import { duplicateApplication } from "actions/applicationActions"; +import HelpModal from "components/designSystems/appsmith/help/HelpModal"; const OrgDropDown = styled.div` display: flex; @@ -337,6 +338,7 @@ class Applications extends Component< ); })} + ); } diff --git a/app/client/src/pages/Editor/ConfirmRunModal.tsx b/app/client/src/pages/Editor/ConfirmRunModal.tsx index ce4aa9facf..5d9f572092 100644 --- a/app/client/src/pages/Editor/ConfirmRunModal.tsx +++ b/app/client/src/pages/Editor/ConfirmRunModal.tsx @@ -19,12 +19,14 @@ class ConfirmRunModal extends React.Component { const { dispatch, isModalOpen } = this.props; const handleClose = () => { dispatch(showRunActionConfirmModal(false)); + + dispatch(cancelRunActionConfirmModal()); }; return ( - +
- Are you sure you want to refresh your current data + Are you sure you want to perform this action?
@@ -39,7 +41,7 @@ class ConfirmRunModal extends React.Component { />