diff --git a/app/client/.dockerignore b/app/client/.dockerignore index 4376f41539..94d5de98bc 100644 --- a/app/client/.dockerignore +++ b/app/client/.dockerignore @@ -1,5 +1,4 @@ .git .idea node_modules -build build.tgz \ No newline at end of file diff --git a/app/client/.gitignore b/app/client/.gitignore index 33be47b5f7..e019741285 100755 --- a/app/client/.gitignore +++ b/app/client/.gitignore @@ -29,4 +29,5 @@ yarn-error.log* .idea .storybook-out/ cypress/videos -results/ \ No newline at end of file +cypress/screenshots +results/ diff --git a/app/client/.gitlab-ci.yml b/app/client/.gitlab-ci.yml index 7d38185c39..89ec8a5b57 100644 --- a/app/client/.gitlab-ci.yml +++ b/app/client/.gitlab-ci.yml @@ -4,6 +4,17 @@ - master - merge_requests +.set_env_variables: &set_env_variables + - | + if [ "$CI_COMMIT_BRANCH" == "master" ]; then + REACT_APP_ENVIRONMENT="PRODUCTION" + elif [ "$CI_COMMIT_BRANCH" == "release" ]; then + REACT_APP_ENVIRONMENT="STAGING" + REACT_APP_BASE_URL="https://release-api.appsmith.com" + else + REACT_APP_ENVIRONMENT="DEVELOPMENT" + REACT_APP_BASE_URL="https://release-api.appsmith.com" + fi image: cypress/base:10.16.3 variables: @@ -25,32 +36,38 @@ stages: - package - deploy -react-build: +react-build-release: stage: build script: + - *set_env_variables - yarn install # show where the Cypress test runner binaries are cached - $(npm bin)/cypress cache path # show all installed versions of Cypress binary - $(npm bin)/cypress cache list - $(npm bin)/cypress verify + - REACT_APP_ENVIRONMENT=$REACT_APP_ENVIRONMENT REACT_APP_BASE_URL=$REACT_APP_BASE_URL GIT_SHA=$CI_COMMIT_SHORT_SHA yarn build + artifacts: + expire_in: 1 week + paths: + - build/ only: - release - merge_requests -cypress-test: +cypress-test-release: stage: test script: - - REACT_APP_ENVIRONMENT=DEVELOPMENT REACT_APP_BASE_URL="https://release-api.appsmith.com" GIT_SHA=$CI_COMMIT_SHORT_SHA yarn build + - *set_env_variables - yarn global add serve - serve -s build -p 3000 & # This is required in order to ensure that all the test cases pass - echo "127.0.0.1 dev.appsmith.com" >> /etc/hosts - yarn test artifacts: + when: always expire_in: 1 week paths: - - build/ - cypress/screenshots - cypress/videos only: @@ -61,22 +78,38 @@ cypress-test: docker-package-release: image: docker:dind services: - - docker:dind + - docker:dind stage: package script: - - docker build --build-arg REACT_APP_ENVIRONMENT=STAGING --build-arg GIT_SHA=$CI_COMMIT_SHORT_SHA -t appsmith/appsmith-editor:release . + - *set_env_variables + - docker build -t appsmith/appsmith-editor:release . - docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_ACCESS_TOKEN - docker push appsmith/appsmith-editor:release only: - release +react-build-prod: + stage: build + script: + - *set_env_variables + - yarn install + - REACT_APP_ENVIRONMENT=$REACT_APP_ENVIRONMENT GIT_SHA=$CI_COMMIT_SHORT_SHA yarn build + artifacts: + when: on_success + expire_in: 1 week + paths: + - build/ + only: + - master + docker-package-prod: image: docker:dind services: - docker:dind stage: package - script: - - docker build --build-arg REACT_APP_ENVIRONMENT=PRODUCTION --build-arg GIT_SHA=$CI_COMMIT_SHORT_SHA -t appsmith/appsmith-editor:latest . + script: + - *set_env_variables + - docker build -t appsmith/appsmith-editor:latest . - docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_ACCESS_TOKEN - docker push appsmith/appsmith-editor:latest only: diff --git a/app/client/Dockerfile b/app/client/Dockerfile index 92c0e97200..73bf2c4480 100644 --- a/app/client/Dockerfile +++ b/app/client/Dockerfile @@ -1,22 +1,7 @@ -FROM node:10.19-alpine as build-deps +FROM nginx:1.17.9-alpine -WORKDIR /usr/src/app - -ARG REACT_APP_ENVIRONMENT="DEVELOPMENT" -ARG GIT_SHA="" - -ENV REACT_APP_ENVIRONMENT=${REACT_APP_ENVIRONMENT} -ENV REACT_APP_BASE_URL=${REACT_APP_BASE_URL} -ENV GIT_SHA=${GIT_SHA} - -COPY package.json yarn.lock ./ -COPY . ./ -RUN yarn install && yarn build - -# Use the output from the previous Docker build to create the nginx container -FROM nginx:1.17.9-alpine as final-image -COPY --from=build-deps /usr/src/app/docker/nginx.conf /etc/nginx/conf.d/default.conf -COPY --from=build-deps /usr/src/app/build /var/www/appsmith +COPY ./build /var/www/appsmith +RUN ls -al /var/www/appsmith EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] diff --git a/app/client/build.sh b/app/client/build.sh index c71f8d9d7a..85cf9bd4e8 100755 --- a/app/client/build.sh +++ b/app/client/build.sh @@ -1,13 +1,11 @@ #!/bin/sh -# GIT_SHA=$(eval git rev-parse HEAD) # GIT_BRANCH=$(git branch --no-color | grep -E '^\*' | sed 's/\*[^a-z]*//g') - # RELEASE="${GIT_BRANCH}_${GIT_SHA}" - - # RELEASE=$(echo "$RELEASE" | sed -e 's/[\/\\\ .]/\-/g') # echo $RELEASE +GIT_SHA=$(eval git rev-parse HEAD) +echo $GIT_SHA REACT_APP_SENTRY_RELEASE=$GIT_SHA craco --max-old-space-size=4096 build --config craco.build.config.js rm ./build/static/js/*.js.map diff --git a/app/client/package.json b/app/client/package.json index 94d09268a5..c13d5ccc58 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -15,6 +15,7 @@ "@blueprintjs/timezone": "^3.6.0", "@craco/craco": "^5.6.1", "@sentry/browser": "^5.6.3", + "@sentry/webpack-plugin": "^1.10.0", "@syncfusion/ej2-react-grids": "^17.4.40", "@types/chance": "^1.0.7", "@types/fontfaceobserver": "^0.0.6", @@ -117,7 +118,6 @@ ], "devDependencies": { "@babel/core": "^7.7.4", - "@sentry/webpack-plugin": "^1.10.0", "@storybook/addon-contexts": "^5.2.6", "@storybook/addon-docs": "^5.2.8", "@storybook/addon-knobs": "^5.2.6", @@ -158,8 +158,7 @@ }, "husky": { "hooks": { - "pre-commit": "lint-staged", - "pre-push": "yarn run test" + "pre-commit": "lint-staged" } } } diff --git a/app/client/src/components/designSystems/blueprint/DatePickerComponent.tsx b/app/client/src/components/designSystems/blueprint/DatePickerComponent.tsx index 36b6e49b5b..0646480710 100644 --- a/app/client/src/components/designSystems/blueprint/DatePickerComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/DatePickerComponent.tsx @@ -71,14 +71,14 @@ class DatePickerComponent extends React.Component { this.props.enableTimePicker ? { useAmPm: true, - value: this.props.selectedDate || this.props.defaultDate, + value: this.props.selectedDate, showArrowButtons: true, } : undefined } closeOnSelection={true} onChange={this.onDateSelected} - value={this.props.selectedDate || this.props.defaultDate} + value={this.props.selectedDate} /> ) : ( { export interface DatePickerComponentProps extends ComponentProps { label: string; - defaultDate?: Date; dateFormat: string; enableTimePicker?: boolean; selectedDate?: Date; diff --git a/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx b/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx index 70644d9ae1..a3072d6113 100644 --- a/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx @@ -228,7 +228,8 @@ class DropDownComponent extends React.Component { rightIcon={IconNames.CHEVRON_DOWN} text={ !_.isEmpty(this.props.options) && - this.props.selectedIndex !== undefined + this.props.selectedIndex !== undefined && + this.props.selectedIndex > -1 ? this.props.options[this.props.selectedIndex].label : "-- Empty --" } diff --git a/app/client/src/components/editorComponents/DraggableComponent.tsx b/app/client/src/components/editorComponents/DraggableComponent.tsx index a4aba58481..2bae8590a0 100644 --- a/app/client/src/components/editorComponents/DraggableComponent.tsx +++ b/app/client/src/components/editorComponents/DraggableComponent.tsx @@ -144,10 +144,19 @@ const DraggableComponent = (props: DraggableComponentProps) => { props.widgetId === propertyPaneState.widgetId) || props.widgetId !== propertyPaneState.widgetId ) { + AnalyticsUtil.logEvent("PROPERTY_PANE_OPEN_CLICK", { + widgetType: props.type, + widgetId: props.widgetId, + }); showPropertyPane && showPropertyPane(props.widgetId, undefined, true); } else { + AnalyticsUtil.logEvent("PROPERTY_PANE_CLOSE_CLICK", { + widgetType: props.type, + widgetId: props.widgetId, + }); showPropertyPane && showPropertyPane(); } + e.preventDefault(); e.stopPropagation(); }; diff --git a/app/client/src/components/editorComponents/DynamicActionCreator.tsx b/app/client/src/components/editorComponents/DynamicActionCreator.tsx index 2b06c2aac2..92d121d5e6 100644 --- a/app/client/src/components/editorComponents/DynamicActionCreator.tsx +++ b/app/client/src/components/editorComponents/DynamicActionCreator.tsx @@ -318,7 +318,7 @@ class DynamicActionCreator extends React.Component { ) => void, ) => { return ( -
+
{selectedOption.arguments.map(arg => { switch (arg.field) { case "ACTION_SELECTOR_FIELD": @@ -357,14 +357,15 @@ class DynamicActionCreator extends React.Component { ); case "TEXT_FIELD": return ( - + + handleUpdate(e, arg.valueChangeHandler)} isValid={true} /> - + ); case "ALERT_TYPE_SELECTOR_FIELD": return ( diff --git a/app/client/src/components/propertyControls/BaseControl.tsx b/app/client/src/components/propertyControls/BaseControl.tsx index a78f2e8dfe..78dbef6dfd 100644 --- a/app/client/src/components/propertyControls/BaseControl.tsx +++ b/app/client/src/components/propertyControls/BaseControl.tsx @@ -30,6 +30,8 @@ export interface ControlData { id: string; label: string; propertyName: string; + helpText?: string; + isJSConvertible?: boolean; controlType: ControlType; propertyValue?: any; isValid: boolean; diff --git a/app/client/src/components/propertyControls/InputTextControl.tsx b/app/client/src/components/propertyControls/InputTextControl.tsx index 342bdebf8e..208b0446c0 100644 --- a/app/client/src/components/propertyControls/InputTextControl.tsx +++ b/app/client/src/components/propertyControls/InputTextControl.tsx @@ -11,8 +11,9 @@ export function InputText(props: { onChange: (event: React.ChangeEvent | string) => void; isValid: boolean; validationMessage?: string; + placeholder?: string; }) { - const { validationMessage, value, isValid, onChange } = props; + const { validationMessage, value, isValid, onChange, placeholder } = props; return ( ); @@ -33,7 +35,13 @@ export function InputText(props: { class InputTextControl extends BaseControl { render() { - const { validationMessage, propertyValue, isValid, label } = this.props; + const { + validationMessage, + propertyValue, + isValid, + label, + placeholderText, + } = this.props; return ( { onChange={this.onTextChange} isValid={isValid} validationMessage={validationMessage} + placeholder={placeholderText} /> ); } diff --git a/app/client/src/components/propertyControls/OptionControl.tsx b/app/client/src/components/propertyControls/OptionControl.tsx index 3395bb9693..db35c9d42d 100644 --- a/app/client/src/components/propertyControls/OptionControl.tsx +++ b/app/client/src/components/propertyControls/OptionControl.tsx @@ -10,6 +10,7 @@ import { ControlType } from "constants/PropertyControlConstants"; import styled from "constants/DefaultTheme"; import { FormIcons } from "icons/FormIcons"; import { AnyStyledComponent } from "styled-components"; +import { generateReactKey } from "utils/generators"; const StyledDeleteIcon = styled(FormIcons.DELETE_ICON as AnyStyledComponent)` padding: 5px 5px; @@ -28,16 +29,64 @@ const StyledOptionControlWrapper = styled(ControlWrapper)` padding-right: 16px; `; -class OptionControl extends BaseControl { +function updateOptionLabel( + options: Array, + index: number, + updatedLabel: string, +) { + return options.map((option: T, optionIndex) => { + if (index !== optionIndex) { + return option; + } + return { + ...option, + label: updatedLabel, + }; + }); +} + +function updateOptionValue( + options: Array, + index: number, + updatedValue: string, +) { + return options.map((option, optionIndex) => { + if (index !== optionIndex) { + return option; + } + return { + ...option, + value: updatedValue, + }; + }); +} + +type DropDownOptionWithKey = DropdownOption & { + key: string; +}; + +class OptionControl extends BaseControl< + ControlProps, + { + renderOptions: DropDownOptionWithKey[]; + } +> { + constructor(props: ControlProps) { + super(props); + this.state = { + renderOptions: [], + }; + } render() { - const options: DropdownOption[] = this.props.propertyValue || [{}]; + const { renderOptions } = this.state; + debugger; return ( - {options.map((option, index) => { + {renderOptions.map((option, index) => { return ( { ); } + componentDidMount() { + const { propertyValue } = this.props; + + const options: DropdownOption[] = Array.isArray(propertyValue) + ? propertyValue + : [{}]; + + options.map(option => { + return { + ...option, + key: generateReactKey(), + }; + }); + this.setState({ + renderOptions: options.map(option => { + return { + ...option, + key: generateReactKey(), + }; + }), + }); + } + deleteOption = (index: number) => { - const options: DropdownOption[] = this.props.propertyValue.slice(); - options.splice(index, 1); - this.updateProperty("options", options); + const { propertyValue } = this.props; + const options: DropdownOption[] = Array.isArray(propertyValue) + ? propertyValue + : [{}]; + const { renderOptions } = this.state; + + const newOptions = options.filter((o, i) => i !== index); + const newRenderOptions = renderOptions.filter((o, i) => i !== index); + + this.updateProperty("options", newOptions); + this.setState({ + renderOptions: newRenderOptions, + }); }; updateOptionLabel = (index: number, updatedLabel: string) => { - const options: DropdownOption[] = this.props.propertyValue; + const { propertyValue } = this.props; + const options: DropdownOption[] = Array.isArray(propertyValue) + ? propertyValue + : [{}]; this.updateProperty( "options", - options.map((option, optionIndex) => { - if (index !== optionIndex) { - return option; - } - return { - ...option, - label: updatedLabel, - }; - }), + updateOptionLabel(options, index, updatedLabel), ); + + this.setState({ + renderOptions: updateOptionLabel( + this.state.renderOptions, + index, + updatedLabel, + ), + }); }; updateOptionValue = (index: number, updatedValue: string) => { - const options: DropdownOption[] = this.props.propertyValue; + const { propertyValue } = this.props; + const options: DropdownOption[] = Array.isArray(propertyValue) + ? propertyValue + : [{}]; this.updateProperty( "options", - options.map((option, optionIndex) => { - if (index !== optionIndex) { - return option; - } - return { - ...option, - value: updatedValue, - }; - }), + updateOptionValue(options, index, updatedValue), ); + + this.setState({ + renderOptions: updateOptionValue( + this.state.renderOptions, + index, + updatedValue, + ), + }); }; addOption = () => { - const options: DropdownOption[] = this.props.propertyValue - ? this.props.propertyValue.slice() - : []; + const { propertyValue } = this.props; + const options: DropdownOption[] = Array.isArray(propertyValue) + ? propertyValue + : [{}]; + const { renderOptions } = this.state; + options.push({ label: "", value: "" }); + renderOptions.push({ + label: "", + value: "", + key: generateReactKey(), + }); + + this.setState({ + renderOptions: renderOptions, + }); this.updateProperty("options", options); }; diff --git a/app/client/src/constants/PropertyControlConstants.tsx b/app/client/src/constants/PropertyControlConstants.tsx index f69fe7445b..86a5fa3ae7 100644 --- a/app/client/src/constants/PropertyControlConstants.tsx +++ b/app/client/src/constants/PropertyControlConstants.tsx @@ -19,10 +19,3 @@ export type ControlType = | "TIME_ZONE" | "CODE_EDITOR" | "COLUMN_ACTION_SELECTOR"; - -export const CONVERTIBLE_CONTROLS = [ - "SWITCH", - "OPTION_INPUT", - "ACTION_SELECTOR", - "DATE_PICKER", -]; diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index 127be33feb..64424a6627 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -150,6 +150,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = { FILE_PICKER_WIDGET: { rows: 1, files: [], + label: "Select Files", columns: 4, widgetName: "FilePicker", isDefaultClickDisabled: true, @@ -201,14 +202,14 @@ const WidgetConfigResponse: WidgetConfigReducerState = { isDefaultClickDisabled: true, }, FORM_WIDGET: { - rows: 10, - columns: 10, + rows: 13, + columns: 6, widgetName: "Form", blueprint: { view: [ { type: "TEXT_WIDGET", - size: { rows: 1, cols: 4 }, + size: { rows: 1, cols: 12 }, position: { top: 0, left: 0 }, props: { text: "Title", @@ -217,8 +218,8 @@ const WidgetConfigResponse: WidgetConfigReducerState = { }, { type: "FORM_BUTTON_WIDGET", - size: { rows: 1, cols: 3 }, - position: { top: 8, left: 13 }, + size: { rows: 1, cols: 4 }, + position: { top: 11, left: 12 }, props: { text: "Submit", buttonStyle: "PRIMARY_BUTTON", @@ -228,8 +229,8 @@ const WidgetConfigResponse: WidgetConfigReducerState = { }, { type: "FORM_BUTTON_WIDGET", - size: { rows: 1, cols: 3 }, - position: { top: 8, left: 10 }, + size: { rows: 1, cols: 4 }, + position: { top: 11, left: 8 }, props: { text: "Reset", buttonStyle: "SECONDARY_BUTTON", diff --git a/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx b/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx index a37ad3b08b..d778f1eae2 100644 --- a/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx +++ b/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx @@ -1,6 +1,5 @@ import React from "react"; import _ from "lodash"; -import { CONVERTIBLE_CONTROLS } from "constants/PropertyControlConstants"; import { ControlPropertyLabelContainer, ControlWrapper, @@ -10,6 +9,7 @@ import { ControlIcons } from "icons/ControlIcons"; import PropertyControlFactory from "utils/PropertyControlFactory"; import { WidgetProps } from "widgets/BaseWidget"; import { ControlConfig } from "reducers/entityReducers/propertyPaneConfigReducer"; +import { Tooltip } from "@blueprintjs/core"; type Props = { widgetProperties: WidgetProps; @@ -18,6 +18,47 @@ type Props = { onPropertyChange: (propertyName: string, propertyValue: any) => void; }; +function UnderlinedLabel({ + tooltip, + label, +}: { + tooltip?: string; + label: string; +}) { + const toolTipDefined = tooltip !== undefined; + return ( + +
+ {label} + +
+
+ ); +} + const PropertyControl = (props: Props) => { const { widgetProperties, @@ -54,7 +95,7 @@ const PropertyControl = (props: Props) => { ["dynamicProperties", propertyName], false, ); - const isConvertible = CONVERTIBLE_CONTROLS.indexOf(config.controlType) > -1; + const isConvertible = !!propertyConfig.isJSConvertible; const className = propertyConfig.label .split(" ") .join("") @@ -71,7 +112,11 @@ const PropertyControl = (props: Props) => { } > - + + {isConvertible && ( { + AnalyticsUtil.logEvent("PROPERTY_PANE_CLOSE_CLICK", { + widgetType: this.props.widgetProperties + ? this.props.widgetProperties.type + : "", + widgetId: this.props.widgetId, + }); this.props.hidePropertyPane(); e.preventDefault(); e.stopPropagation(); @@ -167,6 +173,40 @@ class PropertyPane extends Component< } }; + componentDidUpdate(prevProps: PropertyPaneProps & PropertyPaneFunctions) { + if ( + this.props.widgetId !== prevProps.widgetId && + this.props.widgetId !== undefined + ) { + if (prevProps.widgetId && prevProps.widgetProperties) { + AnalyticsUtil.logEvent("PROPERTY_PANE_CLOSE", { + widgetType: prevProps.widgetProperties.type, + widgetId: prevProps.widgetId, + }); + } + if (this.props.widgetProperties) { + AnalyticsUtil.logEvent("PROPERTY_PANE_OPEN", { + widgetType: this.props.widgetProperties.type, + widgetId: this.props.widgetId, + }); + } + } + + if ( + this.props.widgetId === prevProps.widgetId && + this.props.isVisible && + !prevProps.isVisible && + this.props.widgetProperties !== undefined + ) { + AnalyticsUtil.logEvent("PROPERTY_PANE_OPEN", { + widgetType: this.props.widgetProperties.type, + widgetId: this.props.widgetId, + }); + } + + return true; + } + onPropertyChange(propertyName: string, propertyValue: any) { this.props.updateWidgetProperty( this.props.widgetId, diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index 707c30e2f6..b02a3999e4 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -134,12 +134,10 @@ export function* evaluateDynamicBoundValueSaga(path: string): any { export function* getActionParams(jsonPathKeys: string[] | undefined) { if (_.isNil(jsonPathKeys)) return []; - const values: any = _.flatten( - yield all( - jsonPathKeys.map((jsonPath: string) => { - return call(evaluateDynamicBoundValueSaga, jsonPath); - }), - ), + const values: any = yield all( + jsonPathKeys.map((jsonPath: string) => { + return call(evaluateDynamicBoundValueSaga, jsonPath); + }), ); const dynamicBindings: Record = {}; jsonPathKeys.forEach((key, i) => { @@ -302,7 +300,13 @@ export function* executeActionTriggers( AnalyticsUtil.logEvent("NAVIGATE", { navUrl: trigger.payload.url, }); - window.location.href = trigger.payload.url; + // Add a default protocol if it doesn't exist. + let url = trigger.payload.url; + if (url.indexOf("://") === -1) { + url = "https://" + url; + } + window.location.assign(url); + if (event.callback) event.callback({ success: true }); } else { if (event.callback) event.callback({ success: false }); @@ -329,10 +333,12 @@ export function* executeAppAction(action: ReduxAction) { const { dynamicString, event, responseData } = action.payload; const tree = yield select(evaluateDataTree); const { triggers } = getDynamicValue(dynamicString, tree, responseData, true); - if (triggers) { + if (triggers && triggers.length) { yield all( triggers.map(trigger => call(executeActionTriggers, trigger, event)), ); + } else { + if (event.callback) event.callback({ success: true }); } } @@ -585,8 +591,7 @@ function* executePageLoadActionsSaga(action: ReduxAction) { const pageActions = action.payload; for (const actionSet of pageActions) { const apiResponses = yield select(getActionResponses); - const filteredSet = actionSet.filter(action => !apiResponses[action.id]); - yield* yield all(filteredSet.map(a => call(executePageLoadAction, a))); + yield* yield all(actionSet.map(a => call(executePageLoadAction, a))); } } @@ -687,7 +692,7 @@ function* copyActionSaga( export function* watchActionSagas() { yield all([ takeEvery(ReduxActionTypes.FETCH_ACTIONS_INIT, fetchActionsSaga), - takeLatest(ReduxActionTypes.EXECUTE_ACTION, executeAppAction), + takeEvery(ReduxActionTypes.EXECUTE_ACTION, executeAppAction), takeLatest(ReduxActionTypes.RUN_API_REQUEST, runApiActionSaga), takeLatest(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga), takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga), diff --git a/app/client/src/sagas/ErrorSagas.tsx b/app/client/src/sagas/ErrorSagas.tsx index b7d0f145a7..48ecb821eb 100644 --- a/app/client/src/sagas/ErrorSagas.tsx +++ b/app/client/src/sagas/ErrorSagas.tsx @@ -80,7 +80,8 @@ export function* errorSaga( type, payload: { error, show = true }, } = errorAction; - const message = error.message || ActionErrorDisplayMap[type](error); + const message = + error && error.message ? error.message : ActionErrorDisplayMap[type](error); if (show) AppToaster.show({ message, type: ToastType.ERROR }); yield put({ type: ReduxActionTypes.REPORT_ERROR, diff --git a/app/client/src/sagas/PageSagas.tsx b/app/client/src/sagas/PageSagas.tsx index 59fb0bd1cd..abb254f15e 100644 --- a/app/client/src/sagas/PageSagas.tsx +++ b/app/client/src/sagas/PageSagas.tsx @@ -123,12 +123,12 @@ export function* fetchPageSaga( if (isValidResponse) { // Get Canvas payload const canvasWidgetsPayload = getCanvasWidgetsPayload(fetchPageResponse); - // Execute page load actions - yield put(executePageLoadActions(canvasWidgetsPayload.pageActions)); // Update the canvas yield put(updateCanvas(canvasWidgetsPayload)); // dispatch fetch page success yield put(fetchPageSuccess()); + // Execute page load actions + yield put(executePageLoadActions(canvasWidgetsPayload.pageActions)); } } catch (error) { yield put({ @@ -155,8 +155,6 @@ export function* fetchPublishedPageSaga( const isValidResponse = yield validateResponse(response); if (isValidResponse) { const canvasWidgetsPayload = getCanvasWidgetsPayload(response); - // Execute page load actions - yield put(executePageLoadActions(canvasWidgetsPayload.pageActions)); yield put(updateCanvas(canvasWidgetsPayload)); yield put({ type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS, @@ -167,6 +165,8 @@ export function* fetchPublishedPageSaga( pageWidgetId: canvasWidgetsPayload.pageWidgetId, }, }); + // Execute page load actions + yield put(executePageLoadActions(canvasWidgetsPayload.pageActions)); } } catch (error) { yield put({ diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index b137b27b0c..568c740d22 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -24,7 +24,7 @@ import { takeLatest, all, } from "redux-saga/effects"; -import { getNextEntityName } from "utils/AppsmithUtils"; +import { convertToString, getNextEntityName } from "utils/AppsmithUtils"; import { SetWidgetDynamicPropertyPayload, updateWidgetProperty, @@ -262,7 +262,8 @@ function* setWidgetDynamicPropertySaga( }; if (isDynamic) { dynamicProperties[propertyName] = true; - yield put(updateWidgetProperty(widgetId, propertyName, "{{}}")); + const value = convertToString(widget[propertyName]); + yield put(updateWidgetProperty(widgetId, propertyName, value)); } else { delete dynamicProperties[propertyName]; yield put(updateWidgetProperty(widgetId, propertyName, undefined)); diff --git a/app/client/src/sagas/userSagas.tsx b/app/client/src/sagas/userSagas.tsx index 5f58ee0417..2ac095e0e3 100644 --- a/app/client/src/sagas/userSagas.tsx +++ b/app/client/src/sagas/userSagas.tsx @@ -22,6 +22,7 @@ import { getResponseErrorMessage, callAPI, } from "./ErrorSagas"; +import * as Sentry from "@sentry/browser"; import { fetchOrgsSaga } from "./OrgSagas"; @@ -295,6 +296,9 @@ export function* setCurrentUserSaga(action: ReduxAction) { const me = yield call(fetchUserSaga, action); if (me) { AnalyticsUtil.identifyUser(me.id, me); + Sentry.configureScope(function(scope) { + scope.setUser({ email: me.email, id: me.id }); + }); resetAuthExpiration(); yield put({ type: ReduxActionTypes.SET_CURRENT_USER_SUCCESS, diff --git a/app/client/src/utils/AnalyticsUtil.tsx b/app/client/src/utils/AnalyticsUtil.tsx index f9f3d301cc..10cc441eb4 100644 --- a/app/client/src/utils/AnalyticsUtil.tsx +++ b/app/client/src/utils/AnalyticsUtil.tsx @@ -42,7 +42,11 @@ export type EventName = | "SAVE_DATA_SOURCE" | "NAVIGATE" | "PAGE_LOAD" - | "NAVIGATE_EDITOR"; + | "NAVIGATE_EDITOR" + | "PROPERTY_PANE_OPEN" + | "PROPERTY_PANE_CLOSE" + | "PROPERTY_PANE_OPEN_CLICK" + | "PROPERTY_PANE_CLOSE_CLICK"; export type Gender = "MALE" | "FEMALE"; export interface User { diff --git a/app/client/src/utils/AppsmithUtils.tsx b/app/client/src/utils/AppsmithUtils.tsx index b98a5251a4..cb43078d18 100644 --- a/app/client/src/utils/AppsmithUtils.tsx +++ b/app/client/src/utils/AppsmithUtils.tsx @@ -1,4 +1,4 @@ -import { ReduxAction } from "../constants/ReduxActionConstants"; +import { ReduxAction } from "constants/ReduxActionConstants"; import { getAppsmithConfigs } from "configs"; import * as Sentry from "@sentry/browser"; import AnalyticsUtil from "./AnalyticsUtil"; @@ -77,3 +77,14 @@ export const getNextEntityName = (prefix: string, existingNames: string[]) => { export const noop = () => { console.log("noop"); }; + +export const convertToString = (value: any): string => { + if (_.isUndefined(value)) { + return ""; + } + if (_.isObject(value)) { + return JSON.stringify(value, null, 2); + } + if (_.isString(value)) return value; + return value.toString(); +}; diff --git a/app/client/src/utils/ValidationFactory.ts b/app/client/src/utils/ValidationFactory.ts index 8b31085e74..21911ce730 100644 --- a/app/client/src/utils/ValidationFactory.ts +++ b/app/client/src/utils/ValidationFactory.ts @@ -1,12 +1,18 @@ import { WidgetType } from "constants/WidgetConstants"; import WidgetFactory from "./WidgetFactory"; import { + VALIDATION_TYPES, ValidationResponse, ValidationType, Validator, } from "constants/WidgetValidation"; -// TODO: need to be strict about what the key can be +export const BASE_WIDGET_VALIDATION = { + isLoading: VALIDATION_TYPES.BOOLEAN, + isVisible: VALIDATION_TYPES.BOOLEAN, + isDisabled: VALIDATION_TYPES.BOOLEAN, +}; + export type WidgetPropertyValidationType = Record; class ValidationFactory { diff --git a/app/client/src/utils/Validators.ts b/app/client/src/utils/Validators.ts index 150c94602a..812ad7253e 100644 --- a/app/client/src/utils/Validators.ts +++ b/app/client/src/utils/Validators.ts @@ -84,20 +84,16 @@ export const VALIDATORS: Record = { message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`, }; } - let isValid = _.isBoolean(value); + const isBoolean = _.isBoolean(value); + const isStringTrueFalse = value === "true" || value === "false"; + const isValid = isBoolean || isStringTrueFalse; + if (isStringTrueFalse) parsed = value !== "false"; if (!isValid) { - try { - parsed = !!value; - isValid = true; - } catch (e) { - console.error(`Error when parsing ${value} to boolean`); - console.error(e); - return { - isValid: false, - parsed: false, - message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`, - }; - } + return { + isValid: isValid, + parsed: parsed, + message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`, + }; } return { isValid, parsed }; }, @@ -217,6 +213,15 @@ export const VALIDATORS: Record = { return { isValid, parsed }; }, [VALIDATION_TYPES.DATE]: (value: any): ValidationResponse => { + if (value === undefined) { + const today = new Date(); + today.setHours(0, 0, 0); + return { + isValid: false, + parsed: today, + message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date`, + }; + } const isValid = moment(value).isValid(); const parsed = isValid ? moment(value).toDate() : new Date(); return { diff --git a/app/client/src/utils/WidgetFactory.tsx b/app/client/src/utils/WidgetFactory.tsx index fe6391efb5..87a92a0169 100644 --- a/app/client/src/utils/WidgetFactory.tsx +++ b/app/client/src/utils/WidgetFactory.tsx @@ -4,7 +4,10 @@ import { WidgetProps, WidgetDataProps, } from "widgets/BaseWidget"; -import { WidgetPropertyValidationType } from "./ValidationFactory"; +import { + WidgetPropertyValidationType, + BASE_WIDGET_VALIDATION, +} from "./ValidationFactory"; import React from "react"; type WidgetDerivedPropertyType = any; @@ -78,7 +81,7 @@ class WidgetFactory { const map = this.widgetPropValidationMap.get(widgetType); if (!map) { console.error("Widget type validation is not defined"); - return {}; + return BASE_WIDGET_VALIDATION; } return map; } diff --git a/app/client/src/widgets/BaseWidget.tsx b/app/client/src/widgets/BaseWidget.tsx index e48d801cee..196f432bc7 100644 --- a/app/client/src/widgets/BaseWidget.tsx +++ b/app/client/src/widgets/BaseWidget.tsx @@ -27,7 +27,10 @@ import { EditorContext } from "components/editorComponents/EditorContextProvider import { PositionTypes } from "constants/WidgetConstants"; import ErrorBoundary from "components/editorComponents/ErrorBoundry"; -import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { + BASE_WIDGET_VALIDATION, + WidgetPropertyValidationType, +} from "utils/ValidationFactory"; import { DerivedPropertiesMap, TriggerPropertiesMap, @@ -66,7 +69,7 @@ abstract class BaseWidget< // Needed to send a default no validation option. In case a widget needs // validation implement this in the widget class again static getPropertyValidationMap(): WidgetPropertyValidationType { - return {}; + return BASE_WIDGET_VALIDATION; } static getDerivedPropertiesMap(): DerivedPropertiesMap { diff --git a/app/client/src/widgets/ButtonWidget.tsx b/app/client/src/widgets/ButtonWidget.tsx index 0e300955e4..b3a0016060 100644 --- a/app/client/src/widgets/ButtonWidget.tsx +++ b/app/client/src/widgets/ButtonWidget.tsx @@ -5,7 +5,10 @@ import ButtonComponent, { ButtonType, } from "components/designSystems/blueprint/ButtonComponent"; import { EventType } from "constants/ActionConstants"; -import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { + WidgetPropertyValidationType, + BASE_WIDGET_VALIDATION, +} from "utils/ValidationFactory"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { TriggerPropertiesMap } from "utils/WidgetFactory"; @@ -30,9 +33,8 @@ class ButtonWidget extends BaseWidget< static getPropertyValidationMap(): WidgetPropertyValidationType { return { + ...BASE_WIDGET_VALIDATION, text: VALIDATION_TYPES.TEXT, - isDisabled: VALIDATION_TYPES.BOOLEAN, - isVisible: VALIDATION_TYPES.BOOLEAN, buttonStyle: VALIDATION_TYPES.TEXT, }; } diff --git a/app/client/src/widgets/CheckboxWidget.tsx b/app/client/src/widgets/CheckboxWidget.tsx index 8cb12001c1..7de2b1b50a 100644 --- a/app/client/src/widgets/CheckboxWidget.tsx +++ b/app/client/src/widgets/CheckboxWidget.tsx @@ -4,13 +4,16 @@ import { WidgetType } from "constants/WidgetConstants"; import CheckboxComponent from "components/designSystems/blueprint/CheckboxComponent"; import { EventType } from "constants/ActionConstants"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; -import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { + WidgetPropertyValidationType, + BASE_WIDGET_VALIDATION, +} from "utils/ValidationFactory"; import { TriggerPropertiesMap } from "utils/WidgetFactory"; class CheckboxWidget extends BaseWidget { static getPropertyValidationMap(): WidgetPropertyValidationType { return { - isDisabled: VALIDATION_TYPES.BOOLEAN, + ...BASE_WIDGET_VALIDATION, label: VALIDATION_TYPES.TEXT, defaultCheckedState: VALIDATION_TYPES.BOOLEAN, }; @@ -34,18 +37,16 @@ class CheckboxWidget extends BaseWidget { componentDidUpdate(prevProps: CheckboxWidgetProps) { super.componentDidUpdate(prevProps); - if (this.props.defaultCheckedState.toString()) { - if ( - (this.props.isChecked !== prevProps.isChecked && - this.props.isChecked === undefined) || - this.props.defaultCheckedState.toString() !== - prevProps.defaultCheckedState.toString() - ) { - this.updateWidgetMetaProperty( - "isChecked", - this.props.defaultCheckedState, - ); - } + if ( + (this.props.isChecked !== prevProps.isChecked && + this.props.isChecked === undefined) || + this.props.defaultCheckedState.toString() !== + prevProps.defaultCheckedState.toString() + ) { + this.updateWidgetMetaProperty( + "isChecked", + this.props.defaultCheckedState, + ); } } diff --git a/app/client/src/widgets/DatePickerWidget.tsx b/app/client/src/widgets/DatePickerWidget.tsx index bdcaed7b23..be64785973 100644 --- a/app/client/src/widgets/DatePickerWidget.tsx +++ b/app/client/src/widgets/DatePickerWidget.tsx @@ -3,7 +3,10 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import { EventType } from "constants/ActionConstants"; import DatePickerComponent from "components/designSystems/blueprint/DatePickerComponent"; -import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { + WidgetPropertyValidationType, + BASE_WIDGET_VALIDATION, +} from "utils/ValidationFactory"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { DerivedPropertiesMap, @@ -13,8 +16,8 @@ import { class DatePickerWidget extends BaseWidget { static getPropertyValidationMap(): WidgetPropertyValidationType { return { + ...BASE_WIDGET_VALIDATION, defaultDate: VALIDATION_TYPES.DATE, - selectedDate: VALIDATION_TYPES.DATE, timezone: VALIDATION_TYPES.TEXT, enableTimePicker: VALIDATION_TYPES.BOOLEAN, dateFormat: VALIDATION_TYPES.TEXT, @@ -37,6 +40,21 @@ class DatePickerWidget extends BaseWidget { onDateSelected: true, }; } + + componentDidUpdate(prevProps: DatePickerWidgetProps) { + super.componentDidUpdate(prevProps); + if (this.props.defaultDate) { + if ( + (this.props.selectedDate !== prevProps.selectedDate && + this.props.selectedDate === undefined) || + this.props.defaultDate.toDateString() !== + prevProps.defaultDate.toDateString() + ) { + this.updateWidgetMetaProperty("selectedDate", this.props.defaultDate); + } + } + } + getPageView() { return ( { widgetId={this.props.widgetId} timezone={this.props.timezone} enableTimePicker={this.props.enableTimePicker} - defaultDate={this.props.defaultDate} datePickerType={"DATE_PICKER"} onDateSelected={this.onDateSelected} selectedDate={this.props.selectedDate} @@ -74,7 +91,7 @@ class DatePickerWidget extends BaseWidget { export type DatePickerType = "DATE_PICKER" | "DATE_RANGE_PICKER"; export interface DatePickerWidgetProps extends WidgetProps { - defaultDate?: Date; + defaultDate: Date; selectedDate: Date; timezone?: string; enableTimePicker: boolean; diff --git a/app/client/src/widgets/DropdownWidget.tsx b/app/client/src/widgets/DropdownWidget.tsx index 196aed1e53..88b87441ee 100644 --- a/app/client/src/widgets/DropdownWidget.tsx +++ b/app/client/src/widgets/DropdownWidget.tsx @@ -4,19 +4,24 @@ import { WidgetType } from "constants/WidgetConstants"; import { EventType } from "constants/ActionConstants"; import DropDownComponent from "components/designSystems/blueprint/DropdownComponent"; import _ from "lodash"; -import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { + WidgetPropertyValidationType, + BASE_WIDGET_VALIDATION, +} from "utils/ValidationFactory"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { TriggerPropertiesMap } from "utils/WidgetFactory"; class DropdownWidget extends BaseWidget { static getPropertyValidationMap(): WidgetPropertyValidationType { return { + ...BASE_WIDGET_VALIDATION, placeholderText: VALIDATION_TYPES.TEXT, label: VALIDATION_TYPES.TEXT, options: VALIDATION_TYPES.OPTIONS_DATA, selectionType: VALIDATION_TYPES.TEXT, selectedIndexArr: VALIDATION_TYPES.ARRAY, isRequired: VALIDATION_TYPES.BOOLEAN, + defaultOptionValue: VALIDATION_TYPES.TEXT, }; } static getDerivedPropertiesMap() { @@ -60,7 +65,8 @@ class DropdownWidget extends BaseWidget { ) { this.updateWidgetMetaProperty("selectedIndex", undefined); this.updateWidgetMetaProperty("selectedIndexArr", []); - } else if (this.props.defaultOptionValue) { + } + if (this.props.defaultOptionValue) { if ( (this.props.selectedIndex !== prevProps.selectedIndex && this.props.selectedIndex === undefined) || @@ -69,7 +75,11 @@ class DropdownWidget extends BaseWidget { const selectedIndex = _.findIndex(this.props.options, option => { return option.value === this.props.defaultOptionValue; }); - this.updateWidgetMetaProperty("selectedIndex", selectedIndex); + if (selectedIndex > -1) { + this.updateWidgetMetaProperty("selectedIndex", selectedIndex); + } else { + this.updateWidgetMetaProperty("selectedIndex", undefined); + } } } } diff --git a/app/client/src/widgets/FilepickerWidget.tsx b/app/client/src/widgets/FilepickerWidget.tsx index 331c168666..3904c28678 100644 --- a/app/client/src/widgets/FilepickerWidget.tsx +++ b/app/client/src/widgets/FilepickerWidget.tsx @@ -7,7 +7,10 @@ import Webcam from "@uppy/webcam"; import Url from "@uppy/url"; import OneDrive from "@uppy/onedrive"; import FilePickerComponent from "components/designSystems/appsmith/FilePickerComponent"; -import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { + WidgetPropertyValidationType, + BASE_WIDGET_VALIDATION, +} from "utils/ValidationFactory"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { EventType, ExecutionResult } from "constants/ActionConstants"; import { @@ -28,9 +31,11 @@ class FilePickerWidget extends BaseWidget { static getPropertyValidationMap(): WidgetPropertyValidationType { return { + ...BASE_WIDGET_VALIDATION, label: VALIDATION_TYPES.TEXT, maxNumFiles: VALIDATION_TYPES.NUMBER, allowedFileTypes: VALIDATION_TYPES.ARRAY, + files: VALIDATION_TYPES.ARRAY, isRequired: VALIDATION_TYPES.BOOLEAN, }; } diff --git a/app/client/src/widgets/FormButtonWidget.tsx b/app/client/src/widgets/FormButtonWidget.tsx index 31347b3209..52c9fedb32 100644 --- a/app/client/src/widgets/FormButtonWidget.tsx +++ b/app/client/src/widgets/FormButtonWidget.tsx @@ -5,7 +5,10 @@ import ButtonComponent, { ButtonType, } from "components/designSystems/blueprint/ButtonComponent"; import { EventType, ExecutionResult } from "constants/ActionConstants"; -import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { + BASE_WIDGET_VALIDATION, + WidgetPropertyValidationType, +} from "utils/ValidationFactory"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { TriggerPropertiesMap } from "utils/WidgetFactory"; @@ -30,10 +33,11 @@ class FormButtonWidget extends BaseWidget< static getPropertyValidationMap(): WidgetPropertyValidationType { return { + ...BASE_WIDGET_VALIDATION, text: VALIDATION_TYPES.TEXT, disabledWhenInvalid: VALIDATION_TYPES.BOOLEAN, - isVisible: VALIDATION_TYPES.BOOLEAN, buttonStyle: VALIDATION_TYPES.TEXT, + buttonType: VALIDATION_TYPES.TEXT, }; } diff --git a/app/client/src/widgets/ImageWidget.tsx b/app/client/src/widgets/ImageWidget.tsx index 7f62d6b796..b43722ea76 100644 --- a/app/client/src/widgets/ImageWidget.tsx +++ b/app/client/src/widgets/ImageWidget.tsx @@ -2,12 +2,16 @@ import * as React from "react"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import ImageComponent from "components/designSystems/appsmith/ImageComponent"; -import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { + WidgetPropertyValidationType, + BASE_WIDGET_VALIDATION, +} from "utils/ValidationFactory"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; class ImageWidget extends BaseWidget { static getPropertyValidationMap(): WidgetPropertyValidationType { return { + ...BASE_WIDGET_VALIDATION, image: VALIDATION_TYPES.TEXT, imageShape: VALIDATION_TYPES.TEXT, defaultImage: VALIDATION_TYPES.TEXT, diff --git a/app/client/src/widgets/InputWidget.tsx b/app/client/src/widgets/InputWidget.tsx index 6778194587..9779fd86ce 100644 --- a/app/client/src/widgets/InputWidget.tsx +++ b/app/client/src/widgets/InputWidget.tsx @@ -5,7 +5,10 @@ import InputComponent, { InputComponentProps, } from "components/designSystems/blueprint/InputComponent"; import { EventType } from "constants/ActionConstants"; -import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { + WidgetPropertyValidationType, + BASE_WIDGET_VALIDATION, +} from "utils/ValidationFactory"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { FIELD_REQUIRED_ERROR } from "constants/messages"; import { @@ -16,6 +19,7 @@ import { class InputWidget extends BaseWidget { static getPropertyValidationMap(): WidgetPropertyValidationType { return { + ...BASE_WIDGET_VALIDATION, inputType: VALIDATION_TYPES.TEXT, defaultText: VALIDATION_TYPES.TEXT, isDisabled: VALIDATION_TYPES.BOOLEAN, @@ -30,7 +34,9 @@ class InputWidget extends BaseWidget { inputValidators: VALIDATION_TYPES.ARRAY, focusIndex: VALIDATION_TYPES.NUMBER, isAutoFocusEnabled: VALIDATION_TYPES.BOOLEAN, + onTextChanged: VALIDATION_TYPES.TEXT, isRequired: VALIDATION_TYPES.BOOLEAN, + isValid: VALIDATION_TYPES.BOOLEAN, }; } static getTriggerPropertyMap(): TriggerPropertiesMap { @@ -59,20 +65,17 @@ class InputWidget extends BaseWidget { componentDidMount() { super.componentDidMount(); - if (this.props.defaultText) { - this.updateWidgetMetaProperty("text", this.props.defaultText); - } + const text = this.props.defaultText || ""; + this.updateWidgetMetaProperty("text", text); } componentDidUpdate(prevProps: InputWidgetProps) { super.componentDidUpdate(prevProps); - if (this.props.defaultText) { - if ( - (this.props.text !== prevProps.text && this.props.text === undefined) || - this.props.defaultText !== prevProps.defaultText - ) { - this.updateWidgetMetaProperty("text", this.props.defaultText); - } + if ( + (this.props.text !== prevProps.text && this.props.text === undefined) || + this.props.defaultText !== prevProps.defaultText + ) { + this.updateWidgetMetaProperty("text", this.props.defaultText); } } diff --git a/app/client/src/widgets/RadioGroupWidget.tsx b/app/client/src/widgets/RadioGroupWidget.tsx index e6dca8d6b4..1a631b0163 100644 --- a/app/client/src/widgets/RadioGroupWidget.tsx +++ b/app/client/src/widgets/RadioGroupWidget.tsx @@ -3,16 +3,22 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import RadioGroupComponent from "components/designSystems/blueprint/RadioGroupComponent"; import { EventType } from "constants/ActionConstants"; -import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { + WidgetPropertyValidationType, + BASE_WIDGET_VALIDATION, +} from "utils/ValidationFactory"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { TriggerPropertiesMap } from "utils/WidgetFactory"; class RadioGroupWidget extends BaseWidget { static getPropertyValidationMap(): WidgetPropertyValidationType { return { + ...BASE_WIDGET_VALIDATION, label: VALIDATION_TYPES.TEXT, options: VALIDATION_TYPES.OPTIONS_DATA, selectedOptionValue: VALIDATION_TYPES.TEXT, + onSelectionChange: VALIDATION_TYPES.TEXT, + defaultOptionValue: VALIDATION_TYPES.TEXT, isRequired: VALIDATION_TYPES.BOOLEAN, }; } @@ -41,17 +47,15 @@ class RadioGroupWidget extends BaseWidget { componentDidUpdate(prevProps: RadioGroupWidgetProps) { super.componentDidUpdate(prevProps); - if (this.props.defaultOptionValue) { - if ( - (this.props.selectedOptionValue !== prevProps.selectedOptionValue && - this.props.selectedOptionValue === undefined) || - this.props.defaultOptionValue !== prevProps.defaultOptionValue - ) { - this.updateWidgetMetaProperty( - "selectedOptionValue", - this.props.defaultOptionValue, - ); - } + if ( + (this.props.selectedOptionValue !== prevProps.selectedOptionValue && + this.props.selectedOptionValue === undefined) || + this.props.defaultOptionValue !== prevProps.defaultOptionValue + ) { + this.updateWidgetMetaProperty( + "selectedOptionValue", + this.props.defaultOptionValue, + ); } } getPageView() { diff --git a/app/client/src/widgets/SpinnerWidget.tsx b/app/client/src/widgets/SpinnerWidget.tsx index e210e66811..5cfa9c32f2 100644 --- a/app/client/src/widgets/SpinnerWidget.tsx +++ b/app/client/src/widgets/SpinnerWidget.tsx @@ -3,12 +3,16 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import { Intent } from "@blueprintjs/core"; import SpinnerComponent from "components/designSystems/blueprint/SpinnerComponent"; -import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { + WidgetPropertyValidationType, + BASE_WIDGET_VALIDATION, +} from "utils/ValidationFactory"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; class SpinnerWidget extends BaseWidget { static getPropertyValidationMap(): WidgetPropertyValidationType { return { + ...BASE_WIDGET_VALIDATION, size: VALIDATION_TYPES.NUMBER, value: VALIDATION_TYPES.NUMBER, ellipsize: VALIDATION_TYPES.BOOLEAN, diff --git a/app/client/src/widgets/TableWidget.tsx b/app/client/src/widgets/TableWidget.tsx index 98be0c9a7b..573e421311 100644 --- a/app/client/src/widgets/TableWidget.tsx +++ b/app/client/src/widgets/TableWidget.tsx @@ -6,7 +6,10 @@ import { forIn } from "lodash"; import TableComponent from "components/designSystems/syncfusion/TableComponent"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; -import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { + WidgetPropertyValidationType, + BASE_WIDGET_VALIDATION, +} from "utils/ValidationFactory"; import { ColumnModel } from "@syncfusion/ej2-grids"; import { ColumnDirTypecast } from "@syncfusion/ej2-react-grids"; import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl"; @@ -30,6 +33,7 @@ function constructColumns(data: object[]): ColumnModel[] | ColumnDirTypecast[] { class TableWidget extends BaseWidget { static getPropertyValidationMap(): WidgetPropertyValidationType { return { + ...BASE_WIDGET_VALIDATION, tableData: VALIDATION_TYPES.TABLE_DATA, nextPageKey: VALIDATION_TYPES.TEXT, prevPageKey: VALIDATION_TYPES.TEXT, @@ -156,11 +160,6 @@ class TableWidget extends BaseWidget { } } -type RowData = { - rowIndex: number; -}; -type SelectedRow = object & RowData; - export interface TableWidgetProps extends WidgetProps { nextPageKey?: string; prevPageKey?: string; diff --git a/app/client/src/widgets/TextWidget.tsx b/app/client/src/widgets/TextWidget.tsx index 32c5b8b635..8f26a596ea 100644 --- a/app/client/src/widgets/TextWidget.tsx +++ b/app/client/src/widgets/TextWidget.tsx @@ -3,7 +3,10 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import TextComponent from "components/designSystems/blueprint/TextComponent"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; -import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { + WidgetPropertyValidationType, + BASE_WIDGET_VALIDATION, +} from "utils/ValidationFactory"; const LINE_HEIGHTS: { [key in TextStyle]: number } = { // The following values are arrived at by multiplying line-height with font-size @@ -16,9 +19,10 @@ const LINE_HEIGHTS: { [key in TextStyle]: number } = { class TextWidget extends BaseWidget { static getPropertyValidationMap(): WidgetPropertyValidationType { return { + ...BASE_WIDGET_VALIDATION, text: VALIDATION_TYPES.TEXT, textStyle: VALIDATION_TYPES.TEXT, - isVisible: VALIDATION_TYPES.BOOLEAN, + shouldScroll: VALIDATION_TYPES.BOOLEAN, }; }