Merge branch 'release' of https://github.com/appsmithorg/appsmith into release

This commit is contained in:
Automated Github Action 2020-08-14 10:06:03 +00:00
commit fa72df3c36
22 changed files with 466 additions and 217 deletions

View File

@ -33,6 +33,9 @@ server {
sub_filter __APPSMITH_TNC_PP__ '${APPSMITH_TNC_PP}'; sub_filter __APPSMITH_TNC_PP__ '${APPSMITH_TNC_PP}';
sub_filter __APPSMITH_SENTRY_RELEASE__ '${APPSMITH_SENTRY_RELEASE}'; sub_filter __APPSMITH_SENTRY_RELEASE__ '${APPSMITH_SENTRY_RELEASE}';
sub_filter __APPSMITH_SENTRY_ENVIRONMENT__ '${APPSMITH_SENTRY_ENVIRONMENT}'; sub_filter __APPSMITH_SENTRY_ENVIRONMENT__ '${APPSMITH_SENTRY_ENVIRONMENT}';
sub_filter __APPSMITH_VERSION_ID__ '${APPSMITH_VERSION_ID}';
sub_filter __APPSMITH_VERSION_RELEASE_DATE__ '${APPSMITH_VERSION_RELEASE_DATE}';
sub_filter __APPSMITH_INTERCOM_APP_ID__ '${APPSMITH_INTERCOM_APP_ID}';
} }
location /f { location /f {
@ -97,6 +100,9 @@ server {
sub_filter __APPSMITH_TNC_PP__ '${APPSMITH_TNC_PP}'; sub_filter __APPSMITH_TNC_PP__ '${APPSMITH_TNC_PP}';
sub_filter __APPSMITH_SENTRY_RELEASE__ '${APPSMITH_SENTRY_RELEASE}'; sub_filter __APPSMITH_SENTRY_RELEASE__ '${APPSMITH_SENTRY_RELEASE}';
sub_filter __APPSMITH_SENTRY_ENVIRONMENT__ '${APPSMITH_SENTRY_ENVIRONMENT}'; sub_filter __APPSMITH_SENTRY_ENVIRONMENT__ '${APPSMITH_SENTRY_ENVIRONMENT}';
sub_filter __APPSMITH_VERSION_ID__ '${APPSMITH_VERSION_ID}';
sub_filter __APPSMITH_VERSION_RELEASE_DATE__ '${APPSMITH_VERSION_RELEASE_DATE}';
sub_filter __APPSMITH_INTERCOM_APP_ID__ '${APPSMITH_INTERCOM_APP_ID}';
} }
location /f { location /f {

View File

@ -33,6 +33,9 @@ server {
sub_filter __APPSMITH_TNC_PP__ '${APPSMITH_TNC_PP}'; sub_filter __APPSMITH_TNC_PP__ '${APPSMITH_TNC_PP}';
sub_filter __APPSMITH_SENTRY_RELEASE__ '${APPSMITH_SENTRY_RELEASE}'; sub_filter __APPSMITH_SENTRY_RELEASE__ '${APPSMITH_SENTRY_RELEASE}';
sub_filter __APPSMITH_SENTRY_ENVIRONMENT__ '${APPSMITH_SENTRY_ENVIRONMENT}'; sub_filter __APPSMITH_SENTRY_ENVIRONMENT__ '${APPSMITH_SENTRY_ENVIRONMENT}';
sub_filter __APPSMITH_VERSION_ID__ '${APPSMITH_VERSION_ID}';
sub_filter __APPSMITH_VERSION_RELEASE_DATE__ '${APPSMITH_VERSION_RELEASE_DATE}';
sub_filter __APPSMITH_INTERCOM_APP_ID__ '${APPSMITH_INTERCOM_APP_ID}';
} }
location /f { location /f {
@ -98,6 +101,9 @@ server {
sub_filter __APPSMITH_TNC_PP__ '${APPSMITH_TNC_PP}'; sub_filter __APPSMITH_TNC_PP__ '${APPSMITH_TNC_PP}';
sub_filter __APPSMITH_SENTRY_RELEASE__ '${APPSMITH_SENTRY_RELEASE}'; sub_filter __APPSMITH_SENTRY_RELEASE__ '${APPSMITH_SENTRY_RELEASE}';
sub_filter __APPSMITH_SENTRY_ENVIRONMENT__ '${APPSMITH_SENTRY_ENVIRONMENT}'; sub_filter __APPSMITH_SENTRY_ENVIRONMENT__ '${APPSMITH_SENTRY_ENVIRONMENT}';
sub_filter __APPSMITH_VERSION_ID__ '${APPSMITH_VERSION_ID}';
sub_filter __APPSMITH_VERSION_RELEASE_DATE__ '${APPSMITH_VERSION_RELEASE_DATE}';
sub_filter __APPSMITH_INTERCOM_APP_ID__ '${APPSMITH_INTERCOM_APP_ID}';
} }

View File

@ -76,9 +76,20 @@
logLevel: CONFIG_LOG_LEVEL_INDEX > -1 ? LOG_LEVELS[CONFIG_LOG_LEVEL_INDEX] : LOG_LEVELS[1], logLevel: CONFIG_LOG_LEVEL_INDEX > -1 ? LOG_LEVELS[CONFIG_LOG_LEVEL_INDEX] : LOG_LEVELS[1],
google: parseConfig("__APPSMITH_GOOGLE_MAPS_API_KEY__"), google: parseConfig("__APPSMITH_GOOGLE_MAPS_API_KEY__"),
cloudHosting: parseConfig("__APPSMITH_CLOUD_HOSTING__").length > 0, cloudHosting: parseConfig("__APPSMITH_CLOUD_HOSTING__").length > 0,
enableTNCPP: parseConfig("__APPSMITH_TNC_PP__").length > 0 enableTNCPP: parseConfig("__APPSMITH_TNC_PP__").length > 0,
appVersion: {
id: parseConfig("__APPSMITH_VERSION_ID__"),
releaseDate: parseConfig("__APPSMITH_VERSION_RELEASE_DATE__")
},
intercomAppID: parseConfig("__APPSMITH_INTERCOM_APP_ID__"),
}; };
</script> </script>
<script>
if(window.APPSMITH_FEATURE_CONFIGS.cloudHosting && window.APPSMITH_FEATURE_CONFIGS.intercomAppID) {
const WIDGET_URL = `https://widget.intercom.io/widget/${window.APPSMITH_FEATURE_CONFIGS.intercomAppID}`;
(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',w.intercomSettings);}else{var d=document;var i=function(){i.c(arguments);};i.q=[];i.c=function(args){i.q.push(args);};w.Intercom=i;var l=function(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src=WIDGET_URL;var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);};if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})();
}
</script>
<script rel="prefetch" type="text/javascript" src="/shims/realms-shim.umd.min.js"></script> <script rel="prefetch" type="text/javascript" src="/shims/realms-shim.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/5.4.0/tinymce.min.js" referrerpolicy="origin"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/5.4.0/tinymce.min.js" referrerpolicy="origin"></script>
</body> </body>

View File

@ -0,0 +1 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 245 240"><style>.st0{fill:#FFFFFF;}</style><path class="st0" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path class="st0" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,9 +1,7 @@
<svg width="13" height="16" viewBox="0 0 13 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="13" height="16" viewBox="0 0 13 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.7">
<path d="M8 1H2.4C2.0287 1 1.6726 1.1475 1.41005 1.41005C1.1475 1.6726 1 2.0287 1 2.4V13.6C1 13.9713 1.1475 14.3274 1.41005 14.5899C1.6726 14.8525 2.0287 15 2.4 15H10.8C11.1713 15 11.5274 14.8525 11.7899 14.5899C12.0525 14.3274 12.2 13.9713 12.2 13.6V5.2L8 1Z" stroke="white" stroke-linecap="round" stroke-linejoin="round"/> <path d="M8 1H2.4C2.0287 1 1.6726 1.1475 1.41005 1.41005C1.1475 1.6726 1 2.0287 1 2.4V13.6C1 13.9713 1.1475 14.3274 1.41005 14.5899C1.6726 14.8525 2.0287 15 2.4 15H10.8C11.1713 15 11.5274 14.8525 11.7899 14.5899C12.0525 14.3274 12.2 13.9713 12.2 13.6V5.2L8 1Z" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 1V5.2H12.2" stroke="white" stroke-linecap="round" stroke-linejoin="round"/> <path d="M8 1V5.2H12.2" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.40005 8.70117H3.80005" stroke="white" stroke-linecap="round" stroke-linejoin="round"/> <path d="M9.40005 8.70117H3.80005" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.40005 11.5H3.80005" stroke="white" stroke-linecap="round" stroke-linejoin="round"/> <path d="M9.40005 11.5H3.80005" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.20005 5.89941H4.50005H3.80005" stroke="white" stroke-linecap="round" stroke-linejoin="round"/> <path d="M5.20005 5.89941H4.50005H3.80005" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 841 B

After

Width:  |  Height:  |  Size: 818 B

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="438.549px" height="438.549px" viewBox="0 0 438.549 438.549"
xml:space="preserve">
<path d="M409.132,114.573c-19.608-33.596-46.205-60.194-79.798-79.8C295.736,15.166,259.057,5.365,219.271,5.365
c-39.781,0-76.472,9.804-110.063,29.408c-33.596,19.605-60.192,46.204-79.8,79.8C9.803,148.168,0,184.854,0,224.63
c0,47.78,13.94,90.745,41.827,128.906c27.884,38.164,63.906,64.572,108.063,79.227c5.14,0.954,8.945,0.283,11.419-1.996
c2.475-2.282,3.711-5.14,3.711-8.562c0-0.571-0.049-5.708-0.144-15.417c-0.098-9.709-0.144-18.179-0.144-25.406l-6.567,1.136
c-4.187,0.767-9.469,1.092-15.846,1c-6.374-0.089-12.991-0.757-19.842-1.999c-6.854-1.231-13.229-4.086-19.13-8.559
c-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559
c-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-0.951-2.568-2.098-3.711-3.429c-1.142-1.331-1.997-2.663-2.568-3.997
c-0.572-1.335-0.098-2.43,1.427-3.289c1.525-0.859,4.281-1.276,8.28-1.276l5.708,0.853c3.807,0.763,8.516,3.042,14.133,6.851
c5.614,3.806,10.229,8.754,13.846,14.842c4.38,7.806,9.657,13.754,15.846,17.847c6.184,4.093,12.419,6.136,18.699,6.136
c6.28,0,11.704-0.476,16.274-1.423c4.565-0.952,8.848-2.383,12.847-4.285c1.713-12.758,6.377-22.559,13.988-29.41
c-10.848-1.14-20.601-2.857-29.264-5.14c-8.658-2.286-17.605-5.996-26.835-11.14c-9.235-5.137-16.896-11.516-22.985-19.126
c-6.09-7.614-11.088-17.61-14.987-29.979c-3.901-12.374-5.852-26.648-5.852-42.826c0-23.035,7.52-42.637,22.557-58.817
c-7.044-17.318-6.379-36.732,1.997-58.24c5.52-1.715,13.706-0.428,24.554,3.853c10.85,4.283,18.794,7.952,23.84,10.994
c5.046,3.041,9.089,5.618,12.135,7.708c17.705-4.947,35.976-7.421,54.818-7.421s37.117,2.474,54.823,7.421l10.849-6.849
c7.419-4.57,16.18-8.758,26.262-12.565c10.088-3.805,17.802-4.853,23.134-3.138c8.562,21.509,9.325,40.922,2.279,58.24
c15.036,16.18,22.559,35.787,22.559,58.817c0,16.178-1.958,30.497-5.853,42.966c-3.9,12.471-8.941,22.457-15.125,29.979
c-6.191,7.521-13.901,13.85-23.131,18.986c-9.232,5.14-18.182,8.85-26.84,11.136c-8.662,2.286-18.415,4.004-29.263,5.146
c9.894,8.562,14.842,22.077,14.842,40.539v60.237c0,3.422,1.19,6.279,3.572,8.562c2.379,2.279,6.136,2.95,11.276,1.995
c44.163-14.653,80.185-41.062,108.068-79.226c27.88-38.161,41.825-81.126,41.825-128.906
C438.536,184.851,428.728,148.168,409.132,114.573z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { SyntheticEvent } from "react";
import algoliasearch from "algoliasearch/lite"; import algoliasearch from "algoliasearch/lite";
import { import {
InstantSearch, InstantSearch,
@ -8,25 +8,29 @@ import {
Configure, Configure,
PoweredBy, PoweredBy,
} from "react-instantsearch-dom"; } from "react-instantsearch-dom";
import "instantsearch.css/themes/algolia.css"; import "instantsearch.css/themes/algolia.css";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { Icon } from "@blueprintjs/core";
import { useDispatch, useSelector } from "react-redux";
import {
setHelpModalVisibility,
setHelpDefaultRefinement,
} from "actions/helpActions";
import styled from "styled-components"; import styled from "styled-components";
import { HelpIcons } from "icons/HelpIcons"; import { HelpIcons } from "icons/HelpIcons";
import { HelpBaseURL } from "constants/HelpConstants"; import { HelpBaseURL } from "constants/HelpConstants";
import { getDefaultRefinement } from "selectors/helpSelectors"; import { getDefaultRefinement } from "selectors/helpSelectors";
import { getAppsmithConfigs } from "configs"; import { getAppsmithConfigs } from "configs";
const { algolia } = getAppsmithConfigs(); import { AppState } from "reducers";
import {
setHelpDefaultRefinement,
setHelpModalVisibility,
} from "actions/helpActions";
import { Icon } from "@blueprintjs/core";
import moment from "moment";
const { algolia, appVersion, cloudHosting } = getAppsmithConfigs();
const searchClient = algoliasearch(algolia.apiId, algolia.apiKey); const searchClient = algoliasearch(algolia.apiId, algolia.apiKey);
const OenLinkIcon = HelpIcons.OPEN_LINK; const OenLinkIcon = HelpIcons.OPEN_LINK;
const DocumentIcon = HelpIcons.DOCUMENT; const DocumentIcon = HelpIcons.DOCUMENT;
const GithubIcon = HelpIcons.GITHUB;
const ChatIcon = HelpIcons.CHAT;
const DiscordIcon = HelpIcons.DISCORD;
const StyledOpenLinkIcon = styled(OenLinkIcon)` const StyledOpenLinkIcon = styled(OenLinkIcon)`
position: absolute; position: absolute;
@ -47,43 +51,78 @@ const StyledDocumentIcon = styled(DocumentIcon)`
margin-top: 1px; margin-top: 1px;
position: absolute; position: absolute;
`; `;
function Hit(props: any) {
const StyledGithubIcon = styled(GithubIcon)`
margin-left: 14px;
margin-right: 10.8px;
margin-top: 1px;
position: absolute;
`;
const StyledChatIcon = styled(ChatIcon)`
&&& {
margin-left: 14px;
margin-right: 10.8px;
margin-top: 1px;
position: absolute;
}
`;
const StyledDiscordIcon = styled(DiscordIcon)`
&&& {
margin-left: 12px;
margin-right: 10.8px;
margin-top: 1px;
position: absolute;
}
`;
const Hit = (props: { hit: { path: string } }) => {
return ( return (
<div <div
className="t--docHit" className="t--docHit"
onClick={() => { onClick={() => {
window.open( window.open(props.hit.path.replace("master", HelpBaseURL), "_blank");
(props.hit.path as string).replace("master", HelpBaseURL),
"_blank",
);
}} }}
> >
<div className="hit-name t--docHitTitle"> <div className="hit-name t--docHitTitle">
<StyledDocumentIcon <StyledDocumentIcon width={11.2} height={14} color="#181F24" />
width={11.2}
height={14}
color="#181F24"
></StyledDocumentIcon>
<Highlight attribute="title" hit={props.hit} /> <Highlight attribute="title" hit={props.hit} />
<StyledOpenLinkIcon <StyledOpenLinkIcon
className="t--docOpenLink open-link" className="t--docOpenLink open-link"
color={"#181F24"} color={"#181F24"}
></StyledOpenLinkIcon> />
</div> </div>
</div> </div>
); );
}
Hit.propTypes = {
hit: PropTypes.object.isRequired,
}; };
const Header = styled.div` const DefaultHelpMenuItem = (props: {
position: absolute; item: { label: string; link?: string; id?: string; icon: React.ReactNode };
width: 100%; onSelect: Function;
border-top-right-radius: 3px; }) => {
border-top-left-radius: 3px; return (
`; <li className="ais-Hits-item">
<div
className="t--docHit"
id={props.item.id}
onClick={() => {
if (props.item.link) window.open(props.item.link, "_blank");
props.onSelect();
}}
>
<div className="hit-name t--docHitTitle">
{props.item.icon}
<span className="ais-Highlight">{props.item.label}</span>
<StyledOpenLinkIcon
className="t--docOpenLink open-link"
color={"#181F24"}
/>
</div>
</div>
</li>
);
};
const SearchContainer = styled.div` const SearchContainer = styled.div`
height: 100%; height: 100%;
@ -93,7 +132,7 @@ const SearchContainer = styled.div`
position: relative; position: relative;
height: 30px; height: 30px;
margin: 14px; margin: 14px;
margin-top: 0; margin-top: 10px;
} }
.ais-SearchBox-form { .ais-SearchBox-form {
@ -107,8 +146,6 @@ const SearchContainer = styled.div`
} }
.ais-Hits { .ais-Hits {
margin-top: 86px;
height: calc(100% - 86px);
overflow: auto; overflow: auto;
border-bottom-left-radius: 3px; border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px; border-bottom-right-radius: 3px;
@ -205,10 +242,19 @@ const SearchContainer = styled.div`
} }
`; `;
const Header = styled.div`
padding: 10px 0;
position: absolute;
width: 100%;
border-top-right-radius: 3px;
border-top-left-radius: 3px;
height: 50px;
`;
const StyledPoweredBy = styled(PoweredBy)` const StyledPoweredBy = styled(PoweredBy)`
position: absolute; position: absolute;
right: 21px; right: 21px;
bottom: 23px; top: 30px;
z-index: 1; z-index: 1;
.ais-PoweredBy-text { .ais-PoweredBy-text {
@ -216,9 +262,95 @@ const StyledPoweredBy = styled(PoweredBy)`
} }
`; `;
export default function DocumentationSearch(props: { hitsPerPage: number }) { const HelpContainer = styled.div`
const dispatch = useDispatch(); height: 100%;
const defaultRefinement = useSelector(getDefaultRefinement); position: relative;
display: flex;
flex-direction: column;
`;
const HelpFooter = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1px solid rgba(255, 255, 255, 0.1);
padding: 5px 10px;
height: 30px;
color: rgba(255, 255, 255, 0.7);
`;
const HelpBody = styled.div`
padding-top: 60px;
flex: 5;
`;
type Props = { hitsPerPage: number; defaultRefinement: string; dispatch: any };
type State = { showResults: boolean };
type HelpItem = {
label: string;
link?: string;
id?: string;
icon: React.ReactNode;
};
const HELP_MENU_ITEMS: HelpItem[] = [
{
icon: <StyledDocumentIcon width={11.2} height={14} color="#181F24" />,
label: "Documentation",
link: "https://docs.appsmith.com/",
},
{
icon: <StyledGithubIcon width={11.2} height={14} color="#fff" />,
label: "Report a bug",
link: "https://github.com/appsmithorg/appsmith/issues/new/choose",
},
{
icon: <StyledChatIcon width={11.2} height={14} color="#fff" />,
label: "Chat with us",
link: "https://github.com/appsmithorg/appsmith/discussions",
},
{
icon: <StyledDiscordIcon width={16} height={16} />,
label: "Join our Discord",
link: "https://discord.gg/rBTTVJp",
},
];
if (cloudHosting) {
HELP_MENU_ITEMS[2] = {
icon: <StyledChatIcon width={11.2} height={14} color="#fff" />,
label: "Chat with us",
id: "intercom-trigger",
};
}
class DocumentationSearch extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
showResults: props.defaultRefinement.length > 0,
};
}
onSearchValueChange = (event: SyntheticEvent<HTMLInputElement, Event>) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
const value = event.target.value;
if (value === "" && this.state.showResults) {
this.setState({
showResults: false,
});
} else if (value !== "" && !this.state.showResults) {
this.setState({
showResults: true,
});
}
};
handleClose = () => {
this.props.dispatch(setHelpModalVisibility(false));
this.props.dispatch(setHelpDefaultRefinement(""));
};
render() {
if (!algolia.enabled) return null; if (!algolia.enabled) return null;
return ( return (
<SearchContainer className="ais-InstantSearch t--docSearchModal"> <SearchContainer className="ais-InstantSearch t--docSearchModal">
@ -226,63 +358,59 @@ export default function DocumentationSearch(props: { hitsPerPage: number }) {
className="t--docsMinimize" className="t--docsMinimize"
style={{ style={{
position: "absolute", position: "absolute",
top: 4, top: 6,
right: 6, right: 10,
padding: 8,
cursor: "pointer", cursor: "pointer",
zIndex: 1, zIndex: 1,
}} }}
icon="minus" icon="minus"
color="white" color="white"
iconSize={14} iconSize={14}
onClick={() => { onClick={this.handleClose}
dispatch(setHelpModalVisibility(false)); />
dispatch(setHelpDefaultRefinement(""));
}}
></Icon>
<div
style={{
height: "100%",
overflow: "auto",
}}
>
<InstantSearch <InstantSearch
indexName={algolia.indexName} indexName={algolia.indexName}
searchClient={searchClient} searchClient={searchClient}
> >
<Configure hitsPerPage={props.hitsPerPage} /> <Configure hitsPerPage={this.props.hitsPerPage} />
<HelpContainer>
<Header> <Header>
<h3 <StyledPoweredBy />
style={{ <SearchBox
padding: "0 69px", onChange={this.onSearchValueChange}
marginTop: "14px", defaultRefinement={this.props.defaultRefinement}
marginBottom: "14px", />
lineHeight: "14px",
}}
>
<span
style={{
textAlign: "center",
color: "white",
position: "relative",
fontWeight: 500,
fontSize: "14px",
lineHeight: "14px",
letterSpacing: "0.2px",
margin: "0 auto",
width: "121px",
}}
>
Documentation
</span>
</h3>
<StyledPoweredBy></StyledPoweredBy>
<SearchBox defaultRefinement={defaultRefinement}></SearchBox>
</Header> </Header>
<HelpBody>
{this.state.showResults ? (
<Hits hitComponent={Hit as any} /> <Hits hitComponent={Hit as any} />
) : (
<ul className="ais-Hits-list">
{HELP_MENU_ITEMS.map(item => (
<DefaultHelpMenuItem
key={item.label}
item={item}
onSelect={this.handleClose}
/>
))}
</ul>
)}
</HelpBody>
{appVersion.id && (
<HelpFooter>
<span>Appsmith {appVersion.id}</span>
<span>Released {moment(appVersion.releaseDate).fromNow()}</span>
</HelpFooter>
)}
</HelpContainer>
</InstantSearch> </InstantSearch>
</div>
</SearchContainer> </SearchContainer>
); );
}
} }
const mapStateToProps = (state: AppState) => ({
defaultRefinement: getDefaultRefinement(state),
});
export default connect(mapStateToProps)(DocumentationSearch);

View File

@ -1,12 +1,6 @@
import React, { useContext } from "react"; import React, { SyntheticEvent } from "react";
import DocumentationSearch from "components/designSystems/appsmith/help/DocumentationSearch"; import DocumentationSearch from "components/designSystems/appsmith/help/DocumentationSearch";
import { getHelpModalOpen } from "selectors/helpSelectors";
import { useSelector } from "store";
import { useDispatch } from "react-redux";
import {
getHelpModalOpen,
getHelpModalDimensions,
} from "selectors/helpSelectors";
import { import {
setHelpDefaultRefinement, setHelpDefaultRefinement,
setHelpModalVisibility, setHelpModalVisibility,
@ -14,17 +8,20 @@ import {
import styled from "styled-components"; import styled from "styled-components";
import { theme } from "constants/DefaultTheme"; import { theme } from "constants/DefaultTheme";
import ModalComponent from "components/designSystems/blueprint/ModalComponent"; import ModalComponent from "components/designSystems/blueprint/ModalComponent";
import { LayersContext } from "constants/Layers";
import { HelpIcons } from "icons/HelpIcons"; import { HelpIcons } from "icons/HelpIcons";
import { getAppsmithConfigs } from "configs"; import { getAppsmithConfigs } from "configs";
import { LayersContext } from "constants/Layers";
import { connect } from "react-redux";
import { AppState } from "reducers";
const { algolia } = getAppsmithConfigs(); const { algolia } = getAppsmithConfigs();
const HelpButton = styled.div<{ const HelpButton = styled.button<{
highlight: boolean; highlight: boolean;
layer: number; layer: number;
}>` }>`
&&&&& { &&&&& {
position: absolute; position: absolute;
bottom: 46px; bottom: 27px;
right: 27px; right: 27px;
z-index: ${props => props.layer}; z-index: ${props => props.layer};
background: ${props => background: ${props =>
@ -47,14 +44,23 @@ const HelpButton = styled.div<{
} }
`; `;
const MODAL_WIDTH = 240;
const MODAL_HEIGHT = 210;
const MODAL_BOTTOM_DISTANCE = 45;
const MODAL_RIGHT_DISTANCE = 30;
const HelpIcon = HelpIcons.HELP_ICON; const HelpIcon = HelpIcons.HELP_ICON;
export function HelpModal() { type Props = {
const isHelpModalOpen = useSelector(getHelpModalOpen); isHelpModalOpen: boolean;
const helpDimensions = useSelector(getHelpModalDimensions); dispatch: any;
const helpModalOpen = useSelector(getHelpModalOpen); };
const dispatch = useDispatch();
const layers = useContext(LayersContext); class HelpModal extends React.Component<Props> {
static contextType = LayersContext;
render() {
const { dispatch, isHelpModalOpen } = this.props;
const layers = this.context;
return ( return (
<> <>
@ -62,28 +68,30 @@ export function HelpModal() {
canOutsideClickClose canOutsideClickClose
canEscapeKeyClose canEscapeKeyClose
scrollContents scrollContents
width={helpDimensions.width} height={MODAL_HEIGHT}
height={helpDimensions.height} width={MODAL_WIDTH}
top={window.innerHeight - 105 - helpDimensions.height} top={window.innerHeight - MODAL_BOTTOM_DISTANCE - MODAL_HEIGHT}
left={window.innerWidth - 31 - helpDimensions.width} left={window.innerWidth - MODAL_RIGHT_DISTANCE - MODAL_WIDTH}
data-cy={"help-modal"} data-cy={"help-modal"}
hasBackDrop={false} hasBackDrop={false}
onClose={() => { onClose={(event: SyntheticEvent<HTMLElement>) => {
dispatch(setHelpModalVisibility(false)); dispatch(setHelpModalVisibility(false));
dispatch(setHelpDefaultRefinement("")); dispatch(setHelpDefaultRefinement(""));
event.stopPropagation();
event.preventDefault();
}} }}
isOpen={isHelpModalOpen} isOpen={isHelpModalOpen}
zIndex={layers.help} zIndex={layers.help}
> >
<DocumentationSearch hitsPerPage={5} /> <DocumentationSearch hitsPerPage={4} />
</ModalComponent> </ModalComponent>
{algolia.enabled && ( {algolia.enabled && (
<HelpButton <HelpButton
className="t--helpGlobalButton" className="t--helpGlobalButton"
highlight={!helpModalOpen} highlight={!isHelpModalOpen}
layer={layers.help} layer={layers.help}
onClick={() => { onClick={() => {
dispatch(setHelpModalVisibility(!helpModalOpen)); dispatch(setHelpModalVisibility(!isHelpModalOpen));
}} }}
> >
<HelpIcon /> <HelpIcon />
@ -91,4 +99,11 @@ export function HelpModal() {
)} )}
</> </>
); );
}
} }
const mapStateToProps = (state: AppState) => ({
isHelpModalOpen: getHelpModalOpen(state),
});
export default connect(mapStateToProps)(HelpModal);

View File

@ -24,10 +24,16 @@ type INJECTED_CONFIGS = {
indexName: string; indexName: string;
}; };
logLevel: "debug" | "error"; logLevel: "debug" | "error";
appVersion: {
id: string;
releaseDate: string;
};
intercomAppID: string;
}; };
declare global { declare global {
interface Window { interface Window {
APPSMITH_FEATURE_CONFIGS: INJECTED_CONFIGS; APPSMITH_FEATURE_CONFIGS: INJECTED_CONFIGS;
Intercom: any;
} }
} }
@ -79,6 +85,11 @@ const getConfigsFromEnvVars = (): INJECTED_CONFIGS => {
cloudHosting: process.env.REACT_APP_CLOUD_HOSTING cloudHosting: process.env.REACT_APP_CLOUD_HOSTING
? process.env.REACT_APP_CLOUD_HOSTING.length > 0 ? process.env.REACT_APP_CLOUD_HOSTING.length > 0
: false, : false,
appVersion: {
id: process.env.REACT_APP_VERSION_ID || "",
releaseDate: process.env.REACT_APP_VERSION_RELEASE_DATE || "",
},
intercomAppID: process.env.REACT_APP_INTERCOM_APP_ID || "",
}; };
}; };
@ -192,5 +203,8 @@ export const getAppsmithConfigs = (): AppsmithUIConfigs => {
), ),
logLevel: ENV_CONFIG.logLevel || APPSMITH_FEATURE_CONFIGS.logLevel, logLevel: ENV_CONFIG.logLevel || APPSMITH_FEATURE_CONFIGS.logLevel,
enableTNCPP: ENV_CONFIG.enableTNCPP || APPSMITH_FEATURE_CONFIGS.enableTNCPP, enableTNCPP: ENV_CONFIG.enableTNCPP || APPSMITH_FEATURE_CONFIGS.enableTNCPP,
appVersion: ENV_CONFIG.appVersion || APPSMITH_FEATURE_CONFIGS.appVersion,
intercomAppID:
ENV_CONFIG.intercomAppID || APPSMITH_FEATURE_CONFIGS.intercomAppID,
}; };
}; };

