Skip to content

Commit

Permalink
chore: Adding no access state for Datasource tab based on permissions…
Browse files Browse the repository at this point in the history
… given via GAC (#38268)

## Description

- Today, when a user gives only View/Edit access to a Datasource, the
schema tables are still being seen on the Datasource tabs while it
doesn't show on the Datasource Editor page. This has been fixed with
this PR. It should only be seen when Datasource has create action
permissions, hence we show the "We can’t show the schema for this
datasource" screen in this case.
- When the user has not given View access to a Datasource, the UI is
broken in Datasource tab. This has been fixed with this PR. We now show
the No access state in this case.

**BEFORE**:

When view access is not given:
<img width="1147" alt="Screenshot 2024-12-20 at 5 52 58 PM"
src="https://github.com/user-attachments/assets/c1d1fd39-d6d3-4fd8-99bf-895698f61490"
/>

When create action permission is not given but view access is given:
<img width="1138" alt="Screenshot 2024-12-20 at 5 54 10 PM"
src="https://github.com/user-attachments/assets/abf0aa86-e541-4453-b7e4-071d123f7a60"
/>


**AFTER**:

When view access is not given:
<img width="1136" alt="Screenshot 2024-12-20 at 5 58 22 PM"
src="https://github.com/user-attachments/assets/e160250b-963c-457e-81b1-380aef1332a1"
/>


When create action permission is not given but view access is given:
<img width="1139" alt="Screenshot 2024-12-20 at 5 57 53 PM"
src="https://github.com/user-attachments/assets/1967a657-622c-46f7-b6d4-78451b6106f0"
/>


Fixes [#38093](#38093)

## Automation

/ok-to-test tags="@tag.Sanity, @tag.Datasource, @tag.IDE, @tag.JS,
@tag.Git"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/12452839374>
> Commit: ee5bc17
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12452839374&attempt=3"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Sanity, @tag.Datasource, @tag.IDE, @tag.JS, @tag.Git`
> Spec:
> <hr>Mon, 23 Dec 2024 08:29:06 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced new icons and improved conditional rendering for datasource
components.
  - Added a "not found" message for empty datasource options.
  - Enhanced user feedback with clearer messaging for missing resources.

- **Bug Fixes**
- Adjusted rendering logic to ensure edit buttons only appear when both
conditions are met.

- **Enhancements**
- Improved permission checks and logic for managing datasource
visibility.
- Streamlined component logic for better readability and
maintainability.
  - Enhanced error handling practices in saga functions.

- **Tests**
  - Simplified test structure by removing unnecessary context providers.

- **Chores**
  - Updated import statements and component names for consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
ankitakinger authored Dec 23, 2024
1 parent e45cbdf commit 5855115
Show file tree
Hide file tree
Showing 17 changed files with 215 additions and 169 deletions.
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
import React from "react";
import { Flex } from "@appsmith/ads";
import { Flex, Icon } from "@appsmith/ads";
import { getAssetUrl } from "ee/utils/airgapHelpers";
import { EntityIcon } from "pages/Editor/Explorer/ExplorerIcons";
import { useSelector } from "react-redux";
import {
getPluginIdFromDatasourceId,
getPluginImages,
} from "ee/selectors/entitiesSelector";
import { getPluginImages } from "ee/selectors/entitiesSelector";

interface Props {
datasourceId: string;
datasourceName: string;
pluginId: string;
}

const CurrentDataSource = ({ datasourceId, datasourceName }: Props) => {
const { pluginId, pluginImages } = useSelector((state) => ({
pluginId: getPluginIdFromDatasourceId(state, datasourceId),
pluginImages: getPluginImages(state),
}));
const CurrentDataSource = ({ datasourceName, pluginId }: Props) => {
const pluginImages = useSelector((state) => getPluginImages(state));

const datasourceIcon = pluginId ? pluginImages?.[pluginId] : undefined;

return (
<Flex alignItems="center" gap="spaces-2">
<EntityIcon height="16px" width="16px">
<img alt="entityIcon" src={getAssetUrl(datasourceIcon)} />
{datasourceIcon ? (
<img alt="entityIcon" src={getAssetUrl(datasourceIcon)} />
) : (
<Icon name="datasource-v3" />
)}
</EntityIcon>
{datasourceName}
{datasourceName || "NA"}
</Flex>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { useGoToDatasource } from "PluginActionEditor/components/PluginActionRes
const CurrentDataSourceLink = ({
datasourceId,
datasourceName,
pluginId,
}: {
datasourceId: string;
datasourceName: string;
pluginId: string;
}) => {
const { goToDatasource } = useGoToDatasource();

Expand All @@ -19,10 +21,7 @@ const CurrentDataSourceLink = ({

return (
<Link onClick={handleClick}>
<CurrentDataSource
datasourceId={datasourceId}
datasourceName={datasourceName}
/>
<CurrentDataSource datasourceName={datasourceName} pluginId={pluginId} />
</Link>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const DatasourceInfo = ({
datasourceName={datasourceName}
plugin={plugin}
/>
{showEditButton && (
{showEditButton && datasourceName && (
<Tooltip content={createMessage(EDIT_DS_CONFIG)} placement="top">
<Button
isIconButton
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React from "react";
import { useSelector } from "react-redux";
import { Flex } from "@appsmith/ads";
import { CREATE_NEW_DATASOURCE, createMessage } from "ee/constants/messages";
import {
CREATE_NEW_DATASOURCE,
createMessage,
NOT_FOUND,
} from "ee/constants/messages";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
import {
Expand Down Expand Up @@ -66,7 +70,7 @@ export const PluginDatasourceSelector = ({
const userWorkspacePermissions = useSelector(
(state: AppState) => getCurrentAppWorkspace(state).userPermissions ?? [],
);
const isChangePermitted = getHasManageActionPermission(
const isActionChangePermitted = getHasManageActionPermission(
isFeatureEnabled,
currentActionConfig?.userPermissions,
);
Expand Down Expand Up @@ -108,15 +112,23 @@ export const PluginDatasourceSelector = ({
});
}

if (!showDatasourceSelector || !isChangePermitted) {
if (!showDatasourceSelector || !isActionChangePermitted) {
return (
<CurrentDataSourceLink
datasourceId={datasourceId}
datasourceName={datasourceName}
pluginId={plugin?.id || ""}
/>
);
}

if (DATASOURCES_OPTIONS.length < 1) {
DATASOURCES_OPTIONS.push({
label: createMessage(NOT_FOUND),
value: "not found",
});
}

return (
<Flex>
<MenuField
Expand All @@ -126,8 +138,8 @@ export const PluginDatasourceSelector = ({
options={DATASOURCES_OPTIONS}
>
<CurrentDataSource
datasourceId={datasourceId}
datasourceName={datasourceName}
pluginId={plugin?.id || ""}
/>
</MenuField>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,12 @@ export interface DatasourceProps {
}

const DatasourceSelector = (props: DatasourceProps) => {
return props.plugin ? (
return props.plugin &&
API_FORM_COMPONENTS.includes(props.plugin.uiComponent) ? (
<ApiDatasourceSelector {...props} formName={API_EDITOR_FORM_NAME} />
) : (
<QueryDatasourceSelector {...props} formName={QUERY_EDITOR_FORM_NAME} />
)
) : null;
<ApiDatasourceSelector {...props} formName={API_EDITOR_FORM_NAME} />
) : (
<QueryDatasourceSelector {...props} formName={QUERY_EDITOR_FORM_NAME} />
);
};

export default DatasourceSelector;
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getIsFetchingDatasourceStructure,
getPluginIdFromDatasourceId,
getPluginDatasourceComponentFromId,
getDatasource,
} from "ee/selectors/entitiesSelector";
import type { AppState } from "ee/reducers";
import { fetchDatasourceStructure } from "actions/datasourceActions";
Expand All @@ -26,24 +27,33 @@ import { useEditorType } from "ee/hooks";
import { useParentEntityInfo } from "ee/hooks/datasourceEditorHooks";
import DatasourceInfo from "./DatasourceInfo";
import { getPlugin } from "ee/selectors/entitiesSelector";
import {
getHasCreateDatasourceActionPermission,
getHasManageDatasourcePermission,
getHasReadDatasourcePermission,
} from "ee/utils/BusinessFeatures/permissionPageHelpers";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";

interface Props {
datasourceId: string;
datasourceName: string;
currentActionId: string;
}

const Datasource = (props: Props) => {
const DatasourceTab = (props: Props) => {
const dispatch = useDispatch();

const { datasourceId, datasourceName } = props;

const datasourceStructure = useSelector((state) =>
getDatasourceStructureById(state, props.datasourceId),
getDatasourceStructureById(state, datasourceId),
);

const { responseTabHeight } = useSelector(getPluginActionDebuggerState);

const pluginId = useSelector((state) =>
getPluginIdFromDatasourceId(state, props.datasourceId),
getPluginIdFromDatasourceId(state, datasourceId),
);

const plugin = useSelector((state) => getPlugin(state, pluginId || ""));
Expand All @@ -54,32 +64,51 @@ const Datasource = (props: Props) => {
const [selectedTable, setSelectedTable] = useState<string>();

const isLoading = useSelector((state: AppState) =>
getIsFetchingDatasourceStructure(state, props.datasourceId),
getIsFetchingDatasourceStructure(state, datasourceId),
);

const pluginDatasourceForm = useSelector((state) =>
getPluginDatasourceComponentFromId(state, pluginId || ""),
);

const datasource = useSelector((state) => getDatasource(state, datasourceId));

const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);

const canCreateDatasourceActions = getHasCreateDatasourceActionPermission(
isFeatureEnabled,
datasource?.userPermissions || [],
);

const canReadDatasource = getHasReadDatasourcePermission(
isFeatureEnabled,
datasource?.userPermissions || [],
);

const canManageDatasource = getHasManageDatasourcePermission(
isFeatureEnabled,
datasource?.userPermissions || [],
);

useEffect(
function resetSelectedTable() {
setSelectedTable(undefined);
},
[props.datasourceId],
[datasourceId],
);

useEffect(
function fetchDatasourceStructureEffect() {
function fetchStructure() {
if (
props.datasourceId &&
datasourceId &&
datasourceStructure === undefined &&
pluginDatasourceForm !==
DatasourceComponentTypes.RestAPIDatasourceForm
) {
dispatch(
fetchDatasourceStructure(
props.datasourceId,
datasourceId,
true,
DatasourceStructureContext.QUERY_EDITOR,
),
Expand All @@ -89,7 +118,7 @@ const Datasource = (props: Props) => {

fetchStructure();
},
[props.datasourceId, datasourceStructure, dispatch, pluginDatasourceForm],
[datasourceId, datasourceStructure, dispatch, pluginDatasourceForm],
);

useEffect(
Expand All @@ -98,22 +127,22 @@ const Datasource = (props: Props) => {
setSelectedTable(datasourceStructure.tables[0].name);
}
},
[selectedTable, props.datasourceId, isLoading, datasourceStructure],
[selectedTable, datasourceId, isLoading, datasourceStructure],
);

// eslint-disable-next-line react-perf/jsx-no-new-function-as-prop
const editDatasource = () => {
const entryPoint = DatasourceEditEntryPoints.QUERY_EDITOR_DATASOURCE_SCHEMA;

AnalyticsUtil.logEvent("EDIT_DATASOURCE_CLICK", {
datasourceId: props.datasourceId,
datasourceId,
pluginName: "",
entryPoint: entryPoint,
});

const url = datasourcesEditorIdURL({
baseParentEntityId: parentEntityId,
datasourceId: props.datasourceId,
datasourceId,
params: { ...omit(getQueryParams(), "viewMode"), viewMode: false },
generateEditorPath: true,
});
Expand All @@ -124,7 +153,12 @@ const Datasource = (props: Props) => {
const getStatusState = () => {
if (isLoading) return SchemaDisplayStatus.SCHEMA_LOADING;

if (!datasourceStructure) return SchemaDisplayStatus.NOSCHEMA;
/* When a user doesn't have view access on a datasource */
if (!canReadDatasource) return SchemaDisplayStatus.NOACCESS;

/* When a user doesn't have create new query access but has view access on the datasource */
if (!datasourceStructure || !canCreateDatasourceActions)
return SchemaDisplayStatus.NOSCHEMA;

if (datasourceStructure && "error" in datasourceStructure)
return SchemaDisplayStatus.FAILED;
Expand All @@ -144,10 +178,10 @@ const Datasource = (props: Props) => {
return (
<Flex flexDirection="column" padding="spaces-3">
<DatasourceInfo
datasourceId={props.datasourceId}
datasourceName={props.datasourceName}
datasourceId={datasourceId}
datasourceName={datasourceName}
plugin={plugin}
showEditButton={!isLoading}
showEditButton={!isLoading && canManageDatasource}
/>
<StatusDisplay
editDatasource={editDatasource}
Expand All @@ -171,8 +205,8 @@ const Datasource = (props: Props) => {
<Flex h="100%">
<DatasourceTables
currentActionId={props.currentActionId}
datasourceId={props.datasourceId}
datasourceName={props.datasourceName}
datasourceId={datasourceId}
datasourceName={datasourceName}
datasourceStructure={datasourceStructure}
plugin={plugin}
selectedTable={selectedTable}
Expand Down Expand Up @@ -200,4 +234,4 @@ const Datasource = (props: Props) => {
);
};

export { Datasource };
export { DatasourceTab };
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from "./Datasource";
export * from "./DatasourceTab";
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
} from "PluginActionEditor/store";
import { doesPluginRequireDatasource } from "ee/entities/Engine/actionHelpers";
import useShowSchema from "PluginActionEditor/components/PluginActionResponse/hooks/useShowSchema";
import { Datasource } from "PluginActionEditor/components/PluginActionResponse/components/DatasourceTab";
import { DatasourceTab } from "PluginActionEditor/components/PluginActionResponse/components/DatasourceTab";
import {
useBlockExecution,
useHandleRunClick,
Expand Down Expand Up @@ -64,10 +64,10 @@ function usePluginActionResponseTabs() {
key: DEBUGGER_TAB_KEYS.DATASOURCE_TAB,
title: "Datasource",
panelComponent: (
<Datasource
<DatasourceTab
currentActionId={action.id}
datasourceId={datasource?.id || ""}
datasourceName={datasource?.name || ""}
datasourceId={datasource?.id || action.datasource.id || ""}
datasourceName={datasource?.name || action.datasource.name || ""}
/>
),
});
Expand Down Expand Up @@ -119,10 +119,10 @@ function usePluginActionResponseTabs() {
key: DEBUGGER_TAB_KEYS.DATASOURCE_TAB,
title: "Datasource",
panelComponent: (
<Datasource
<DatasourceTab
currentActionId={action.id}
datasourceId={datasource?.id || ""}
datasourceName={datasource?.name || ""}
datasourceId={datasource?.id || action.datasource.id || ""}
datasourceName={datasource?.name || action.datasource.name || ""}
/>
),
});
Expand Down
1 change: 1 addition & 0 deletions app/client/src/ce/constants/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ export const CREATE_NEW_DATASOURCE_MOST_POPULAR_HEADER = () => "Most popular";
export const CREATE_NEW_DATASOURCE_REST_API = () => "REST API";
export const SAMPLE_DATASOURCES = () => "Sample datasources";
export const EDIT_DS_CONFIG = () => "Edit datasource configuration";
export const NOT_FOUND = () => "Not found";

export const ERROR_EVAL_ERROR_GENERIC = () =>
`Unexpected error occurred while evaluating the application`;
Expand Down
Loading

0 comments on commit 5855115

Please sign in to comment.