chore: Adding no access state for Datasource tab based on permissions 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](https://github.com/appsmithorg/appsmith/issues/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: ee5bc1774c02b4b29a702c8baefbad35390708c3 > <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 -->
This commit is contained in:
parent
e45cbdfa83
commit
5855115abb
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ import { useGoToDatasource } from "PluginActionEditor/components/PluginActionRes
|
|||
const CurrentDataSourceLink = ({
|
||||
datasourceId,
|
||||
datasourceName,
|
||||
pluginId,
|
||||
}: {
|
||||
datasourceId: string;
|
||||
datasourceName: string;
|
||||
pluginId: string;
|
||||
}) => {
|
||||
const { goToDatasource } = useGoToDatasource();
|
||||
|
||||
|
|
@ -19,10 +21,7 @@ const CurrentDataSourceLink = ({
|
|||
|
||||
return (
|
||||
<Link onClick={handleClick}>
|
||||
<CurrentDataSource
|
||||
datasourceId={datasourceId}
|
||||
datasourceName={datasourceName}
|
||||
/>
|
||||
<CurrentDataSource datasourceName={datasourceName} pluginId={pluginId} />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ const DatasourceInfo = ({
|
|||
datasourceName={datasourceName}
|
||||
plugin={plugin}
|
||||
/>
|
||||
{showEditButton && (
|
||||
{showEditButton && datasourceName && (
|
||||
<Tooltip content={createMessage(EDIT_DS_CONFIG)} placement="top">
|
||||
<Button
|
||||
isIconButton
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -66,7 +70,7 @@ export const PluginDatasourceSelector = ({
|
|||
const userWorkspacePermissions = useSelector(
|
||||
(state: AppState) => getCurrentAppWorkspace(state).userPermissions ?? [],
|
||||
);
|
||||
const isChangePermitted = getHasManageActionPermission(
|
||||
const isActionChangePermitted = getHasManageActionPermission(
|
||||
isFeatureEnabled,
|
||||
currentActionConfig?.userPermissions,
|
||||
);
|
||||
|
|
@ -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
|
||||
|
|
@ -126,8 +138,8 @@ export const PluginDatasourceSelector = ({
|
|||
options={DATASOURCES_OPTIONS}
|
||||
>
|
||||
<CurrentDataSource
|
||||
datasourceId={datasourceId}
|
||||
datasourceName={datasourceName}
|
||||
pluginId={plugin?.id || ""}
|
||||
/>
|
||||
</MenuField>
|
||||
</Flex>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
getIsFetchingDatasourceStructure,
|
||||
getPluginIdFromDatasourceId,
|
||||
getPluginDatasourceComponentFromId,
|
||||
getDatasource,
|
||||
} from "ee/selectors/entitiesSelector";
|
||||
import type { AppState } from "ee/reducers";
|
||||
import { fetchDatasourceStructure } from "actions/datasourceActions";
|
||||
|
|
@ -26,6 +27,13 @@ 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;
|
||||
|
|
@ -33,17 +41,19 @@ interface Props {
|
|||
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 || ""));
|
||||
|
|
@ -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,
|
||||
),
|
||||
|
|
@ -89,7 +118,7 @@ const Datasource = (props: Props) => {
|
|||
|
||||
fetchStructure();
|
||||
},
|
||||
[props.datasourceId, datasourceStructure, dispatch, pluginDatasourceForm],
|
||||
[datasourceId, datasourceStructure, dispatch, pluginDatasourceForm],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
|
|
@ -98,7 +127,7 @@ 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
|
||||
|
|
@ -106,14 +135,14 @@ const Datasource = (props: Props) => {
|
|||
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,
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
@ -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}
|
||||
|
|
@ -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}
|
||||
|
|
@ -200,4 +234,4 @@ const Datasource = (props: Props) => {
|
|||
);
|
||||
};
|
||||
|
||||
export { Datasource };
|
||||
export { DatasourceTab };
|
||||
|
|
@ -1 +1 @@
|
|||
export * from "./Datasource";
|
||||
export * from "./DatasourceTab";
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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 || ""}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
|
@ -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 || ""}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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`;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
import { getJSTabs, getQueryTabs } from "selectors/ideSelectors";
|
||||
import type { AppState } from "ee/reducers";
|
||||
import { identifyEntityFromPath } from "navigation/FocusEntity";
|
||||
import { getCurrentPageId } from "selectors/editorSelectors";
|
||||
import { getCurrentBasePageId } from "selectors/editorSelectors";
|
||||
import { getQueryEntityItemUrl } from "ee/pages/Editor/IDE/EditorPane/Query/utils";
|
||||
|
||||
export type EditorSegmentList = Array<{
|
||||
|
|
@ -74,10 +74,10 @@ export const selectQuerySegmentEditorTabs = (state: AppState) => {
|
|||
|
||||
export const getLastQueryTab = createSelector(
|
||||
selectQuerySegmentEditorTabs,
|
||||
getCurrentPageId,
|
||||
(tabs, pageId) => {
|
||||
getCurrentBasePageId,
|
||||
(tabs, basePageId) => {
|
||||
if (tabs.length) {
|
||||
const url = getQueryEntityItemUrl(tabs[tabs.length - 1], pageId);
|
||||
const url = getQueryEntityItemUrl(tabs[tabs.length - 1], basePageId);
|
||||
const urlWithoutQueryParams = url.split("?")[0];
|
||||
|
||||
return identifyEntityFromPath(urlWithoutQueryParams);
|
||||
|
|
|
|||
|
|
@ -1645,7 +1645,9 @@ export const getQuerySegmentItems = createSelector(
|
|||
? "AI Queries"
|
||||
: datasourceIdToNameMap[action.config.datasource.id] ?? "AI Queries";
|
||||
} else {
|
||||
group = datasourceIdToNameMap[action.config.datasource.id];
|
||||
group =
|
||||
action.config.datasource?.name ??
|
||||
datasourceIdToNameMap[action.config.datasource?.id];
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import { hasCreateWorkspacePermission as hasCreateWorkspacePermission_EE } from
|
|||
import { hasCreateDatasourcePermission as hasCreateDatasourcePermission_CE } from "ce/utils/permissionHelpers";
|
||||
import { hasCreateDatasourcePermission as hasCreateDatasourcePermission_EE } from "ee/utils/permissionHelpers";
|
||||
|
||||
import { hasReadDatasourcePermission as hasReadDatasourcePermission_CE } from "ce/utils/permissionHelpers";
|
||||
import { hasReadDatasourcePermission as hasReadDatasourcePermission_EE } from "ee/utils/permissionHelpers";
|
||||
|
||||
import { hasManageDatasourcePermission as hasManageDatasourcePermission_CE } from "ce/utils/permissionHelpers";
|
||||
import { hasManageDatasourcePermission as hasManageDatasourcePermission_EE } from "ee/utils/permissionHelpers";
|
||||
|
||||
|
|
@ -58,6 +61,14 @@ export const getHasCreateDatasourcePermission = (
|
|||
else return hasCreateDatasourcePermission_CE(permissions);
|
||||
};
|
||||
|
||||
export const getHasReadDatasourcePermission = (
|
||||
isEnabled: boolean,
|
||||
permissions?: string[],
|
||||
) => {
|
||||
if (isEnabled) return hasReadDatasourcePermission_EE(permissions);
|
||||
else return hasReadDatasourcePermission_CE(permissions);
|
||||
};
|
||||
|
||||
export const getHasManageDatasourcePermission = (
|
||||
isEnabled: boolean,
|
||||
permissions?: string[],
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export enum PERMISSION_TYPE {
|
|||
CREATE_APPLICATION = "create:applications",
|
||||
/* Datasource permissions */
|
||||
CREATE_DATASOURCES = "create:datasources",
|
||||
READ_DATASOURCES = "read:datasources",
|
||||
EXECUTE_DATASOURCES = "execute:datasources",
|
||||
CREATE_DATASOURCE_ACTIONS = "create:datasourceActions",
|
||||
DELETE_DATASOURCES = "delete:datasources",
|
||||
|
|
@ -85,6 +86,8 @@ export const hasCreateWorkspacePermission = (_permissions?: string[]) => true;
|
|||
|
||||
export const hasCreateDatasourcePermission = (_permissions?: string[]) => true;
|
||||
|
||||
export const hasReadDatasourcePermission = (_permissions?: string[]) => true;
|
||||
|
||||
export const hasManageDatasourcePermission = (_permissions?: string[]) => true;
|
||||
|
||||
export const hasManageWorkspaceDatasourcePermission = (
|
||||
|
|
|
|||
|
|
@ -14,9 +14,6 @@ import { getIDETestState } from "test/factories/AppIDEFactoryUtils";
|
|||
import { PageFactory } from "test/factories/PageFactory";
|
||||
import { screen, waitFor } from "@testing-library/react";
|
||||
import { GoogleSheetFactory } from "test/factories/Actions/GoogleSheetFactory";
|
||||
import { PluginActionContextProvider } from "PluginActionEditor";
|
||||
import { PluginPackageName, PluginType } from "entities/Action";
|
||||
import { DatasourceComponentTypes, UIComponentTypes } from "api/PluginApi";
|
||||
|
||||
const FeatureFlags = {
|
||||
rollout_side_by_side_enabled: true,
|
||||
|
|
@ -329,17 +326,6 @@ describe("IDE URL rendering of Queries", () => {
|
|||
});
|
||||
|
||||
describe("Postgres Routes", () => {
|
||||
const mockPlugin = {
|
||||
id: "plugin_id",
|
||||
name: "Postgres",
|
||||
packageName: PluginPackageName.POSTGRES,
|
||||
type: PluginType.DB,
|
||||
uiComponent: UIComponentTypes.UQIDbEditorForm,
|
||||
datasourceComponent: DatasourceComponentTypes.AutoForm,
|
||||
templates: {},
|
||||
requiresDatasource: true,
|
||||
};
|
||||
|
||||
it("Renders Postgres routes in Full Screen", async () => {
|
||||
const page = PageFactory.build();
|
||||
const anQuery = PostgresFactory.build({
|
||||
|
|
@ -358,9 +344,7 @@ describe("IDE URL rendering of Queries", () => {
|
|||
|
||||
const { getAllByText, getByRole, getByTestId } = render(
|
||||
<Route path={BUILDER_PATH}>
|
||||
<PluginActionContextProvider action={anQuery} plugin={mockPlugin}>
|
||||
<IDE />
|
||||
</PluginActionContextProvider>
|
||||
<IDE />
|
||||
</Route>,
|
||||
{
|
||||
url: `/app/applicationSlug/pageSlug-${page.basePageId}/edit/queries/${anQuery.baseId}`,
|
||||
|
|
@ -418,9 +402,7 @@ describe("IDE URL rendering of Queries", () => {
|
|||
|
||||
const { getAllByText, getByRole, getByTestId } = render(
|
||||
<Route path={BUILDER_PATH}>
|
||||
<PluginActionContextProvider action={anQuery} plugin={mockPlugin}>
|
||||
<IDE />
|
||||
</PluginActionContextProvider>
|
||||
<IDE />
|
||||
</Route>,
|
||||
{
|
||||
url: `/app/applicationSlug/pageSlug-${page.basePageId}/edit/queries/${anQuery.baseId}`,
|
||||
|
|
@ -551,17 +533,6 @@ describe("IDE URL rendering of Queries", () => {
|
|||
});
|
||||
|
||||
describe("Google Sheets Routes", () => {
|
||||
const mockPlugin = {
|
||||
id: "plugin_id",
|
||||
name: "Google Sheets",
|
||||
packageName: PluginPackageName.GOOGLE_SHEETS,
|
||||
type: PluginType.DB,
|
||||
uiComponent: UIComponentTypes.GraphQLEditorForm,
|
||||
datasourceComponent: DatasourceComponentTypes.RestAPIDatasourceForm,
|
||||
templates: {},
|
||||
requiresDatasource: false,
|
||||
};
|
||||
|
||||
it("Renders Google Sheets routes in Full Screen", async () => {
|
||||
const page = PageFactory.build();
|
||||
const anQuery = GoogleSheetFactory.build({
|
||||
|
|
@ -581,9 +552,7 @@ describe("IDE URL rendering of Queries", () => {
|
|||
|
||||
const { getAllByText, getByRole, getByTestId } = render(
|
||||
<Route path={BUILDER_PATH}>
|
||||
<PluginActionContextProvider action={anQuery} plugin={mockPlugin}>
|
||||
<IDE />
|
||||
</PluginActionContextProvider>
|
||||
<IDE />
|
||||
</Route>,
|
||||
{
|
||||
url: `/app/applicationSlug/pageSlug-${page.basePageId}/edit/saas/google-sheets-plugin/api/${anQuery.baseId}`,
|
||||
|
|
@ -634,9 +603,7 @@ describe("IDE URL rendering of Queries", () => {
|
|||
|
||||
const { container, getAllByText, getByRole, getByTestId } = render(
|
||||
<Route path={BUILDER_PATH}>
|
||||
<PluginActionContextProvider action={anQuery} plugin={mockPlugin}>
|
||||
<IDE />
|
||||
</PluginActionContextProvider>
|
||||
<IDE />
|
||||
</Route>,
|
||||
{
|
||||
url: `/app/applicationSlug/pageSlug-${page.basePageId}/edit/saas/google-sheets-plugin/api/${anQuery.baseId}`,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ import { closeJSActionTab } from "actions/jsActionActions";
|
|||
import { closeQueryActionTab } from "actions/pluginActionActions";
|
||||
import { getCurrentBasePageId } from "selectors/editorSelectors";
|
||||
import { getCurrentEntityInfo } from "../utils";
|
||||
import { useEditorType } from "ee/hooks";
|
||||
import { useParentEntityInfo } from "ee/hooks/datasourceEditorHooks";
|
||||
|
||||
export const useCurrentEditorState = () => {
|
||||
const [selectedSegment, setSelectedSegment] = useState<EditorEntityTab>(
|
||||
|
|
@ -58,7 +60,9 @@ export const useCurrentEditorState = () => {
|
|||
export const useSegmentNavigation = (): {
|
||||
onSegmentChange: (value: string) => void;
|
||||
} => {
|
||||
const basePageId = useSelector(getCurrentBasePageId);
|
||||
const editorType = useEditorType(location.pathname);
|
||||
const { parentEntityId: baseParentEntityId } =
|
||||
useParentEntityInfo(editorType);
|
||||
|
||||
/**
|
||||
* Callback to handle the segment change
|
||||
|
|
@ -70,17 +74,17 @@ export const useSegmentNavigation = (): {
|
|||
const onSegmentChange = (value: string) => {
|
||||
switch (value) {
|
||||
case EditorEntityTab.QUERIES:
|
||||
history.push(queryListURL({ basePageId }), {
|
||||
history.push(queryListURL({ baseParentEntityId }), {
|
||||
invokedBy: NavigationMethod.SegmentControl,
|
||||
});
|
||||
break;
|
||||
case EditorEntityTab.JS:
|
||||
history.push(jsCollectionListURL({ basePageId }), {
|
||||
history.push(jsCollectionListURL({ baseParentEntityId }), {
|
||||
invokedBy: NavigationMethod.SegmentControl,
|
||||
});
|
||||
break;
|
||||
case EditorEntityTab.UI:
|
||||
history.push(widgetListURL({ basePageId }), {
|
||||
history.push(widgetListURL({ baseParentEntityId }), {
|
||||
invokedBy: NavigationMethod.SegmentControl,
|
||||
});
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from "ee/constants/messages";
|
||||
import DebuggerLogs from "components/editorComponents/Debugger/DebuggerLogs";
|
||||
import ErrorLogs from "components/editorComponents/Debugger/Errors";
|
||||
import { Datasource } from "PluginActionEditor/components/PluginActionResponse/components/DatasourceTab";
|
||||
import { DatasourceTab } from "PluginActionEditor/components/PluginActionResponse/components/DatasourceTab";
|
||||
import type { ActionResponse } from "api/ActionAPI";
|
||||
import type { SourceEntity } from "entities/AppsmithConsole";
|
||||
import type { Action } from "entities/Action";
|
||||
|
|
@ -219,10 +219,12 @@ function QueryDebuggerTabs({
|
|||
key: DEBUGGER_TAB_KEYS.DATASOURCE_TAB,
|
||||
title: "Datasource",
|
||||
panelComponent: (
|
||||
<Datasource
|
||||
<DatasourceTab
|
||||
currentActionId={currentActionConfig.id}
|
||||
datasourceId={currentActionConfig.datasource.id || ""}
|
||||
datasourceName={datasource?.name || ""}
|
||||
datasourceName={
|
||||
datasource?.name || currentActionConfig.datasource.name || ""
|
||||
}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1561,66 +1561,101 @@ function* fetchDatasourceStructureSaga(
|
|||
yield take(ReduxActionTypes.FETCH_ENVIRONMENT_SUCCESS);
|
||||
}
|
||||
|
||||
const datasource = shouldBeDefined<Datasource>(
|
||||
yield select(getDatasource, action.payload.id),
|
||||
`Datasource not found for id - ${action.payload.id}`,
|
||||
);
|
||||
const plugin: Plugin = yield select(getPlugin, datasource?.pluginId);
|
||||
let errorMessage = "";
|
||||
let isSuccess = false;
|
||||
|
||||
try {
|
||||
const response: ApiResponse = yield DatasourcesApi.fetchDatasourceStructure(
|
||||
action.payload.id,
|
||||
action.payload.ignoreCache,
|
||||
const datasource = shouldBeDefined<Datasource>(
|
||||
yield select(getDatasource, action.payload.id),
|
||||
`Datasource not found for id - ${action.payload.id}`,
|
||||
);
|
||||
const isValidResponse: boolean = yield validateResponse(response, false);
|
||||
const plugin: Plugin = yield select(getPlugin, datasource?.pluginId);
|
||||
|
||||
if (isValidResponse) {
|
||||
try {
|
||||
const response: ApiResponse =
|
||||
yield DatasourcesApi.fetchDatasourceStructure(
|
||||
action.payload.id,
|
||||
action.payload.ignoreCache,
|
||||
);
|
||||
const isValidResponse: boolean = yield validateResponse(response, false);
|
||||
|
||||
if (isValidResponse) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.FETCH_DATASOURCE_STRUCTURE_SUCCESS,
|
||||
payload: {
|
||||
data: response.data,
|
||||
datasourceId: action.payload.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (isEmpty(response.data)) {
|
||||
errorMessage = createMessage(DATASOURCE_SCHEMA_NOT_AVAILABLE);
|
||||
AppsmithConsole.warning({
|
||||
text: "Datasource structure could not be retrieved",
|
||||
source: {
|
||||
id: action.payload.id,
|
||||
name: datasource.name,
|
||||
type: ENTITY_TYPE.DATASOURCE,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
isSuccess = true;
|
||||
AppsmithConsole.info({
|
||||
text: "Datasource structure retrieved",
|
||||
source: {
|
||||
id: action.payload.id,
|
||||
name: datasource.name,
|
||||
type: ENTITY_TYPE.DATASOURCE,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (!!(response.data as any)?.error) {
|
||||
isSuccess = false;
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
errorMessage = (response.data as any).error?.message;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
errorMessage = (error as any)?.message;
|
||||
isSuccess = false;
|
||||
yield put({
|
||||
type: ReduxActionTypes.FETCH_DATASOURCE_STRUCTURE_SUCCESS,
|
||||
type: ReduxActionErrorTypes.FETCH_DATASOURCE_STRUCTURE_ERROR,
|
||||
payload: {
|
||||
data: response.data,
|
||||
error,
|
||||
show: false,
|
||||
datasourceId: action.payload.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (isEmpty(response.data)) {
|
||||
errorMessage = createMessage(DATASOURCE_SCHEMA_NOT_AVAILABLE);
|
||||
AppsmithConsole.warning({
|
||||
text: "Datasource structure could not be retrieved",
|
||||
source: {
|
||||
id: action.payload.id,
|
||||
name: datasource.name,
|
||||
type: ENTITY_TYPE.DATASOURCE,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
isSuccess = true;
|
||||
AppsmithConsole.info({
|
||||
text: "Datasource structure retrieved",
|
||||
source: {
|
||||
id: action.payload.id,
|
||||
name: datasource.name,
|
||||
type: ENTITY_TYPE.DATASOURCE,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (!!(response.data as any)?.error) {
|
||||
isSuccess = false;
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
errorMessage = (response.data as any).error?.message;
|
||||
}
|
||||
AppsmithConsole.error({
|
||||
text: "Datasource structure could not be retrieved",
|
||||
source: {
|
||||
id: action.payload.id,
|
||||
name: datasource.name,
|
||||
type: ENTITY_TYPE.DATASOURCE,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const currentEnvDetails: { id: string; name: string } = yield select(
|
||||
getCurrentEnvironmentDetails,
|
||||
);
|
||||
|
||||
AnalyticsUtil.logEvent("DATASOURCE_SCHEMA_FETCH", {
|
||||
datasourceId: datasource?.id,
|
||||
pluginName: plugin?.name,
|
||||
environmentId: currentEnvDetails.id,
|
||||
environmentName: currentEnvDetails.name,
|
||||
errorMessage: errorMessage,
|
||||
isSuccess: isSuccess,
|
||||
source: action.payload.schemaFetchContext,
|
||||
});
|
||||
} catch (error) {
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
errorMessage = (error as any)?.message;
|
||||
isSuccess = false;
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.FETCH_DATASOURCE_STRUCTURE_ERROR,
|
||||
payload: {
|
||||
|
|
@ -1629,28 +1664,7 @@ function* fetchDatasourceStructureSaga(
|
|||
datasourceId: action.payload.id,
|
||||
},
|
||||
});
|
||||
AppsmithConsole.error({
|
||||
text: "Datasource structure could not be retrieved",
|
||||
source: {
|
||||
id: action.payload.id,
|
||||
name: datasource.name,
|
||||
type: ENTITY_TYPE.DATASOURCE,
|
||||
},
|
||||
});
|
||||
}
|
||||
const currentEnvDetails: { id: string; name: string } = yield select(
|
||||
getCurrentEnvironmentDetails,
|
||||
);
|
||||
|
||||
AnalyticsUtil.logEvent("DATASOURCE_SCHEMA_FETCH", {
|
||||
datasourceId: datasource?.id,
|
||||
pluginName: plugin?.name,
|
||||
environmentId: currentEnvDetails.id,
|
||||
environmentName: currentEnvDetails.name,
|
||||
errorMessage: errorMessage,
|
||||
isSuccess: isSuccess,
|
||||
source: action.payload.schemaFetchContext,
|
||||
});
|
||||
}
|
||||
|
||||
function* addAndFetchDatasourceStructureSaga(
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user