View File

@ -61,4 +61,9 @@ export type AppsmithUIConfigs = {
featureFlag?: FeatureFlagConfig; featureFlag?: FeatureFlagConfig;
logLevel: LogLevelDesc; logLevel: LogLevelDesc;
appVersion: {
id: string;
releaseDate: string;
};
intercomAppID: string;
}; };

View File

@ -3,6 +3,9 @@ import { IconProps, IconWrapper } from "constants/IconConstants";
import { ReactComponent as OpenLinkIcon } from "assets/icons/help/openlink.svg"; import { ReactComponent as OpenLinkIcon } from "assets/icons/help/openlink.svg";
import { ReactComponent as DocumentIcon } from "assets/icons/help/document.svg"; import { ReactComponent as DocumentIcon } from "assets/icons/help/document.svg";
import { ReactComponent as HelpIcon } from "assets/icons/help/help.svg"; import { ReactComponent as HelpIcon } from "assets/icons/help/help.svg";
import { ReactComponent as GithubIcon } from "assets/icons/help/github-icon.svg";
import { ReactComponent as DiscordIcon } from "assets/icons/help/discord.svg";
import { Icon } from "@blueprintjs/core";
/* eslint-disable react/display-name */ /* eslint-disable react/display-name */
@ -24,6 +27,21 @@ export const HelpIcons: {
<HelpIcon /> <HelpIcon />
</IconWrapper> </IconWrapper>
), ),
GITHUB: (props: IconProps) => (
<IconWrapper {...props}>
<GithubIcon />
</IconWrapper>
),
CHAT: (props: IconProps) => (
<IconWrapper {...props}>
<Icon icon={"chat"} />
</IconWrapper>
),
DISCORD: (props: IconProps) => (
<IconWrapper {...props}>
<DiscordIcon />
</IconWrapper>
),
}; };
export type HelpIconName = keyof typeof HelpIcons; export type HelpIconName = keyof typeof HelpIcons;

