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 <nikhil@appsmith.com> * 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 <trisha@appsmith.com> * Fix migration id for correcting action IDs * Reformat code Co-authored-by: Trisha Anand <trisha@appsmith.com> * 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 <nikhil@appsmith.com> Co-authored-by: Hetu Nandu <hetunandu@gmail.com> * 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 <nikhil.nandagopal@gmail.com> Co-authored-by: devrk96 <rohit.kumawat@primathon.in> Co-authored-by: NandanAnantharamu <67676905+NandanAnantharamu@users.noreply.github.com> Co-authored-by: Nikhil Nandagopal <nikhil@appsmith.com> 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 <shrikant@appsmith.com> Co-authored-by: Trisha Anand <trisha@appsmith.com>
This commit is contained in:
parent
f7931701d8
commit
86b2f2b017
|
|
@ -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}}");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<boolean>(false);
|
||||
|
||||
const onChangeHandler = (checked: boolean) => {
|
||||
setChecked(checked);
|
||||
props.onCheckChange && props.onCheckChange(checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledCheckbox disabled={props.disabled}>
|
||||
{props.label}
|
||||
<input
|
||||
type="checkbox"
|
||||
disabled={props.disabled}
|
||||
checked={checked}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChangeHandler(e.target.checked)
|
||||
}
|
||||
/>
|
||||
<Checkmark disabled={props.disabled} isChecked={checked} />
|
||||
</StyledCheckbox>
|
||||
);
|
||||
};
|
||||
|
||||
export default Checkbox;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<RadioGroup
|
||||
rows={props.rows}
|
||||
onChange={(e: any) => onChangeHandler(e.target.value)}
|
||||
>
|
||||
{props.options.map((option: OptionProps, index: number) => (
|
||||
<Radio
|
||||
key={index}
|
||||
columns={props.columns}
|
||||
rows={props.rows}
|
||||
disabled={props.disabled || option.disabled}
|
||||
>
|
||||
{option.label}
|
||||
<input
|
||||
type="radio"
|
||||
value={option.value}
|
||||
disabled={props.disabled || option.disabled}
|
||||
onChange={e => option.onSelect && option.onSelect(e.target.value)}
|
||||
checked={selected === option.value}
|
||||
name="radio"
|
||||
/>
|
||||
<span className="checkbox"></span>
|
||||
</Radio>
|
||||
))}
|
||||
</RadioGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")<SpinnerProp>`
|
||||
const SvgContainer = styled.svg<SpinnerProp>`
|
||||
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 (
|
||||
<SvgContainer viewBox="0 0 50 50" className="new-spinner" size={props.size}>
|
||||
<SvgContainer
|
||||
viewBox="0 0 50 50"
|
||||
className={Classes.SPINNER}
|
||||
size={props.size}
|
||||
>
|
||||
<SvgCircle cx="25" cy="25" r="20" fill="none"></SvgCircle>
|
||||
</SvgContainer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<StyledToggle
|
||||
isLoading={props.isLoading}
|
||||
disabled={props.disabled}
|
||||
value={value}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
disabled={props.disabled || props.isLoading}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChangeHandler(e.target.checked)
|
||||
}
|
||||
/>
|
||||
<span className="slider"></span>
|
||||
{props.isLoading ? (
|
||||
<div className="toggle-spinner">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : null}
|
||||
</StyledToggle>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
59
app/client/src/components/ads/Tooltip.tsx
Normal file
59
app/client/src/components/ads/Tooltip.tsx
Normal file
|
|
@ -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 (
|
||||
<TooltipWrapper variant={props.variant}>
|
||||
<Tooltip
|
||||
content={props.content}
|
||||
position={props.position}
|
||||
usePortal={false}
|
||||
>
|
||||
{props.children}
|
||||
</Tooltip>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
TooltipComponent.defaultProps = {
|
||||
position: Position.TOP,
|
||||
variant: "dark",
|
||||
};
|
||||
|
||||
export default TooltipComponent;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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<Props> {
|
||||
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<Props> {
|
|||
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<Props> {
|
|||
|
||||
const mapStateToProps = (state: AppState) => ({
|
||||
isHelpModalOpen: getHelpModalOpen(state),
|
||||
user: getCurrentUser(state),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(HelpModal);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export class SwitchField extends React.Component<Props> {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<SwitchWrapped>
|
||||
<SwitchWrapped data-cy={this.props.configProperty}>
|
||||
<StyledFormLabel>
|
||||
{label} {isRequired && "*"}
|
||||
</StyledFormLabel>
|
||||
|
|
|
|||
|
|
@ -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")}
|
||||
|
|
|
|||
22
app/client/src/components/stories/Checkbox.stories.tsx
Normal file
22
app/client/src/components/stories/Checkbox.stories.tsx
Normal file
|
|
@ -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 = () => (
|
||||
<StoryWrapper>
|
||||
<Checkbox
|
||||
label={text("label", "Checked")}
|
||||
disabled={boolean("disabled", false)}
|
||||
onCheckChange={action("check-change")}
|
||||
/>
|
||||
</StoryWrapper>
|
||||
);
|
||||
|
|
@ -28,18 +28,7 @@ export const ButtonIcon = () => (
|
|||
export const BordelessIcon = () => (
|
||||
<StoryWrapper>
|
||||
<Icon
|
||||
size={select(
|
||||
"Icon size",
|
||||
[
|
||||
IconSize.SMALL,
|
||||
IconSize.MEDIUM,
|
||||
IconSize.LARGE,
|
||||
IconSize.XL,
|
||||
IconSize.XXL,
|
||||
IconSize.XXXL,
|
||||
],
|
||||
IconSize.LARGE,
|
||||
)}
|
||||
size={select("Icon size", Object.values(IconSize), IconSize.LARGE)}
|
||||
name={select("Icon name", IconCollection, "delete")}
|
||||
/>
|
||||
</StoryWrapper>
|
||||
|
|
@ -48,11 +37,7 @@ export const BordelessIcon = () => (
|
|||
export const AppIconVariant = () => (
|
||||
<StoryWrapper>
|
||||
<AppIcon
|
||||
size={select(
|
||||
"Icon size",
|
||||
[Size.small, Size.medium, Size.large],
|
||||
Size.small,
|
||||
)}
|
||||
size={select("Icon size", Object.values(Size), Size.small)}
|
||||
color={select(
|
||||
"Icon color",
|
||||
[
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import EditableText, {
|
|||
EditInteractionKind,
|
||||
SavingStateHandler,
|
||||
} from "components/ads/EditableText";
|
||||
import { IconCollection } from "components/ads/Icon";
|
||||
import { IconCollection, IconName } from "components/ads/Icon";
|
||||
|
||||
export default {
|
||||
title: "Menu",
|
||||
|
|
@ -104,7 +104,11 @@ export const MenuStory = () => {
|
|||
<MenuDivider />
|
||||
<MenuItem
|
||||
text={text("First option", "Invite user")}
|
||||
icon={select("First Icon", IconCollection, undefined)}
|
||||
icon={select(
|
||||
"First Icon",
|
||||
["Select icon" as IconName, ...IconCollection],
|
||||
"Select icon" as IconName,
|
||||
)}
|
||||
onSelect={action("clicked-first-option")}
|
||||
label={<span>W</span>}
|
||||
disabled={boolean("First option disabled", false)}
|
||||
|
|
@ -112,7 +116,11 @@ export const MenuStory = () => {
|
|||
{boolean("First menu item divider", false) ? <MenuDivider /> : null}
|
||||
<MenuItem
|
||||
text={text("Second option", "Are you sure")}
|
||||
icon={select("Second Icon", IconCollection, undefined)}
|
||||
icon={select(
|
||||
"Second Icon",
|
||||
["Select icon" as IconName, ...IconCollection],
|
||||
"Select icon" as IconName,
|
||||
)}
|
||||
onSelect={action("clicked-second-option")}
|
||||
label={<span>W</span>}
|
||||
/>
|
||||
|
|
|
|||
56
app/client/src/components/stories/Radio.stories.tsx
Normal file
56
app/client/src/components/stories/Radio.stories.tsx
Normal file
|
|
@ -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 = () => (
|
||||
<StoryWrapper>
|
||||
<div style={{ height: "133px" }}>
|
||||
<RadioComponent
|
||||
defaultValue={select(
|
||||
"defaultValue",
|
||||
["React", "Angular", "Vue"],
|
||||
"React",
|
||||
)}
|
||||
columns={number("Column number", 2)}
|
||||
rows={number("Row number", 2)}
|
||||
disabled={boolean("Radio group disabled", false)}
|
||||
onSelect={action("selected-radio-option")}
|
||||
options={[
|
||||
{
|
||||
label: "React",
|
||||
value: "React",
|
||||
onSelect: action("first-radio-option"),
|
||||
disabled: boolean("Option-1-disabled", false),
|
||||
},
|
||||
{
|
||||
label: "Angular",
|
||||
value: "Angular",
|
||||
onSelect: action("second-radio-option"),
|
||||
disabled: boolean("Option-2-disabled", false),
|
||||
},
|
||||
{
|
||||
label: "Vue",
|
||||
value: "Vue",
|
||||
onSelect: action("third-radio-option"),
|
||||
disabled: boolean("Option-3-disabled", false),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</StoryWrapper>
|
||||
);
|
||||
|
|
@ -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 = () => (
|
||||
<TabStory
|
||||
icon1={select("Icon 1", IconCollection, "general")}
|
||||
icon1={select(
|
||||
"Icon 1",
|
||||
["Select icon" as IconName, ...IconCollection],
|
||||
"Select icon" as IconName,
|
||||
)}
|
||||
title1={text("Title 1", "General")}
|
||||
icon2={select("Icon 2", IconCollection, "user")}
|
||||
icon2={select(
|
||||
"Icon 2",
|
||||
["Select icon" as IconName, ...IconCollection],
|
||||
"Select icon" as IconName,
|
||||
)}
|
||||
title2={text("Title 2", "User")}
|
||||
icon3={select("Icon 3", IconCollection, "billing")}
|
||||
icon3={select(
|
||||
"Icon 3",
|
||||
["Select icon" as IconName, ...IconCollection],
|
||||
"Select icon" as IconName,
|
||||
)}
|
||||
title3={text("Title 3", "Billing")}
|
||||
icon4={select("Icon 4", IconCollection, undefined)}
|
||||
icon4={select(
|
||||
"Icon 4",
|
||||
["Select icon" as IconName, ...IconCollection],
|
||||
"Select icon" as IconName,
|
||||
)}
|
||||
title4={text("Title 4", "")}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
23
app/client/src/components/stories/Toggle.stories.tsx
Normal file
23
app/client/src/components/stories/Toggle.stories.tsx
Normal file
|
|
@ -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 = () => (
|
||||
<StoryWrapper>
|
||||
<Toggle
|
||||
value={boolean("switchOn", false)}
|
||||
disabled={boolean("disabled", false)}
|
||||
isLoading={boolean("isLoading", false)}
|
||||
onToggle={action("toggle-on")}
|
||||
/>
|
||||
</StoryWrapper>
|
||||
);
|
||||
34
app/client/src/components/stories/Tooltip.stories.tsx
Normal file
34
app/client/src/components/stories/Tooltip.stories.tsx
Normal file
|
|
@ -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 = () => (
|
||||
<StoryWrapper>
|
||||
<div style={{ paddingTop: "50px", paddingLeft: "50px", width: "200px" }}>
|
||||
<TooltipComponent
|
||||
position={select("Position", Object.values(Position), Position.RIGHT)}
|
||||
content={
|
||||
<Text type={TextType.P1} highlight>
|
||||
This is a tooltip
|
||||
</Text>
|
||||
}
|
||||
variant={select("variant", ["dark", "light"], "dark")}
|
||||
>
|
||||
<Text type={TextType.P1} highlight>
|
||||
Hover to show tooltip
|
||||
</Text>
|
||||
</TooltipComponent>
|
||||
</div>
|
||||
</StoryWrapper>
|
||||
);
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
109
app/client/src/constants/defs/moment.json
Normal file
109
app/client/src/constants/defs/moment.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ export interface Action {
|
|||
providerId?: string;
|
||||
provider?: ActionProvider;
|
||||
documentation?: { text: string };
|
||||
confirmBeforeExecute?: boolean;
|
||||
}
|
||||
|
||||
export interface RestAction extends Action {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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<
|
|||
</OrgSection>
|
||||
);
|
||||
})}
|
||||
<HelpModal page={"Applications"} />
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ class ConfirmRunModal extends React.Component<Props> {
|
|||
const { dispatch, isModalOpen } = this.props;
|
||||
const handleClose = () => {
|
||||
dispatch(showRunActionConfirmModal(false));
|
||||
|
||||
dispatch(cancelRunActionConfirmModal());
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog title="Confirm run" isOpen={isModalOpen} onClose={handleClose}>
|
||||
<Dialog title="Confirm Action" isOpen={isModalOpen} onClose={handleClose}>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
Are you sure you want to refresh your current data
|
||||
Are you sure you want to perform this action?
|
||||
</div>
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
|
|
@ -39,7 +41,7 @@ class ConfirmRunModal extends React.Component<Props> {
|
|||
/>
|
||||
<Button
|
||||
filled
|
||||
text="Confirm and run"
|
||||
text="Confirm"
|
||||
intent="primary"
|
||||
onClick={() => {
|
||||
dispatch(acceptRunActionConfirmModal());
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ export const EditorHeader = (props: EditorHeaderProps) => {
|
|||
/>
|
||||
</DeploySection>
|
||||
</HeaderSection>
|
||||
<HelpModal />
|
||||
<HelpModal page={"Editor"} />
|
||||
</HeaderWrapper>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -227,6 +227,7 @@ const TabContainerView = styled.div`
|
|||
const SettingsWrapper = styled.div`
|
||||
padding-left: 15px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
`;
|
||||
|
||||
type QueryFormProps = {
|
||||
|
|
|
|||
|
|
@ -34,14 +34,11 @@ import {
|
|||
WIDGETS_SEARCH_ID,
|
||||
} from "constants/Explorer";
|
||||
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
||||
import { getAppsmithConfigs } from "configs";
|
||||
import { getCurrentUser } from "selectors/usersSelectors";
|
||||
import { User } from "constants/userConstants";
|
||||
import ConfirmRunModal from "pages/Editor/ConfirmRunModal";
|
||||
import * as Sentry from "@sentry/react";
|
||||
|
||||
const { cloudHosting, intercomAppID } = getAppsmithConfigs();
|
||||
|
||||
type EditorProps = {
|
||||
currentApplicationId?: string;
|
||||
currentPageId?: string;
|
||||
|
|
@ -85,7 +82,6 @@ class Editor extends Component<Props> {
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { user } = this.props;
|
||||
editorInitializer().then(() => {
|
||||
this.setState({ registered: true });
|
||||
});
|
||||
|
|
@ -93,21 +89,8 @@ class Editor extends Component<Props> {
|
|||
if (applicationId && pageId) {
|
||||
this.props.initEditor(applicationId, pageId);
|
||||
}
|
||||
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
|
||||
custom_launcher_selector: "#intercom-trigger",
|
||||
name: user?.username,
|
||||
email: user?.email,
|
||||
});
|
||||
}
|
||||
}
|
||||
componentDidUpdate(previously: Props) {
|
||||
if (cloudHosting && intercomAppID && window.Intercom) {
|
||||
window.Intercom("update");
|
||||
}
|
||||
if (
|
||||
previously.isPublishing &&
|
||||
!(this.props.isPublishing || this.props.errorPublishing)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import {
|
|||
} from "../Applications/permissionHelpers";
|
||||
import { getAppsmithConfigs } from "configs";
|
||||
import { ReactComponent as NoEmailConfigImage } from "assets/images/email-not-configured.svg";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
|
||||
const OrgInviteTitle = styled.div`
|
||||
font-weight: bold;
|
||||
|
|
@ -228,6 +229,7 @@ const OrgInviteUsersForm = (props: any) => {
|
|||
<StyledForm
|
||||
onSubmit={handleSubmit((values: any, dispatch: any) => {
|
||||
validateFormValues(values);
|
||||
AnalyticsUtil.logEvent("INVITE_USER", values);
|
||||
return inviteUsersToOrg({ ...values, orgId: props.orgId }, dispatch);
|
||||
})}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const usersReducer = createReducer(initialState, {
|
|||
action: ReduxAction<User>,
|
||||
) => {
|
||||
const users = [...state.users];
|
||||
const userIndex = _.findIndex(users, { id: action.payload.id });
|
||||
const userIndex = _.findIndex(users, { username: action.payload.username });
|
||||
if (userIndex > -1) {
|
||||
users[userIndex] = action.payload;
|
||||
} else {
|
||||
|
|
@ -54,7 +54,7 @@ const usersReducer = createReducer(initialState, {
|
|||
action: ReduxAction<User>,
|
||||
) => {
|
||||
const users = [...state.list];
|
||||
const userIndex = _.findIndex(users, { id: action.payload.id });
|
||||
const userIndex = _.findIndex(users, { username: action.payload.username });
|
||||
if (userIndex > -1) {
|
||||
users[userIndex] = action.payload;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -284,8 +284,19 @@ export function* executeActionSaga(
|
|||
) {
|
||||
const { actionId, onSuccess, onError, params } = apiAction;
|
||||
try {
|
||||
yield put(executeApiActionRequest({ id: apiAction.actionId }));
|
||||
const api: RestAction = yield select(getAction, actionId);
|
||||
|
||||
if (api.confirmBeforeExecute) {
|
||||
const confirmed = yield call(confirmRunActionSaga);
|
||||
if (!confirmed) {
|
||||
if (event.callback) {
|
||||
event.callback({ success: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
yield put(executeApiActionRequest({ id: apiAction.actionId }));
|
||||
const actionParams: Property[] = yield call(
|
||||
getActionParams,
|
||||
api.jsonPathKeys,
|
||||
|
|
@ -454,6 +465,25 @@ function* executeAppAction(action: ReduxAction<ExecuteActionPayload>) {
|
|||
}
|
||||
}
|
||||
|
||||
function* runActionInitSaga(
|
||||
reduxAction: ReduxAction<{
|
||||
id: string;
|
||||
paginationField: PaginationField;
|
||||
}>,
|
||||
) {
|
||||
const action = yield select(getAction, reduxAction.payload.id);
|
||||
|
||||
if (action.confirmBeforeExecute) {
|
||||
const confirmed = yield call(confirmRunActionSaga);
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
yield put({
|
||||
type: ReduxActionTypes.RUN_ACTION_REQUEST,
|
||||
payload: reduxAction.payload,
|
||||
});
|
||||
}
|
||||
|
||||
function* runActionSaga(
|
||||
reduxAction: ReduxAction<{
|
||||
id: string;
|
||||
|
|
@ -537,33 +567,15 @@ function* runActionSaga(
|
|||
}
|
||||
}
|
||||
|
||||
function* confirmRunActionSaga(
|
||||
reduxAction: ReduxAction<{
|
||||
id: string;
|
||||
paginationField: PaginationField;
|
||||
}>,
|
||||
) {
|
||||
const action = yield select(getAction, reduxAction.payload.id);
|
||||
if (action.requestConfirmation) {
|
||||
yield put(showRunActionConfirmModal(true));
|
||||
function* confirmRunActionSaga() {
|
||||
yield put(showRunActionConfirmModal(true));
|
||||
|
||||
const { accept } = yield race({
|
||||
cancel: take(ReduxActionTypes.CANCEL_RUN_ACTION_CONFIRM_MODAL),
|
||||
accept: take(ReduxActionTypes.ACCEPT_RUN_ACTION_CONFIRM_MODAL),
|
||||
});
|
||||
const { accept } = yield race({
|
||||
cancel: take(ReduxActionTypes.CANCEL_RUN_ACTION_CONFIRM_MODAL),
|
||||
accept: take(ReduxActionTypes.ACCEPT_RUN_ACTION_CONFIRM_MODAL),
|
||||
});
|
||||
|
||||
if (accept) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.RUN_ACTION_REQUEST,
|
||||
payload: reduxAction.payload,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
yield put({
|
||||
type: ReduxActionTypes.RUN_ACTION_REQUEST,
|
||||
payload: reduxAction.payload,
|
||||
});
|
||||
}
|
||||
return !!accept;
|
||||
}
|
||||
|
||||
function* executePageLoadAction(pageAction: PageAction) {
|
||||
|
|
@ -611,7 +623,7 @@ export function* watchActionExecutionSagas() {
|
|||
yield all([
|
||||
takeEvery(ReduxActionTypes.EXECUTE_ACTION, executeAppAction),
|
||||
takeLatest(ReduxActionTypes.RUN_ACTION_REQUEST, runActionSaga),
|
||||
takeLatest(ReduxActionTypes.RUN_ACTION_INIT, confirmRunActionSaga),
|
||||
takeLatest(ReduxActionTypes.RUN_ACTION_INIT, runActionInitSaga),
|
||||
takeLatest(
|
||||
ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS,
|
||||
executePageLoadActionsSaga,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import FeatureFlag from "./featureFlags";
|
|||
import smartlookClient from "smartlook-client";
|
||||
import { getAppsmithConfigs } from "configs";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { User } from "../constants/userConstants";
|
||||
|
||||
export type EventName =
|
||||
| "LOGIN_CLICK"
|
||||
|
|
@ -63,18 +64,10 @@ export type EventName =
|
|||
| "PROPERTY_PANE_OPEN"
|
||||
| "PROPERTY_PANE_CLOSE"
|
||||
| "PROPERTY_PANE_OPEN_CLICK"
|
||||
| "OPEN_HELP"
|
||||
| "INVITE_USER"
|
||||
| "PROPERTY_PANE_CLOSE_CLICK";
|
||||
|
||||
export type Gender = "MALE" | "FEMALE";
|
||||
export interface User {
|
||||
username: string;
|
||||
name: string;
|
||||
email: string;
|
||||
gender: Gender;
|
||||
currentOrganizationId?: string;
|
||||
applications: any[];
|
||||
}
|
||||
|
||||
function getApplicationId(location: Location) {
|
||||
const pathSplit = location.pathname.split("/");
|
||||
const applicationsIndex = pathSplit.findIndex(
|
||||
|
|
@ -204,6 +197,9 @@ class AnalyticsUtil {
|
|||
|
||||
static reset() {
|
||||
const windowDoc: any = window;
|
||||
if (windowDoc.Intercom) {
|
||||
windowDoc.Intercom("shutdown");
|
||||
}
|
||||
windowDoc.analytics && windowDoc.analytics.reset();
|
||||
windowDoc.mixpanel && windowDoc.mixpanel.reset();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,12 +123,7 @@ export const evaluateDynamicBoundValue = (
|
|||
path: string,
|
||||
callbackData?: any,
|
||||
): JSExecutorResult => {
|
||||
const unescapedInput = unescapeJS(path);
|
||||
return JSExecutionManagerSingleton.evaluateSync(
|
||||
unescapedInput,
|
||||
data,
|
||||
callbackData,
|
||||
);
|
||||
return JSExecutionManagerSingleton.evaluateSync(path, data, callbackData);
|
||||
};
|
||||
|
||||
// For creating a final value where bindings could be in a template format
|
||||
|
|
@ -265,6 +260,8 @@ export function getEvaluatedDataTree(dataTree: DataTree): DataTree {
|
|||
// Create Dependencies DAG
|
||||
const createDepsStart = performance.now();
|
||||
const dataTreeString = JSON.stringify(dataTree);
|
||||
// Stringify before doing a fast equals because the data tree has functions and fast equal will always treat those as changed values
|
||||
// Better solve will be to prune functions
|
||||
if (!equal(dataTreeString, cachedDataTreeString)) {
|
||||
cachedDataTreeString = dataTreeString;
|
||||
dependencyTreeCache = createDependencyTree(dataTree);
|
||||
|
|
@ -333,10 +330,20 @@ export const createDependencyTree = (
|
|||
];
|
||||
});
|
||||
if (entity.dynamicBindings) {
|
||||
Object.keys(entity.dynamicBindings).forEach(prop => {
|
||||
const { jsSnippets } = getDynamicBindings(_.get(entity, prop));
|
||||
const existingDeps = dependencyMap[`${entityKey}.${prop}`] || [];
|
||||
dependencyMap[`${entityKey}.${prop}`] = existingDeps.concat(
|
||||
Object.keys(entity.dynamicBindings).forEach(propertyName => {
|
||||
// using unescape to remove new lines from bindings which interfere with our regex extraction
|
||||
let unevalPropValue = _.get(entity, propertyName);
|
||||
if (
|
||||
_.isString(unevalPropValue) &&
|
||||
isDynamicValue(unevalPropValue)
|
||||
) {
|
||||
unevalPropValue = unescapeJS(unevalPropValue);
|
||||
}
|
||||
_.set(entity, propertyName, unevalPropValue);
|
||||
const { jsSnippets } = getDynamicBindings(unevalPropValue);
|
||||
const existingDeps =
|
||||
dependencyMap[`${entityKey}.${propertyName}`] || [];
|
||||
dependencyMap[`${entityKey}.${propertyName}`] = existingDeps.concat(
|
||||
jsSnippets.filter(jsSnippet => !!jsSnippet),
|
||||
);
|
||||
});
|
||||
|
|
@ -350,7 +357,16 @@ export const createDependencyTree = (
|
|||
if (entity.ENTITY_TYPE === ENTITY_TYPE.ACTION) {
|
||||
if (entity.dynamicBindingPathList.length) {
|
||||
entity.dynamicBindingPathList.forEach(prop => {
|
||||
const { jsSnippets } = getDynamicBindings(_.get(entity, prop.key));
|
||||
// using unescape to remove new lines from bindings which interfere with our regex extraction
|
||||
let unevalPropValue = _.get(entity, prop.key);
|
||||
if (
|
||||
_.isString(unevalPropValue) &&
|
||||
isDynamicValue(unevalPropValue)
|
||||
) {
|
||||
unevalPropValue = unescapeJS(unevalPropValue);
|
||||
}
|
||||
_.set(entity, prop.key, unevalPropValue);
|
||||
const { jsSnippets } = getDynamicBindings(unevalPropValue);
|
||||
const existingDeps =
|
||||
dependencyMap[`${entityKey}.${prop.key}`] || [];
|
||||
dependencyMap[`${entityKey}.${prop.key}`] = existingDeps.concat(
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ export const entityDefinitions = {
|
|||
"!doc": "The value selected in a single select dropdown",
|
||||
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
|
||||
},
|
||||
selectedOptionValueArr: {
|
||||
selectedOptionValues: {
|
||||
"!type": "[string]",
|
||||
"!doc": "The array of values selected in a multi select dropdown",
|
||||
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import tern, { Server, Def } from "tern";
|
|||
import ecma from "tern/defs/ecmascript.json";
|
||||
import lodash from "constants/defs/lodash.json";
|
||||
import base64 from "constants/defs/base64-js.json";
|
||||
import moment from "constants/defs/moment.json";
|
||||
import { dataTreeTypeDefCreator } from "utils/autocomplete/dataTreeTypeDefCreator";
|
||||
import CodeMirror, { Hint, Pos, cmpPos } from "codemirror";
|
||||
import {
|
||||
|
|
@ -12,7 +13,7 @@ import {
|
|||
isDynamicValue,
|
||||
} from "utils/DynamicBindingUtils";
|
||||
|
||||
const DEFS = [ecma, lodash, base64];
|
||||
const DEFS = [ecma, lodash, base64, moment];
|
||||
const bigDoc = 250;
|
||||
const cls = "CodeMirror-Tern-";
|
||||
const hintDelay = 1700;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
|
|||
isRequired: VALIDATION_TYPES.BOOLEAN,
|
||||
// onOptionChange: VALIDATION_TYPES.ACTION_SELECTOR,
|
||||
selectedOptionValueArr: VALIDATION_TYPES.ARRAY,
|
||||
selectedOptionValues: VALIDATION_TYPES.ARRAY,
|
||||
defaultOptionValue: (
|
||||
value: string | string[],
|
||||
props: WidgetProps,
|
||||
|
|
@ -73,6 +74,7 @@ class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
|
|||
selectedIndex: `{{ _.findIndex(this.options, { value: this.selectedOption.value } ) }}`,
|
||||
selectedIndexArr: `{{ this.selectedOptionValueArr.map(o => _.findIndex(this.options, { value: o })) }}`,
|
||||
value: `{{ this.selectionType === 'SINGLE_SELECT' ? this.selectedOptionValue : this.selectedOptionValueArr }}`,
|
||||
selectedOptionValues: `{{ this.selectedOptionValueArr }}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -93,6 +95,7 @@ class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
|
|||
return {
|
||||
selectedOptionValue: undefined,
|
||||
selectedOptionValueArr: undefined,
|
||||
selectedOptionValues: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
pageNo: 1,
|
||||
pageSize: undefined,
|
||||
selectedRowIndex: -1,
|
||||
selectedRowIndexes: "",
|
||||
selectedRowIndices: [],
|
||||
searchText: undefined,
|
||||
selectedRow: {},
|
||||
selectedRows: [],
|
||||
|
|
@ -351,32 +351,29 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
this.getSelectedRow(filteredTableData),
|
||||
);
|
||||
} else {
|
||||
const selectedRowIndexes = this.getSelectedRowIndexes(
|
||||
this.props.selectedRowIndexes,
|
||||
);
|
||||
super.updateWidgetMetaProperty(
|
||||
"selectedRows",
|
||||
filteredTableData.filter((item: object, i: number) => {
|
||||
return selectedRowIndexes.includes(i);
|
||||
return this.props.selectedRowIndices.includes(i);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (this.props.multiRowSelection !== prevProps.multiRowSelection) {
|
||||
if (this.props.multiRowSelection) {
|
||||
const selectedRowIndexes = this.props.selectedRowIndex
|
||||
const selectedRowIndices = this.props.selectedRowIndex
|
||||
? [this.props.selectedRowIndex]
|
||||
: [];
|
||||
super.updateWidgetMetaProperty(
|
||||
"selectedRowIndexes",
|
||||
selectedRowIndexes.join(","),
|
||||
"selectedRowIndices",
|
||||
selectedRowIndices,
|
||||
);
|
||||
super.updateWidgetMetaProperty("selectedRowIndex", -1);
|
||||
const filteredTableData = this.filterTableData();
|
||||
super.updateWidgetMetaProperty(
|
||||
"selectedRows",
|
||||
filteredTableData.filter((item: object, i: number) => {
|
||||
return selectedRowIndexes.includes(i);
|
||||
return selectedRowIndices.includes(i);
|
||||
}),
|
||||
);
|
||||
super.updateWidgetMetaProperty(
|
||||
|
|
@ -385,7 +382,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
);
|
||||
} else {
|
||||
const filteredTableData = this.filterTableData();
|
||||
super.updateWidgetMetaProperty("selectedRowIndexes", "");
|
||||
super.updateWidgetMetaProperty("selectedRowIndices", []);
|
||||
super.updateWidgetMetaProperty("selectedRows", []);
|
||||
super.updateWidgetMetaProperty(
|
||||
"selectedRow",
|
||||
|
|
@ -406,7 +403,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
tableData,
|
||||
hiddenColumns,
|
||||
filteredTableData,
|
||||
selectedRowIndexes,
|
||||
selectedRowIndices,
|
||||
} = this.props;
|
||||
const tableColumns = this.getTableColumns(tableData);
|
||||
|
||||
|
|
@ -469,7 +466,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
: this.props.selectedRowIndex
|
||||
}
|
||||
multiRowSelection={this.props.multiRowSelection}
|
||||
selectedRowIndexes={this.getSelectedRowIndexes(selectedRowIndexes)}
|
||||
selectedRowIndices={selectedRowIndices}
|
||||
serverSidePaginationEnabled={serverSidePaginationEnabled}
|
||||
onRowClick={this.handleRowClick}
|
||||
pageNo={pageNo}
|
||||
|
|
@ -561,25 +558,19 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
};
|
||||
|
||||
handleRowClick = (rowData: object, index: number) => {
|
||||
const { onRowSelected } = this.props;
|
||||
const { onRowSelected, selectedRowIndices } = this.props;
|
||||
if (this.props.multiRowSelection) {
|
||||
const selectedRowIndexes = this.getSelectedRowIndexes(
|
||||
this.props.selectedRowIndexes,
|
||||
);
|
||||
if (selectedRowIndexes.includes(index)) {
|
||||
const rowIndex = selectedRowIndexes.indexOf(index);
|
||||
selectedRowIndexes.splice(rowIndex, 1);
|
||||
if (selectedRowIndices.includes(index)) {
|
||||
const rowIndex = selectedRowIndices.indexOf(index);
|
||||
selectedRowIndices.splice(rowIndex, 1);
|
||||
} else {
|
||||
selectedRowIndexes.push(index);
|
||||
selectedRowIndices.push(index);
|
||||
}
|
||||
super.updateWidgetMetaProperty(
|
||||
"selectedRowIndexes",
|
||||
selectedRowIndexes.join(","),
|
||||
);
|
||||
super.updateWidgetMetaProperty("selectedRowIndices", selectedRowIndices);
|
||||
super.updateWidgetMetaProperty(
|
||||
"selectedRows",
|
||||
this.props.filteredTableData.filter((item: object, i: number) => {
|
||||
return selectedRowIndexes.includes(i);
|
||||
return selectedRowIndices.includes(i);
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
|
|
@ -616,7 +607,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
|
||||
resetSelectedRowIndex = () => {
|
||||
super.updateWidgetMetaProperty("selectedRowIndex", -1);
|
||||
super.updateWidgetMetaProperty("selectedRowIndexes", "");
|
||||
super.updateWidgetMetaProperty("selectedRowIndices", []);
|
||||
};
|
||||
|
||||
handlePrevPageClick = () => {
|
||||
|
|
@ -679,7 +670,7 @@ export interface TableWidgetProps extends WidgetProps {
|
|||
onRowSelected?: string;
|
||||
onSearchTextChanged: string;
|
||||
selectedRowIndex?: number;
|
||||
selectedRowIndexes: string;
|
||||
selectedRowIndices: number[];
|
||||
columnActions?: ColumnAction[];
|
||||
serverSidePaginationEnabled?: boolean;
|
||||
multiRowSelection?: boolean;
|
||||
|
|
|
|||
|
|
@ -2719,14 +2719,14 @@
|
|||
dependencies:
|
||||
any-observable "^0.3.0"
|
||||
|
||||
"@sentry/browser@5.22.2":
|
||||
version "5.22.2"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.22.2.tgz#c37c61c8612a169059ddcd8c08cd09441da7776c"
|
||||
integrity sha512-kkNRFMcNErtWvz9WI0bG5Va2W+mRWhk5CxaJKWUMdMcGR2rIrl3D+kcMdpYDi9tNYPHUdUzTCb3vJQfO8o6TbA==
|
||||
"@sentry/browser@5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.22.3.tgz#7a64bd1cf01bf393741a3e4bf35f82aa927f5b4e"
|
||||
integrity sha512-2TzE/CoBa5ZkvxJizDdi1Iz1ldmXSJpFQ1mL07PIXBjCt0Wxf+WOuFSj5IP4L40XHfJE5gU8wEvSH0VDR8nXtA==
|
||||
dependencies:
|
||||
"@sentry/core" "5.22.2"
|
||||
"@sentry/types" "5.22.2"
|
||||
"@sentry/utils" "5.22.2"
|
||||
"@sentry/core" "5.22.3"
|
||||
"@sentry/types" "5.22.3"
|
||||
"@sentry/utils" "5.22.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/cli@^1.55.0":
|
||||
|
|
@ -2740,69 +2740,69 @@
|
|||
progress "^2.0.3"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
"@sentry/core@5.22.2":
|
||||
version "5.22.2"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.22.2.tgz#3b57300d92c13163c26174311ad82482a08b9266"
|
||||
integrity sha512-Tj3FlHiqK8uveKh56QP3PhNNrH13LTWqN1TwRwE2B2FLiqwIHGmJsCQNfyslQdBAkNeGRnnQrQxqH53KeuJGGA==
|
||||
"@sentry/core@5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.22.3.tgz#030f435f2b518f282ba8bd954dac90cd70888bd7"
|
||||
integrity sha512-eGL5uUarw3o4i9QUb9JoFHnhriPpWCaqeaIBB06HUpdcvhrjoowcKZj1+WPec5lFg5XusE35vez7z/FPzmJUDw==
|
||||
dependencies:
|
||||
"@sentry/hub" "5.22.2"
|
||||
"@sentry/minimal" "5.22.2"
|
||||
"@sentry/types" "5.22.2"
|
||||
"@sentry/utils" "5.22.2"
|
||||
"@sentry/hub" "5.22.3"
|
||||
"@sentry/minimal" "5.22.3"
|
||||
"@sentry/types" "5.22.3"
|
||||
"@sentry/utils" "5.22.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/hub@5.22.2":
|
||||
version "5.22.2"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.22.2.tgz#812c8250970e44c63ee2bf1f779777d32dd88f54"
|
||||
integrity sha512-6McBsonfpOY5hzlowzDfdLZklFQ1wWTGtiA0eByKxS/H1GePJc+UUSsu6D3bZJG0bIjFoq5vxLQFSZq6C7BPlQ==
|
||||
"@sentry/hub@5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.22.3.tgz#08309a70d2ea8d5e313d05840c1711f34f2fffe5"
|
||||
integrity sha512-INo47m6N5HFEs/7GMP9cqxOIt7rmRxdERunA3H2L37owjcr77MwHVeeJ9yawRS6FMtbWXplgWTyTIWIYOuqVbw==
|
||||
dependencies:
|
||||
"@sentry/types" "5.22.2"
|
||||
"@sentry/utils" "5.22.2"
|
||||
"@sentry/types" "5.22.3"
|
||||
"@sentry/utils" "5.22.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/minimal@5.22.2":
|
||||
version "5.22.2"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.22.2.tgz#bb050a49158c48596094184cff2806ce8cb63c9d"
|
||||
integrity sha512-jfth6bY19FXE/kQc6hLBCKg5CjfX1MG+weyEXnPFstCb5JFMvSt6YPRI3OsY1hG3rQLxTX0mVSbe2YrBJE5kXA==
|
||||
"@sentry/minimal@5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.22.3.tgz#706e4029ae5494123d3875c658ba8911aa5cc440"
|
||||
integrity sha512-HoINpYnVYCpNjn2XIPIlqH5o4BAITpTljXjtAftOx6Hzj+Opjg8tR8PWliyKDvkXPpc4kXK9D6TpEDw8MO0wZA==
|
||||
dependencies:
|
||||
"@sentry/hub" "5.22.2"
|
||||
"@sentry/types" "5.22.2"
|
||||
"@sentry/hub" "5.22.3"
|
||||
"@sentry/types" "5.22.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/react@^5.22.2":
|
||||
version "5.22.2"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.22.2.tgz#8a0f9ee475659faf780080569ca7c7c5cb92b218"
|
||||
integrity sha512-DgpLWZV5htumjXaE13E/KMODwKyr56bNtk1gaTOPbm368b6zdcM/bwutdNQSTuNlhkJ4bhnhpVs7OzErJkcDLw==
|
||||
"@sentry/react@^5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.22.3.tgz#ed692f9e2aff718da6cd15d2941ddda4f1d63385"
|
||||
integrity sha512-Or/tLayuxpOJhIWOXiDKdaJQZ981uRS9NT0QcPvU+Si1qTElSqtH1zB94GlwhgpglkbmLPiYq6VPrG2HOiZ79Q==
|
||||
dependencies:
|
||||
"@sentry/browser" "5.22.2"
|
||||
"@sentry/minimal" "5.22.2"
|
||||
"@sentry/types" "5.22.2"
|
||||
"@sentry/utils" "5.22.2"
|
||||
"@sentry/browser" "5.22.3"
|
||||
"@sentry/minimal" "5.22.3"
|
||||
"@sentry/types" "5.22.3"
|
||||
"@sentry/utils" "5.22.3"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/tracing@^5.22.2":
|
||||
version "5.22.2"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.22.2.tgz#6a326bf43b3491c3e462330f8e7d6cd52a975a2d"
|
||||
integrity sha512-M4F4CN85luWoHBuBiQMk2/dtpn5L2CriBTcLsKQNkA/fk6lv8CbE1WZ/SkCMWzK4hxo5I2vSyzLlb2OwcMHmdQ==
|
||||
"@sentry/tracing@^5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.22.3.tgz#9b5a376e3164c007a22e8642ec094104468cac0c"
|
||||
integrity sha512-Zp59kMCk5v56ZAyErqjv/QvGOWOQ5fRltzeVQVp8unIDTk6gEFXfhwPsYHOokJe1mfkmrgPDV6xAkYgtL3KCDQ==
|
||||
dependencies:
|
||||
"@sentry/hub" "5.22.2"
|
||||
"@sentry/minimal" "5.22.2"
|
||||
"@sentry/types" "5.22.2"
|
||||
"@sentry/utils" "5.22.2"
|
||||
"@sentry/hub" "5.22.3"
|
||||
"@sentry/minimal" "5.22.3"
|
||||
"@sentry/types" "5.22.3"
|
||||
"@sentry/utils" "5.22.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/types@5.22.2":
|
||||
version "5.22.2"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.22.2.tgz#32d7f82537afe7712971fd6222c7744f0d8a27fe"
|
||||
integrity sha512-Ko9pri0D0TNaSHocLVLQQZlnTtMXrBhP5AZYjB193aYqc1x52dFchQlhiKLEgyFCKjTEIlD/J9ZD7QkQoeYT+A==
|
||||
"@sentry/types@5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.22.3.tgz#d1d547b30ee8bd7771fa893af74c4f3d71f0fd18"
|
||||
integrity sha512-cv+VWK0YFgCVDvD1/HrrBWOWYG3MLuCUJRBTkV/Opdy7nkdNjhCAJQrEyMM9zX0sac8FKWKOHT0sykNh8KgmYw==
|
||||
|
||||
"@sentry/utils@5.22.2":
|
||||
version "5.22.2"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.22.2.tgz#7296cd9036e8d34638743a27fe8c6fe0e70db902"
|
||||
integrity sha512-HGpPohNgwRhR+7bf2OlziX84JVdwQAauesqcL4Y78e+U09+E06cQAEQkRIQJfn2Ai2NvVmMVhdj51v+AqmaAWQ==
|
||||
"@sentry/utils@5.22.3":
|
||||
version "5.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.22.3.tgz#e3bda3e789239eb16d436f768daa12829f33d18f"
|
||||
integrity sha512-AHNryXMBvIkIE+GQxTlmhBXD0Ksh+5w1SwM5qi6AttH+1qjWLvV6WB4+4pvVvEoS8t5F+WaVUZPQLmCCWp6zKw==
|
||||
dependencies:
|
||||
"@sentry/types" "5.22.2"
|
||||
"@sentry/types" "5.22.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/webpack-plugin@^1.12.1":
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.appsmith.server.migrations;
|
||||
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.BaseDomain;
|
||||
import com.appsmith.external.models.Policy;
|
||||
import com.appsmith.server.acl.AppsmithRole;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
|
|
@ -11,6 +12,7 @@ import com.appsmith.server.domains.Config;
|
|||
import com.appsmith.server.domains.Datasource;
|
||||
import com.appsmith.server.domains.Group;
|
||||
import com.appsmith.server.domains.InviteUser;
|
||||
import com.appsmith.server.domains.Layout;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.OrganizationPlugin;
|
||||
import com.appsmith.server.domains.Page;
|
||||
|
|
@ -24,6 +26,7 @@ import com.appsmith.server.domains.Role;
|
|||
import com.appsmith.server.domains.Sequence;
|
||||
import com.appsmith.server.domains.Setting;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.dtos.DslActionDTO;
|
||||
import com.appsmith.server.dtos.OrganizationPluginStatus;
|
||||
import com.appsmith.server.services.EncryptionService;
|
||||
import com.appsmith.server.services.OrganizationService;
|
||||
|
|
@ -42,6 +45,7 @@ import org.springframework.data.mongodb.core.MongoTemplate;
|
|||
import org.springframework.data.mongodb.core.index.CompoundIndexDefinition;
|
||||
import org.springframework.data.mongodb.core.index.Index;
|
||||
import org.springframework.data.mongodb.core.index.IndexOperations;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
|
|
@ -827,4 +831,89 @@ public class DatabaseChangelog {
|
|||
);
|
||||
}
|
||||
|
||||
@ChangeSet(order = "024", id = "update-erroneous-action-ids", author = "")
|
||||
public void updateErroneousActionIdsInPage(MongoTemplate mongoTemplate) {
|
||||
final org.springframework.data.mongodb.core.query.Query configQuery = query(where("name").is("template-organization"));
|
||||
|
||||
final Config config = mongoTemplate.findOne(
|
||||
configQuery,
|
||||
Config.class
|
||||
);
|
||||
|
||||
if (config == null) {
|
||||
// No template organization configured. Nothing to migrate.
|
||||
return;
|
||||
}
|
||||
|
||||
final String organizationId = config.getConfig().getAsString("organizationId");
|
||||
|
||||
final org.springframework.data.mongodb.core.query.Query query = query(where(FieldName.ORGANIZATION_ID).is(organizationId));
|
||||
query.fields().include("_id");
|
||||
|
||||
// Get IDs of applications in the template org.
|
||||
final List<String> applicationIds = mongoTemplate
|
||||
.find(query, Application.class)
|
||||
.stream()
|
||||
.map(BaseDomain::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Get IDs of actions in the template org.
|
||||
final List<String> actionIds = mongoTemplate
|
||||
.find(query, Action.class)
|
||||
.stream()
|
||||
.map(BaseDomain::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Get pages that are not in applications in the template org, and have template org's action IDs in their
|
||||
// layoutOnload lists.
|
||||
final Criteria incorrectActionIdCriteria = new Criteria().orOperator(
|
||||
where("layouts.layoutOnLoadActions").elemMatch(new Criteria().elemMatch(where("_id").in(actionIds))),
|
||||
where("layouts.publishedLayoutOnLoadActions").elemMatch(new Criteria().elemMatch(where("_id").in(actionIds)))
|
||||
);
|
||||
final List<Page> pagesToFix = mongoTemplate.find(
|
||||
query(where(FieldName.APPLICATION_ID).not().in(applicationIds))
|
||||
.addCriteria(incorrectActionIdCriteria),
|
||||
Page.class
|
||||
);
|
||||
|
||||
|
||||
for (Page page : pagesToFix) {
|
||||
for (Layout layout : page.getLayouts()) {
|
||||
final ArrayList<HashSet<DslActionDTO>> layoutOnLoadActions = new ArrayList<>();
|
||||
if (layout.getLayoutOnLoadActions() != null) {
|
||||
layoutOnLoadActions.addAll(layout.getLayoutOnLoadActions());
|
||||
}
|
||||
if (layout.getPublishedLayoutOnLoadActions() != null) {
|
||||
layoutOnLoadActions.addAll(layout.getPublishedLayoutOnLoadActions());
|
||||
}
|
||||
for (HashSet<DslActionDTO> actionSet : layoutOnLoadActions) {
|
||||
for (DslActionDTO actionDTO : actionSet) {
|
||||
final String actionName = actionDTO.getName();
|
||||
final Action action = mongoTemplate.findOne(
|
||||
query(where(FieldName.PAGE_ID).is(page.getId()))
|
||||
.addCriteria(where(FieldName.NAME).is(actionName)),
|
||||
Action.class
|
||||
);
|
||||
if (action != null) {
|
||||
// Update the erroneous action id (template action id) to the cloned action id
|
||||
actionDTO.setId(action.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mongoTemplate.save(page);
|
||||
}
|
||||
|
||||
final long unfixablePagesCount = mongoTemplate.count(
|
||||
query(where(FieldName.APPLICATION_ID).not().in(applicationIds))
|
||||
.addCriteria(where("layouts.layoutOnLoadActions").elemMatch(new Criteria().elemMatch(where("_id").in(actionIds)))),
|
||||
Page.class
|
||||
);
|
||||
|
||||
if (unfixablePagesCount > 0) {
|
||||
log.info("Not all pages' onLoad actions could be fixed. Some old applications might not auto-run actions.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -303,6 +303,18 @@ echo_contact_support() {
|
|||
}
|
||||
|
||||
bye() { # Prints a friendly good bye message and exits the script.
|
||||
echo "Please share your email to receive support with the installation"
|
||||
read -rp 'Email: ' email
|
||||
curl -s -O --location --request POST 'https://api.segment.io/v1/track' \
|
||||
--header 'Authorization: Basic QjJaM3hXRThXdDRwYnZOWDRORnJPNWZ3VXdnYWtFbk06' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"userId": "'"$email"'",
|
||||
"event": "Installation Support",
|
||||
"properties": {
|
||||
"osEnum": '$desired_os'
|
||||
}
|
||||
}'
|
||||
echo -e "\nExiting for now. Bye! \U1F44B\n"
|
||||
exit 1
|
||||
}
|
||||
|
|
@ -544,7 +556,7 @@ if [[ $status_code -ne 401 ]]; then
|
|||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"userId": "'"$email"'",
|
||||
"event": "Installation Failed",
|
||||
"event": "Installation Support",
|
||||
"properties": {
|
||||
"osEnum": '$desired_os'
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user