From a17735c891f6ee6099d49cb717bac7c7ca65d3b1 Mon Sep 17 00:00:00 2001 From: John Joyce Date: Fri, 18 Oct 2024 17:49:39 -0700 Subject: [PATCH 1/4] final touches From c5542f7404ca59cd9e2a4e9c863b1e65e3d2656c Mon Sep 17 00:00:00 2001 From: John Joyce Date: Mon, 21 Oct 2024 12:31:48 -0700 Subject: [PATCH 2/4] Adding incidents to ML From 62ebfbe22f6d0ab8b5295d22aaff52da71818485 Mon Sep 17 00:00:00 2001 From: John Joyce Date: Mon, 21 Oct 2024 17:44:41 -0700 Subject: [PATCH 3/4] Adding incidents to ML Models and ML Features --- .../datahub/graphql/GmsGraphQLEngine.java | 3 ++- .../resolvers/config/AppConfigResolver.java | 8 ++++++ .../src/graphql/incident.graphql | 16 ++++++++++++ .../authorization/PoliciesConfig.java | 25 ++++++++++++++++--- 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java index 5b265b6714452..eae1ad1ab1b2c 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java @@ -3109,7 +3109,8 @@ private void configureIncidentResolvers(final RuntimeWiring.Builder builder) { // Add incidents attribute to all entities that support it final List entitiesWithIncidents = - ImmutableList.of("Dataset", "DataJob", "DataFlow", "Dashboard", "Chart"); + ImmutableList.of( + "Dataset", "DataJob", "DataFlow", "Dashboard", "Chart", "MLModel", "MLFeature"); for (String entity : entitiesWithIncidents) { builder.type( entity, diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/config/AppConfigResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/config/AppConfigResolver.java index 259d05c631557..23e19c09632ff 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/config/AppConfigResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/config/AppConfigResolver.java @@ -276,6 +276,14 @@ private EntityType mapResourceTypeToEntityType(final String resourceType) { .getResourceType() .equals(resourceType)) { return EntityType.BUSINESS_ATTRIBUTE; + } else if (com.linkedin.metadata.authorization.PoliciesConfig.ML_MODEL_PRIVILEGES + .getResourceType() + .equals(resourceType)) { + return EntityType.MLMODEL; + } else if (com.linkedin.metadata.authorization.PoliciesConfig.ML_FEATURE_PRIVILEGES + .getResourceType() + .equals(resourceType)) { + return EntityType.MLFEATURE; } else { return null; } diff --git a/datahub-web-react/src/graphql/incident.graphql b/datahub-web-react/src/graphql/incident.graphql index c6708ab894e93..45fe5990c7fd1 100644 --- a/datahub-web-react/src/graphql/incident.graphql +++ b/datahub-web-react/src/graphql/incident.graphql @@ -73,5 +73,21 @@ query getEntityIncidents($urn: String!, $start: Int!, $count: Int!, $state: Inci ...incidentsFields } } + ... on MLFeature { + incidents(start: $start, count: $count, state: $state) { + ...incidentsFields + } + privileges { + canEditIncidents + } + } + ... on MLModel { + incidents(start: $start, count: $count, state: $state) { + ...incidentsFields + } + privileges { + canEditIncidents + } + } } } diff --git a/metadata-utils/src/main/java/com/linkedin/metadata/authorization/PoliciesConfig.java b/metadata-utils/src/main/java/com/linkedin/metadata/authorization/PoliciesConfig.java index bc676e94ecd4f..0efb5fadce861 100644 --- a/metadata-utils/src/main/java/com/linkedin/metadata/authorization/PoliciesConfig.java +++ b/metadata-utils/src/main/java/com/linkedin/metadata/authorization/PoliciesConfig.java @@ -726,10 +726,25 @@ public class PoliciesConfig { // ERModelRelationship Privileges public static final ResourcePrivileges ER_MODEL_RELATIONSHIP_PRIVILEGES = ResourcePrivileges.of( - "erModelRelationship", - "ERModelRelationship", - "update privileges for ermodelrelations", + "ER Model Relationships", + "", + "Privileges for ER Model Relationships", COMMON_ENTITY_PRIVILEGES); + + public static final ResourcePrivileges ML_FEATURE_PRIVILEGES = + ResourcePrivileges.of( + "mlFeature", + "ML Feature", + "ML Features ingested to DataHub.", + COMMON_ENTITY_PRIVILEGES); + + public static final ResourcePrivileges ML_MODEL_PRIVILEGES = + ResourcePrivileges.of( + "mlModel", + "ML Model", + "ML Models ingested to DataHub.", + COMMON_ENTITY_PRIVILEGES); + public static final ResourcePrivileges BUSINESS_ATTRIBUTE_PRIVILEGES = ResourcePrivileges.of( "businessAttribute", @@ -760,7 +775,9 @@ public class PoliciesConfig { DATA_PRODUCT_PRIVILEGES, ER_MODEL_RELATIONSHIP_PRIVILEGES, BUSINESS_ATTRIBUTE_PRIVILEGES, - STRUCTURED_PROPERTIES_PRIVILEGES); + STRUCTURED_PROPERTIES_PRIVILEGES, + ML_FEATURE_PRIVILEGES, + ML_MODEL_PRIVILEGES); // Merge all entity specific resource privileges to create a superset of all resource privileges public static final ResourcePrivileges ALL_RESOURCE_PRIVILEGES = From 225b02d7775a75aaf46f178543851f0744fa6499 Mon Sep 17 00:00:00 2001 From: John Joyce Date: Mon, 21 Oct 2024 19:02:52 -0700 Subject: [PATCH 4/4] Adding incidents to ML models and ML features --- .../entity/EntityPrivilegesResolver.java | 2 + .../resolvers/incident/IncidentUtils.java | 26 +++++++++++++ .../src/main/resources/auth.graphql | 5 +++ .../src/main/resources/incident.graphql | 38 +++++++++++++++++++ .../app/entity/mlFeature/MLFeatureEntity.tsx | 9 +++++ .../src/app/entity/mlModel/MLModelEntity.tsx | 9 +++++ .../shared/tabs/Incident/IncidentTab.tsx | 30 ++++++++++----- .../Incident/components/AddIncidentModal.tsx | 1 + .../src/graphql/fragments.graphql | 1 + .../src/graphql/mlFeature.graphql | 3 ++ datahub-web-react/src/graphql/mlModel.graphql | 3 ++ .../com/linkedin/incident/IncidentInfo.pdl | 2 +- .../src/main/resources/entity-registry.yml | 2 + 13 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/incident/IncidentUtils.java diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/entity/EntityPrivilegesResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/entity/EntityPrivilegesResolver.java index 67ab9bb287814..1bc7f543d9023 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/entity/EntityPrivilegesResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/entity/EntityPrivilegesResolver.java @@ -12,6 +12,7 @@ import com.linkedin.datahub.graphql.concurrency.GraphQLConcurrencyUtils; import com.linkedin.datahub.graphql.generated.Entity; import com.linkedin.datahub.graphql.generated.EntityPrivileges; +import com.linkedin.datahub.graphql.resolvers.incident.IncidentUtils; import com.linkedin.datahub.graphql.resolvers.mutate.util.EmbedUtils; import com.linkedin.datahub.graphql.resolvers.mutate.util.GlossaryUtils; import com.linkedin.entity.client.EntityClient; @@ -141,5 +142,6 @@ private void addCommonPrivileges( @Nonnull EntityPrivileges result, @Nonnull Urn urn, @Nonnull QueryContext context) { result.setCanEditLineage(canEditEntityLineage(urn, context)); result.setCanEditProperties(AuthorizationUtils.canEditProperties(urn, context)); + result.setCanEditIncidents(IncidentUtils.isAuthorizedToEditIncidentForResource(urn, context)); } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/incident/IncidentUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/incident/IncidentUtils.java new file mode 100644 index 0000000000000..2f6d1278b190e --- /dev/null +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/incident/IncidentUtils.java @@ -0,0 +1,26 @@ +package com.linkedin.datahub.graphql.resolvers.incident; + +import com.datahub.authorization.ConjunctivePrivilegeGroup; +import com.datahub.authorization.DisjunctivePrivilegeGroup; +import com.google.common.collect.ImmutableList; +import com.linkedin.common.urn.Urn; +import com.linkedin.datahub.graphql.QueryContext; +import com.linkedin.datahub.graphql.authorization.AuthorizationUtils; +import com.linkedin.metadata.authorization.PoliciesConfig; + +public class IncidentUtils { + public static boolean isAuthorizedToEditIncidentForResource( + final Urn resourceUrn, final QueryContext context) { + final DisjunctivePrivilegeGroup orPrivilegeGroups = + new DisjunctivePrivilegeGroup( + ImmutableList.of( + AuthorizationUtils.ALL_PRIVILEGES_GROUP, + new ConjunctivePrivilegeGroup( + ImmutableList.of(PoliciesConfig.EDIT_ENTITY_INCIDENTS_PRIVILEGE.getType())))); + + return AuthorizationUtils.isAuthorized( + context, resourceUrn.getEntityType(), resourceUrn.toString(), orPrivilegeGroups); + } + + private IncidentUtils() {} +} diff --git a/datahub-graphql-core/src/main/resources/auth.graphql b/datahub-graphql-core/src/main/resources/auth.graphql index 5ce26067fe58e..7fa7e4d597a00 100644 --- a/datahub-graphql-core/src/main/resources/auth.graphql +++ b/datahub-graphql-core/src/main/resources/auth.graphql @@ -286,6 +286,11 @@ type EntityPrivileges { Whether or not a user can update the properties for the entity (e.g. dataset) """ canEditProperties: Boolean + + """ + Whether or not a user can update incidents for the entity + """ + canEditIncidents: Boolean } """ diff --git a/datahub-graphql-core/src/main/resources/incident.graphql b/datahub-graphql-core/src/main/resources/incident.graphql index c2938543ed949..976e4127b0610 100644 --- a/datahub-graphql-core/src/main/resources/incident.graphql +++ b/datahub-graphql-core/src/main/resources/incident.graphql @@ -377,4 +377,42 @@ extend type Chart { Optional start offset, defaults to 20. """ count: Int): EntityIncidentsResult +} + +extend type MLModel { + """ + Incidents associated with the ML Model + """ + incidents( + """ + Optional incident state to filter by, defaults to any state. + """ + state: IncidentState, + """ + Optional start offset, defaults to 0. + """ + start: Int, + """ + Optional start offset, defaults to 20. + """ + count: Int): EntityIncidentsResult +} + +extend type MLFeature { + """ + Incidents associated with the ML Feature + """ + incidents( + """ + Optional incident state to filter by, defaults to any state. + """ + state: IncidentState, + """ + Optional start offset, defaults to 0. + """ + start: Int, + """ + Optional start offset, defaults to 20. + """ + count: Int): EntityIncidentsResult } \ No newline at end of file diff --git a/datahub-web-react/src/app/entity/mlFeature/MLFeatureEntity.tsx b/datahub-web-react/src/app/entity/mlFeature/MLFeatureEntity.tsx index 2f2786b1c0d96..2956262ccc484 100644 --- a/datahub-web-react/src/app/entity/mlFeature/MLFeatureEntity.tsx +++ b/datahub-web-react/src/app/entity/mlFeature/MLFeatureEntity.tsx @@ -18,6 +18,7 @@ import { EntityMenuItems } from '../shared/EntityDropdown/EntityDropdown'; import DataProductSection from '../shared/containers/profile/sidebar/DataProduct/DataProductSection'; import { getDataProduct } from '../shared/utils'; import { PropertiesTab } from '../shared/tabs/Properties/PropertiesTab'; +import { IncidentTab } from '../shared/tabs/Incident/IncidentTab'; /** * Definition of the DataHub MLFeature entity. @@ -92,6 +93,14 @@ export class MLFeatureEntity implements Entity { name: 'Properties', component: PropertiesTab, }, + { + name: 'Incidents', + component: IncidentTab, + getDynamicName: (_, mlFeature) => { + const activeIncidentCount = mlFeature?.mlFeature?.activeIncidents.total; + return `Incidents${(activeIncidentCount && ` (${activeIncidentCount})`) || ''}`; + }, + }, ]} sidebarSections={this.getSidebarSections()} /> diff --git a/datahub-web-react/src/app/entity/mlModel/MLModelEntity.tsx b/datahub-web-react/src/app/entity/mlModel/MLModelEntity.tsx index d4d0b37da9ec9..982feb3fd67cc 100644 --- a/datahub-web-react/src/app/entity/mlModel/MLModelEntity.tsx +++ b/datahub-web-react/src/app/entity/mlModel/MLModelEntity.tsx @@ -18,6 +18,7 @@ import { DocumentationTab } from '../shared/tabs/Documentation/DocumentationTab' import MlModelFeaturesTab from './profile/MlModelFeaturesTab'; import { EntityMenuItems } from '../shared/EntityDropdown/EntityDropdown'; import DataProductSection from '../shared/containers/profile/sidebar/DataProduct/DataProductSection'; +import { IncidentTab } from '../shared/tabs/Incident/IncidentTab'; /** * Definition of the DataHub MlModel entity. @@ -120,6 +121,14 @@ export class MLModelEntity implements Entity { name: 'Properties', component: PropertiesTab, }, + { + name: 'Incidents', + component: IncidentTab, + getDynamicName: (_, mlModel) => { + const activeIncidentCount = mlModel?.mlModel?.activeIncidents.total; + return `Incidents${(activeIncidentCount && ` (${activeIncidentCount})`) || ''}`; + }, + }, ]} sidebarSections={this.getSidebarSections()} /> diff --git a/datahub-web-react/src/app/entity/shared/tabs/Incident/IncidentTab.tsx b/datahub-web-react/src/app/entity/shared/tabs/Incident/IncidentTab.tsx index f2b03a11ffb55..4920711e523de 100644 --- a/datahub-web-react/src/app/entity/shared/tabs/Incident/IncidentTab.tsx +++ b/datahub-web-react/src/app/entity/shared/tabs/Incident/IncidentTab.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import styled from 'styled-components'; -import { Button, Empty, List, Select, Typography } from 'antd'; +import { Button, Empty, List, Select, Tooltip, Typography } from 'antd'; import { PlusOutlined } from '@ant-design/icons'; import { useGetEntityIncidentsQuery } from '../../../../../graphql/incident.generated'; import TabToolbar from '../../components/styled/TabToolbar'; @@ -78,18 +78,30 @@ export const IncidentTab = () => { title: incident?.title, })); + const canEditIncidents = (data?.entity as any)?.privileges?.canEditIncidents || false; + return ( <>
- - setIsRaiseIncidentModalVisible(false)} - /> + + + setIsRaiseIncidentModalVisible(false)} + /> + diff --git a/datahub-web-react/src/app/entity/shared/tabs/Incident/components/AddIncidentModal.tsx b/datahub-web-react/src/app/entity/shared/tabs/Incident/components/AddIncidentModal.tsx index fda8c9cda2d0d..9f5047b3c73fe 100644 --- a/datahub-web-react/src/app/entity/shared/tabs/Incident/components/AddIncidentModal.tsx +++ b/datahub-web-react/src/app/entity/shared/tabs/Incident/components/AddIncidentModal.tsx @@ -80,6 +80,7 @@ export const AddIncidentModal = ({ open, onClose, refetch }: AddIncidentProps) = time: Date.now(), actor: user?.urn, }, + tags: null, }; message.success({ content: 'Incident Added', duration: 2 }); analytics.event({ diff --git a/datahub-web-react/src/graphql/fragments.graphql b/datahub-web-react/src/graphql/fragments.graphql index 7ce4082c42f61..cbc64b32bb5bf 100644 --- a/datahub-web-react/src/graphql/fragments.graphql +++ b/datahub-web-react/src/graphql/fragments.graphql @@ -1432,6 +1432,7 @@ fragment entityPrivileges on EntityPrivileges { canManageEntity canManageChildren canEditProperties + canEditIncidents } fragment businessAttribute on BusinessAttributeAssociation { diff --git a/datahub-web-react/src/graphql/mlFeature.graphql b/datahub-web-react/src/graphql/mlFeature.graphql index 2ed5ecfb37fda..f0585218ecd77 100644 --- a/datahub-web-react/src/graphql/mlFeature.graphql +++ b/datahub-web-react/src/graphql/mlFeature.graphql @@ -20,5 +20,8 @@ query getMLFeature($urn: String!) { forms { ...formsFields } + activeIncidents: incidents(start: 0, count: 1, state: ACTIVE) { + total + } } } diff --git a/datahub-web-react/src/graphql/mlModel.graphql b/datahub-web-react/src/graphql/mlModel.graphql index 2192888caef70..6d5086867776c 100644 --- a/datahub-web-react/src/graphql/mlModel.graphql +++ b/datahub-web-react/src/graphql/mlModel.graphql @@ -34,5 +34,8 @@ query getMLModel($urn: String!) { forms { ...formsFields } + activeIncidents: incidents(start: 0, count: 1, state: ACTIVE) { + total + } } } diff --git a/metadata-models/src/main/pegasus/com/linkedin/incident/IncidentInfo.pdl b/metadata-models/src/main/pegasus/com/linkedin/incident/IncidentInfo.pdl index 44baff5270bed..34e701f9cd0c9 100644 --- a/metadata-models/src/main/pegasus/com/linkedin/incident/IncidentInfo.pdl +++ b/metadata-models/src/main/pegasus/com/linkedin/incident/IncidentInfo.pdl @@ -45,7 +45,7 @@ record IncidentInfo { @Relationship = { "/*": { "name": "IncidentOn", - "entityTypes": [ "dataset", "chart", "dashboard", "dataFlow", "dataJob", "schemaField" ] + "entityTypes": [ "dataset", "chart", "dashboard", "dataFlow", "dataJob", "schemaField", "mlModel", "mlFeature" ] } } @Searchable = { diff --git a/metadata-models/src/main/resources/entity-registry.yml b/metadata-models/src/main/resources/entity-registry.yml index ec9c3fee1c404..07fe8187ff236 100644 --- a/metadata-models/src/main/resources/entity-registry.yml +++ b/metadata-models/src/main/resources/entity-registry.yml @@ -356,6 +356,7 @@ entities: - structuredProperties - forms - testResults + - incidentsSummary - name: mlModelGroup category: core keyAspect: mlModelGroupKey @@ -423,6 +424,7 @@ entities: - structuredProperties - forms - testResults + - incidentsSummary - name: mlPrimaryKey category: core keyAspect: mlPrimaryKeyKey