View File

@ -12,7 +12,7 @@ import AppInviteUsersForm from "pages/organization/AppInviteUsersForm";
import Button from "components/editorComponents/Button"; import Button from "components/editorComponents/Button";
import StyledHeader from "components/designSystems/appsmith/StyledHeader"; import StyledHeader from "components/designSystems/appsmith/StyledHeader";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import { HelpModal } from "components/designSystems/appsmith/help/HelpModal"; import HelpModal from "components/designSystems/appsmith/help/HelpModal";
import { FormDialogComponent } from "components/editorComponents/form/FormDialogComponent"; import { FormDialogComponent } from "components/editorComponents/form/FormDialogComponent";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import AppsmithLogo from "assets/images/appsmith_logo_white.png"; import AppsmithLogo from "assets/images/appsmith_logo_white.png";
@ -257,7 +257,6 @@ export const EditorHeader = (props: EditorHeaderProps) => {
/> />
</DeploySection> </DeploySection>
</HeaderSection> </HeaderSection>
{}
<HelpModal /> <HelpModal />
</HeaderWrapper> </HeaderWrapper>
); );

View File

@ -36,6 +36,11 @@ import {
} from "constants/Explorer"; } from "constants/Explorer";
import history from "utils/history"; import history from "utils/history";
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
import { getAppsmithConfigs } from "configs";
import { getCurrentUser } from "selectors/usersSelectors";
import { User } from "constants/userConstants";
const { cloudHosting, intercomAppID } = getAppsmithConfigs();
type EditorProps = { type EditorProps = {
currentApplicationId?: string; currentApplicationId?: string;
@ -45,6 +50,7 @@ type EditorProps = {
isEditorLoading: boolean; isEditorLoading: boolean;
isEditorInitialized: boolean; isEditorInitialized: boolean;
errorPublishing: boolean; errorPublishing: boolean;
user?: User;
}; };
type Props = EditorProps & RouteComponentProps<BuilderRouteParams>; type Props = EditorProps & RouteComponentProps<BuilderRouteParams>;
@ -84,6 +90,7 @@ class Editor extends Component<Props> {
}; };
componentDidMount() { componentDidMount() {
const { user } = this.props;
editorInitializer().then(() => { editorInitializer().then(() => {
this.setState({ registered: true }); this.setState({ registered: true });
}); });
@ -91,8 +98,19 @@ class Editor extends Component<Props> {
if (applicationId && pageId) { if (applicationId && pageId) {
this.props.initEditor(applicationId, pageId); this.props.initEditor(applicationId, pageId);
} }
if (cloudHosting) {
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) { componentDidUpdate(previously: Props) {
if (cloudHosting) window.Intercom("update");
if ( if (
previously.isPublishing && previously.isPublishing &&
!(this.props.isPublishing || this.props.errorPublishing) !(this.props.isPublishing || this.props.errorPublishing)
@ -184,6 +202,7 @@ const mapStateToProps = (state: AppState) => ({
isPublishing: getIsPublishingApplication(state), isPublishing: getIsPublishingApplication(state),
isEditorLoading: getIsEditorLoading(state), isEditorLoading: getIsEditorLoading(state),
isEditorInitialized: getIsEditorInitialized(state), isEditorInitialized: getIsEditorInitialized(state),
user: getCurrentUser(state),
}); });
const mapDispatchToProps = (dispatch: any) => { const mapDispatchToProps = (dispatch: any) => {

View File

@ -4,8 +4,6 @@ import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
const initialState: HelpReduxState = { const initialState: HelpReduxState = {
url: "", url: "",
modalOpen: false, modalOpen: false,
height: 243,
width: 269,
defaultRefinement: "", defaultRefinement: "",
}; };
@ -27,8 +25,6 @@ const helpReducer = createReducer(initialState, {
export interface HelpReduxState { export interface HelpReduxState {
url: string; url: string;
modalOpen: boolean; modalOpen: boolean;
height: number;
width: number;
defaultRefinement: string; defaultRefinement: string;
} }

View File

@ -1,18 +1,8 @@
import { AppState } from "reducers"; import { AppState } from "reducers";
export const getHelpUrl = (state: AppState): string => state.ui.help.url;
export const getHelpModalOpen = (state: AppState): boolean => export const getHelpModalOpen = (state: AppState): boolean =>
state.ui.help.modalOpen; state.ui.help.modalOpen;
export const getHelpModalDimensions = (
state: AppState,
): { height: number; width: number } => {
return {
height: state.ui.help.height,
width: state.ui.help.width,
};
};
export const getDefaultRefinement = (state: AppState): string => { export const getDefaultRefinement = (state: AppState): string => {
return state.ui.help.defaultRefinement || ""; return state.ui.help.defaultRefinement || "";
}; };

View File

@ -19,6 +19,8 @@ public interface OrganizationService extends CrudService<Organization, String> {
Mono<String> getNextUniqueSlug(String initialSlug); Mono<String> getNextUniqueSlug(String initialSlug);
Mono<Organization> createPersonal(Organization organization, User user);
Mono<Organization> create(Organization organization, User user); Mono<Organization> create(Organization organization, User user);
Mono<Organization> findById(String id, AclPermission permission); Mono<Organization> findById(String id, AclPermission permission);

View File

@ -46,9 +46,7 @@ import static java.util.stream.Collectors.toMap;
@Service @Service
public class OrganizationServiceImpl extends BaseService<OrganizationRepository, Organization, String> implements OrganizationService { public class OrganizationServiceImpl extends BaseService<OrganizationRepository, Organization, String> implements OrganizationService {
private final OrganizationRepository repository;
private final SettingService settingService; private final SettingService settingService;
private final GroupService groupService;
private final PluginRepository pluginRepository; private final PluginRepository pluginRepository;
private final SessionUserService sessionUserService; private final SessionUserService sessionUserService;
private final UserOrganizationService userOrganizationService; private final UserOrganizationService userOrganizationService;
@ -63,16 +61,13 @@ public class OrganizationServiceImpl extends BaseService<OrganizationRepository,
OrganizationRepository repository, OrganizationRepository repository,
SettingService settingService, SettingService settingService,
AnalyticsService analyticsService, AnalyticsService analyticsService,
GroupService groupService,
PluginRepository pluginRepository, PluginRepository pluginRepository,
SessionUserService sessionUserService, SessionUserService sessionUserService,
UserOrganizationService userOrganizationService, UserOrganizationService userOrganizationService,
UserRepository userRepository, UserRepository userRepository,
RoleGraph roleGraph) { RoleGraph roleGraph) {
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService); super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
this.repository = repository;
this.settingService = settingService; this.settingService = settingService;
this.groupService = groupService;
this.pluginRepository = pluginRepository; this.pluginRepository = pluginRepository;
this.sessionUserService = sessionUserService; this.sessionUserService = sessionUserService;
this.userOrganizationService = userOrganizationService; this.userOrganizationService = userOrganizationService;
@ -104,6 +99,20 @@ public class OrganizationServiceImpl extends BaseService<OrganizationRepository,
.map(max -> initialSlug + (max == 0 ? "" : (max + 1))); .map(max -> initialSlug + (max == 0 ? "" : (max + 1)));
} }
/**
* Creates the given organization as a personal organization for the given user. That is, the organization's name
* is changed to "[username]'s Personal Organization" and then created. The current value of the organization name
* is discarded.
* @param organization Organization object to be created.
* @param user User to whom this organization will belong to, as a personal organization.
* @return Publishes the saved organization.
*/
@Override
public Mono<Organization> createPersonal(final Organization organization, User user) {
organization.setName(user.computeFirstName() + "'s Personal Organization");
return create(organization, user);
}
/** /**
* This function does the following: * This function does the following:
* 1. Creates the organization for the user * 1. Creates the organization for the user
@ -112,10 +121,11 @@ public class OrganizationServiceImpl extends BaseService<OrganizationRepository,
* 4. Adds the user to the newly created organization * 4. Adds the user to the newly created organization
* 5. Assigns the default groups to the user creating the organization * 5. Assigns the default groups to the user creating the organization
* *
* @param organization * @param organization Organization object to be created.
* @param user * @param user User to whom this organization will belong to.
* @return * @return Publishes the saved organization.
*/ */
@Override
public Mono<Organization> create(Organization organization, User user) { public Mono<Organization> create(Organization organization, User user) {
if (organization == null) { if (organization == null) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ORGANIZATION)); return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ORGANIZATION));
@ -123,9 +133,7 @@ public class OrganizationServiceImpl extends BaseService<OrganizationRepository,
// Does the user have permissions to create an organization? // Does the user have permissions to create an organization?
boolean isManageOrgPolicyPresent = user.getPolicies().stream() boolean isManageOrgPolicyPresent = user.getPolicies().stream()
.filter(policy -> policy.getPermission().equals(USER_MANAGE_ORGANIZATIONS.getValue())) .anyMatch(policy -> policy.getPermission().equals(USER_MANAGE_ORGANIZATIONS.getValue()));
.findFirst()
.isPresent();
if (!isManageOrgPolicyPresent) { if (!isManageOrgPolicyPresent) {
return Mono.error(new AppsmithException(AppsmithError.UNAUTHORIZED_ACCESS)); return Mono.error(new AppsmithException(AppsmithError.UNAUTHORIZED_ACCESS));
@ -254,7 +262,7 @@ public class OrganizationServiceImpl extends BaseService<OrganizationRepository,
Mono<Organization> organizationMono = repository.findById(orgId, ORGANIZATION_INVITE_USERS); Mono<Organization> organizationMono = repository.findById(orgId, ORGANIZATION_INVITE_USERS);
Mono<String> usernameMono = sessionUserService Mono<String> usernameMono = sessionUserService
.getCurrentUser() .getCurrentUser()
.map(user -> user.getUsername()); .map(User::getUsername);
return organizationMono return organizationMono
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ORGANIZATION, orgId))) .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ORGANIZATION, orgId)))
@ -280,7 +288,7 @@ public class OrganizationServiceImpl extends BaseService<OrganizationRepository,
Map<String, String> appsmithRolesMap = appsmithRoles Map<String, String> appsmithRolesMap = appsmithRoles
.stream() .stream()
.collect(toMap(role -> role.getName(), AppsmithRole::getDescription)); .collect(toMap(AppsmithRole::getName, AppsmithRole::getDescription));
return Mono.just(appsmithRolesMap); return Mono.just(appsmithRolesMap);
}); });

