feat: Feat/add google sheets metdata (#22577)

This PR does three things:

1. Cleans up the Datasource Form page and makes the code more readable
and discoverable
2. Adds the Account information for google sheets
3. Adds a way to render form configs specifically in view or edit modes.
This commit is contained in:
Ayangade Adeoluwa 2023-04-27 14:52:41 +01:00 committed by GitHub
parent 82d931e173
commit f87ba6a671
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 115 additions and 56 deletions

View File

@ -1571,8 +1571,7 @@ export const RECONNECT_BUTTON_TEXT = () => "RECONNECT";
export const SAVE_BUTTON_TEXT = () => "SAVE"; export const SAVE_BUTTON_TEXT = () => "SAVE";
export const SAVE_AND_AUTHORIZE_BUTTON_TEXT = () => "SAVE AND AUTHORIZE"; export const SAVE_AND_AUTHORIZE_BUTTON_TEXT = () => "SAVE AND AUTHORIZE";
export const DISCARD_POPUP_DONT_SAVE_BUTTON_TEXT = () => "DON'T SAVE"; export const DISCARD_POPUP_DONT_SAVE_BUTTON_TEXT = () => "DON'T SAVE";
export const GSHEET_AUTHORISED_FILE_IDS_KEY = () => export const GSHEET_AUTHORISED_FILE_IDS_KEY = () => "userAuthorizedSheetIds";
"Google sheets authorised file ids key";
export const GOOGLE_SHEETS_INFO_BANNER_MESSAGE = () => export const GOOGLE_SHEETS_INFO_BANNER_MESSAGE = () =>
"Appsmith will require access to your google drive to access google sheets."; "Appsmith will require access to your google drive to access google sheets.";
export const GOOGLE_SHEETS_AUTHORIZE_DATASOURCE = () => "Authorize Datasource"; export const GOOGLE_SHEETS_AUTHORIZE_DATASOURCE = () => "Authorize Datasource";

View File

@ -20,7 +20,12 @@ export type ComparisonOperations =
| "GREATER" | "GREATER"
| "IN" | "IN"
| "NOT_IN" | "NOT_IN"
| "FEATURE_FLAG"; | "FEATURE_FLAG"
| "VIEW_MODE";
export enum ComparisonOperationsEnum {
VIEW_MODE = "VIEW_MODE",
}
export type HiddenType = boolean | Condition | ConditionObject; export type HiddenType = boolean | Condition | ConditionObject;

View File

@ -65,6 +65,7 @@ export const caculateIsHidden = (
values: any, values: any,
hiddenConfig?: HiddenType, hiddenConfig?: HiddenType,
featureFlags?: FeatureFlags, featureFlags?: FeatureFlags,
viewMode?: boolean,
) => { ) => {
if (!!hiddenConfig && !isBoolean(hiddenConfig)) { if (!!hiddenConfig && !isBoolean(hiddenConfig)) {
let valueAtPath; let valueAtPath;
@ -102,6 +103,9 @@ export const caculateIsHidden = (
// and show new configs if feature flag is enabled, if disabled/ not present, // and show new configs if feature flag is enabled, if disabled/ not present,
// previous config would be shown as is // previous config would be shown as is
return !!featureFlags && featureFlags[flagValue] === value; return !!featureFlags && featureFlags[flagValue] === value;
case "VIEW_MODE":
// This can be used to decide which form controls to show in view mode or edit mode depending on the value.
return viewMode === value;
default: default:
return true; return true;
} }
@ -112,13 +116,14 @@ export const isHidden = (
values: any, values: any,
hiddenConfig?: HiddenType, hiddenConfig?: HiddenType,
featureFlags?: FeatureFlags, featureFlags?: FeatureFlags,
viewMode?: boolean,
) => { ) => {
if (!!hiddenConfig && !isBoolean(hiddenConfig)) { if (!!hiddenConfig && !isBoolean(hiddenConfig)) {
if ("conditionType" in hiddenConfig) { if ("conditionType" in hiddenConfig) {
//check if nested conditions exist //check if nested conditions exist
return isHiddenConditionsEvaluation(values, hiddenConfig); return isHiddenConditionsEvaluation(values, hiddenConfig);
} else { } else {
return caculateIsHidden(values, hiddenConfig, featureFlags); return caculateIsHidden(values, hiddenConfig, featureFlags, viewMode);
} }
} }
return !!hiddenConfig; return !!hiddenConfig;

View File

@ -2,12 +2,10 @@ import React from "react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { useParams } from "react-router"; import { useParams } from "react-router";
import type { AppState } from "@appsmith/reducers"; import type { AppState } from "@appsmith/reducers";
import { isNil } from "lodash";
import { getDatasource, getPlugin } from "selectors/entitiesSelector"; import { getDatasource, getPlugin } from "selectors/entitiesSelector";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import { HeaderIcons } from "icons/HeaderIcons"; import { HeaderIcons } from "icons/HeaderIcons";
import styled from "styled-components"; import styled from "styled-components";
import RenderDatasourceInformation from "./DatasourceSection";
import NewActionButton from "./NewActionButton"; import NewActionButton from "./NewActionButton";
import { hasCreateDatasourceActionPermission } from "@appsmith/utils/permissionHelpers"; import { hasCreateDatasourceActionPermission } from "@appsmith/utils/permissionHelpers";
@ -28,16 +26,8 @@ const Header = styled.div`
justify-content: space-between; justify-content: space-between;
`; `;
const Wrapper = styled.div`
display: flex;
flex-direction: column;
border-bottom: 1px solid #d0d7dd;
padding: 24px 20px;
`;
function Connected({ function Connected({
errorComponent, errorComponent,
hideDatasourceRenderSection = false,
showDatasourceSavedText = true, showDatasourceSavedText = true,
}: { }: {
errorComponent?: JSX.Element | null; errorComponent?: JSX.Element | null;
@ -50,10 +40,6 @@ function Connected({
getDatasource(state, params.datasourceId), getDatasource(state, params.datasourceId),
); );
const datasourceFormConfigs = useSelector(
(state: AppState) => state.entities.plugins.formConfigs,
);
const plugin = useSelector((state: AppState) => const plugin = useSelector((state: AppState) =>
getPlugin(state, datasource?.pluginId ?? ""), getPlugin(state, datasource?.pluginId ?? ""),
); );
@ -67,11 +53,8 @@ function Connected({
...pagePermissions, ...pagePermissions,
]); ]);
const currentFormConfig: Array<any> =
datasourceFormConfigs[datasource?.pluginId ?? ""];
return ( return (
<Wrapper> <>
{showDatasourceSavedText && ( {showDatasourceSavedText && (
<Header> <Header>
<ConnectedText> <ConnectedText>
@ -91,17 +74,7 @@ function Connected({
</Header> </Header>
)} )}
{errorComponent} {errorComponent}
<div style={{ marginTop: showDatasourceSavedText ? "30px" : "" }}> </>
{!isNil(currentFormConfig) &&
!isNil(datasource) &&
!hideDatasourceRenderSection ? (
<RenderDatasourceInformation
config={currentFormConfig[0]}
datasource={datasource}
/>
) : undefined}
</div>
</Wrapper>
); );
} }

View File

@ -34,6 +34,7 @@ import Debugger, {
} from "./Debugger"; } from "./Debugger";
import { getAssetUrl } from "@appsmith/utils/airgapHelpers"; import { getAssetUrl } from "@appsmith/utils/airgapHelpers";
import { showDebuggerFlag } from "selectors/debuggerSelectors"; import { showDebuggerFlag } from "selectors/debuggerSelectors";
import DatasourceInformation from "./DatasourceSection";
import { DocsLink, openDoc } from "../../../constants/DocumentationLinks"; import { DocsLink, openDoc } from "../../../constants/DocumentationLinks";
const { cloudHosting } = getAppsmithConfigs(); const { cloudHosting } = getAppsmithConfigs();
@ -86,6 +87,13 @@ export const Form = styled.form`
flex: 1; flex: 1;
`; `;
const ViewModeWrapper = styled.div`
display: flex;
flex-direction: column;
border-bottom: 1px solid #d0d7dd;
padding: 24px 20px;
`;
class DatasourceDBEditor extends JSONtoForm<Props> { class DatasourceDBEditor extends JSONtoForm<Props> {
componentDidUpdate(prevProps: Props) { componentDidUpdate(prevProps: Props) {
if (prevProps.datasourceId !== this.props.datasourceId) { if (prevProps.datasourceId !== this.props.datasourceId) {
@ -125,6 +133,7 @@ class DatasourceDBEditor extends JSONtoForm<Props> {
datasourceButtonConfiguration, datasourceButtonConfiguration,
datasourceDeleteTrigger, datasourceDeleteTrigger,
datasourceId, datasourceId,
formConfig,
formData, formData,
messages, messages,
pluginType, pluginType,
@ -200,7 +209,20 @@ class DatasourceDBEditor extends JSONtoForm<Props> {
{""} {""}
</> </>
)} )}
{viewMode && <Connected />} {viewMode && (
<ViewModeWrapper>
<Connected />
<div style={{ marginTop: "30px" }}>
{!_.isNil(formConfig) && !_.isNil(datasource) ? (
<DatasourceInformation
config={formConfig[0]}
datasource={datasource}
viewMode={viewMode}
/>
) : undefined}
</div>
</ViewModeWrapper>
)}
{/* Render datasource form call-to-actions */} {/* Render datasource form call-to-actions */}
{datasource && ( {datasource && (
<DatasourceAuth <DatasourceAuth

View File

@ -5,6 +5,7 @@ import { Colors } from "constants/Colors";
import styled from "styled-components"; import styled from "styled-components";
import { isHidden, isKVArray } from "components/formControls/utils"; import { isHidden, isKVArray } from "components/formControls/utils";
import log from "loglevel"; import log from "loglevel";
import { ComparisonOperationsEnum } from "components/formControls/BaseControl";
const Key = styled.div` const Key = styled.div`
color: ${Colors.DOVE_GRAY}; color: ${Colors.DOVE_GRAY};
@ -35,6 +36,7 @@ const FieldWrapper = styled.div`
export default class RenderDatasourceInformation extends React.Component<{ export default class RenderDatasourceInformation extends React.Component<{
config: any; config: any;
datasource: Datasource; datasource: Datasource;
viewMode?: boolean;
}> { }> {
renderKVArray = (children: Array<any>) => { renderKVArray = (children: Array<any>) => {
try { try {
@ -85,11 +87,12 @@ export default class RenderDatasourceInformation extends React.Component<{
}; };
renderDatasourceSection(section: any) { renderDatasourceSection(section: any) {
const { datasource } = this.props; const { datasource, viewMode } = this.props;
return ( return (
<React.Fragment key={datasource.id}> <React.Fragment key={datasource.id}>
{map(section.children, (section) => { {map(section.children, (section) => {
if (isHidden(datasource, section.hidden)) return null; if (isHidden(datasource, section.hidden, undefined, viewMode))
return null;
if ("children" in section) { if ("children" in section) {
if (isKVArray(section.children)) { if (isKVArray(section.children)) {
return this.renderKVArray(section.children); return this.renderKVArray(section.children);
@ -123,6 +126,15 @@ export default class RenderDatasourceInformation extends React.Component<{
} }
} }
if (
!value &&
!!viewMode &&
"comparison" in section.hidden &&
section.hidden.comparison === ComparisonOperationsEnum.VIEW_MODE
) {
value = section.initialValue;
}
if (!value || (isArray(value) && value.length < 1)) { if (!value || (isArray(value) && value.length < 1)) {
return; return;
} }

View File

@ -246,7 +246,14 @@ export class JSONtoForm<
// hides features/configs that are hidden behind feature flag // hides features/configs that are hidden behind feature flag
// TODO: remove hidden config property as well as this param, // TODO: remove hidden config property as well as this param,
// when feature flag is removed // when feature flag is removed
if (isHidden(this.props.formData, section.hidden, this.props?.featureFlags)) if (
isHidden(
this.props.formData,
section.hidden,
this.props?.featureFlags,
false, // viewMode is false here.
)
)
return null; return null;
return ( return (
<Collapsible <Collapsible
@ -314,8 +321,9 @@ export class JSONtoForm<
if ( if (
isHidden( isHidden(
this.props.formData, this.props.formData,
section.hidden, propertyControlOrSection.hidden,
this.props?.featureFlags, this.props?.featureFlags,
false,
) )
) )
return null; return null;

View File

@ -738,7 +738,8 @@ export function EditorJSONtoForm(props: Props) {
(section: any): any => { (section: any): any => {
return section.children.map( return section.children.map(
(formControlOrSection: ControlProps, idx: number) => { (formControlOrSection: ControlProps, idx: number) => {
if (isHidden(props.formData, section.hidden)) return null; if (isHidden(props.formData, section.hidden, undefined, false))
return null;
if (formControlOrSection.hasOwnProperty("children")) { if (formControlOrSection.hasOwnProperty("children")) {
return renderEachConfig(formName)(formControlOrSection); return renderEachConfig(formName)(formControlOrSection);
} else { } else {

View File

@ -29,7 +29,6 @@ import {
} from "../DataSourceEditor/JSONtoForm"; } from "../DataSourceEditor/JSONtoForm";
import { getConfigInitialValues } from "components/formControls/utils"; import { getConfigInitialValues } from "components/formControls/utils";
import Connected from "../DataSourceEditor/Connected"; import Connected from "../DataSourceEditor/Connected";
import { import {
getCurrentApplicationId, getCurrentApplicationId,
getGsheetProjectID, getGsheetProjectID,
@ -71,6 +70,8 @@ import { getDatasourceErrorMessage } from "./errorUtils";
import { getAssetUrl } from "@appsmith/utils/airgapHelpers"; import { getAssetUrl } from "@appsmith/utils/airgapHelpers";
import { DocumentationLink } from "../QueryEditor/EditorJSONtoForm"; import { DocumentationLink } from "../QueryEditor/EditorJSONtoForm";
import GoogleSheetFilePicker from "./GoogleSheetFilePicker"; import GoogleSheetFilePicker from "./GoogleSheetFilePicker";
import DatasourceInformation from "./../DataSourceEditor/DatasourceSection";
import styled from "styled-components";
interface StateProps extends JSONtoFormProps { interface StateProps extends JSONtoFormProps {
applicationId: string; applicationId: string;
@ -133,6 +134,13 @@ type State = {
navigation(): void; navigation(): void;
}; };
const ViewModeWrapper = styled.div`
display: flex;
flex-direction: column;
border-bottom: 1px solid #d0d7dd;
padding: 24px 20px;
`;
class DatasourceSaaSEditor extends JSONtoForm<Props, State> { class DatasourceSaaSEditor extends JSONtoForm<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@ -272,6 +280,7 @@ class DatasourceSaaSEditor extends JSONtoForm<Props, State> {
datasourceId, datasourceId,
documentationLink, documentationLink,
featureFlags, featureFlags,
formConfig,
formData, formData,
gsheetProjectID, gsheetProjectID,
gsheetToken, gsheetToken,
@ -410,20 +419,32 @@ class DatasourceSaaSEditor extends JSONtoForm<Props, State> {
</> </>
)} )}
{viewMode && ( {viewMode && (
<Connected <ViewModeWrapper>
errorComponent={ <Connected
datasource && isGoogleSheetPlugin && !isPluginAuthorized ? ( errorComponent={
<AuthMessage datasource && isGoogleSheetPlugin && !isPluginAuthorized ? (
actionType={ActionType.AUTHORIZE} <AuthMessage
actionType="authorize"
datasource={datasource}
description={authErrorMessage}
pageId={pageId}
/>
) : null
}
showDatasourceSavedText={!isGoogleSheetPlugin}
/>
<div style={{ marginTop: "30px" }}>
{!_.isNil(formConfig) &&
!_.isNil(datasource) &&
!hideDatasourceSection ? (
<DatasourceInformation
config={formConfig[0]}
datasource={datasource} datasource={datasource}
description={authErrorMessage} viewMode={!!viewMode}
pageId={pageId}
/> />
) : null ) : undefined}
} </div>
hideDatasourceRenderSection={hideDatasourceSection} </ViewModeWrapper>
showDatasourceSavedText={!isGoogleSheetPlugin}
/>
)} )}
{/* Render datasource form call-to-actions */} {/* Render datasource form call-to-actions */}
{datasource && ( {datasource && (

View File

@ -1213,7 +1213,8 @@ function* filePickerActionCallbackSaga(
// Once users selects/cancels the file selection, // Once users selects/cancels the file selection,
// Sending sheet ids selected as part of datasource // Sending sheet ids selected as part of datasource
// config properties in order to save it in database // config properties in order to save it in database
set(datasource, "datasourceConfiguration.properties[0]", { // using the second index specifically for file ids.
set(datasource, "datasourceConfiguration.properties[1]", {
key: createMessage(GSHEET_AUTHORISED_FILE_IDS_KEY), key: createMessage(GSHEET_AUTHORISED_FILE_IDS_KEY),
value: fileIds, value: fileIds,
}); });

View File

@ -15,6 +15,7 @@ import com.appsmith.external.models.DatasourceConfiguration;
public class SheetsUtil { public class SheetsUtil {
private static final String FILE_SPECIFIC_DRIVE_SCOPE = "https://www.googleapis.com/auth/drive.file"; private static final String FILE_SPECIFIC_DRIVE_SCOPE = "https://www.googleapis.com/auth/drive.file";
private static final int USER_AUTHORIZED_SHEET_IDS_INDEX = 1;
static Pattern COLUMN_NAME_PATTERN = Pattern.compile("[a-zA-Z]+"); static Pattern COLUMN_NAME_PATTERN = Pattern.compile("[a-zA-Z]+");
public static int getColumnNumber(String columnName) { public static int getColumnNumber(String columnName) {
@ -33,11 +34,11 @@ public class SheetsUtil {
public static Set<String> getUserAuthorizedSheetIds(DatasourceConfiguration datasourceConfiguration) { public static Set<String> getUserAuthorizedSheetIds(DatasourceConfiguration datasourceConfiguration) {
OAuth2 oAuth2 = (OAuth2) datasourceConfiguration.getAuthentication(); OAuth2 oAuth2 = (OAuth2) datasourceConfiguration.getAuthentication();
if (!isEmpty(datasourceConfiguration.getProperties()) if (!isEmpty(datasourceConfiguration.getProperties())
&& datasourceConfiguration.getProperties().get(0) != null && datasourceConfiguration.getProperties().get(USER_AUTHORIZED_SHEET_IDS_INDEX) != null
&& datasourceConfiguration.getProperties().get(0).getValue() != null && datasourceConfiguration.getProperties().get(USER_AUTHORIZED_SHEET_IDS_INDEX).getValue() != null
&& oAuth2.getScope() != null && oAuth2.getScope() != null
&& oAuth2.getScope().contains(FILE_SPECIFIC_DRIVE_SCOPE)) { && oAuth2.getScope().contains(FILE_SPECIFIC_DRIVE_SCOPE)) {
ArrayList<String> temp = (ArrayList) datasourceConfiguration.getProperties().get(0).getValue(); ArrayList<String> temp = (ArrayList) datasourceConfiguration.getProperties().get(USER_AUTHORIZED_SHEET_IDS_INDEX).getValue();
return new HashSet<String>(temp); return new HashSet<String>(temp);
} }
return null; return null;

View File

@ -72,6 +72,17 @@
"hidden": true, "hidden": true,
"initialValue": "authorization_code" "initialValue": "authorization_code"
}, },
{
"label": "Account",
"configProperty": "datasourceConfiguration.properties[0].value",
"controlType": "INPUT_TEXT",
"isRequired": false,
"hidden": {
"comparison": "VIEW_MODE",
"value": false
},
"initialValue": "Authorize datasource to fetch account name"
},
{ {
"label": "Permissions | Scope", "label": "Permissions | Scope",
"configProperty": "datasourceConfiguration.authentication.scopeString", "configProperty": "datasourceConfiguration.authentication.scopeString",