fix: gsheet preview data UI using tabs for preview and configuration (#28318)

This commit is contained in:
Aman Agarwal 2023-11-03 19:15:00 +05:30 committed by GitHub
parent 8dab2c84e6
commit 72c4b4d996
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 887 additions and 524 deletions

View File

@ -1903,6 +1903,11 @@ export const ERR_FETCHING_DATASOURCE_PREVIEW_DATA = () =>
export const FETCHING_DATASOURCE_PREVIEW_DATA = () => "Loading data";
export const SCHEMA_PREVIEW_NO_DATA = () =>
"No data records to show or the table header begins with an index other than 1";
export const GSHEET_SPREADSHEET_LABEL = () => "Spreadsheets";
export const GSHEET_SPREADSHEET_LOADING = () => "Loading Spreadsheets";
export const GSHEET_SHEET_LOADING = () => "Loading Sheets";
export const GSHEET_DATA_LOADING = () => "Loading attributes";
export const GSHEET_SEARCH_PLACEHOLDER = () => "Search for spreadsheet";
//Layout Conversion flow
export const CONVERT = () => "Convert layout";
@ -2161,6 +2166,8 @@ export const COMMUNITY_TEMPLATES = {
export const EMPTY_TABLE_TITLE_TEXT = () => "Empty table";
export const EMPTY_TABLE_MESSAGE_TEXT = () =>
"There are no data records to show";
export const LOADING_RECORDS_TITLE_TEXT = () => "Loading records";
export const LOADING_RECORDS_MESSAGE_TEXT = () => "This may take a few seconds";
export const EMPTY_TABLE_SVG_ALT_TEXT = () => "Empty table image";
export const DATA_PANE_TITLE = () => "Datasources in your Workspace";

View File

@ -350,7 +350,8 @@ export type DATASOURCE_SCHEMA_EVENTS =
| "GSHEET_PREVIEW_DATA_SHOWN"
| "DATASOURCE_GENERATE_PAGE_BUTTON_CLICKED"
| "DATASOURCE_PREVIEW_TABLE_CHANGE"
| "DATASOURCE_PREVIEW_DATA_SHOWN";
| "DATASOURCE_PREVIEW_DATA_SHOWN"
| "GSHEET_SPREADSHEET_SEARCH";
type WALKTHROUGH_EVENTS = "WALKTHROUGH_DISMISSED" | "WALKTHROUGH_SHOWN";

View File

@ -51,6 +51,7 @@ import { getAssetUrl } from "@appsmith/utils/airgapHelpers";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
import { getHasManagePagePermission } from "@appsmith/utils/BusinessFeatures/permissionPageHelpers";
import type { Datasource } from "entities/Datasource";
import { DatasourceStructureContext } from "entities/Datasource";
const SCHEMA_GUIDE_GIF = `${ASSETS_CDN_URL}/schema.gif`;
@ -152,7 +153,7 @@ const Placeholder = styled.div`
`;
const DataStructureListWrapper = styled.div`
overflow-y: auto;
overflow-y: hidden;
height: 100%;
display: flex;
flex-direction: column;
@ -176,7 +177,7 @@ interface CollapsibleProps {
label: string;
CustomLabelComponent?: (props: any) => JSX.Element;
isDisabled?: boolean;
datasourceId?: string;
datasource?: Partial<Datasource>;
containerRef?: MutableRefObject<HTMLDivElement | null>;
}
@ -189,7 +190,7 @@ export function Collapsible({
children,
containerRef,
CustomLabelComponent,
datasourceId,
datasource,
expand = true,
label,
}: CollapsibleProps) {
@ -220,7 +221,7 @@ export function Collapsible({
/>
{!!CustomLabelComponent ? (
<CustomLabelComponent
datasourceId={datasourceId}
datasource={datasource}
onRefreshCallback={() => handleCollapse(true)}
/>
) : (
@ -427,7 +428,7 @@ function ActionSidebar({
<Collapsible
CustomLabelComponent={DatasourceStructureHeader}
containerRef={schemaRef}
datasourceId={datasourceId}
datasource={{ id: datasourceId }}
expand={!showSuggestedWidgets}
label="Schema"
>

View File

@ -181,9 +181,11 @@ export function renderDatasourceSection(
if (
!value &&
!!viewMode &&
!!section.hidden &&
"comparison" in section.hidden &&
section.hidden.comparison === ComparisonOperationsEnum.VIEW_MODE
(!section.hidden ||
(!!section.hidden &&
"comparison" in section.hidden &&
section.hidden.comparison ===
ComparisonOperationsEnum.VIEW_MODE))
) {
value = section.initialValue;
}

View File

@ -78,12 +78,16 @@ export const ResizerContentContainer = styled.div`
border-bottom: 1px solid var(--ads-v2-color-border);
}
}
&.db-form-resizer-content.db-form-resizer-content-show-tabs {
&.db-form-resizer-content.db-form-resizer-content-show-tabs,
&.saas-form-resizer-content.saas-form-resizer-content-show-tabs {
padding: 0;
& .t--ds-form-header {
border-bottom: none;
}
}
&.saas-form-resizer-content.saas-form-resizer-content-show-tabs form {
padding-bottom: 0;
}
border-top: none;
.db-form-content-container {
display: flex;

View File

@ -871,16 +871,9 @@ class DatasourceEditorRouter extends React.Component<Props, State> {
};
shouldShowTabs = () => {
const {
isEnabledForDSViewModeSchema,
isPluginAllowedToPreviewData,
pluginDatasourceForm,
} = this.props;
const isRestAPI =
pluginDatasourceForm === DatasourceComponentTypes.RestAPIDatasourceForm;
return (
isEnabledForDSViewModeSchema && isPluginAllowedToPreviewData && !isRestAPI
);
const { isEnabledForDSViewModeSchema, isPluginAllowedToPreviewData } =
this.props;
return isEnabledForDSViewModeSchema && isPluginAllowedToPreviewData;
};
renderTabsForViewMode = () => {

View File

@ -13,8 +13,11 @@ import DatasourceViewModeSchema from "./DatasourceViewModeSchema";
import { getCurrentEnvironmentId } from "@appsmith/selectors/environmentSelectors";
import { isEnvironmentValid } from "@appsmith/utils/Environments";
import type { Datasource } from "entities/Datasource";
import { isGoogleSheetPluginDS } from "utils/editorContextUtils";
import { getPluginNameFromId } from "@appsmith/selectors/entitiesSelector";
import {
isDatasourceAuthorizedForQueryCreation,
isGoogleSheetPluginDS,
} from "utils/editorContextUtils";
import { getPlugin } from "@appsmith/selectors/entitiesSelector";
import GoogleSheetSchema from "./GoogleSheetSchema";
const TabsContainer = styled(Tabs)`
@ -55,14 +58,22 @@ const DatasourceTabs = (props: DatasourceTabProps) => {
const setDatasourceViewModeFlagClick = (value: boolean) => {
dispatch(setDatasourceViewModeFlag(value));
};
const pluginName = useSelector((state) =>
getPluginNameFromId(state, props.datasource.pluginId),
const plugin = useSelector((state) =>
getPlugin(state, props.datasource.pluginId),
);
const isGoogleSheetPlugin = isGoogleSheetPluginDS(pluginName);
const isGoogleSheetPlugin = isGoogleSheetPluginDS(plugin?.packageName);
const isPluginAuthorized =
!!plugin && !!props.datasource
? isDatasourceAuthorizedForQueryCreation(
props.datasource,
plugin,
currentEnvironmentId,
)
: false;
return (
<TabsContainer
defaultValue={
isDatasourceValid
isDatasourceValid || isPluginAuthorized
? VIEW_MODE_TABS.VIEW_DATA
: VIEW_MODE_TABS.CONFIGURATIONS
}

View File

@ -11,11 +11,10 @@ import React, { memo, useEffect, useState, useContext } from "react";
import EntityPlaceholder from "../Explorer/Entity/Placeholder";
import DatasourceStructure from "./DatasourceStructure";
import { SearchInput, Text } from "design-system";
import styled from "styled-components";
import { getIsFetchingDatasourceStructure } from "@appsmith/selectors/entitiesSelector";
import { useSelector } from "react-redux";
import type { AppState } from "@appsmith/reducers";
import DatasourceStructureLoadingContainer from "./DatasourceStructureLoadingContainer";
import ItemLoadingIndicator from "./ItemLoadingIndicator";
import DatasourceStructureNotFound from "./DatasourceStructureNotFound";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { PluginName } from "entities/Action";
@ -23,6 +22,7 @@ import WalkthroughContext from "components/featureWalkthrough/walkthroughContext
import { setFeatureWalkthroughShown } from "utils/storage";
import { FEATURE_WALKTHROUGH_KEYS } from "constants/WalkthroughConstants";
import { SCHEMA_SECTION_ID } from "entities/Action";
import { DatasourceStructureSearchContainer } from "./SchemaViewModeCSS";
interface Props {
datasourceId: string;
@ -50,15 +50,6 @@ export const SCHEMALESS_PLUGINS: Array<string> = [
PluginName.OPEN_AI,
];
const DatasourceStructureSearchContainer = styled.div`
margin-bottom: 8px;
position: sticky;
top: 0;
overflow: hidden;
z-index: 10;
background: white;
`;
const Container = (props: Props) => {
const isLoading = useSelector((state: AppState) =>
getIsFetchingDatasourceStructure(state, props.datasourceId),
@ -113,7 +104,7 @@ const Container = (props: Props) => {
const filteredDastasourceStructure =
props.datasourceStructure.tables.filter((table) =>
table.name.includes(value),
table.name.toLowerCase().includes(value.toLowerCase()),
);
setDatasourceStructure({ tables: filteredDastasourceStructure });
@ -129,7 +120,9 @@ const Container = (props: Props) => {
view = (
<>
{props.context !== DatasourceStructureContext.EXPLORER && (
<DatasourceStructureSearchContainer>
<DatasourceStructureSearchContainer
className={`t--search-container--${props.context.toLowerCase()}`}
>
<SearchInput
className="datasourceStructure-search"
endIcon="close"
@ -137,7 +130,7 @@ const Container = (props: Props) => {
placeholder={createMessage(
DATASOURCE_STRUCTURE_INPUT_PLACEHOLDER_TEXT,
)}
size={"md"}
size={"sm"}
startIcon="search"
type="text"
/>
@ -199,7 +192,7 @@ const Container = (props: Props) => {
props.context !== DatasourceStructureContext.EXPLORER &&
isLoading
) {
view = <DatasourceStructureLoadingContainer />;
view = <ItemLoadingIndicator type="SCHEMA" />;
}
return view;

View File

@ -1,15 +1,23 @@
import React, { useCallback } from "react";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import { Button, Text } from "design-system";
import styled from "styled-components";
import { refreshDatasourceStructure } from "actions/datasourceActions";
import { SCHEMA_LABEL, createMessage } from "@appsmith/constants/messages";
import {
GSHEET_SPREADSHEET_LABEL,
SCHEMA_LABEL,
createMessage,
} from "@appsmith/constants/messages";
import type { Datasource } from "entities/Datasource";
import { DatasourceStructureContext } from "entities/Datasource";
import { getPluginPackageNameFromId } from "@appsmith/selectors/entitiesSelector";
import { isGoogleSheetPluginDS } from "utils/editorContextUtils";
interface Props {
datasourceId: string;
datasource?: Datasource;
onRefreshCallback?: () => void;
paddingBottom?: boolean;
refetchFn?: () => void;
}
const HeaderWrapper = styled.div<{ paddingBottom: boolean }>`
@ -24,27 +32,41 @@ export default function DatasourceStructureHeader(props: Props) {
const dispatch = useDispatch();
const dispatchRefresh = useCallback(
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
event.stopPropagation();
dispatch(
refreshDatasourceStructure(
props.datasourceId,
DatasourceStructureContext.QUERY_EDITOR,
),
);
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
if (props.datasource?.id) {
event.stopPropagation();
!!props.onRefreshCallback && props.onRefreshCallback();
if (props.refetchFn) {
props.refetchFn();
} else {
dispatch(
refreshDatasourceStructure(
props.datasource?.id,
DatasourceStructureContext.QUERY_EDITOR,
),
);
}
!!props.onRefreshCallback && props.onRefreshCallback();
}
},
[dispatch, props.datasourceId],
[dispatch, props.datasource?.id],
);
const pluginPackageName = useSelector((state) =>
getPluginPackageNameFromId(state, props.datasource?.pluginId || ""),
);
const isGoogleSheetPlugin = isGoogleSheetPluginDS(pluginPackageName);
return (
<HeaderWrapper
className="datasourceStructure-header"
paddingBottom={!!props.paddingBottom}
>
<Text kind="heading-xs" renderAs="h3">
{createMessage(SCHEMA_LABEL)}
{createMessage(
isGoogleSheetPlugin ? GSHEET_SPREADSHEET_LABEL : SCHEMA_LABEL,
)}
</Text>
<Button
className="datasourceStructure-refresh"

View File

@ -1,36 +0,0 @@
import React from "react";
import { createMessage, LOADING_SCHEMA } from "@appsmith/constants/messages";
import { Spinner, Text } from "design-system";
import styled from "styled-components";
const LoadingContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
& > p {
margin-left: 0.5rem;
}
`;
const SpinnerWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
`;
const DatasourceStructureLoadingContainer = () => {
return (
<LoadingContainer>
<SpinnerWrapper>
<Spinner size={"sm"} />
</SpinnerWrapper>
<Text kind="body-m" renderAs="p">
{createMessage(LOADING_SCHEMA)}
</Text>
</LoadingContainer>
);
};
export default DatasourceStructureLoadingContainer;

View File

@ -1,5 +1,4 @@
import React, { useState, useEffect, useRef } from "react";
import styled from "styled-components";
import { useDispatch, useSelector } from "react-redux";
import { DatasourceStructureContainer as DatasourceStructureList } from "./DatasourceStructureContainer";
import {
@ -8,16 +7,10 @@ import {
getNumberOfEntitiesInCurrentPage,
} from "@appsmith/selectors/entitiesSelector";
import DatasourceStructureHeader from "./DatasourceStructureHeader";
import { MessageWrapper, TableWrapper } from "./GoogleSheetSchema";
import { Spinner, Text, Button } from "design-system";
import { Button } from "design-system";
import {
ERR_FETCHING_DATASOURCE_PREVIEW_DATA,
FETCHING_DATASOURCE_PREVIEW_DATA,
DATASOURCE_GENERATE_PAGE_BUTTON,
EMPTY_TABLE_TITLE_TEXT,
EMPTY_TABLE_MESSAGE_TEXT,
createMessage,
EMPTY_TABLE_SVG_ALT_TEXT,
} from "@appsmith/constants/messages";
import Table from "pages/Editor/QueryEditor/Table";
import { generateTemplateToUpdatePage } from "actions/pageActions";
@ -44,98 +37,22 @@ import {
getHasCreatePagePermission,
hasCreateDSActionPermissionInApp,
} from "@appsmith/utils/BusinessFeatures/permissionPageHelpers";
import EmptyTableSVG from "assets/images/empty-table-in-display-preview.svg";
const ViewModeSchemaContainer = styled.div`
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
`;
const DataWrapperContainer = styled.div`
display: flex;
flex: 1;
overflow: hidden;
`;
const StructureContainer = styled.div`
height: 100%;
width: 25%;
padding: var(--ads-v2-spaces-4) var(--ads-v2-spaces-5);
padding-left: var(--ads-v2-spaces-7);
display: flex;
flex-direction: column;
overflow: hidden;
border-right: 1px solid var(--ads-v2-color-gray-300);
flex-shrink: 0;
`;
const DatasourceDataContainer = styled.div`
height: 100%;
width: 75%;
display: flex;
flex-direction: column;
`;
const DatasourceListContainer = styled.div`
height: 100%;
display: flex;
flex-direction: column;
div {
flex-shrink: 0;
}
div ~ div {
flex: 1;
}
.t--schema-virtuoso-container {
height: 100%;
}
`;
const SchemaStateMessageWrapper = styled.div`
width: auto;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
img {
padding-bottom: var(--ads-v2-spaces-7);
}
span:first-child {
padding-bottom: var(--ads-v2-spaces-2);
}
`;
const ButtonContainer = styled.div`
display: flex;
flex-shrink: 0;
justify-content: flex-end;
border-top: 1px solid var(--ads-v2-color-gray-300);
padding: var(--ads-v2-spaces-4);
`;
import RenderInterimDataState from "./RenderInterimDataState";
import {
ButtonContainer,
DataWrapperContainer,
DatasourceDataContainer,
DatasourceListContainer,
StructureContainer,
TableWrapper,
ViewModeSchemaContainer,
} from "./SchemaViewModeCSS";
interface Props {
datasource: Datasource;
setDatasourceViewModeFlag: (viewMode: boolean) => void;
}
const renderEmptyTablePage = () => {
return (
<SchemaStateMessageWrapper>
{/* Render empty table image */}
<img alt={createMessage(EMPTY_TABLE_SVG_ALT_TEXT)} src={EmptyTableSVG} />
{/* Show description below the image */}
{/* Show title */}
<Text style={{ fontWeight: "bold" }}>
{createMessage(EMPTY_TABLE_TITLE_TEXT)}
</Text>
{/* Show description */}
<Text>{createMessage(EMPTY_TABLE_MESSAGE_TEXT)}</Text>
</SchemaStateMessageWrapper>
);
};
const DatasourceViewModeSchema = (props: Props) => {
const dispatch = useDispatch();
@ -200,6 +117,7 @@ const DatasourceViewModeSchema = (props: Props) => {
if (
isDatasourceStructureLoading ||
!datasourceStructure ||
!datasourceStructure.tables ||
(datasourceStructure && datasourceStructure?.error)
) {
setPreviewData([]);
@ -248,6 +166,10 @@ const DatasourceViewModeSchema = (props: Props) => {
}
}, [previewData]);
useEffect(() => {
setPreviewData([]);
}, [props.datasource.id]);
const onEntityTableClick = (table: string) => {
AnalyticsUtil.logEvent("DATASOURCE_PREVIEW_TABLE_CHANGE", {
datasourceId: props.datasource.id,
@ -304,10 +226,12 @@ const DatasourceViewModeSchema = (props: Props) => {
<ViewModeSchemaContainer>
<DataWrapperContainer>
<StructureContainer>
<DatasourceStructureHeader
datasourceId={props.datasource.id}
paddingBottom
/>
{props.datasource && (
<DatasourceStructureHeader
datasource={props.datasource}
paddingBottom
/>
)}
<DatasourceListContainer>
<DatasourceStructureList
context={DatasourceStructureContext.DATASOURCE_VIEW_MODE}
@ -322,31 +246,25 @@ const DatasourceViewModeSchema = (props: Props) => {
</StructureContainer>
<DatasourceDataContainer>
<TableWrapper>
{isLoading && (
<MessageWrapper>
<Spinner size="md" />
<Text style={{ marginLeft: "8px" }}>
{createMessage(FETCHING_DATASOURCE_PREVIEW_DATA)}
</Text>
</MessageWrapper>
{(isLoading || isDatasourceStructureLoading) && (
<RenderInterimDataState state="LOADING" />
)}
{!isLoading && failedFetchingPreviewData && (
<MessageWrapper>
<Text color="var(--ads-color-red-500)">
{createMessage(ERR_FETCHING_DATASOURCE_PREVIEW_DATA)}
</Text>
</MessageWrapper>
)}
{!isLoading &&
!failedFetchingPreviewData &&
!previewDataError &&
previewData?.length > 0 && <Table data={previewData} />}
{!isLoading &&
!failedFetchingPreviewData &&
!previewDataError &&
previewData?.length < 1 && (
<MessageWrapper>{renderEmptyTablePage()}</MessageWrapper>
{(!isLoading || !isDatasourceStructureLoading) &&
(failedFetchingPreviewData || previewDataError) && (
<RenderInterimDataState state="FAILED" />
)}
{!isLoading &&
!isDatasourceStructureLoading &&
!failedFetchingPreviewData &&
!previewDataError &&
previewData?.length > 0 && (
<Table data={previewData} shouldResize={false} />
)}
{!isLoading &&
!isDatasourceStructureLoading &&
!failedFetchingPreviewData &&
!previewDataError &&
!previewData?.length && <RenderInterimDataState state="NODATA" />}
</TableWrapper>
</DatasourceDataContainer>
</DataWrapperContainer>

View File

@ -1,24 +1,16 @@
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import type { DropdownOption } from "design-system-old";
import { Button, Option, Select, Spinner, Text } from "design-system";
import { Button, SearchInput } from "design-system";
import {
useSheetData,
useSheetsList,
useSpreadSheets,
} from "../GeneratePage/components/GeneratePageForm/hooks";
import type {
DatasourceTableDropdownOption,
DropdownOptions,
} from "../GeneratePage/components/constants";
import {
DEFAULT_DROPDOWN_OPTION,
DROPDOWN_DIMENSION,
} from "../GeneratePage/components/constants";
import { SelectWrapper } from "../GeneratePage/components/GeneratePageForm/styles";
import type { DropdownOptions } from "../GeneratePage/components/constants";
import { DEFAULT_DROPDOWN_OPTION } from "../GeneratePage/components/constants";
import { isEmpty } from "lodash";
import Table from "pages/Editor/QueryEditor/Table";
import styled from "styled-components";
import {
getCurrentApplicationId,
getPagePermissions,
@ -26,10 +18,8 @@ import {
import { generateTemplateToUpdatePage } from "actions/pageActions";
import {
createMessage,
ERR_FETCHING_DATASOURCE_PREVIEW_DATA,
FETCHING_DATASOURCE_PREVIEW_DATA,
DATASOURCE_GENERATE_PAGE_BUTTON,
SCHEMA_PREVIEW_NO_DATA,
GSHEET_SEARCH_PLACEHOLDER,
} from "@appsmith/constants/messages";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { getCurrentApplication } from "@appsmith/selectors/applicationSelectors";
@ -41,76 +31,23 @@ import {
getHasCreatePagePermission,
hasCreateDSActionPermissionInApp,
} from "@appsmith/utils/BusinessFeatures/permissionPageHelpers";
export const MessageWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
height: 200px;
`;
export const TableWrapper = styled.div`
overflow-x: auto;
height: 100%;
&& > div {
width: 100%;
}
&& > ${MessageWrapper} {
width: 100%;
height: 100%;
}
&& .t--table-response {
border: none;
height: 100%;
overflow: hidden;
}
&& .tableWrap {
overflow: auto;
}
& .table {
background: none;
}
& .table div:first-of-type .tr {
background: var(--ads-v2-color-black-5);
border-right: none;
border-bottom: 1px solid var(--ads-v2-color-black-75);
}
&& .table div.tbody .tr {
background: var(--ads-v2-color-white);
border-bottom: 1px solid var(--ads-v2-color-black-75);
}
&& .table .td,
&& .table .th {
border-right: none;
border-bottom: none;
}
button {
margin-right: 24px;
}
&& .tableWrap {
}
`;
const SelectContainer = styled.div`
display: flex;
margin-top: 16px;
margin-bottom: 16px;
.t--datasource-generate-page {
align-self: flex-end;
margin-left: auto;
}
`;
const SelectListWrapper = styled.div`
display: flex;
flex-direction: column;
width: 278px;
margin-right: 16px;
& div {
margin-bottom: 0px;
}
`;
import RenderInterimDataState from "./RenderInterimDataState";
import {
ButtonContainer,
DataWrapperContainer,
DatasourceAttributesWrapper,
DatasourceDataContainer,
DatasourceListContainer,
DatasourceStructureSearchContainer,
StructureContainer,
TableWrapper,
ViewModeSchemaContainer,
} from "./SchemaViewModeCSS";
import DatasourceStructureHeader from "./DatasourceStructureHeader";
import Entity from "../Explorer/Entity";
import DatasourceField from "./DatasourceField";
import { setEntityCollapsibleState } from "actions/editorContextActions";
import ItemLoadingIndicator from "./ItemLoadingIndicator";
interface Props {
datasourceId: string;
@ -122,25 +59,47 @@ const MAX_SHEET_ROWS_LENGTH = 12;
// ---------- GoogleSheetSchema Component -------
function GoogleSheetSchema(props: Props) {
const [datasourceTableOptions, setSelectedDatasourceTableOptions] =
useState<DropdownOptions>([]);
const [selectedDatasourceIsInvalid, setSelectedDatasourceIsInvalid] =
useState(false);
const { fetchAllSpreadsheets, isFetchingSpreadsheets } = useSpreadSheets({
setSelectedDatasourceTableOptions,
setSelectedDatasourceIsInvalid,
});
const {
failedFetchingSheetsList,
fetchSheetsList,
isFetchingSheetsList,
sheetsList,
} = useSheetsList();
const { fetchSheetData, isFetchingSheetData, sheetData } = useSheetData();
const [spreadsheetOptions, setSpreadsheetOptions] = useState<DropdownOptions>(
[],
);
const [sheetOptions, setSheetOptions] = useState<DropdownOptions>([]);
const [sheetData, setSheetData] = useState<any>([]);
const [selectedSpreadsheet, setSelectedSpreadsheet] =
useState<DropdownOption>({});
const [selectedSheet, setSelectedSheet] = useState<DropdownOption>({});
const [currentSheetData, setCurrentSheetData] = useState<any>();
const [searchString, setSearchString] = useState<string>("");
const [selectedDatasourceIsInvalid, setSelectedDatasourceIsInvalid] =
useState(false);
const { fetchAllSpreadsheets, isFetchingSpreadsheets } = useSpreadSheets({
setSelectedDatasourceTableOptions: setSpreadsheetOptions,
setSelectedDatasourceIsInvalid,
});
const handleSearch = (value: string) => {
setSearchString(value.toLowerCase());
AnalyticsUtil.logEvent("GSHEET_SPREADSHEET_SEARCH", {
datasourceId: props.datasourceId,
pluginId: props.pluginId,
});
};
const setSlicedSheetData = (response: DropdownOptions) => {
// Getting the top 12 rows as for experimentation we need to keep this number fixed for preview
AnalyticsUtil.logEvent("GSHEET_PREVIEW_DATA_SHOWN", {
datasourceId: props.datasourceId,
pluginId: props.pluginId,
});
setSheetData(response.slice(0, MAX_SHEET_ROWS_LENGTH));
};
const { failedFetchingSheetsList, fetchSheetsList, isFetchingSheetsList } =
useSheetsList({ setSheetOptions });
const { failedFetchingSheetData, fetchSheetData, isFetchingSheetData } =
useSheetData({
setSheetData: setSlicedSheetData,
});
const applicationId: string = useSelector(getCurrentApplicationId);
const datasource = useSelector((state) =>
getDatasource(state, props.datasourceId),
@ -148,101 +107,176 @@ function GoogleSheetSchema(props: Props) {
const dispatch = useDispatch();
// Fetch spreadsheets if datasourceId present
useEffect(() => {
const scrollIntoView = (
elementId: string,
containerId: string,
offset: number = 0,
) => {
try {
const element = document.querySelector(elementId);
const container = document.querySelector(containerId);
if (element && container) {
const elementRect = element.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
const scrollTop = container.scrollTop;
const scrollAmount =
elementRect.top - containerRect.top + scrollTop + offset;
container.scrollTop = scrollAmount;
}
} catch {}
};
const collapseAccordions = (
datasourceId: string,
spreadSheet?: string,
sheet?: string,
collapseSpreadsheet: boolean = true,
) => {
if (!isEmpty(spreadSheet) && !isEmpty(sheet)) {
dispatch(
setEntityCollapsibleState(
`${datasourceId}-${spreadSheet}-${sheet}`,
false,
),
);
}
if (!isEmpty(spreadSheet) && collapseSpreadsheet) {
dispatch(
setEntityCollapsibleState(`${datasourceId}-${spreadSheet}`, false),
);
}
};
const selectSpreadsheetAndToggle = (option: DropdownOption) => {
collapseAccordions(
datasource?.id || "",
selectedSpreadsheet.value,
selectedSheet.value,
);
setSelectedSheet(DEFAULT_DROPDOWN_OPTION);
setSheetOptions([]);
setSheetData(undefined);
dispatch(
setEntityCollapsibleState(`${datasource?.id}-${option.value}`, true),
);
scrollIntoView(
`#${CSS.escape(`entity-${datasource?.id}-${option.value}`)}`,
".t--gsheet-structure",
);
setSelectedSpreadsheet(option);
fetchSheetsList({
requestObject: {},
selectedDatasourceId: props.datasourceId,
selectedSpreadsheetUrl: option.value || "",
pluginId: props.pluginId || "",
});
};
const selectSheetAndToggle = (option: DropdownOption) => {
collapseAccordions(
datasource?.id || "",
selectedSpreadsheet.value,
selectedSheet.value,
false,
);
dispatch(
setEntityCollapsibleState(
`${datasource?.id}-${selectedSpreadsheet.value}-${option.value}`,
true,
),
);
scrollIntoView(
`#${CSS.escape(
`entity-${datasource?.id}-${selectedSpreadsheet.value}-${option.value}`,
)}`,
".t--gsheet-structure",
-30,
);
setSelectedSheet(option);
setSheetData(undefined);
fetchSheetData({
selectedDatasourceId: datasource?.id || "",
selectedSpreadsheetUrl: selectedSpreadsheet.value || "",
selectedSheetName: option.value || "",
pluginId: props.pluginId || "",
});
};
const refetchAllSpreadsheets = () => {
if (!!props.datasourceId && !!props.pluginId) {
fetchAllSpreadsheets({
selectedDatasourceId: props.datasourceId,
pluginId: props.pluginId || "",
requestObject: {},
});
setSpreadsheetOptions([]);
setSheetOptions([]);
setSheetData(undefined);
setSelectedSpreadsheet((ss) => {
setSelectedSheet((s) => {
collapseAccordions(datasource?.id || "", ss.value, s.value);
return {};
});
return {};
});
}
};
// Fetch spreadsheets if datasourceId present
useEffect(() => {
fetchAllSpreadsheets({
selectedDatasourceId: props.datasourceId,
pluginId: props.pluginId || "",
requestObject: {},
});
}, [props.datasourceId, props.pluginId, dispatch]);
// When user selects a spreadsheet
// Fetch all sheets inside that spreadsheet
useEffect(() => {
if (!!props.datasourceId && !!props.pluginId && selectedSpreadsheet.value) {
setSelectedSheet(DEFAULT_DROPDOWN_OPTION);
setCurrentSheetData(undefined);
fetchSheetsList({
requestObject: {},
selectedDatasourceId: props.datasourceId,
selectedSpreadsheetUrl: selectedSpreadsheet.value,
pluginId: props.pluginId,
});
}
}, [
selectedSpreadsheet.value,
props.datasourceId,
props.pluginId,
dispatch,
fetchSheetsList,
]);
// When user selects a sheet name
// Fetch all sheet data inside that sheet
useEffect(() => {
if (!!props.datasourceId && !!props.pluginId && selectedSheet.value) {
setCurrentSheetData(undefined);
fetchSheetData({
selectedDatasourceId: props.datasourceId,
selectedSpreadsheetUrl: selectedSpreadsheet.value || "",
selectedSheetName: selectedSheet.value,
pluginId: props.pluginId || "",
});
}
}, [
selectedSheet.value,
props.datasourceId,
props.pluginId,
dispatch,
fetchSheetData,
]);
// Set first spreadsheet as default option in the dropdown
useEffect(() => {
if (
datasourceTableOptions?.length > 0 &&
isEmpty(selectedSpreadsheet.value)
) {
setSelectedSpreadsheet(datasourceTableOptions[0]);
if (spreadsheetOptions?.length > 0 && isEmpty(selectedSpreadsheet.value)) {
selectSpreadsheetAndToggle(spreadsheetOptions[0]);
}
}, [selectedSpreadsheet, datasourceTableOptions]);
}, [selectedSpreadsheet, spreadsheetOptions]);
// Set first sheet as default option in the dropdown
useEffect(() => {
if (
sheetsList?.length > 0 &&
sheetOptions?.length > 0 &&
isEmpty(selectedSheet.value) &&
!isFetchingSheetsList
!isFetchingSheetsList &&
!isFetchingSpreadsheets
) {
setSelectedSheet(sheetsList[0]);
selectSheetAndToggle(sheetOptions[0]);
}
}, [selectedSheet, sheetsList, isFetchingSheetsList]);
}, [
selectedSheet,
sheetOptions,
isFetchingSheetsList,
isFetchingSpreadsheets,
]);
// Set current sheet data
useEffect(() => {
if (sheetData) {
// Getting the top 12 rows as for experimentation we need to keep this number fixed for preview
AnalyticsUtil.logEvent("GSHEET_PREVIEW_DATA_SHOWN", {
datasourceId: props.datasourceId,
pluginId: props.pluginId,
});
setCurrentSheetData(sheetData.slice(0, MAX_SHEET_ROWS_LENGTH));
}
}, [sheetData]);
return () => {
collapseAccordions(
datasource?.id || "",
selectedSpreadsheet.value,
selectedSheet.value,
);
};
}, [datasource?.id]);
const onSelectSpreadsheet = (
table: string | undefined,
tableObj: DatasourceTableDropdownOption | undefined,
tableObj: DropdownOption | undefined,
) => {
if (table && tableObj) {
AnalyticsUtil.logEvent("GSHEET_PREVIEW_SPREADSHEET_CHANGE", {
datasourceId: props.datasourceId,
pluginId: props.pluginId,
});
setSelectedSpreadsheet(tableObj);
selectSpreadsheetAndToggle(tableObj);
}
};
@ -255,25 +289,22 @@ function GoogleSheetSchema(props: Props) {
datasourceId: props.datasourceId,
pluginId: props.pluginId,
});
setSelectedSheet(sheetObj);
selectSheetAndToggle(sheetObj);
}
};
const isError = selectedDatasourceIsInvalid || failedFetchingSheetsList;
const isError =
selectedDatasourceIsInvalid ||
failedFetchingSheetsList ||
failedFetchingSheetData;
const isLoading =
isFetchingSpreadsheets ||
isFetchingSheetsList ||
isFetchingSheetData ||
(!isError && !currentSheetData);
isFetchingSpreadsheets || isFetchingSheetsList || isFetchingSheetData;
const onGsheetGeneratePage = () => {
const payload = {
applicationId: applicationId || "",
pageId: "",
columns:
!!currentSheetData && currentSheetData.length > 0
? Object.keys(currentSheetData[0])
: [],
columns: sheetData?.length > 0 ? Object.keys(sheetData[0]) : [],
searchColumn: "",
tableName: selectedSheet?.value || "",
datasourceId: props.datasourceId || "",
@ -312,76 +343,144 @@ function GoogleSheetSchema(props: Props) {
pagePermissions,
);
const refreshSpreadSheetButton = (option: DropdownOption) => (
<Button
isIconButton
kind="tertiary"
onClick={() => selectSpreadsheetAndToggle(option)}
startIcon="refresh"
/>
);
const showGeneratePageBtn =
!isLoading &&
!isError &&
currentSheetData &&
sheetData?.length &&
canCreateDatasourceActions &&
canCreatePages;
const filteredSpreadsheets = spreadsheetOptions.filter(
(option) => (option.label || "").toLowerCase()?.includes(searchString),
);
return (
<>
<SelectContainer>
{!!props.datasourceId ? (
<SelectListWrapper>
<Text>Spreadsheet</Text>
<SelectWrapper width={DROPDOWN_DIMENSION.WIDTH}>
<Select
data-testid="t--table-dropdown"
isLoading={isFetchingSpreadsheets}
onChange={(value: any) =>
onSelectSpreadsheet(
value,
datasourceTableOptions.find(
(table) => table.value === value,
) as DatasourceTableDropdownOption,
)
}
value={selectedSpreadsheet}
>
{datasourceTableOptions.map((table) => {
<ViewModeSchemaContainer>
<DataWrapperContainer>
<StructureContainer>
{datasource && (
<DatasourceStructureHeader
datasource={datasource}
paddingBottom
refetchFn={refetchAllSpreadsheets}
/>
)}
<DatasourceListContainer className="t--gsheet-structure">
{!isFetchingSpreadsheets && (
<DatasourceStructureSearchContainer className="t--gsheet-search-container">
<SearchInput
className="datasourceStructure-search"
endIcon="close"
onChange={(value) => handleSearch(value)}
placeholder={createMessage(GSHEET_SEARCH_PLACEHOLDER)}
size={"sm"}
startIcon="search"
type="text"
/>
</DatasourceStructureSearchContainer>
)}
<div className="t--gsheet-structure-list">
{isFetchingSpreadsheets ? (
<ItemLoadingIndicator type="SPREADSHEET" />
) : (
filteredSpreadsheets.map((spreadsheet) => {
return (
<Option key={table.value} value={table.value}>
{table.label}
</Option>
<Entity
className="t--spreadsheet-structure"
customAddButton={refreshSpreadSheetButton(spreadsheet)}
entityId={`${datasource?.id}-${spreadsheet.value}`}
icon={null}
key={`${datasource?.id}-${spreadsheet.value}`}
name={spreadsheet.label as string}
onToggle={(isOpen) => {
isOpen &&
onSelectSpreadsheet(spreadsheet.value, spreadsheet);
}}
showAddButton={
spreadsheet.value === selectedSpreadsheet.value
}
step={0}
>
{isFetchingSheetsList ? (
<ItemLoadingIndicator type="SHEET" />
) : sheetOptions.length > 0 ? (
sheetOptions.map((sheet) => (
<Entity
className={`t--sheet-structure ${
sheet.value === selectedSheet.value
? "t--sheet-structure-active"
: ""
}`}
entityId={`${datasource?.id}-${selectedSpreadsheet.value}-${sheet.value}`}
icon={null}
key={`${datasource?.id}-${selectedSpreadsheet.value}-${sheet.value}`}
name={sheet.label as string}
onToggle={(isOpen) => {
isOpen && onSelectSheetOption(sheet.value, sheet);
}}
step={1}
>
{selectedSheet.value === sheet.value ? (
isFetchingSheetData ? (
<ItemLoadingIndicator type="DATA" />
) : sheetData?.length > 0 ? (
<DatasourceAttributesWrapper>
{Object.keys(sheetData[0]).map(
(fieldValue, index) => (
<DatasourceField
field={{
name: fieldValue,
type: "string",
}}
key={`${fieldValue}${index}`}
step={2}
/>
),
)}
</DatasourceAttributesWrapper>
) : null
) : (
<ItemLoadingIndicator type="DATA" />
)}
</Entity>
))
) : (
<ItemLoadingIndicator type="SPREADSHEET" />
)}
</Entity>
);
})}
</Select>
</SelectWrapper>
</SelectListWrapper>
) : null}
{selectedSpreadsheet.value ? (
<SelectListWrapper>
<Text>Sheet</Text>
<SelectWrapper width={DROPDOWN_DIMENSION.WIDTH}>
<Select
data-testid="t--sheetName-dropdown"
isLoading={isFetchingSheetsList}
onChange={(value: any) =>
onSelectSheetOption(
value,
sheetsList.find(
(sheet: DropdownOption) => sheet.value === value,
),
)
}
value={selectedSheet}
>
{sheetsList.map((sheet) => {
return (
<Option key={sheet.label} value={sheet.label}>
{sheet?.label}
</Option>
);
})}
</Select>
</SelectWrapper>
</SelectListWrapper>
) : null}
{showGeneratePageBtn && (
})
)}
</div>
</DatasourceListContainer>
</StructureContainer>
<DatasourceDataContainer>
<TableWrapper>
{isLoading ? (
<RenderInterimDataState state="LOADING" />
) : isError ? (
<RenderInterimDataState state="FAILED" />
) : sheetData?.length > 0 ? (
<Table data={sheetData} shouldResize={false} />
) : (
<RenderInterimDataState state="NODATA" />
)}
</TableWrapper>
</DatasourceDataContainer>
</DataWrapperContainer>
{showGeneratePageBtn && (
<ButtonContainer>
<Button
className="t--datasource-generate-page"
isDisabled={!currentSheetData || currentSheetData?.length == 0}
key="datasource-generate-page"
kind="secondary"
onClick={onGsheetGeneratePage}
@ -389,29 +488,9 @@ function GoogleSheetSchema(props: Props) {
>
{createMessage(DATASOURCE_GENERATE_PAGE_BUTTON)}
</Button>
)}
</SelectContainer>
<TableWrapper>
{isLoading ? (
<MessageWrapper>
<Spinner size="md" />
<Text style={{ marginLeft: "8px" }}>
{createMessage(FETCHING_DATASOURCE_PREVIEW_DATA)}
</Text>
</MessageWrapper>
) : isError ? (
<Text color="var(--ads-color-red-500)">
{createMessage(ERR_FETCHING_DATASOURCE_PREVIEW_DATA)}
</Text>
) : currentSheetData?.length > 0 ? (
<Table data={currentSheetData} />
) : (
<MessageWrapper>
<Text>{createMessage(SCHEMA_PREVIEW_NO_DATA)}</Text>
</MessageWrapper>
)}
</TableWrapper>
</>
</ButtonContainer>
)}
</ViewModeSchemaContainer>
);
}

View File

@ -0,0 +1,35 @@
import React from "react";
import {
createMessage,
GSHEET_DATA_LOADING,
GSHEET_SHEET_LOADING,
GSHEET_SPREADSHEET_LOADING,
LOADING_SCHEMA,
} from "@appsmith/constants/messages";
import { Spinner, Text } from "design-system";
import { MessageWrapper } from "./SchemaViewModeCSS";
type LoadingItemType = "SPREADSHEET" | "SHEET" | "DATA" | "SCHEMA";
const ItemLoadingIndicator = ({ type }: { type: LoadingItemType }) => {
return (
<MessageWrapper
className={`t--item-loading-indicator t--item-loading-indicator--${type.toLowerCase()}`}
>
<Spinner size="md" />
<Text style={{ marginLeft: "8px" }}>
{createMessage(
type === "SPREADSHEET"
? GSHEET_SPREADSHEET_LOADING
: type === "SHEET"
? GSHEET_SHEET_LOADING
: type === "SCHEMA"
? LOADING_SCHEMA
: GSHEET_DATA_LOADING,
)}
</Text>
</MessageWrapper>
);
};
export default ItemLoadingIndicator;

View File

@ -0,0 +1,61 @@
import React from "react";
import EmptyTableSVG from "assets/images/empty-table-in-display-preview.svg";
import { Spinner, Text } from "design-system";
import {
EMPTY_TABLE_TITLE_TEXT,
EMPTY_TABLE_MESSAGE_TEXT,
createMessage,
EMPTY_TABLE_SVG_ALT_TEXT,
LOADING_RECORDS_MESSAGE_TEXT,
LOADING_RECORDS_TITLE_TEXT,
ERR_FETCHING_DATASOURCE_PREVIEW_DATA,
} from "@appsmith/constants/messages";
import { MessageWrapper, SchemaStateMessageWrapper } from "./SchemaViewModeCSS";
type InterimState = "LOADING" | "NODATA" | "FAILED";
interface RenderInterimDataStateProps {
state: InterimState;
}
const RenderInterimDataState = ({ state }: RenderInterimDataStateProps) => {
return (
<MessageWrapper>
<SchemaStateMessageWrapper>
{state === "NODATA" ? (
<>
{/* Render empty table image */}
<img
alt={createMessage(EMPTY_TABLE_SVG_ALT_TEXT)}
src={EmptyTableSVG}
/>
{/* Show description below the image */}
{/* Show title */}
<Text style={{ fontWeight: "bold" }}>
{createMessage(EMPTY_TABLE_TITLE_TEXT)}
</Text>
{/* Show description */}
<Text>{createMessage(EMPTY_TABLE_MESSAGE_TEXT)}</Text>
</>
) : state === "FAILED" ? (
<Text color="var(--ads-color-red-500)">
{createMessage(ERR_FETCHING_DATASOURCE_PREVIEW_DATA)}
</Text>
) : state === "LOADING" ? (
<>
{/* Show spinner */}
<Spinner size="md" />
{/* Show title */}
<Text style={{ fontWeight: "bold" }}>
{createMessage(LOADING_RECORDS_TITLE_TEXT)}
</Text>
{/* Show description */}
<Text>{createMessage(LOADING_RECORDS_MESSAGE_TEXT)}</Text>
</>
) : null}
</SchemaStateMessageWrapper>
</MessageWrapper>
);
};
export default RenderInterimDataState;

View File

@ -0,0 +1,212 @@
import styled from "styled-components";
export const ViewModeSchemaContainer = styled.div`
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
`;
export const DataWrapperContainer = styled.div`
display: flex;
flex: 1;
overflow: hidden;
.t--datasource-column:hover {
background: none;
cursor: default;
}
`;
export const StructureContainer = styled.div`
height: 100%;
width: 25%;
padding: var(--ads-v2-spaces-4) 0 var(--ads-v2-spaces-4)
var(--ads-v2-spaces-5);
display: flex;
flex-direction: column;
overflow: hidden;
border-right: 1px solid var(--ads-v2-color-gray-300);
flex-shrink: 0;
& > .datasourceStructure-header {
padding: 0 var(--ads-v2-spaces-5);
}
`;
export const DatasourceDataContainer = styled.div`
height: 100%;
width: 75%;
display: flex;
flex-direction: column;
`;
export const DatasourceListContainer = styled.div`
height: 100%;
display: flex;
flex-direction: column;
padding-left: var(--ads-v2-spaces-5);
.t--entity.datasourceStructure-datasource-view-mode {
padding-right: var(--ads-v2-spaces-5);
}
&.t--gsheet-structure {
padding-bottom: var(--ads-v2-spaces-8);
.t--gsheet-structure-list {
overflow-y: auto;
flex-shrink: 1;
flex-grow: 0;
scrollbar-gutter: stable;
}
.t--spreadsheet-structure > .t--entity-item {
font-weight: var(--ads-v2-font-weight-bold);
height: 36px;
padding-left: 0;
& .ads-v2-button__content:hover {
background: none;
}
}
.t--spreadsheet-structure {
padding-right: var(--ads-spaces-3);
flex-grow: 0;
flex-shrink: 0;
}
.t--sheet-structure:not(:last-of-type) {
padding-bottom: var(--ads-spaces-3);
}
.t--sheet-structure .t--datasource-column {
padding-top: var(--ads-spaces-1);
padding-bottom: var(--ads-spaces-1);
}
.t--sheet-structure > .t--entity-item {
color: var(--ads-v2-color-gray-600);
height: 36px;
padding-left: var(--ads-spaces-10);
}
.t--sheet-structure .t--datasource-column {
color: var(--ads-v2-color-gray-600);
height: 30px;
padding-left: var(--ads-spaces-10);
}
.t--sheet-structure.t--sheet-structure-active > .t--entity-item {
background: var(--ads-v2-color-gray-100);
}
.t--sheet-structure .t--entity-name {
padding: var(--ads-spaces-2) 0;
}
}
.t--schema-virtuoso-container {
height: 100%;
}
`;
export const DatasourceAttributesWrapper = styled.div`
padding: var(--ads-spaces-1) 0 var(--ads-spaces-1) var(--ads-spaces-10);
.t--datasource-column > div > div:first-of-type {
padding-right: var(--ads-spaces-3);
}
.t--datasource-column > div > div:last-of-type {
flex-shrink: 0;
flex-grow: 0;
}
`;
export const ButtonContainer = styled.div`
display: flex;
flex-shrink: 0;
justify-content: flex-end;
border-top: 1px solid var(--ads-v2-color-gray-300);
padding: var(--ads-v2-spaces-4);
`;
export const MessageWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: auto;
&.t--item-loading-indicator {
justify-content: flex-start;
padding: var(--ads-spaces-1) 0 var(--ads-spaces-1) var(--ads-spaces-11);
}
&.t--item-loading-indicator--spreadsheet,
&.t--item-loading-indicator--schema {
padding-left: 0;
}
`;
export const SchemaStateMessageWrapper = styled.div`
width: auto;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
img {
padding-bottom: var(--ads-v2-spaces-7);
}
span:first-child {
padding-bottom: var(--ads-v2-spaces-2);
}
`;
export const TableWrapper = styled.div`
overflow-x: auto;
height: 100%;
&& > div {
width: 100%;
}
&& > ${MessageWrapper} {
width: 100%;
height: 100%;
}
&& .t--table-response {
border: none;
height: 100%;
overflow: hidden;
}
&& .tableWrap {
overflow: auto;
}
& .table {
background: none;
& > div:first-child {
position: sticky;
top: 0;
z-index: 1;
}
& .draggable-header {
cursor: default;
}
}
& .table div:first-of-type .tr {
background: var(--ads-v2-color-black-5);
border-right: none;
border-bottom: 1px solid var(--ads-v2-color-black-75);
}
&& .table div.tbody .tr {
background: var(--ads-v2-color-white);
border-bottom: 1px solid var(--ads-v2-color-black-75);
}
&& .table .td,
&& .table .th {
border-right: none;
border-bottom: none;
}
button {
margin-right: 24px;
}
&& .tableWrap {
}
`;
export const DatasourceStructureSearchContainer = styled.div`
margin: var(--ads-v2-spaces-3) 0 var(--ads-v2-spaces-4) 0;
background: white;
flex-shrink: 0;
padding-right: var(--ads-v2-spaces-5);
&.t--gsheet-search-container {
margin-top: var(--ads-v2-spaces-4);
margin-bottom: var(--ads-v2-spaces-3);
}
&.t--search-container--query-editor {
padding-right: 0;
}
`;

View File

@ -272,7 +272,17 @@ export interface UseSheetDataReturn {
}: FetchSheetData) => void;
}
export const useSheetsList = (): UseSheetListReturn => {
export interface UseSheetListProps {
setSheetOptions?: (tableOptions: DropdownOptions) => void;
}
export interface UseSheetDataProps {
setSheetData?: (tableOptions: DropdownOptions) => void;
}
export const useSheetsList = (
props: UseSheetListProps = {},
): UseSheetListReturn => {
const dispatch = useDispatch();
const [sheetsList, setSheetsList] = useState<DropdownOption[]>([]);
@ -298,12 +308,13 @@ export const useSheetsList = (): UseSheetListReturn => {
const responseBody = payload.data.trigger;
if (Array.isArray(responseBody)) {
setSheetsList(responseBody);
props.setSheetOptions && props.setSheetOptions(responseBody);
} else {
// to handle error like "401 Unauthorized"
}
}
},
[setSheetsList, setIsFetchingSheetsList],
[setSheetsList, setIsFetchingSheetsList, props.setSheetOptions],
);
const fetchSheetsList = useCallback(
@ -314,6 +325,7 @@ export const useSheetsList = (): UseSheetListReturn => {
selectedSpreadsheetUrl,
}: FetchSheetsList) => {
setSheetsList([]);
props.setSheetOptions && props.setSheetOptions([]);
setIsFetchingSheetsList(true);
setFailedFetchingSheetsList(false);
const formattedRequestData = {
@ -343,6 +355,7 @@ export const useSheetsList = (): UseSheetListReturn => {
onFetchAllSheetFailure,
setIsFetchingSheetsList,
setFailedFetchingSheetsList,
props.setSheetOptions,
],
);
@ -354,7 +367,9 @@ export const useSheetsList = (): UseSheetListReturn => {
};
};
export const useSheetData = (): UseSheetDataReturn => {
export const useSheetData = (
props: UseSheetDataProps = {},
): UseSheetDataReturn => {
const dispatch = useDispatch();
const [sheetData, setSheetData] = useState<any>([]);
@ -380,12 +395,13 @@ export const useSheetData = (): UseSheetDataReturn => {
const responseBody = payload.data.trigger;
if (Array.isArray(responseBody)) {
setSheetData(responseBody);
props.setSheetData && props.setSheetData(responseBody);
} else {
// to handle error like "401 Unauthorized"
}
}
},
[setSheetData, setIsFetchingSheetData],
[setSheetData, setIsFetchingSheetData, props.setSheetData],
);
const fetchSheetData = useCallback(
@ -396,6 +412,7 @@ export const useSheetData = (): UseSheetDataReturn => {
selectedSpreadsheetUrl,
}: FetchSheetData) => {
setSheetData([]);
props.setSheetData && props.setSheetData([]);
setIsFetchingSheetData(true);
setFailedFetchingSheetData(false);
const formattedRequestData = {
@ -428,6 +445,7 @@ export const useSheetData = (): UseSheetDataReturn => {
onFetchAllSheetFailure,
setIsFetchingSheetData,
setFailedFetchingSheetData,
props.setSheetData,
],
);

View File

@ -17,6 +17,7 @@ import type { Theme } from "constants/DefaultTheme";
interface TableProps {
data: Record<string, any>[];
tableBodyHeight?: number;
shouldResize?: boolean;
}
const TABLE_SIZES = {
@ -208,6 +209,7 @@ export const getScrollBarWidth = (tableBodyEle: any, scrollBarW: number) => {
function Table(props: TableProps) {
const theme = useTheme() as Theme;
const tableBodyRef = React.useRef<HTMLElement>();
const { shouldResize = true } = props;
const data = React.useMemo(() => {
/* Check for length greater than 0 of rows returned from the query for mappings keys */
@ -346,12 +348,14 @@ function Table(props: TableProps) {
>
<AutoToolTipComponent title={column.render("Header")}>
{column.render("Header")}
<div
{...column.getResizerProps()}
className={`resizer ${
column.isResizing ? "isResizing" : ""
}`}
/>
{shouldResize && (
<div
{...column.getResizerProps()}
className={`resizer ${
column.isResizing ? "isResizing" : ""
}`}
/>
)}
</AutoToolTipComponent>
</div>
</div>

View File

@ -90,7 +90,7 @@ import {
} from "@appsmith/utils/BusinessFeatures/permissionPageHelpers";
import { selectFeatureFlagCheck } from "@appsmith/selectors/featureFlagsSelectors";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
import GoogleSheetSchema from "../DatasourceInfo/GoogleSheetSchema";
import DatasourceTabs from "../DatasourceInfo/DatasorceTabs";
const ViewModeContainer = styled.div`
display: flex;
@ -468,13 +468,77 @@ class DatasourceSaaSEditor extends JSONtoForm<Props, State> {
}
renderDatasourceInfo = () => {
const { datasource, formConfig, viewMode } = this.props;
const {
datasource,
formConfig,
formData,
isPluginAuthFailed,
isPluginAuthorized,
pageId,
plugin,
pluginPackageName,
viewMode,
} = this.props;
const isGoogleSheetPlugin = isGoogleSheetPluginDS(pluginPackageName);
const authErrorMessage = getDatasourceErrorMessage(
formData,
plugin,
this.state.filterParams.id,
);
const hideDatasourceSection =
isGoogleSheetPlugin &&
!isPluginAuthorized &&
authErrorMessage == GSHEET_AUTHORIZATION_ERROR;
return (
<DatasourceInformation
config={formConfig[0]}
datasource={datasource}
viewMode={viewMode}
/>
<ViewModeWrapper data-testid="t--ds-review-section">
{datasource &&
isGoogleSheetPlugin &&
isPluginAuthFailed &&
datasource.id !== TEMP_DATASOURCE_ID && (
<AuthMessage
actionType={ActionType.AUTHORIZE}
datasource={datasource}
description={authErrorMessage}
isInViewMode
pageId={pageId}
/>
)}
{!isNil(formConfig) && !isNil(datasource) && !hideDatasourceSection && (
<DatasourceInformation
config={formConfig[0]}
datasource={datasource}
viewMode={viewMode}
/>
)}
</ViewModeWrapper>
);
};
shouldShowTabs = () => {
const { datasource, isPluginAuthorized, pluginPackageName } = this.props;
const isGoogleSheetPlugin = isGoogleSheetPluginDS(pluginPackageName);
const isGoogleSheetSchemaAvailable =
isGoogleSheetPlugin && isPluginAuthorized;
return isGoogleSheetSchemaAvailable && datasource;
};
renderTabsForViewMode = () => {
const { datasource } = this.props;
return (
<ViewModeContainer>
{this.shouldShowTabs() ? (
<DatasourceTabs
configChild={this.renderDatasourceInfo()}
datasource={datasource as Datasource}
/>
) : (
this.renderDatasourceInfo()
)}
</ViewModeContainer>
);
};
@ -486,7 +550,6 @@ class DatasourceSaaSEditor extends JSONtoForm<Props, State> {
datasource,
datasourceButtonConfiguration,
datasourceId,
formConfig,
formData,
gsheetProjectID,
gsheetToken,
@ -526,13 +589,8 @@ class DatasourceSaaSEditor extends JSONtoForm<Props, State> {
GOOGLE_SHEETS_INFO_BANNER_MESSAGE,
);
const hideDatasourceSection =
isGoogleSheetPlugin &&
!isPluginAuthorized &&
authErrorMessage == GSHEET_AUTHORIZATION_ERROR;
const isGoogleSheetSchemaAvailable =
isGoogleSheetPlugin && isPluginAuthorized;
const showingTabsOnViewMode =
this.shouldShowTabs() && viewMode && !isInsideReconnectModal;
return (
<>
@ -546,6 +604,7 @@ class DatasourceSaaSEditor extends JSONtoForm<Props, State> {
isDeleting={isDeleting}
isNewDatasource={createFlow}
isPluginAuthorized={isPluginAuthorized}
noBottomBorder={showingTabsOnViewMode}
pluginImage={pluginImage}
pluginName={plugin?.name || ""}
pluginType={plugin?.type || ""}
@ -554,7 +613,11 @@ class DatasourceSaaSEditor extends JSONtoForm<Props, State> {
/>
)}
<ResizerMainContainer>
<ResizerContentContainer className="saas-form-resizer-content">
<ResizerContentContainer
className={`saas-form-resizer-content ${
showingTabsOnViewMode && "saas-form-resizer-content-show-tabs"
}`}
>
<DSEditorWrapper>
<DSDataFilter
filterId={this.state.filterParams.id}
@ -601,35 +664,9 @@ class DatasourceSaaSEditor extends JSONtoForm<Props, State> {
{""}
</>
)}
{viewMode && !isInsideReconnectModal && (
<ViewModeContainer>
<ViewModeWrapper>
{datasource &&
isGoogleSheetPlugin &&
isPluginAuthFailed ? (
<AuthMessage
actionType={ActionType.AUTHORIZE}
datasource={datasource}
description={authErrorMessage}
isInViewMode
pageId={pageId}
/>
) : null}
{!isNil(formConfig) &&
!isNil(datasource) &&
!hideDatasourceSection
? this.renderDatasourceInfo()
: undefined}
</ViewModeWrapper>
{isGoogleSheetSchemaAvailable && datasource && (
<GoogleSheetSchema
datasourceId={datasourceId}
key={datasourceId}
pluginId={plugin?.id}
/>
)}
</ViewModeContainer>
)}
{viewMode &&
!isInsideReconnectModal &&
this.renderTabsForViewMode()}
</Form>
{/* Render datasource form call-to-actions */}
{datasource && (

View File

@ -622,4 +622,5 @@ export const ColumnWrapper = styled.div`
display: flex;
align-items: center;
height: 100%;
width: 100%;
`;