View File

@ -66,6 +66,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
private final OrganizationRepository organizationRepository; private final OrganizationRepository organizationRepository;
private final UserOrganizationService userOrganizationService; private final UserOrganizationService userOrganizationService;
private final RoleGraph roleGraph; private final RoleGraph roleGraph;
private final ConfigService configService;
private static final String WELCOME_USER_EMAIL_TEMPLATE = "email/welcomeUserTemplate.html"; private static final String WELCOME_USER_EMAIL_TEMPLATE = "email/welcomeUserTemplate.html";
private static final String FORGOT_PASSWORD_EMAIL_TEMPLATE = "email/forgotPasswordTemplate.html"; private static final String FORGOT_PASSWORD_EMAIL_TEMPLATE = "email/forgotPasswordTemplate.html";
@ -92,7 +93,8 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
PolicyUtils policyUtils, PolicyUtils policyUtils,
OrganizationRepository organizationRepository, OrganizationRepository organizationRepository,
UserOrganizationService userOrganizationService, UserOrganizationService userOrganizationService,
RoleGraph roleGraph) { RoleGraph roleGraph,
ConfigService configService) {
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService); super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
this.organizationService = organizationService; this.organizationService = organizationService;
this.analyticsService = analyticsService; this.analyticsService = analyticsService;
@ -105,6 +107,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
this.organizationRepository = organizationRepository; this.organizationRepository = organizationRepository;
this.userOrganizationService = userOrganizationService; this.userOrganizationService = userOrganizationService;
this.roleGraph = roleGraph; this.roleGraph = roleGraph;
this.configService = configService;
} }
@Override @Override
@ -429,30 +432,32 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
if (user.getPassword() == null || user.getPassword().isBlank()) { if (user.getPassword() == null || user.getPassword().isBlank()) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_CREDENTIALS)); return Mono.error(new AppsmithException(AppsmithError.INVALID_CREDENTIALS));
} }
user.setPassword(this.passwordEncoder.encode(user.getPassword())); user.setPassword(passwordEncoder.encode(user.getPassword()));
} }
Organization personalOrg = new Organization(); if (!StringUtils.hasText(user.getName())) {
if (user.getName() == null) {
user.setName(user.getEmail()); user.setName(user.getEmail());
} }
String personalOrganizationName = user.computeFirstName() + "'s Personal Organization";
personalOrg.setName(personalOrganizationName);
// Set the permissions for the user // Set the permissions for the user
user.getPolicies().addAll(crudUserPolicy(user)); user.getPolicies().addAll(crudUserPolicy(user));
// Save the new user // Save the new user
Mono<User> savedUserMono = Mono.just(user) return Mono.just(user)
.flatMap(this::validateObject) .flatMap(this::validateObject)
.flatMap(repository::save); .flatMap(repository::save)
.zipWith(configService.getTemplateOrganizationId().defaultIfEmpty(""))
.flatMap(tuple -> {
final String templateOrganizationId = tuple.getT2();
return savedUserMono if (!StringUtils.hasText(templateOrganizationId)) {
.flatMap(savedUser -> { // Since template organization is not configured, we create an empty personal organization.
// Creating the personal workspace and assigning the default groups to the new user final User savedUser = tuple.getT1();
log.debug("Going to create organization: {} for user: {}", personalOrg, savedUser.getEmail()); log.debug("Creating blank personal organization for user '{}'.", savedUser.getEmail());
return organizationService.create(personalOrg, savedUser); return organizationService.createPersonal(new Organization(), savedUser);
}
return Mono.empty();
}) })
.then(repository.findByEmail(user.getUsername())) .then(repository.findByEmail(user.getUsername()))
.flatMap(analyticsService::trackNewUser); .flatMap(analyticsService::trackNewUser);

