mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-31 12:39:01 +00:00
feat(ui): supported stored procedure in database (#13031)
* feat: supported stored procedure in database * minor fixes * fix sonar * optimize stored procedure code and supported deleted flag for it * feat(ui): supported code and activity tab in stored procedure (#13054) * supported code and activity tab in stored procedure * added support for task functionality * feat(ui): supported lineage tab in stored procedure (#13060) * supported code and activity tab in stored procedure * added support for task functionality * feat: supported lineage tab in stored procedure * feat(ui): supported custom property tab in stored procedure and setting page (#13063) * feat: supported custom property tab in stored procedure and setting page * added extension field * chanegs as per comments * chanegs as per comments
This commit is contained in:
parent
db592a157e
commit
fe7f2828b2
@ -0,0 +1,19 @@
|
||||
<svg viewBox="0 0 58 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="7" cy="6" r="4.5" stroke="currentColor" stroke-width="3"/>
|
||||
<path d="M11 6H41.5" stroke="currentColor" stroke-width="3"/>
|
||||
<path d="M37 1.5L40.7929 5.29289C41.1834 5.68342 41.1834 6.31658 40.7929 6.70711L37 10.5" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="51" cy="38" r="4.5" stroke="currentColor" stroke-width="3"/>
|
||||
<path d="M47 38H36" stroke="currentColor" stroke-width="3"/>
|
||||
<path d="M40.5 42L36.7071 38.2071C36.3166 37.8166 36.3166 37.1834 36.7071 36.7929L40.5 33" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="51" cy="6" r="4.5" stroke="currentColor" stroke-width="3"/>
|
||||
<path d="M51.25 28L51.25 9" stroke="currentColor" stroke-width="3"/>
|
||||
<path d="M56 25.75L52.2071 29.5429C51.8166 29.9334 51.1834 29.9334 50.7929 29.5429L47 25.75" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M2 38H32.5" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M21 27V33" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M21 44V50" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M27 27V33" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M27 44V50" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M33 27V33" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M33 44V50" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 23H36C37.1046 23 38 23.8954 38 25V28H41V25C41 22.2386 38.7614 20 36 20H5C2.23858 20 0 22.2386 0 25V51C0 53.7614 2.23858 56 5 56H36C38.7614 56 41 53.7614 41 51V49H38V51C38 52.1046 37.1046 53 36 53H5C3.89543 53 3 52.1046 3 51V25C3 23.8954 3.89543 23 5 23Z" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -21,6 +21,7 @@ import { Database } from 'generated/entity/data/database';
|
||||
import { DatabaseSchema } from 'generated/entity/data/databaseSchema';
|
||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import { Pipeline } from 'generated/entity/data/pipeline';
|
||||
import { StoredProcedure } from 'generated/entity/data/storedProcedure';
|
||||
import { Table } from 'generated/entity/data/table';
|
||||
import { Topic } from 'generated/entity/data/topic';
|
||||
import { DashboardService } from 'generated/entity/services/dashboardService';
|
||||
@ -43,6 +44,7 @@ export type DataAssetsType =
|
||||
| Container
|
||||
| Database
|
||||
| DashboardDataModel
|
||||
| StoredProcedure
|
||||
| DatabaseSchema
|
||||
| DatabaseService
|
||||
| MessagingService
|
||||
@ -90,6 +92,7 @@ export type DataAssetsHeaderProps = {
|
||||
| DataAssetMlmodel
|
||||
| DataAssetContainer
|
||||
| DataAssetDashboardDataModel
|
||||
| DataAssetStoredProcedure
|
||||
| DataAssetDatabase
|
||||
| DataAssetDatabaseSchema
|
||||
| DataAssetDatabaseService
|
||||
@ -135,6 +138,11 @@ export interface DataAssetDashboardDataModel {
|
||||
entityType: EntityType.DASHBOARD_DATA_MODEL;
|
||||
}
|
||||
|
||||
export interface DataAssetStoredProcedure {
|
||||
dataAsset: StoredProcedure;
|
||||
entityType: EntityType.STORED_PROCEDURE;
|
||||
}
|
||||
|
||||
export interface DataAssetDatabase {
|
||||
dataAsset: Database;
|
||||
entityType: EntityType.DATABASE;
|
||||
|
@ -19,12 +19,14 @@ import DashboardSummary from 'components/Explore/EntitySummaryPanel/DashboardSum
|
||||
import DataModelSummary from 'components/Explore/EntitySummaryPanel/DataModelSummary/DataModelSummary.component';
|
||||
import MlModelSummary from 'components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.component';
|
||||
import PipelineSummary from 'components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.component';
|
||||
import StoredProcedureSummary from 'components/Explore/EntitySummaryPanel/StoredProcedureSummary/StoredProcedureSummary.component';
|
||||
import TableSummary from 'components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component';
|
||||
import TopicSummary from 'components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component';
|
||||
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
|
||||
import { Container } from 'generated/entity/data/container';
|
||||
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import { StoredProcedure } from 'generated/entity/data/storedProcedure';
|
||||
import { EntityDetailUnion } from 'Models';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -33,6 +35,7 @@ import { getDataModelsByName } from 'rest/dataModelsAPI';
|
||||
import { getMlModelByFQN } from 'rest/mlModelAPI';
|
||||
import { getPipelineByFqn } from 'rest/pipelineAPI';
|
||||
import { getContainerByName } from 'rest/storageAPI';
|
||||
import { getStoredProceduresByName } from 'rest/storedProceduresAPI';
|
||||
import { getTableDetailsByFQN } from 'rest/tableAPI';
|
||||
import { getTopicByFqn } from 'rest/topicsAPI';
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
@ -130,6 +133,12 @@ const EntityInfoDrawer = ({
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityType.STORED_PROCEDURE: {
|
||||
response = await getStoredProceduresByName(encodedFqn, 'owner,tags');
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -221,6 +230,16 @@ const EntityInfoDrawer = ({
|
||||
/>
|
||||
);
|
||||
|
||||
case EntityType.STORED_PROCEDURE:
|
||||
return (
|
||||
<StoredProcedureSummary
|
||||
componentType={DRAWER_NAVIGATION_OPTIONS.lineage}
|
||||
entityDetails={entityDetail as StoredProcedure}
|
||||
isLoading={isLoading}
|
||||
tags={tags}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Col, Divider, Row, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component';
|
||||
import SchemaEditor from 'components/schema-editor/SchemaEditor';
|
||||
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
|
||||
import { CSMode } from 'enums/codemirror.enum';
|
||||
import { ExplorePageTabs } from 'enums/Explore.enum';
|
||||
import { StoredProcedureCodeObject } from 'generated/entity/data/storedProcedure';
|
||||
import { isObject } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
DRAWER_NAVIGATION_OPTIONS,
|
||||
getEntityOverview,
|
||||
} from 'utils/EntityUtils';
|
||||
import { StoredProcedureSummaryProps } from './StoredProcedureSummary.interface';
|
||||
|
||||
const StoredProcedureSummary = ({
|
||||
entityDetails,
|
||||
componentType = DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
tags,
|
||||
isLoading,
|
||||
}: StoredProcedureSummaryProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const entityInfo = useMemo(
|
||||
() => getEntityOverview(ExplorePageTabs.STORED_PROCEDURE, entityDetails),
|
||||
[entityDetails]
|
||||
);
|
||||
|
||||
return (
|
||||
<SummaryPanelSkeleton loading={isLoading}>
|
||||
<>
|
||||
<Row className="m-md" gutter={[0, 4]}>
|
||||
<Col span={24}>
|
||||
<Row gutter={[0, 4]}>
|
||||
{entityInfo.map((info) => {
|
||||
const isOwner = info.name === t('label.owner');
|
||||
|
||||
return info.visible?.includes(componentType) ? (
|
||||
<Col key={info.name} span={24}>
|
||||
<Row
|
||||
className={classNames('', {
|
||||
'p-b-md': isOwner,
|
||||
})}
|
||||
gutter={[16, 32]}>
|
||||
{!isOwner ? (
|
||||
<Col data-testid={`${info.name}-label`} span={8}>
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{info.name}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
) : null}
|
||||
<Col data-testid={`${info.name}-value`} span={16}>
|
||||
{info.isLink ? (
|
||||
<Link
|
||||
component={Typography.Link}
|
||||
target="_self"
|
||||
to={{ pathname: info.url }}>
|
||||
{info.value}
|
||||
</Link>
|
||||
) : (
|
||||
<Typography.Text
|
||||
className={classNames('text-grey-muted', {
|
||||
'text-grey-body': !isOwner,
|
||||
})}>
|
||||
{info.value}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
) : null;
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Divider className="m-y-xs" />
|
||||
|
||||
<SummaryTagsDescription
|
||||
entityDetail={entityDetails}
|
||||
tags={tags ?? []}
|
||||
/>
|
||||
<Divider className="m-y-xs" />
|
||||
|
||||
{isObject(entityDetails.storedProcedureCode) && (
|
||||
<Row className="m-md" gutter={[0, 8]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="text-base text-grey-muted"
|
||||
data-testid="column-header">
|
||||
{t('label.code')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<SchemaEditor
|
||||
editorClass="custom-code-mirror-theme custom-query-editor"
|
||||
mode={{ name: CSMode.SQL }}
|
||||
options={{
|
||||
styleActiveLine: false,
|
||||
readOnly: 'nocursor',
|
||||
}}
|
||||
value={
|
||||
(
|
||||
entityDetails.storedProcedureCode as StoredProcedureCodeObject
|
||||
).code ?? ''
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
</>
|
||||
</SummaryPanelSkeleton>
|
||||
);
|
||||
};
|
||||
|
||||
export default StoredProcedureSummary;
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { StoredProcedure } from 'generated/entity/data/storedProcedure';
|
||||
import { TagLabel } from 'generated/type/tagLabel';
|
||||
|
||||
export interface StoredProcedureSummaryProps {
|
||||
entityDetails: StoredProcedure;
|
||||
componentType?: string;
|
||||
tags?: TagLabel[];
|
||||
isLoading: boolean;
|
||||
}
|
@ -20,6 +20,7 @@ import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||
import { Database } from 'generated/entity/data/database';
|
||||
import { DatabaseSchema } from 'generated/entity/data/databaseSchema';
|
||||
import { Glossary } from 'generated/entity/data/glossary';
|
||||
import { StoredProcedure } from 'generated/entity/data/storedProcedure';
|
||||
import { QueryFilterInterface } from 'pages/explore/ExplorePage.interface';
|
||||
import { SearchIndex } from '../../enums/search.enum';
|
||||
import { Dashboard } from '../../generated/entity/data/dashboard';
|
||||
@ -120,7 +121,8 @@ export type EntityUnion =
|
||||
| Database
|
||||
| Glossary
|
||||
| Tag
|
||||
| DashboardDataModel;
|
||||
| DashboardDataModel
|
||||
| StoredProcedure;
|
||||
|
||||
export type EntityWithServices =
|
||||
| Topic
|
||||
|
@ -72,6 +72,7 @@ export enum ResourceEntity {
|
||||
QUERY = 'query',
|
||||
DASHBOARD_DATA_MODEL = 'dashboardDataModel',
|
||||
EVENT_SUBSCRIPTION = 'eventsubscription',
|
||||
STORED_PROCEDURE = 'storedProcedure',
|
||||
}
|
||||
|
||||
export interface PermissionContextType {
|
||||
|
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { Container } from 'generated/entity/data/container';
|
||||
import { StoredProcedure } from 'generated/entity/data/storedProcedure';
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
import { Dashboard } from '../../../generated/entity/data/dashboard';
|
||||
import { Mlmodel } from '../../../generated/entity/data/mlmodel';
|
||||
@ -24,7 +25,8 @@ export type EntityDetails = Table &
|
||||
Dashboard &
|
||||
Pipeline &
|
||||
Mlmodel &
|
||||
Container;
|
||||
Container &
|
||||
StoredProcedure;
|
||||
|
||||
export interface CustomPropertyProps {
|
||||
isVersionView?: boolean;
|
||||
|
@ -132,6 +132,10 @@ const DataModelDetailsPage = withSuspenseFallback(
|
||||
React.lazy(() => import('pages/DataModelPage/DataModelPage.component'))
|
||||
);
|
||||
|
||||
const StoredProcedureDetailsPage = withSuspenseFallback(
|
||||
React.lazy(() => import('pages/StoredProcedure/StoredProcedurePage'))
|
||||
);
|
||||
|
||||
const TableDetailsPageV1 = withSuspenseFallback(
|
||||
React.lazy(() => import('pages/TableDetailsPageV1/TableDetailsPageV1'))
|
||||
);
|
||||
@ -461,6 +465,23 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
|
||||
component={DataModelDetailsPage}
|
||||
path={ROUTES.DATA_MODEL_DETAILS_WITH_SUB_TAB}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
component={StoredProcedureDetailsPage}
|
||||
path={ROUTES.STORED_PROCEDURE_DETAILS}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
component={StoredProcedureDetailsPage}
|
||||
path={ROUTES.STORED_PROCEDURE_DETAILS_WITH_TAB}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
component={StoredProcedureDetailsPage}
|
||||
path={ROUTES.STORED_PROCEDURE_DETAILS_WITH_SUB_TAB}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
component={PipelineDetailsPage}
|
||||
|
@ -118,6 +118,12 @@ export const PAGE_HEADERS = {
|
||||
entity: i18n.t('label.container-plural'),
|
||||
}),
|
||||
},
|
||||
STORED_PROCEDURE_CUSTOM_ATTRIBUTES: {
|
||||
header: i18n.t('label.stored-procedure'),
|
||||
subHeader: i18n.t('message.define-custom-property-for-entity', {
|
||||
entity: i18n.t('label.label.stored-procedure'),
|
||||
}),
|
||||
},
|
||||
BOTS: {
|
||||
header: i18n.t('label.bot-plural'),
|
||||
subHeader: i18n.t('message.page-sub-header-for-bots'),
|
||||
|
@ -139,6 +139,7 @@ export const LOG_ENTITY_NAME = ':logEntityName';
|
||||
export const KPI_NAME = ':kpiName';
|
||||
export const PLACEHOLDER_ACTION = ':action';
|
||||
export const PLACEHOLDER_ROUTE_DATA_MODEL_FQN = ':dashboardDataModelFQN';
|
||||
export const PLACEHOLDER_ROUTE_STORED_PROCEDURE_FQN = ':storedProcedureFQN';
|
||||
|
||||
export const pagingObject = { after: '', before: '', total: 0 };
|
||||
|
||||
@ -264,6 +265,10 @@ export const ROUTES = {
|
||||
CONTAINER_DETAILS_WITH_TAB: `/container/${PLACEHOLDER_ROUTE_ENTITY_FQN}/${PLACEHOLDER_ROUTE_TAB}`,
|
||||
CONTAINER_DETAILS_WITH_SUB_TAB: `/container/${PLACEHOLDER_ROUTE_ENTITY_FQN}/${PLACEHOLDER_ROUTE_TAB}/${PLACEHOLDER_ROUTE_SUB_TAB}`,
|
||||
|
||||
STORED_PROCEDURE_DETAILS: `/storedProcedure/${PLACEHOLDER_ROUTE_STORED_PROCEDURE_FQN}`,
|
||||
STORED_PROCEDURE_DETAILS_WITH_TAB: `/storedProcedure/${PLACEHOLDER_ROUTE_STORED_PROCEDURE_FQN}/${PLACEHOLDER_ROUTE_TAB}`,
|
||||
STORED_PROCEDURE_DETAILS_WITH_SUB_TAB: `/storedProcedure/${PLACEHOLDER_ROUTE_STORED_PROCEDURE_FQN}/${PLACEHOLDER_ROUTE_TAB}/${PLACEHOLDER_ROUTE_SUB_TAB}`,
|
||||
|
||||
USER_LIST: '/user-list',
|
||||
CREATE_USER: '/create-user',
|
||||
CREATE_USER_WITH_BOT: `/create-user/${PLACEHOLDER_USER_BOT}`,
|
||||
@ -352,6 +357,19 @@ export const getTableDetailsPath = (tableFQN: string, columnName?: string) => {
|
||||
return `${path}${columnName ? `.${columnName}` : ''}`;
|
||||
};
|
||||
|
||||
export const getStoredProcedureDetailsPath = (
|
||||
storedProcedureFQN: string,
|
||||
columnName?: string
|
||||
) => {
|
||||
let path = ROUTES.STORED_PROCEDURE_DETAILS;
|
||||
path = path.replace(
|
||||
PLACEHOLDER_ROUTE_STORED_PROCEDURE_FQN,
|
||||
getEncodedFqn(storedProcedureFQN)
|
||||
);
|
||||
|
||||
return `${path}${columnName ? `.${columnName}` : ''}`;
|
||||
};
|
||||
|
||||
export const getTagsDetailsPath = (entityFQN: string, columnName?: string) => {
|
||||
let path = ROUTES.TAG_DETAILS;
|
||||
const classification = getPartialNameFromFQN(entityFQN, ['service']);
|
||||
@ -660,6 +678,32 @@ export const getContainerDetailPath = (
|
||||
return path;
|
||||
};
|
||||
|
||||
export const getStoredProcedureDetailPath = (
|
||||
storedProcedureFQN: string,
|
||||
tab?: string,
|
||||
subTab = 'all'
|
||||
) => {
|
||||
let path = tab
|
||||
? ROUTES.STORED_PROCEDURE_DETAILS_WITH_TAB
|
||||
: ROUTES.STORED_PROCEDURE_DETAILS;
|
||||
|
||||
if (tab === EntityTabs.ACTIVITY_FEED) {
|
||||
path = ROUTES.STORED_PROCEDURE_DETAILS_WITH_SUB_TAB;
|
||||
path = path.replace(PLACEHOLDER_ROUTE_SUB_TAB, subTab);
|
||||
}
|
||||
|
||||
if (tab) {
|
||||
path = path.replace(PLACEHOLDER_ROUTE_TAB, tab);
|
||||
}
|
||||
|
||||
path = path.replace(
|
||||
PLACEHOLDER_ROUTE_STORED_PROCEDURE_FQN,
|
||||
getEncodedFqn(storedProcedureFQN)
|
||||
);
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
export const getGlossaryTermDetailsPath = (
|
||||
glossaryFQN: string,
|
||||
tab?: string
|
||||
@ -784,6 +828,7 @@ export const ENTITY_PATH: Record<string, string> = {
|
||||
containers: 'container',
|
||||
tags: 'tag',
|
||||
glossaries: 'glossary',
|
||||
storedprocedure: 'storedProcedure',
|
||||
};
|
||||
|
||||
export const VALIDATION_MESSAGES = {
|
||||
|
@ -26,4 +26,5 @@ export enum ExplorePageTabs {
|
||||
GLOSSARY = 'glossaries',
|
||||
TAG = 'tags',
|
||||
DASHBOARD_DATA_MODEL = 'dashboardDataModel',
|
||||
STORED_PROCEDURE = 'storedProcedure',
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ export enum EntityType {
|
||||
USER_NAME = 'username',
|
||||
CHART = 'chart',
|
||||
SAMPLE_DATA = 'sampleData',
|
||||
STORED_PROCEDURE = 'storedProcedure',
|
||||
}
|
||||
|
||||
export enum AssetsType {
|
||||
@ -161,6 +162,8 @@ export enum EntityTabs {
|
||||
INGESTIONS = 'ingestions',
|
||||
CONNECTION = 'connection',
|
||||
SQL = 'sql',
|
||||
STORED_PROCEDURE = 'stored_procedure',
|
||||
CODE = 'code',
|
||||
}
|
||||
|
||||
export enum EntityAction {
|
||||
|
@ -134,6 +134,7 @@
|
||||
"closed-task-plural": "Closed Tasks",
|
||||
"closed-this-task-lowercase": "closed this task",
|
||||
"cloud-config-source": "Cloud Config Source",
|
||||
"code": "Code",
|
||||
"collapse-all": "Collapse All",
|
||||
"column": "Column",
|
||||
"column-entity": "Column {{entity}}",
|
||||
@ -877,6 +878,7 @@
|
||||
"stopped": "Stopped",
|
||||
"storage": "Storage",
|
||||
"storage-plural": "Storages",
|
||||
"stored-procedure": "Stored Procedure",
|
||||
"sub-team-plural": "Sub Teams",
|
||||
"submit": "Submit",
|
||||
"success": "Success",
|
||||
|
@ -134,6 +134,7 @@
|
||||
"closed-task-plural": "Tareas cerradas",
|
||||
"closed-this-task-lowercase": "cerró esta tarea",
|
||||
"cloud-config-source": "Fuente de configuración en el cloud",
|
||||
"code": "Code",
|
||||
"collapse-all": "Contraer todo",
|
||||
"column": "Columna",
|
||||
"column-entity": "Columna {{entity}}",
|
||||
@ -877,6 +878,7 @@
|
||||
"stopped": "Stopped",
|
||||
"storage": "Storage",
|
||||
"storage-plural": "Storages",
|
||||
"stored-procedure": "Stored Procedure",
|
||||
"sub-team-plural": "Sub Equipos",
|
||||
"submit": "Enviar",
|
||||
"success": "Éxito",
|
||||
|
@ -134,6 +134,7 @@
|
||||
"closed-task-plural": "Tâches Clôturées",
|
||||
"closed-this-task-lowercase": "fermer cette tâche",
|
||||
"cloud-config-source": "Source de Config Cloud",
|
||||
"code": "Code",
|
||||
"collapse-all": "Tout Réduire",
|
||||
"column": "Colonne",
|
||||
"column-entity": "{{entity}} Colonnes",
|
||||
@ -877,6 +878,7 @@
|
||||
"stopped": "Arrêté",
|
||||
"storage": "Stockage",
|
||||
"storage-plural": "Stockages",
|
||||
"stored-procedure": "Stored Procedure",
|
||||
"sub-team-plural": "Sous Equipes",
|
||||
"submit": "Envoyer",
|
||||
"success": "Succès",
|
||||
|
@ -134,6 +134,7 @@
|
||||
"closed-task-plural": "終了したタスク",
|
||||
"closed-this-task-lowercase": "このタスクを終了する",
|
||||
"cloud-config-source": "Cloud Config Source",
|
||||
"code": "Code",
|
||||
"collapse-all": "全て折り畳む",
|
||||
"column": "カラム",
|
||||
"column-entity": "カラム {{entity}}",
|
||||
@ -877,6 +878,7 @@
|
||||
"stopped": "Stopped",
|
||||
"storage": "Storage",
|
||||
"storage-plural": "Storages",
|
||||
"stored-procedure": "Stored Procedure",
|
||||
"sub-team-plural": "サブチーム",
|
||||
"submit": "Submit",
|
||||
"success": "成功",
|
||||
|
@ -134,6 +134,7 @@
|
||||
"closed-task-plural": "Tarefas fechadas",
|
||||
"closed-this-task-lowercase": "esta tarefa foi fechada",
|
||||
"cloud-config-source": "Origem de configurações de Cloud",
|
||||
"code": "Code",
|
||||
"collapse-all": "Recolher todas",
|
||||
"column": "Coluna",
|
||||
"column-entity": "Coluna {{entity}}",
|
||||
@ -877,6 +878,7 @@
|
||||
"stopped": "Parado",
|
||||
"storage": "Storage",
|
||||
"storage-plural": "Storages",
|
||||
"stored-procedure": "Stored Procedure",
|
||||
"sub-team-plural": "Sub-equipes",
|
||||
"submit": "Enviar",
|
||||
"success": "Sucesso",
|
||||
|
@ -134,6 +134,7 @@
|
||||
"closed-task-plural": "Закрытые задачи",
|
||||
"closed-this-task-lowercase": "закрыть задачу",
|
||||
"cloud-config-source": "Источник облачной конфигурации",
|
||||
"code": "Code",
|
||||
"collapse-all": "Свернуть все",
|
||||
"column": "Столбец",
|
||||
"column-entity": "Столбец {{entity}}",
|
||||
@ -877,6 +878,7 @@
|
||||
"stopped": "Остановлено",
|
||||
"storage": "Хранилище",
|
||||
"storage-plural": "Хранилища",
|
||||
"stored-procedure": "Stored Procedure",
|
||||
"sub-team-plural": "Подгруппы",
|
||||
"submit": "Подтвердить",
|
||||
"success": "Успешно",
|
||||
|
@ -134,6 +134,7 @@
|
||||
"closed-task-plural": "已关闭任务",
|
||||
"closed-this-task-lowercase": "关闭此任务",
|
||||
"cloud-config-source": "云配置源",
|
||||
"code": "Code",
|
||||
"collapse-all": "全部折叠",
|
||||
"column": "列",
|
||||
"column-entity": "列{{entity}}",
|
||||
@ -877,6 +878,7 @@
|
||||
"stopped": "已停止",
|
||||
"storage": "存储",
|
||||
"storage-plural": "存储",
|
||||
"stored-procedure": "Stored Procedure",
|
||||
"sub-team-plural": "子团队",
|
||||
"submit": "提交",
|
||||
"success": "成功",
|
||||
|
@ -139,6 +139,8 @@ const CustomEntityDetailV1 = () => {
|
||||
|
||||
case ENTITY_PATH.containers:
|
||||
return PAGE_HEADERS.CONTAINER_CUSTOM_ATTRIBUTES;
|
||||
case ENTITY_PATH.storedprocedure:
|
||||
return PAGE_HEADERS.STORED_PROCEDURE_CUSTOM_ATTRIBUTES;
|
||||
default:
|
||||
return PAGE_HEADERS.TABLES_CUSTOM_ATTRIBUTES;
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ import { LabelType, State, TagLabel, TagSource } from 'generated/type/tagLabel';
|
||||
import { isEmpty, isString, isUndefined, toString } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import { EntityTags, PagingResponse } from 'Models';
|
||||
import StoredProcedureTab from 'pages/StoredProcedure/StoredProcedureTab';
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
useCallback,
|
||||
@ -50,12 +51,14 @@ import React, {
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { ListDataModelParams } from 'rest/dashboardAPI';
|
||||
import {
|
||||
getDatabaseSchemaDetailsByFQN,
|
||||
patchDatabaseSchemaDetails,
|
||||
restoreDatabaseSchema,
|
||||
} from 'rest/databaseAPI';
|
||||
import { getFeedCount, postThread } from 'rest/feedsAPI';
|
||||
import { getStoredProceduresList } from 'rest/storedProceduresAPI';
|
||||
import { getTableList, TableListParams } from 'rest/tableAPI';
|
||||
import { getEntityMissingError } from 'utils/CommonUtils';
|
||||
import { getDatabaseSchemaVersionPath } from 'utils/RouterUtils';
|
||||
@ -64,6 +67,7 @@ import { default as appState } from '../../AppState';
|
||||
import {
|
||||
getDatabaseSchemaDetailsPath,
|
||||
INITIAL_PAGING_VALUE,
|
||||
pagingObject,
|
||||
} from '../../constants/constants';
|
||||
import { EntityTabs, EntityType } from '../../enums/entity.enum';
|
||||
import { CreateThread } from '../../generated/api/feed/createThread';
|
||||
@ -73,6 +77,7 @@ import { getEntityFeedLink, getEntityName } from '../../utils/EntityUtils';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||
import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils';
|
||||
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||
import { StoredProcedureData } from './DatabaseSchemaPage.interface';
|
||||
import SchemaTablesTab from './SchemaTablesTab';
|
||||
|
||||
const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
@ -109,11 +114,26 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
const [currentTablesPage, setCurrentTablesPage] =
|
||||
useState<number>(INITIAL_PAGING_VALUE);
|
||||
|
||||
const [storedProcedure, setStoredProcedure] = useState<StoredProcedureData>({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
deleted: false,
|
||||
paging: pagingObject,
|
||||
currentPage: INITIAL_PAGING_VALUE,
|
||||
});
|
||||
|
||||
const handleShowDeletedTables = (value: boolean) => {
|
||||
setShowDeletedTables(value);
|
||||
setCurrentTablesPage(INITIAL_PAGING_VALUE);
|
||||
};
|
||||
|
||||
const handleShowDeletedStoredProcedure = (value: boolean) => {
|
||||
setStoredProcedure((prev) => ({
|
||||
...prev,
|
||||
currentPage: INITIAL_PAGING_VALUE,
|
||||
deleted: value,
|
||||
}));
|
||||
};
|
||||
const { version: currentVersion } = useMemo(
|
||||
() => databaseSchema,
|
||||
[databaseSchema]
|
||||
@ -197,6 +217,28 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
}
|
||||
}, [databaseSchemaFQN]);
|
||||
|
||||
const fetchStoreProcedureDetails = useCallback(
|
||||
async (params?: ListDataModelParams) => {
|
||||
try {
|
||||
setStoredProcedure((prev) => ({ ...prev, isLoading: true }));
|
||||
const { data, paging } = await getStoredProceduresList({
|
||||
service: getDecodedFqn(databaseSchemaFQN),
|
||||
fields: 'owner,tags,followers',
|
||||
include: storedProcedure.deleted
|
||||
? Include.Deleted
|
||||
: Include.NonDeleted,
|
||||
...params,
|
||||
});
|
||||
setStoredProcedure((prev) => ({ ...prev, data, paging }));
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setStoredProcedure((prev) => ({ ...prev, isLoading: false }));
|
||||
}
|
||||
},
|
||||
[databaseSchemaFQN, storedProcedure.deleted]
|
||||
);
|
||||
|
||||
const getSchemaTables = useCallback(
|
||||
async (params?: TableListParams) => {
|
||||
setTableDataLoading(true);
|
||||
@ -460,6 +502,25 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
[]
|
||||
);
|
||||
|
||||
const storedProcedurePagingHandler = useCallback(
|
||||
async (cursorType: string | number, activePage?: number) => {
|
||||
const pagingString = {
|
||||
[cursorType]:
|
||||
storedProcedure.paging[
|
||||
cursorType as keyof typeof storedProcedure.paging
|
||||
],
|
||||
};
|
||||
|
||||
await fetchStoreProcedureDetails(pagingString);
|
||||
|
||||
setStoredProcedure((prev) => ({
|
||||
...prev,
|
||||
currentPage: activePage ?? INITIAL_PAGING_VALUE,
|
||||
}));
|
||||
},
|
||||
[storedProcedure.paging]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDatabaseSchemaPermission();
|
||||
}, [databaseSchemaFQN]);
|
||||
@ -467,6 +528,7 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
useEffect(() => {
|
||||
if (viewDatabaseSchemaPermission) {
|
||||
fetchDatabaseSchemaDetails();
|
||||
fetchStoreProcedureDetails({ limit: 0 });
|
||||
getEntityFeedCount();
|
||||
}
|
||||
}, [viewDatabaseSchemaPermission, databaseSchemaFQN]);
|
||||
@ -580,6 +642,25 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
</ActivityFeedProvider>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<TabsLabel
|
||||
count={storedProcedure.paging.total}
|
||||
id={EntityTabs.STORED_PROCEDURE}
|
||||
isActive={activeTab === EntityTabs.STORED_PROCEDURE}
|
||||
name={t('label.stored-procedure')}
|
||||
/>
|
||||
),
|
||||
key: EntityTabs.STORED_PROCEDURE,
|
||||
children: (
|
||||
<StoredProcedureTab
|
||||
fetchStoredProcedure={fetchStoreProcedureDetails}
|
||||
pagingHandler={storedProcedurePagingHandler}
|
||||
storedProcedure={storedProcedure}
|
||||
onShowDeletedStoreProcedureChange={handleShowDeletedStoredProcedure}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
if (isPermissionsLoading) {
|
||||
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Paging } from 'generated/type/paging';
|
||||
import { ServicePageData } from 'pages/ServiceDetailsPage/ServiceDetailsPage';
|
||||
|
||||
export interface StoredProcedureData {
|
||||
isLoading: boolean;
|
||||
deleted: boolean;
|
||||
data: ServicePageData[];
|
||||
paging: Paging;
|
||||
currentPage: number;
|
||||
}
|
@ -54,6 +54,7 @@ import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||
import { Database } from 'generated/entity/data/database';
|
||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import { Pipeline } from 'generated/entity/data/pipeline';
|
||||
import { StoredProcedure } from 'generated/entity/data/storedProcedure';
|
||||
import { Topic } from 'generated/entity/data/topic';
|
||||
import { DashboardConnection } from 'generated/entity/services/dashboardService';
|
||||
import { DatabaseService } from 'generated/entity/services/databaseService';
|
||||
@ -123,7 +124,8 @@ export type ServicePageData =
|
||||
| Mlmodel
|
||||
| Pipeline
|
||||
| Container
|
||||
| DashboardDataModel;
|
||||
| DashboardDataModel
|
||||
| StoredProcedure;
|
||||
|
||||
const ServiceDetailsPage: FunctionComponent = () => {
|
||||
const { t } = useTranslation();
|
||||
|
@ -0,0 +1,688 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Card, Col, Row, Space, Tabs } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useActivityFeedProvider } from 'components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
|
||||
import { ActivityFeedTab } from 'components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.component';
|
||||
import ActivityThreadPanel from 'components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
|
||||
import { CustomPropertyTable } from 'components/common/CustomPropertyTable/CustomPropertyTable';
|
||||
import { CustomPropertyProps } from 'components/common/CustomPropertyTable/CustomPropertyTable.interface';
|
||||
import DescriptionV1 from 'components/common/description/DescriptionV1';
|
||||
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import PageLayoutV1 from 'components/containers/PageLayoutV1';
|
||||
import { DataAssetsHeader } from 'components/DataAssets/DataAssetsHeader/DataAssetsHeader.component';
|
||||
import EntityLineageComponent from 'components/Entity/EntityLineage/EntityLineage.component';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.interface';
|
||||
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
|
||||
import {
|
||||
OperationPermission,
|
||||
ResourceEntity,
|
||||
} from 'components/PermissionProvider/PermissionProvider.interface';
|
||||
import { withActivityFeed } from 'components/router/withActivityFeed';
|
||||
import SchemaEditor from 'components/schema-editor/SchemaEditor';
|
||||
import { SourceType } from 'components/searched-data/SearchedData.interface';
|
||||
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
|
||||
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
|
||||
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
|
||||
import {
|
||||
getStoredProcedureDetailPath,
|
||||
getVersionPath,
|
||||
} from 'constants/constants';
|
||||
import { CSMode } from 'enums/codemirror.enum';
|
||||
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
|
||||
import { EntityTabs, EntityType } from 'enums/entity.enum';
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { CreateThread, ThreadType } from 'generated/api/feed/createThread';
|
||||
import {
|
||||
StoredProcedure,
|
||||
StoredProcedureCodeObject,
|
||||
} from 'generated/entity/data/storedProcedure';
|
||||
import { LabelType, State, TagLabel, TagSource } from 'generated/type/tagLabel';
|
||||
import { EntityTags } from 'Models';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { postThread } from 'rest/feedsAPI';
|
||||
import {
|
||||
addStoredProceduresFollower,
|
||||
getStoredProceduresDetailsByFQN,
|
||||
patchStoredProceduresDetails,
|
||||
removeStoredProceduresFollower,
|
||||
restoreStoredProcedures,
|
||||
} from 'rest/storedProceduresAPI';
|
||||
import {
|
||||
getCurrentUserId,
|
||||
getFeedCounts,
|
||||
sortTagsCaseInsensitive,
|
||||
} from 'utils/CommonUtils';
|
||||
import { getEntityName } from 'utils/EntityUtils';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
|
||||
import { STORED_PROCEDURE_DEFAULT_FIELDS } from 'utils/StoredProceduresUtils';
|
||||
import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils';
|
||||
import { showErrorToast, showSuccessToast } from 'utils/ToastUtils';
|
||||
|
||||
const StoredProcedurePage = () => {
|
||||
const { t } = useTranslation();
|
||||
const USER_ID = getCurrentUserId();
|
||||
const history = useHistory();
|
||||
const { storedProcedureFQN, tab: activeTab = EntityTabs.CODE } =
|
||||
useParams<{ storedProcedureFQN: string; tab: string }>();
|
||||
|
||||
const { getEntityPermissionByFqn } = usePermissionProvider();
|
||||
const { postFeed, deleteFeed, updateFeed } = useActivityFeedProvider();
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [storedProcedure, setStoredProcedure] = useState<StoredProcedure>();
|
||||
const [storedProcedurePermissions, setStoredProcedurePermissions] =
|
||||
useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION);
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
|
||||
const [feedCount, setFeedCount] = useState<number>(0);
|
||||
const [threadLink, setThreadLink] = useState<string>('');
|
||||
|
||||
const [threadType, setThreadType] = useState<ThreadType>(
|
||||
ThreadType.Conversation
|
||||
);
|
||||
|
||||
const {
|
||||
id: storedProcedureId = '',
|
||||
followers,
|
||||
owner,
|
||||
tags,
|
||||
tier,
|
||||
version,
|
||||
code,
|
||||
description,
|
||||
deleted,
|
||||
entityName,
|
||||
entityFQN,
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
...storedProcedure,
|
||||
tier: getTierTags(storedProcedure?.tags ?? []),
|
||||
tags: getTagsWithoutTier(storedProcedure?.tags ?? []),
|
||||
entityName: getEntityName(storedProcedure),
|
||||
entityFQN: storedProcedure?.fullyQualifiedName ?? '',
|
||||
code:
|
||||
(storedProcedure?.storedProcedureCode as StoredProcedureCodeObject)
|
||||
?.code ?? '',
|
||||
};
|
||||
}, [storedProcedure]);
|
||||
|
||||
const { isFollowing } = useMemo(() => {
|
||||
return {
|
||||
isFollowing: followers?.some(({ id }) => id === USER_ID),
|
||||
};
|
||||
}, [followers, USER_ID]);
|
||||
|
||||
const fetchResourcePermission = useCallback(async () => {
|
||||
try {
|
||||
const permission = await getEntityPermissionByFqn(
|
||||
ResourceEntity.STORED_PROCEDURE,
|
||||
storedProcedureFQN
|
||||
);
|
||||
|
||||
setStoredProcedurePermissions(permission);
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
t('server.fetch-entity-permissions-error', {
|
||||
entity: t('label.resource-permission-lowercase'),
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [getEntityPermissionByFqn]);
|
||||
|
||||
const getEntityFeedCount = () => {
|
||||
getFeedCounts(
|
||||
EntityType.STORED_PROCEDURE,
|
||||
storedProcedureFQN,
|
||||
setFeedCount
|
||||
);
|
||||
};
|
||||
|
||||
const fetchStoredProcedureDetails = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await getStoredProceduresDetailsByFQN(
|
||||
storedProcedureFQN,
|
||||
STORED_PROCEDURE_DEFAULT_FIELDS
|
||||
);
|
||||
|
||||
setStoredProcedure(response);
|
||||
} catch (error) {
|
||||
// Error here
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const versionHandler = useCallback(() => {
|
||||
version &&
|
||||
history.push(
|
||||
getVersionPath(
|
||||
EntityType.STORED_PROCEDURE,
|
||||
storedProcedureFQN,
|
||||
version + ''
|
||||
)
|
||||
);
|
||||
}, [storedProcedureFQN, version]);
|
||||
|
||||
const saveUpdatedStoredProceduresData = useCallback(
|
||||
(updatedData: StoredProcedure) => {
|
||||
if (!storedProcedure) {
|
||||
return updatedData;
|
||||
}
|
||||
const jsonPatch = compare(storedProcedure ?? '', updatedData);
|
||||
|
||||
return patchStoredProceduresDetails(storedProcedureId ?? '', jsonPatch);
|
||||
},
|
||||
[storedProcedure]
|
||||
);
|
||||
|
||||
const handleStoreProcedureUpdate = async (
|
||||
updatedData: StoredProcedure,
|
||||
key: keyof StoredProcedure
|
||||
) => {
|
||||
try {
|
||||
const res = await saveUpdatedStoredProceduresData(updatedData);
|
||||
|
||||
setStoredProcedure((previous) => {
|
||||
if (!previous) {
|
||||
return;
|
||||
}
|
||||
if (key === 'tags') {
|
||||
return {
|
||||
...previous,
|
||||
version: res.version,
|
||||
[key]: sortTagsCaseInsensitive(res.tags ?? []),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...previous,
|
||||
version: res.version,
|
||||
[key]: res[key],
|
||||
};
|
||||
});
|
||||
|
||||
getEntityFeedCount();
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
const followEntity = useCallback(async () => {
|
||||
try {
|
||||
const res = await addStoredProceduresFollower(storedProcedureId, USER_ID);
|
||||
const { newValue } = res.changeDescription.fieldsAdded[0];
|
||||
const newFollowers = [...(followers ?? []), ...newValue];
|
||||
setStoredProcedure((prev) => {
|
||||
if (!prev) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return { ...prev, followers: newFollowers };
|
||||
});
|
||||
getEntityFeedCount();
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
t('server.entity-follow-error', {
|
||||
entity: getEntityName(storedProcedure),
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [USER_ID, followers, storedProcedure, storedProcedureId]);
|
||||
|
||||
const unFollowEntity = useCallback(async () => {
|
||||
try {
|
||||
const res = await removeStoredProceduresFollower(
|
||||
storedProcedureId,
|
||||
USER_ID
|
||||
);
|
||||
const { oldValue } = res.changeDescription.fieldsDeleted[0];
|
||||
setStoredProcedure((pre) => {
|
||||
if (!pre) {
|
||||
return pre;
|
||||
}
|
||||
|
||||
return {
|
||||
...pre,
|
||||
followers: pre.followers?.filter(
|
||||
(follower) => follower.id !== oldValue[0].id
|
||||
),
|
||||
};
|
||||
});
|
||||
getEntityFeedCount();
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
t('server.entity-unfollow-error', {
|
||||
entity: getEntityName(storedProcedure),
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [USER_ID, storedProcedureId]);
|
||||
|
||||
const handleDisplayNameUpdate = async (data: EntityName) => {
|
||||
if (!storedProcedure) {
|
||||
return;
|
||||
}
|
||||
const updatedData = { ...storedProcedure, displayName: data.displayName };
|
||||
await handleStoreProcedureUpdate(updatedData, 'displayName');
|
||||
};
|
||||
|
||||
const handleFollow = useCallback(async () => {
|
||||
isFollowing ? await unFollowEntity() : await followEntity();
|
||||
}, [isFollowing]);
|
||||
|
||||
const handleUpdateOwner = useCallback(
|
||||
async (newOwner?: StoredProcedure['owner']) => {
|
||||
if (!storedProcedure) {
|
||||
return;
|
||||
}
|
||||
const updatedEntityDetails = {
|
||||
...storedProcedure,
|
||||
owner: newOwner
|
||||
? {
|
||||
...owner,
|
||||
...newOwner,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
await handleStoreProcedureUpdate(updatedEntityDetails, 'owner');
|
||||
},
|
||||
[owner, storedProcedure]
|
||||
);
|
||||
|
||||
const handleToggleDelete = () => {
|
||||
setStoredProcedure((prev) => {
|
||||
if (!prev) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return { ...prev, deleted: !prev?.deleted };
|
||||
});
|
||||
};
|
||||
|
||||
const handleRestoreStoredProcedures = async () => {
|
||||
try {
|
||||
await restoreStoredProcedures(storedProcedureId);
|
||||
showSuccessToast(
|
||||
t('message.restore-entities-success', {
|
||||
entity: t('label.stored-procedure'),
|
||||
}),
|
||||
2000
|
||||
);
|
||||
handleToggleDelete();
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
t('message.restore-entities-error', {
|
||||
entity: t('label.stored-procedure'),
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onTierUpdate = useCallback(
|
||||
async (newTier?: string) => {
|
||||
if (storedProcedure) {
|
||||
const tierTag: StoredProcedure['tags'] = newTier
|
||||
? [
|
||||
...getTagsWithoutTier(tags ?? []),
|
||||
{
|
||||
tagFQN: newTier,
|
||||
labelType: LabelType.Manual,
|
||||
state: State.Confirmed,
|
||||
},
|
||||
]
|
||||
: getTagsWithoutTier(tags ?? []);
|
||||
const updatedDetails = {
|
||||
...storedProcedure,
|
||||
tags: tierTag,
|
||||
};
|
||||
|
||||
await handleStoreProcedureUpdate(updatedDetails, 'tags');
|
||||
}
|
||||
},
|
||||
[storedProcedure, tags]
|
||||
);
|
||||
|
||||
const afterDeleteAction = useCallback(
|
||||
(isSoftDelete?: boolean) =>
|
||||
isSoftDelete ? handleToggleDelete() : history.push('/'),
|
||||
[]
|
||||
);
|
||||
|
||||
const handleTabChange = (activeKey: EntityTabs) => {
|
||||
if (activeKey !== activeTab) {
|
||||
history.push(getStoredProcedureDetailPath(storedProcedureFQN, activeKey));
|
||||
}
|
||||
};
|
||||
|
||||
const onDescriptionEdit = (): void => {
|
||||
setIsEdit(true);
|
||||
};
|
||||
const onCancel = () => {
|
||||
setIsEdit(false);
|
||||
};
|
||||
|
||||
const onDescriptionUpdate = async (updatedHTML: string) => {
|
||||
if (description !== updatedHTML && storedProcedure) {
|
||||
const updatedData = {
|
||||
...storedProcedure,
|
||||
description: updatedHTML,
|
||||
};
|
||||
try {
|
||||
await handleStoreProcedureUpdate(updatedData, 'description');
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setIsEdit(false);
|
||||
}
|
||||
} else {
|
||||
setIsEdit(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onThreadLinkSelect = (link: string, threadType?: ThreadType) => {
|
||||
setThreadLink(link);
|
||||
if (threadType) {
|
||||
setThreadType(threadType);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTagSelection = async (selectedTags: EntityTags[]) => {
|
||||
const updatedTags: TagLabel[] | undefined = selectedTags?.map((tag) => ({
|
||||
source: tag.source,
|
||||
tagFQN: tag.tagFQN,
|
||||
labelType: LabelType.Manual,
|
||||
state: State.Confirmed,
|
||||
}));
|
||||
|
||||
if (updatedTags && storedProcedure) {
|
||||
const updatedTags = [...(tier ? [tier] : []), ...selectedTags];
|
||||
const updatedData = { ...storedProcedure, tags: updatedTags };
|
||||
await handleStoreProcedureUpdate(updatedData, 'tags');
|
||||
}
|
||||
};
|
||||
|
||||
const createThread = async (data: CreateThread) => {
|
||||
try {
|
||||
await postThread(data);
|
||||
getEntityFeedCount();
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
t('server.create-entity-error', {
|
||||
entity: t('label.conversation'),
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onThreadPanelClose = () => {
|
||||
setThreadLink('');
|
||||
};
|
||||
|
||||
const onExtensionUpdate = async (updatedData: StoredProcedure) => {
|
||||
await handleStoreProcedureUpdate(updatedData, 'extension');
|
||||
};
|
||||
|
||||
const tabs = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: (
|
||||
<TabsLabel
|
||||
data-testid={EntityTabs.CODE}
|
||||
id={EntityTabs.CODE}
|
||||
name={t('label.code')}
|
||||
/>
|
||||
),
|
||||
key: EntityTabs.CODE,
|
||||
children: (
|
||||
<Row gutter={[0, 16]} wrap={false}>
|
||||
<Col
|
||||
className="p-t-sm m-l-lg tab-content-height p-r-lg"
|
||||
flex="auto">
|
||||
<div className="d-flex flex-col gap-4">
|
||||
<DescriptionV1
|
||||
description={description}
|
||||
entityFqn={storedProcedureFQN}
|
||||
entityName={entityName}
|
||||
entityType={EntityType.STORED_PROCEDURE}
|
||||
hasEditAccess={
|
||||
storedProcedurePermissions.EditAll ||
|
||||
storedProcedurePermissions.EditDescription
|
||||
}
|
||||
isEdit={isEdit}
|
||||
isReadOnly={deleted}
|
||||
owner={owner}
|
||||
onCancel={onCancel}
|
||||
onDescriptionEdit={onDescriptionEdit}
|
||||
onDescriptionUpdate={onDescriptionUpdate}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
|
||||
<Card className="m-b-md">
|
||||
<SchemaEditor
|
||||
editorClass="custom-code-mirror-theme full-screen-editor-height"
|
||||
mode={{ name: CSMode.SQL }}
|
||||
options={{
|
||||
styleActiveLine: false,
|
||||
readOnly: 'nocursor',
|
||||
}}
|
||||
value={code}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</Col>
|
||||
<Col
|
||||
className="entity-tag-right-panel-container"
|
||||
data-testid="entity-right-panel"
|
||||
flex="320px">
|
||||
<Space className="w-full" direction="vertical" size="large">
|
||||
<TagsContainerV2
|
||||
displayType={DisplayType.READ_MORE}
|
||||
entityFqn={storedProcedureFQN}
|
||||
entityType={EntityType.STORED_PROCEDURE}
|
||||
permission={
|
||||
(storedProcedurePermissions.EditAll ||
|
||||
storedProcedurePermissions.EditTags) &&
|
||||
!deleted
|
||||
}
|
||||
selectedTags={tags}
|
||||
tagType={TagSource.Classification}
|
||||
onSelectionChange={handleTagSelection}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
|
||||
<TagsContainerV2
|
||||
displayType={DisplayType.READ_MORE}
|
||||
entityFqn={storedProcedureFQN}
|
||||
entityType={EntityType.STORED_PROCEDURE}
|
||||
permission={
|
||||
(storedProcedurePermissions.EditAll ||
|
||||
storedProcedurePermissions.EditTags) &&
|
||||
!deleted
|
||||
}
|
||||
selectedTags={tags}
|
||||
tagType={TagSource.Glossary}
|
||||
onSelectionChange={handleTagSelection}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<TabsLabel
|
||||
count={feedCount}
|
||||
id={EntityTabs.ACTIVITY_FEED}
|
||||
isActive={activeTab === EntityTabs.ACTIVITY_FEED}
|
||||
name={t('label.activity-feed-and-task-plural')}
|
||||
/>
|
||||
),
|
||||
key: EntityTabs.ACTIVITY_FEED,
|
||||
children: (
|
||||
<ActivityFeedTab
|
||||
entityType={EntityType.STORED_PROCEDURE}
|
||||
fqn={entityFQN}
|
||||
onFeedUpdate={getEntityFeedCount}
|
||||
onUpdateEntityDetails={fetchStoredProcedureDetails}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: <TabsLabel id={EntityTabs.LINEAGE} name={t('label.lineage')} />,
|
||||
key: EntityTabs.LINEAGE,
|
||||
children: (
|
||||
<EntityLineageComponent
|
||||
deleted={deleted}
|
||||
entity={storedProcedure as SourceType}
|
||||
entityType={EntityType.STORED_PROCEDURE}
|
||||
hasEditAccess={
|
||||
storedProcedurePermissions.EditAll ||
|
||||
storedProcedurePermissions.EditLineage
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<TabsLabel
|
||||
id={EntityTabs.CUSTOM_PROPERTIES}
|
||||
name={t('label.custom-property-plural')}
|
||||
/>
|
||||
),
|
||||
key: EntityTabs.CUSTOM_PROPERTIES,
|
||||
children: (
|
||||
<CustomPropertyTable
|
||||
entityDetails={
|
||||
storedProcedure as CustomPropertyProps['entityDetails']
|
||||
}
|
||||
entityType={EntityType.STORED_PROCEDURE}
|
||||
handleExtensionUpdate={onExtensionUpdate}
|
||||
hasEditAccess={
|
||||
storedProcedurePermissions.EditAll ||
|
||||
storedProcedurePermissions.EditCustomFields
|
||||
}
|
||||
hasPermission={storedProcedurePermissions.ViewAll}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
[
|
||||
code,
|
||||
tags,
|
||||
isEdit,
|
||||
deleted,
|
||||
feedCount,
|
||||
activeTab,
|
||||
entityFQN,
|
||||
entityName,
|
||||
description,
|
||||
storedProcedure,
|
||||
storedProcedureFQN,
|
||||
storedProcedurePermissions,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (storedProcedureFQN) {
|
||||
fetchResourcePermission();
|
||||
}
|
||||
}, [storedProcedureFQN]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
storedProcedurePermissions.ViewAll ||
|
||||
storedProcedurePermissions.ViewBasic
|
||||
) {
|
||||
fetchStoredProcedureDetails();
|
||||
getEntityFeedCount();
|
||||
}
|
||||
}, [storedProcedureFQN, storedProcedurePermissions]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
if (
|
||||
!(
|
||||
storedProcedurePermissions.ViewAll || storedProcedurePermissions.ViewBasic
|
||||
)
|
||||
) {
|
||||
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
|
||||
}
|
||||
|
||||
if (!storedProcedure) {
|
||||
return <ErrorPlaceHolder />;
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayoutV1 className="bg-white" pageTitle={t('label.stored-procedure')}>
|
||||
<Row gutter={[0, 12]}>
|
||||
<Col className="p-x-lg" data-testid="entity-page-header" span={24}>
|
||||
<DataAssetsHeader
|
||||
afterDeleteAction={afterDeleteAction}
|
||||
dataAsset={storedProcedure}
|
||||
entityType={EntityType.STORED_PROCEDURE}
|
||||
permissions={storedProcedurePermissions}
|
||||
onDisplayNameUpdate={handleDisplayNameUpdate}
|
||||
onFollowClick={handleFollow}
|
||||
onOwnerUpdate={handleUpdateOwner}
|
||||
onRestoreDataAsset={handleRestoreStoredProcedures}
|
||||
onTierUpdate={onTierUpdate}
|
||||
onVersionClick={versionHandler}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
{/* Entity Tabs */}
|
||||
<Col span={24}>
|
||||
<Tabs
|
||||
destroyInactiveTabPane
|
||||
activeKey={activeTab ?? EntityTabs.CODE}
|
||||
className="entity-details-page-tabs"
|
||||
data-testid="tabs"
|
||||
items={tabs}
|
||||
onChange={(activeKey: string) =>
|
||||
handleTabChange(activeKey as EntityTabs)
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
{threadLink ? (
|
||||
<ActivityThreadPanel
|
||||
createThread={createThread}
|
||||
deletePostHandler={deleteFeed}
|
||||
open={Boolean(threadLink)}
|
||||
postFeedHandler={postFeed}
|
||||
threadLink={threadLink}
|
||||
threadType={threadType}
|
||||
updateThreadHandler={updateFeed}
|
||||
onCancel={onThreadPanelClose}
|
||||
/>
|
||||
) : null}
|
||||
</Row>
|
||||
</PageLayoutV1>
|
||||
);
|
||||
};
|
||||
|
||||
export default withActivityFeed(StoredProcedurePage);
|
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Col, Row, Switch, Table, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import NextPrevious from 'components/common/next-previous/NextPrevious';
|
||||
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import { PAGE_SIZE } from 'constants/constants';
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { ServicePageData } from 'pages/ServiceDetailsPage/ServiceDetailsPage';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getEntityName } from 'utils/EntityUtils';
|
||||
import { getEncodedFqn } from 'utils/StringsUtils';
|
||||
import { getEntityLink } from 'utils/TableUtils';
|
||||
import { StoredProcedureTabProps } from './storedProcedure.interface';
|
||||
|
||||
const StoredProcedureTab = ({
|
||||
storedProcedure,
|
||||
pagingHandler,
|
||||
fetchStoredProcedure,
|
||||
onShowDeletedStoreProcedureChange,
|
||||
}: StoredProcedureTabProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { data, isLoading, deleted, paging, currentPage } = storedProcedure;
|
||||
|
||||
const tableColumn: ColumnsType<ServicePageData> = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: t('label.name'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 350,
|
||||
render: (_, record) => (
|
||||
<Link
|
||||
to={getEntityLink(
|
||||
EntityType.STORED_PROCEDURE,
|
||||
getEncodedFqn(record.fullyQualifiedName ?? '')
|
||||
)}>
|
||||
{getEntityName(record)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('label.description'),
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
render: (text: string) =>
|
||||
isEmpty(text) ? (
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{t('label.no-description')}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
<RichTextEditorPreviewer markdown={text} />
|
||||
),
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchStoredProcedure();
|
||||
}, [deleted]);
|
||||
|
||||
return (
|
||||
<Row className="p-lg" data-testid="stored-procedure-table" gutter={[0, 16]}>
|
||||
<Col className="d-flex justify-end" span={24}>
|
||||
<Switch
|
||||
checked={deleted}
|
||||
data-testid="show-deleted-stored-procedure"
|
||||
onClick={onShowDeletedStoreProcedureChange}
|
||||
/>
|
||||
<Typography.Text className="m-l-xs">
|
||||
{t('label.deleted')}
|
||||
</Typography.Text>{' '}
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Table
|
||||
bordered
|
||||
columns={tableColumn}
|
||||
data-testid="data-models-table"
|
||||
dataSource={data}
|
||||
loading={{
|
||||
spinning: isLoading,
|
||||
indicator: <Loader size="small" />,
|
||||
}}
|
||||
locale={{
|
||||
emptyText: <ErrorPlaceHolder className="m-y-md" />,
|
||||
}}
|
||||
pagination={false}
|
||||
rowKey="id"
|
||||
size="small"
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col span={24}>
|
||||
{paging && paging.total > PAGE_SIZE && (
|
||||
<NextPrevious
|
||||
currentPage={currentPage}
|
||||
pageSize={PAGE_SIZE}
|
||||
paging={paging}
|
||||
pagingHandler={pagingHandler}
|
||||
totalCount={paging.total}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default StoredProcedureTab;
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { StoredProcedureData } from 'pages/DatabaseSchemaPage/DatabaseSchemaPage.interface';
|
||||
|
||||
export interface StoredProcedureTabProps {
|
||||
storedProcedure: StoredProcedureData;
|
||||
fetchStoredProcedure: () => void;
|
||||
pagingHandler: (cursorValue: string | number, activePage?: number) => void;
|
||||
onShowDeletedStoreProcedureChange: (value: boolean) => void;
|
||||
}
|
@ -15,6 +15,7 @@ import { Container } from 'generated/entity/data/container';
|
||||
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||
import { Database } from 'generated/entity/data/database';
|
||||
import { DatabaseSchema } from 'generated/entity/data/databaseSchema';
|
||||
import { StoredProcedure } from 'generated/entity/data/storedProcedure';
|
||||
import { Dashboard } from '../../generated/entity/data/dashboard';
|
||||
import { Mlmodel } from '../../generated/entity/data/mlmodel';
|
||||
import { Pipeline } from '../../generated/entity/data/pipeline';
|
||||
@ -28,6 +29,7 @@ export type EntityData =
|
||||
| Pipeline
|
||||
| Mlmodel
|
||||
| Container
|
||||
| StoredProcedure
|
||||
| Database
|
||||
| DatabaseSchema
|
||||
| DashboardDataModel;
|
||||
|
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { StoredProcedure } from 'generated/entity/data/storedProcedure';
|
||||
import { EntityHistory } from 'generated/type/entityHistory';
|
||||
import { EntityReference } from 'generated/type/entityReference';
|
||||
import { Include } from 'generated/type/include';
|
||||
import { PagingResponse, RestoreRequestType } from 'Models';
|
||||
import { ServicePageData } from 'pages/ServiceDetailsPage/ServiceDetailsPage';
|
||||
import { getURLWithQueryFields } from 'utils/APIUtils';
|
||||
import { ListDataModelParams } from './dashboardAPI';
|
||||
import APIClient from './index';
|
||||
|
||||
const URL = '/storedProcedures';
|
||||
|
||||
const configOptionsForPatch = {
|
||||
headers: { 'Content-type': 'application/json-patch+json' },
|
||||
};
|
||||
|
||||
const configOptions = {
|
||||
headers: { 'Content-type': 'application/json' },
|
||||
};
|
||||
|
||||
export const getStoredProceduresList = async (params?: ListDataModelParams) => {
|
||||
const response = await APIClient.get<PagingResponse<ServicePageData[]>>(URL, {
|
||||
params,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getStoredProceduresDetails = async (
|
||||
id: string,
|
||||
arrQueryFields: string | string[]
|
||||
) => {
|
||||
const url = getURLWithQueryFields(`${URL}/${id}`, arrQueryFields);
|
||||
|
||||
const response = await APIClient.get<StoredProcedure>(url);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getStoredProceduresByName = async (
|
||||
name: string,
|
||||
fields: string | string[],
|
||||
include: Include = Include.NonDeleted
|
||||
) => {
|
||||
const response = await APIClient.get<StoredProcedure>(
|
||||
`${URL}/name/${name}?fields=${fields}`,
|
||||
{
|
||||
params: {
|
||||
include,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getStoredProceduresDetailsByFQN = async (
|
||||
storedProceduresName: string,
|
||||
arrQueryFields?: string | string[],
|
||||
include = Include.All
|
||||
) => {
|
||||
const url = `${getURLWithQueryFields(
|
||||
`${URL}/name/${storedProceduresName}`,
|
||||
arrQueryFields,
|
||||
`include=${include}`
|
||||
)}`;
|
||||
|
||||
const response = await APIClient.get<StoredProcedure>(url);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const patchStoredProceduresDetails = async (
|
||||
id: string,
|
||||
data: Operation[]
|
||||
) => {
|
||||
const response = await APIClient.patch<
|
||||
Operation[],
|
||||
AxiosResponse<StoredProcedure>
|
||||
>(`${URL}/${id}`, data, configOptionsForPatch);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const addStoredProceduresFollower = async (
|
||||
id: string,
|
||||
userId: string
|
||||
) => {
|
||||
const response = await APIClient.put<
|
||||
string,
|
||||
AxiosResponse<{
|
||||
changeDescription: { fieldsAdded: { newValue: EntityReference[] }[] };
|
||||
}>
|
||||
>(`${URL}/${id}/followers`, userId, configOptions);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const removeStoredProceduresFollower = async (
|
||||
id: string,
|
||||
userId: string
|
||||
) => {
|
||||
const response = await APIClient.delete<
|
||||
string,
|
||||
AxiosResponse<{
|
||||
changeDescription: { fieldsDeleted: { oldValue: EntityReference[] }[] };
|
||||
}>
|
||||
>(`${URL}/${id}/followers/${userId}`, configOptions);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getStoredProceduresVersionsList = async (id: string) => {
|
||||
const url = `${URL}/${id}/versions`;
|
||||
|
||||
const response = await APIClient.get<EntityHistory>(url);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getStoredProceduresVersion = async (
|
||||
id: string,
|
||||
version: string
|
||||
) => {
|
||||
const url = `${URL}/${id}/versions/${version}`;
|
||||
|
||||
const response = await APIClient.get<StoredProcedure>(url);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const restoreStoredProcedures = async (id: string) => {
|
||||
const response = await APIClient.put<
|
||||
RestoreRequestType,
|
||||
AxiosResponse<StoredProcedure>
|
||||
>(`${URL}/restore`, { id });
|
||||
|
||||
return response.data;
|
||||
};
|
@ -55,6 +55,7 @@ import {
|
||||
getDataModelDetailsPath,
|
||||
getMlModelDetailsPath,
|
||||
getPipelineDetailsPath,
|
||||
getStoredProcedureDetailPath,
|
||||
getTableTabPath,
|
||||
getTeamAndUserDetailsPath,
|
||||
getTopicDetailsPath,
|
||||
@ -837,6 +838,11 @@ export const getEntityDetailLink = (
|
||||
case EntityType.USER_NAME:
|
||||
path = getUserPath(fqn, tab, subTab);
|
||||
|
||||
break;
|
||||
|
||||
case EntityType.STORED_PROCEDURE:
|
||||
path = getStoredProcedureDetailPath(fqn, tab, subTab);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,10 @@ import {
|
||||
DataAssetHeaderInfo,
|
||||
DataAssetsHeaderProps,
|
||||
} from 'components/DataAssets/DataAssetsHeader/DataAssetsHeader.interface';
|
||||
import { getDashboardDetailsPath } from 'constants/constants';
|
||||
import {
|
||||
getDashboardDetailsPath,
|
||||
NO_DATA_PLACEHOLDER,
|
||||
} from 'constants/constants';
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import { Container } from 'generated/entity/data/container';
|
||||
import { Dashboard } from 'generated/entity/data/dashboard';
|
||||
@ -29,6 +32,10 @@ import { Database } from 'generated/entity/data/database';
|
||||
import { DatabaseSchema } from 'generated/entity/data/databaseSchema';
|
||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import { Pipeline } from 'generated/entity/data/pipeline';
|
||||
import {
|
||||
StoredProcedure,
|
||||
StoredProcedureCodeObject,
|
||||
} from 'generated/entity/data/storedProcedure';
|
||||
import { Table } from 'generated/entity/data/table';
|
||||
import { Topic } from 'generated/entity/data/topic';
|
||||
import { DashboardService } from 'generated/entity/services/dashboardService';
|
||||
@ -39,7 +46,7 @@ import { MlmodelService } from 'generated/entity/services/mlmodelService';
|
||||
import { PipelineService } from 'generated/entity/services/pipelineService';
|
||||
import { StorageService } from 'generated/entity/services/storageService';
|
||||
import { t } from 'i18next';
|
||||
import { isUndefined } from 'lodash';
|
||||
import { isObject, isUndefined } from 'lodash';
|
||||
import React from 'react';
|
||||
import {
|
||||
getBreadcrumbForContainer,
|
||||
@ -324,6 +331,28 @@ export const getDataAssetsHeaderInfo = (
|
||||
|
||||
break;
|
||||
|
||||
case EntityType.STORED_PROCEDURE:
|
||||
const storedProcedureDetails = dataAsset as StoredProcedure;
|
||||
|
||||
returnData.extraInfo = (
|
||||
<>
|
||||
{isObject(storedProcedureDetails.storedProcedureCode) && (
|
||||
<ExtraInfoLabel
|
||||
label={t('label.language')}
|
||||
value={
|
||||
(
|
||||
storedProcedureDetails.storedProcedureCode as StoredProcedureCodeObject
|
||||
).language ?? NO_DATA_PLACEHOLDER
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
returnData.breadcrumbs = getBreadcrumbForTable(dataAsset as Table);
|
||||
|
||||
break;
|
||||
|
||||
case EntityType.TABLE:
|
||||
default:
|
||||
const tableDetails = dataAsset as Table;
|
||||
|
@ -1327,6 +1327,8 @@ export const getParamByEntityType = (entityType: EntityType): string => {
|
||||
return 'databaseSchemaFQN';
|
||||
case EntityType.DASHBOARD_DATA_MODEL:
|
||||
return 'dashboardDataModelFQN';
|
||||
case EntityType.STORED_PROCEDURE:
|
||||
return 'storedProcedureFQN';
|
||||
default:
|
||||
return 'entityFQN';
|
||||
}
|
||||
|
@ -38,10 +38,22 @@ import { Database } from 'generated/entity/data/database';
|
||||
import { DatabaseSchema } from 'generated/entity/data/databaseSchema';
|
||||
import { GlossaryTerm } from 'generated/entity/data/glossaryTerm';
|
||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import {
|
||||
StoredProcedure,
|
||||
StoredProcedureCodeObject,
|
||||
} from 'generated/entity/data/storedProcedure';
|
||||
import { Topic } from 'generated/entity/data/topic';
|
||||
import i18next from 'i18next';
|
||||
import { EntityFieldThreadCount } from 'interface/feed.interface';
|
||||
import { get, isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash';
|
||||
import {
|
||||
get,
|
||||
isEmpty,
|
||||
isNil,
|
||||
isObject,
|
||||
isUndefined,
|
||||
lowerCase,
|
||||
startCase,
|
||||
} from 'lodash';
|
||||
import { Bucket, EntityDetailUnion } from 'Models';
|
||||
import React, { Fragment } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
@ -129,6 +141,7 @@ export const getEntityTags = (
|
||||
case EntityType.DASHBOARD:
|
||||
case EntityType.TOPIC:
|
||||
case EntityType.MLMODEL:
|
||||
case EntityType.STORED_PROCEDURE:
|
||||
case EntityType.DASHBOARD_DATA_MODEL: {
|
||||
return entityDetail.tags || [];
|
||||
}
|
||||
@ -558,6 +571,88 @@ export const getEntityOverview = (
|
||||
return overview;
|
||||
}
|
||||
|
||||
case ExplorePageTabs.STORED_PROCEDURE: {
|
||||
const { fullyQualifiedName, owner, tags, storedProcedureCode } =
|
||||
entityDetail as StoredProcedure;
|
||||
const [service, database, schema] = getPartialNameFromTableFQN(
|
||||
fullyQualifiedName ?? '',
|
||||
[FqnPart.Service, FqnPart.Database, FqnPart.Schema],
|
||||
FQN_SEPARATOR_CHAR
|
||||
).split(FQN_SEPARATOR_CHAR);
|
||||
|
||||
const tier = getTierFromTableTags(tags || []);
|
||||
|
||||
const overview = [
|
||||
{
|
||||
name: i18next.t('label.owner'),
|
||||
value:
|
||||
getOwnerNameWithProfilePic(owner) ||
|
||||
i18next.t('label.no-entity', {
|
||||
entity: i18next.t('label.owner'),
|
||||
}),
|
||||
url: getOwnerValue(owner as EntityReference),
|
||||
isLink: owner?.name ? true : false,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.service'),
|
||||
value: service || NO_DATA,
|
||||
url: getServiceDetailsPath(
|
||||
service,
|
||||
ServiceCategory.DATABASE_SERVICES
|
||||
),
|
||||
isLink: true,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.database'),
|
||||
value: database || NO_DATA,
|
||||
url: getDatabaseDetailsPath(
|
||||
getPartialNameFromTableFQN(
|
||||
fullyQualifiedName ?? '',
|
||||
[FqnPart.Service, FqnPart.Database],
|
||||
FQN_SEPARATOR_CHAR
|
||||
)
|
||||
),
|
||||
isLink: true,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.schema'),
|
||||
value: schema || NO_DATA,
|
||||
url: getDatabaseSchemaDetailsPath(
|
||||
getPartialNameFromTableFQN(
|
||||
fullyQualifiedName ?? '',
|
||||
[FqnPart.Service, FqnPart.Database, FqnPart.Schema],
|
||||
FQN_SEPARATOR_CHAR
|
||||
)
|
||||
),
|
||||
isLink: true,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.tier'),
|
||||
value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : NO_DATA,
|
||||
isLink: false,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
...(isObject(storedProcedureCode)
|
||||
? [
|
||||
{
|
||||
name: i18next.t('label.language'),
|
||||
value:
|
||||
(storedProcedureCode as StoredProcedureCodeObject).language ??
|
||||
NO_DATA,
|
||||
isLink: false,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
return overview;
|
||||
}
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import { ReactComponent as TopicIcon } from '../../src/assets/svg/topic-grey.svg
|
||||
import { ReactComponent as UsersIcon } from '../../src/assets/svg/user.svg';
|
||||
import { ReactComponent as CustomLogoIcon } from '../assets/svg/ic-custom-logo.svg';
|
||||
import { ReactComponent as StorageIcon } from '../assets/svg/ic-storage.svg';
|
||||
import { ReactComponent as StoredProcedureIcon } from '../assets/svg/ic-stored-procedure.svg';
|
||||
import { userPermissions } from '../utils/PermissionsUtils';
|
||||
|
||||
export interface MenuListItem {
|
||||
@ -252,6 +253,12 @@ export const getGlobalSettingsMenuWithPermission = (
|
||||
key: 'customAttributes.containers',
|
||||
icon: <StorageIcon className="side-panel-icons" />,
|
||||
},
|
||||
{
|
||||
label: i18next.t('label.stored-procedure'),
|
||||
isProtected: Boolean(isAdminUser),
|
||||
key: 'customAttributes.storedProcedure',
|
||||
icon: <StoredProcedureIcon className="side-panel-icons" />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { TabSpecificField } from 'enums/entity.enum';
|
||||
|
||||
export const STORED_PROCEDURE_DEFAULT_FIELDS = `${TabSpecificField.OWNER}, ${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.EXTENSION}`;
|
@ -53,6 +53,7 @@ import {
|
||||
getMlModelPath,
|
||||
getPipelineDetailsPath,
|
||||
getServiceDetailsPath,
|
||||
getStoredProcedureDetailsPath,
|
||||
getTableDetailsPath,
|
||||
getTableTabPath,
|
||||
getTagsDetailsPath,
|
||||
@ -249,6 +250,9 @@ export const getEntityLink = (
|
||||
case EntityType.DASHBOARD_DATA_MODEL:
|
||||
return getDataModelDetailsPath(getDecodedFqn(fullyQualifiedName));
|
||||
|
||||
case EntityType.STORED_PROCEDURE:
|
||||
return getStoredProcedureDetailsPath(getDecodedFqn(fullyQualifiedName));
|
||||
|
||||
case EntityType.TEST_CASE:
|
||||
return `${getTableTabPath(
|
||||
getTableFQNFromColumnFQN(fullyQualifiedName),
|
||||
|
@ -40,6 +40,7 @@ import { getUserSuggestions } from 'rest/miscAPI';
|
||||
import { getMlModelByFQN } from 'rest/mlModelAPI';
|
||||
import { getPipelineByFqn } from 'rest/pipelineAPI';
|
||||
import { getContainerByFQN } from 'rest/storageAPI';
|
||||
import { getStoredProceduresDetailsByFQN } from 'rest/storedProceduresAPI';
|
||||
import { getTableDetailsByFQN } from 'rest/tableAPI';
|
||||
import { getTopicByFqn } from 'rest/topicsAPI';
|
||||
import {
|
||||
@ -74,6 +75,7 @@ import { getEntityFQN, getEntityType } from './FeedUtils';
|
||||
import { defaultFields as MlModelFields } from './MlModelDetailsUtils';
|
||||
import { defaultFields as PipelineFields } from './PipelineDetailsUtils';
|
||||
import { serviceTypeLogo } from './ServiceUtils';
|
||||
import { STORED_PROCEDURE_DEFAULT_FIELDS } from './StoredProceduresUtils';
|
||||
import { getEntityLink } from './TableUtils';
|
||||
import { showErrorToast } from './ToastUtils';
|
||||
|
||||
@ -269,6 +271,7 @@ export const TASK_ENTITIES = [
|
||||
EntityType.CONTAINER,
|
||||
EntityType.DATABASE_SCHEMA,
|
||||
EntityType.DASHBOARD_DATA_MODEL,
|
||||
EntityType.STORED_PROCEDURE,
|
||||
];
|
||||
|
||||
export const getBreadCrumbList = (
|
||||
@ -353,6 +356,15 @@ export const getBreadCrumbList = (
|
||||
return [service(ServiceCategory.STORAGE_SERVICES), activeEntity];
|
||||
}
|
||||
|
||||
case EntityType.STORED_PROCEDURE: {
|
||||
return [
|
||||
service(ServiceCategory.DATABASE_SERVICES),
|
||||
database,
|
||||
databaseSchema,
|
||||
activeEntity,
|
||||
];
|
||||
}
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
@ -447,6 +459,18 @@ export const fetchEntityDetail = (
|
||||
|
||||
break;
|
||||
|
||||
case EntityType.STORED_PROCEDURE:
|
||||
getStoredProceduresDetailsByFQN(
|
||||
entityFQN,
|
||||
STORED_PROCEDURE_DEFAULT_FIELDS
|
||||
)
|
||||
.then((res) => {
|
||||
setEntityData(res);
|
||||
})
|
||||
.catch((err: AxiosError) => showErrorToast(err));
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user