View File

@ -117,9 +117,8 @@ public class ExamplesOrganizationCloner {
if (!CollectionUtils.isEmpty(organization.getUserRoles())) { if (!CollectionUtils.isEmpty(organization.getUserRoles())) {
organization.getUserRoles().clear(); organization.getUserRoles().clear();
} }
organization.setName(user.computeFirstName() + "'s Examples");
organization.setSlug(null); organization.setSlug(null);
return organizationService.create(organization, user); return organizationService.createPersonal(organization, user);
}) })
.flatMap(newOrganization -> { .flatMap(newOrganization -> {
User userUpdate = new User(); User userUpdate = new User();

View File

@ -63,6 +63,7 @@ public class ExampleApplicationsAreMarked {
.flatMap(tuple -> { .flatMap(tuple -> {
final Organization organization = tuple.getT1(); final Organization organization = tuple.getT1();
assert organization.getId() != null;
Mockito.when(configService.getTemplateOrganizationId()).thenReturn(Mono.just(organization.getId())); Mockito.when(configService.getTemplateOrganizationId()).thenReturn(Mono.just(organization.getId()));
final Application app1 = new Application(); final Application app1 = new Application();

View File

@ -26,7 +26,6 @@ import com.appsmith.server.services.LayoutActionService;
import com.appsmith.server.services.OrganizationService; import com.appsmith.server.services.OrganizationService;
import com.appsmith.server.services.PageService; import com.appsmith.server.services.PageService;
import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.UserService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject; import net.minidev.json.JSONObject;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
@ -68,9 +67,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext @DirtiesContext
public class ExamplesOrganizationClonerTests { public class ExamplesOrganizationClonerTests {
@Autowired
UserService userService;
@Autowired @Autowired
private ExamplesOrganizationCloner examplesOrganizationCloner; private ExamplesOrganizationCloner examplesOrganizationCloner;
@ -161,7 +157,7 @@ public class ExamplesOrganizationClonerTests {
.assertNext(data -> { .assertNext(data -> {
assertThat(data.organization).isNotNull(); assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull(); assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples"); assertThat(data.organization.getName()).isEqualTo("api_user's Personal Organization");
assertThat(data.organization.getPolicies()).isNotEmpty(); assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.applications).isEmpty(); assertThat(data.applications).isEmpty();
@ -203,7 +199,7 @@ public class ExamplesOrganizationClonerTests {
.assertNext(data -> { .assertNext(data -> {
assertThat(data.organization).isNotNull(); assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull(); assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples"); assertThat(data.organization.getName()).isEqualTo("api_user's Personal Organization");
assertThat(data.organization.getPolicies()).isNotEmpty(); assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.applications).hasSize(1); assertThat(data.applications).hasSize(1);
@ -255,7 +251,7 @@ public class ExamplesOrganizationClonerTests {
.assertNext(data -> { .assertNext(data -> {
assertThat(data.organization).isNotNull(); assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull(); assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples"); assertThat(data.organization.getName()).isEqualTo("api_user's Personal Organization");
assertThat(data.organization.getPolicies()).isNotEmpty(); assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.applications).hasSize(2); assertThat(data.applications).hasSize(2);
@ -310,7 +306,7 @@ public class ExamplesOrganizationClonerTests {
.assertNext(data -> { .assertNext(data -> {
assertThat(data.organization).isNotNull(); assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull(); assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples"); assertThat(data.organization.getName()).isEqualTo("api_user's Personal Organization");
assertThat(data.organization.getPolicies()).isNotEmpty(); assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.applications).isEmpty(); assertThat(data.applications).isEmpty();
@ -361,7 +357,7 @@ public class ExamplesOrganizationClonerTests {
.assertNext(data -> { .assertNext(data -> {
assertThat(data.organization).isNotNull(); assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull(); assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples"); assertThat(data.organization.getName()).isEqualTo("api_user's Personal Organization");
assertThat(data.organization.getPolicies()).isNotEmpty(); assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.datasources).hasSize(2); assertThat(data.datasources).hasSize(2);
@ -437,7 +433,7 @@ public class ExamplesOrganizationClonerTests {
.assertNext(data -> { .assertNext(data -> {
assertThat(data.organization).isNotNull(); assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull(); assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples"); assertThat(data.organization.getName()).isEqualTo("api_user's Personal Organization");
assertThat(data.organization.getPolicies()).isNotEmpty(); assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.applications).hasSize(2); assertThat(data.applications).hasSize(2);
@ -561,9 +557,7 @@ public class ExamplesOrganizationClonerTests {
newPageAction.setPageId(page.getId()); newPageAction.setPageId(page.getId());
return applicationPageService.addPageToApplication(app, page, false) return applicationPageService.addPageToApplication(app, page, false)
.then(actionCollectionService.createAction(newPageAction)) .then(actionCollectionService.createAction(newPageAction))
.flatMap(savedAction -> { .flatMap(savedAction -> layoutActionService.updateAction(savedAction.getId(), savedAction))
return layoutActionService.updateAction(savedAction.getId(), savedAction);
})
.then(pageService.findById(page.getId(), READ_PAGES)); .then(pageService.findById(page.getId(), READ_PAGES));
}) })
.map(tuple2 -> { .map(tuple2 -> {
@ -578,16 +572,14 @@ public class ExamplesOrganizationClonerTests {
}) })
.then(examplesOrganizationCloner.cloneOrganizationForUser(organization.getId(), tuple.getT2())); .then(examplesOrganizationCloner.cloneOrganizationForUser(organization.getId(), tuple.getT2()));
}) })
.doOnError(error -> { .doOnError(error -> log.error("Error preparing data for test", error))
log.error("Error preparing data for test", error);
})
.flatMap(this::loadOrganizationData); .flatMap(this::loadOrganizationData);
StepVerifier.create(resultMono) StepVerifier.create(resultMono)
.assertNext(data -> { .assertNext(data -> {
assertThat(data.organization).isNotNull(); assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull(); assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples"); assertThat(data.organization.getName()).isEqualTo("api_user's Personal Organization");
assertThat(data.organization.getPolicies()).isNotEmpty(); assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.applications).hasSize(2); assertThat(data.applications).hasSize(2);
@ -596,10 +588,13 @@ public class ExamplesOrganizationClonerTests {
"second application" "second application"
); );
final Application firstApplication = data.applications.stream().filter(app -> app.getName().equals("first application")).findFirst().get(); final Application firstApplication = data.applications.stream().filter(app -> app.getName().equals("first application")).findFirst().orElse(null);
assert firstApplication != null;
final Page newPage = mongoTemplate.findOne(Query.query(Criteria.where("applicationId").is(firstApplication.getId()).and("name").is("A New Page")), Page.class); final Page newPage = mongoTemplate.findOne(Query.query(Criteria.where("applicationId").is(firstApplication.getId()).and("name").is("A New Page")), Page.class);
assert newPage != null;
final String actionId = newPage.getLayouts().get(0).getLayoutOnLoadActions().get(0).iterator().next().getId(); final String actionId = newPage.getLayouts().get(0).getLayoutOnLoadActions().get(0).iterator().next().getId();
final Action newPageAction = mongoTemplate.findOne(Query.query(Criteria.where("id").is(actionId)), Action.class); final Action newPageAction = mongoTemplate.findOne(Query.query(Criteria.where("id").is(actionId)), Action.class);
assert newPageAction != null;
assertThat(newPageAction.getOrganizationId()).isEqualTo(data.organization.getId()); assertThat(newPageAction.getOrganizationId()).isEqualTo(data.organization.getId());
assertThat(data.datasources).hasSize(2); assertThat(data.datasources).hasSize(2);
@ -628,7 +623,7 @@ public class ExamplesOrganizationClonerTests {
return applicationService return applicationService
.findByOrganizationId(organization.getId(), READ_APPLICATIONS) .findByOrganizationId(organization.getId(), READ_APPLICATIONS)
.flatMap(application -> pageService.findByApplicationId(application.getId(), READ_PAGES)) .flatMap(application -> pageService.findByApplicationId(application.getId(), READ_PAGES))
.flatMap(page -> actionService.get(new LinkedMultiValueMap<String, String>( .flatMap(page -> actionService.get(new LinkedMultiValueMap<>(
Map.of(FieldName.PAGE_ID, Collections.singletonList(page.getId()))))); Map.of(FieldName.PAGE_ID, Collections.singletonList(page.getId())))));
} }
} }

View File

@ -44,6 +44,9 @@ $NGINX_SSL_CMNT server_name $custom_domain ;
sub_filter __APPSMITH_CLIENT_LOG_LEVEL__ '\''${APPSMITH_CLIENT_LOG_LEVEL}'\''; sub_filter __APPSMITH_CLIENT_LOG_LEVEL__ '\''${APPSMITH_CLIENT_LOG_LEVEL}'\'';
sub_filter __APPSMITH_GOOGLE_MAPS_API_KEY__ '\''${APPSMITH_GOOGLE_MAPS_API_KEY}'\''; sub_filter __APPSMITH_GOOGLE_MAPS_API_KEY__ '\''${APPSMITH_GOOGLE_MAPS_API_KEY}'\'';
sub_filter __APPSMITH_TNC_PP__ '\''${APPSMITH_TNC_PP}'\''; sub_filter __APPSMITH_TNC_PP__ '\''${APPSMITH_TNC_PP}'\'';
sub_filter __APPSMITH_VERSION_ID__ '\''${APPSMITH_VERSION_ID}'\'';
sub_filter __APPSMITH_VERSION_RELEASE_DATE__ '\''${APPSMITH_VERSION_RELEASE_DATE}'\'';
sub_filter __APPSMITH_INTERCOM_APP_ID__ '\''${APPSMITH_INTERCOM_APP_ID}'\'';
} }
location /f { location /f {
@ -96,6 +99,9 @@ $NGINX_SSL_CMNT sub_filter __APPSMITH_ALGOLIA_API_KEY__ '\''${APPSMITH_AL
$NGINX_SSL_CMNT sub_filter __APPSMITH_CLIENT_LOG_LEVEL__ '\''${APPSMITH_CLIENT_LOG_LEVEL}'\''; $NGINX_SSL_CMNT sub_filter __APPSMITH_CLIENT_LOG_LEVEL__ '\''${APPSMITH_CLIENT_LOG_LEVEL}'\'';
$NGINX_SSL_CMNT sub_filter __APPSMITH_GOOGLE_MAPS_API_KEY__ '\''${APPSMITH_GOOGLE_MAPS_API_KEY}'\''; $NGINX_SSL_CMNT sub_filter __APPSMITH_GOOGLE_MAPS_API_KEY__ '\''${APPSMITH_GOOGLE_MAPS_API_KEY}'\'';
$NGINX_SSL_CMNT sub_filter __APPSMITH_TNC_PP__ '\''${APPSMITH_TNC_PP}'\''; $NGINX_SSL_CMNT sub_filter __APPSMITH_TNC_PP__ '\''${APPSMITH_TNC_PP}'\'';
$NGINX_SSL_CMNT sub_filter __APPSMITH_VERSION_ID__ '\''${APPSMITH_VERSION_ID}'\'';
$NGINX_SSL_CMNT sub_filter __APPSMITH_VERSION_RELEASE_DATE__ '\''${APPSMITH_VERSION_RELEASE_DATE}'\'';
$NGINX_SSL_CMNT sub_filter __APPSMITH_INTERCOM_APP_ID__ '\''${APPSMITH_INTERCOM_APP_ID}'\'';
$NGINX_SSL_CMNT } $NGINX_SSL_CMNT }
$NGINX_SSL_CMNT $NGINX_SSL_CMNT
$NGINX_SSL_CMNT location /f { $NGINX_SSL_CMNT location /f {