mirror of
https://github.com/datahub-project/datahub.git
synced 2025-08-15 04:37:03 +00:00
feat: update ml system UI (#12334)
Co-authored-by: Andrew Sikowitz <andrew.sikowitz@acryl.io> Co-authored-by: RyanHolstien <RyanHolstien@users.noreply.github.com> Co-authored-by: Shirshanka Das <shirshanka@apache.org> Co-authored-by: ryota-cloud <ryota.egashira@acryl.io>
This commit is contained in:
parent
dbd57c972f
commit
47134c272b
@ -3,8 +3,11 @@ package com.linkedin.datahub.graphql.types.mlmodel.mappers;
|
|||||||
import com.linkedin.common.urn.Urn;
|
import com.linkedin.common.urn.Urn;
|
||||||
import com.linkedin.datahub.graphql.QueryContext;
|
import com.linkedin.datahub.graphql.QueryContext;
|
||||||
import com.linkedin.datahub.graphql.generated.MLModelGroupProperties;
|
import com.linkedin.datahub.graphql.generated.MLModelGroupProperties;
|
||||||
|
import com.linkedin.datahub.graphql.generated.MLModelLineageInfo;
|
||||||
import com.linkedin.datahub.graphql.types.common.mappers.CustomPropertiesMapper;
|
import com.linkedin.datahub.graphql.types.common.mappers.CustomPropertiesMapper;
|
||||||
|
import com.linkedin.datahub.graphql.types.common.mappers.TimeStampToAuditStampMapper;
|
||||||
import com.linkedin.datahub.graphql.types.mappers.EmbeddedModelMapper;
|
import com.linkedin.datahub.graphql.types.mappers.EmbeddedModelMapper;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@ -33,10 +36,40 @@ public class MLModelGroupPropertiesMapper
|
|||||||
result.setVersion(VersionTagMapper.map(context, mlModelGroupProperties.getVersion()));
|
result.setVersion(VersionTagMapper.map(context, mlModelGroupProperties.getVersion()));
|
||||||
}
|
}
|
||||||
result.setCreatedAt(mlModelGroupProperties.getCreatedAt());
|
result.setCreatedAt(mlModelGroupProperties.getCreatedAt());
|
||||||
|
if (mlModelGroupProperties.hasCreated()) {
|
||||||
|
result.setCreated(
|
||||||
|
TimeStampToAuditStampMapper.map(context, mlModelGroupProperties.getCreated()));
|
||||||
|
}
|
||||||
|
if (mlModelGroupProperties.getName() != null) {
|
||||||
|
result.setName(mlModelGroupProperties.getName());
|
||||||
|
} else {
|
||||||
|
// backfill name from URN for backwards compatibility
|
||||||
|
result.setName(entityUrn.getEntityKey().get(1)); // indexed access is safe here
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mlModelGroupProperties.hasLastModified()) {
|
||||||
|
result.setLastModified(
|
||||||
|
TimeStampToAuditStampMapper.map(context, mlModelGroupProperties.getLastModified()));
|
||||||
|
}
|
||||||
|
|
||||||
result.setCustomProperties(
|
result.setCustomProperties(
|
||||||
CustomPropertiesMapper.map(mlModelGroupProperties.getCustomProperties(), entityUrn));
|
CustomPropertiesMapper.map(mlModelGroupProperties.getCustomProperties(), entityUrn));
|
||||||
|
|
||||||
|
final MLModelLineageInfo lineageInfo = new MLModelLineageInfo();
|
||||||
|
if (mlModelGroupProperties.hasTrainingJobs()) {
|
||||||
|
lineageInfo.setTrainingJobs(
|
||||||
|
mlModelGroupProperties.getTrainingJobs().stream()
|
||||||
|
.map(urn -> urn.toString())
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
if (mlModelGroupProperties.hasDownstreamJobs()) {
|
||||||
|
lineageInfo.setDownstreamJobs(
|
||||||
|
mlModelGroupProperties.getDownstreamJobs().stream()
|
||||||
|
.map(urn -> urn.toString())
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
result.setMlModelLineageInfo(lineageInfo);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import static com.linkedin.datahub.graphql.authorization.AuthorizationUtils.canV
|
|||||||
import com.linkedin.common.urn.Urn;
|
import com.linkedin.common.urn.Urn;
|
||||||
import com.linkedin.datahub.graphql.QueryContext;
|
import com.linkedin.datahub.graphql.QueryContext;
|
||||||
import com.linkedin.datahub.graphql.generated.MLModelGroup;
|
import com.linkedin.datahub.graphql.generated.MLModelGroup;
|
||||||
|
import com.linkedin.datahub.graphql.generated.MLModelLineageInfo;
|
||||||
import com.linkedin.datahub.graphql.generated.MLModelProperties;
|
import com.linkedin.datahub.graphql.generated.MLModelProperties;
|
||||||
import com.linkedin.datahub.graphql.types.common.mappers.CustomPropertiesMapper;
|
import com.linkedin.datahub.graphql.types.common.mappers.CustomPropertiesMapper;
|
||||||
import com.linkedin.datahub.graphql.types.common.mappers.TimeStampToAuditStampMapper;
|
import com.linkedin.datahub.graphql.types.common.mappers.TimeStampToAuditStampMapper;
|
||||||
@ -87,6 +88,20 @@ public class MLModelPropertiesMapper
|
|||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
result.setTags(mlModelProperties.getTags());
|
result.setTags(mlModelProperties.getTags());
|
||||||
|
final MLModelLineageInfo lineageInfo = new MLModelLineageInfo();
|
||||||
|
if (mlModelProperties.hasTrainingJobs()) {
|
||||||
|
lineageInfo.setTrainingJobs(
|
||||||
|
mlModelProperties.getTrainingJobs().stream()
|
||||||
|
.map(urn -> urn.toString())
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
if (mlModelProperties.hasDownstreamJobs()) {
|
||||||
|
lineageInfo.setDownstreamJobs(
|
||||||
|
mlModelProperties.getDownstreamJobs().stream()
|
||||||
|
.map(urn -> urn.toString())
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
result.setMlModelLineageInfo(lineageInfo);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -25,3 +25,32 @@ input LineageEdge {
|
|||||||
"""
|
"""
|
||||||
upstreamUrn: String!
|
upstreamUrn: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Represents lineage information for ML entities.
|
||||||
|
"""
|
||||||
|
type MLModelLineageInfo {
|
||||||
|
"""
|
||||||
|
List of jobs or processes used to train the model.
|
||||||
|
"""
|
||||||
|
trainingJobs: [String!]
|
||||||
|
|
||||||
|
"""
|
||||||
|
List of jobs or processes that use this model.
|
||||||
|
"""
|
||||||
|
downstreamJobs: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type MLModelProperties {
|
||||||
|
"""
|
||||||
|
Information related to lineage to this model group
|
||||||
|
"""
|
||||||
|
mlModelLineageInfo: MLModelLineageInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type MLModelGroupProperties {
|
||||||
|
"""
|
||||||
|
Information related to lineage to this model group
|
||||||
|
"""
|
||||||
|
mlModelLineageInfo: MLModelLineageInfo
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.linkedin.datahub.graphql.types.mlmodel.mappers;
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
import static org.testng.Assert.assertNotNull;
|
||||||
|
import static org.testng.Assert.assertNull;
|
||||||
|
|
||||||
|
import com.linkedin.common.urn.Urn;
|
||||||
|
import com.linkedin.ml.metadata.MLModelGroupProperties;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
public class MLModelGroupPropertiesMapperTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMapMLModelGroupProperties() throws URISyntaxException {
|
||||||
|
// Create backend ML Model Group Properties
|
||||||
|
MLModelGroupProperties input = new MLModelGroupProperties();
|
||||||
|
|
||||||
|
// Set description
|
||||||
|
input.setDescription("a ml trust model group");
|
||||||
|
|
||||||
|
// Set Name
|
||||||
|
input.setName("ML trust model group");
|
||||||
|
|
||||||
|
// Create URN
|
||||||
|
Urn groupUrn =
|
||||||
|
Urn.createFromString(
|
||||||
|
"urn:li:mlModelGroup:(urn:li:dataPlatform:sagemaker,another-group,PROD)");
|
||||||
|
|
||||||
|
// Map the properties
|
||||||
|
com.linkedin.datahub.graphql.generated.MLModelGroupProperties result =
|
||||||
|
MLModelGroupPropertiesMapper.map(null, input, groupUrn);
|
||||||
|
|
||||||
|
// Verify mapped properties
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(result.getDescription(), "a ml trust model group");
|
||||||
|
assertEquals(result.getName(), "ML trust model group");
|
||||||
|
|
||||||
|
// Verify lineage info is null as in the mock data
|
||||||
|
assertNotNull(result.getMlModelLineageInfo());
|
||||||
|
assertNull(result.getMlModelLineageInfo().getTrainingJobs());
|
||||||
|
assertNull(result.getMlModelLineageInfo().getDownstreamJobs());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMapWithMinimalProperties() throws URISyntaxException {
|
||||||
|
// Create backend ML Model Group Properties with minimal information
|
||||||
|
MLModelGroupProperties input = new MLModelGroupProperties();
|
||||||
|
|
||||||
|
// Create URN
|
||||||
|
Urn groupUrn =
|
||||||
|
Urn.createFromString(
|
||||||
|
"urn:li:mlModelGroup:(urn:li:dataPlatform:sagemaker,another-group,PROD)");
|
||||||
|
|
||||||
|
// Map the properties
|
||||||
|
com.linkedin.datahub.graphql.generated.MLModelGroupProperties result =
|
||||||
|
MLModelGroupPropertiesMapper.map(null, input, groupUrn);
|
||||||
|
|
||||||
|
// Verify basic mapping with minimal properties
|
||||||
|
assertNotNull(result);
|
||||||
|
assertNull(result.getDescription());
|
||||||
|
|
||||||
|
// Verify lineage info is null
|
||||||
|
assertNotNull(result.getMlModelLineageInfo());
|
||||||
|
assertNull(result.getMlModelLineageInfo().getTrainingJobs());
|
||||||
|
assertNull(result.getMlModelLineageInfo().getDownstreamJobs());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,187 @@
|
|||||||
|
package com.linkedin.datahub.graphql.types.mlmodel.mappers;
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
import static org.testng.Assert.assertNotNull;
|
||||||
|
import static org.testng.Assert.assertNull;
|
||||||
|
|
||||||
|
import com.linkedin.common.MLFeatureUrnArray;
|
||||||
|
import com.linkedin.common.TimeStamp;
|
||||||
|
import com.linkedin.common.VersionTag;
|
||||||
|
import com.linkedin.common.url.Url;
|
||||||
|
import com.linkedin.common.urn.MLFeatureUrn;
|
||||||
|
import com.linkedin.common.urn.MLModelUrn;
|
||||||
|
import com.linkedin.common.urn.Urn;
|
||||||
|
import com.linkedin.data.template.StringArray;
|
||||||
|
import com.linkedin.data.template.StringMap;
|
||||||
|
import com.linkedin.ml.metadata.MLHyperParam;
|
||||||
|
import com.linkedin.ml.metadata.MLHyperParamArray;
|
||||||
|
import com.linkedin.ml.metadata.MLMetric;
|
||||||
|
import com.linkedin.ml.metadata.MLMetricArray;
|
||||||
|
import com.linkedin.ml.metadata.MLModelProperties;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
public class MLModelPropertiesMapperTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMapMLModelProperties() throws URISyntaxException {
|
||||||
|
MLModelProperties input = new MLModelProperties();
|
||||||
|
|
||||||
|
// Set basic properties
|
||||||
|
input.setName("TestModel");
|
||||||
|
input.setDescription("A test ML model");
|
||||||
|
input.setType("Classification");
|
||||||
|
|
||||||
|
// Set version
|
||||||
|
VersionTag versionTag = new VersionTag();
|
||||||
|
versionTag.setVersionTag("1.0.0");
|
||||||
|
input.setVersion(versionTag);
|
||||||
|
|
||||||
|
// Set external URL
|
||||||
|
Url externalUrl = new Url("https://example.com/model");
|
||||||
|
input.setExternalUrl(externalUrl);
|
||||||
|
|
||||||
|
// Set created and last modified timestamps
|
||||||
|
TimeStamp createdTimeStamp = new TimeStamp();
|
||||||
|
createdTimeStamp.setTime(1000L);
|
||||||
|
Urn userUrn = Urn.createFromString("urn:li:corpuser:test");
|
||||||
|
createdTimeStamp.setActor(userUrn);
|
||||||
|
input.setCreated(createdTimeStamp);
|
||||||
|
|
||||||
|
TimeStamp lastModifiedTimeStamp = new TimeStamp();
|
||||||
|
lastModifiedTimeStamp.setTime(2000L);
|
||||||
|
lastModifiedTimeStamp.setActor(userUrn);
|
||||||
|
input.setLastModified(lastModifiedTimeStamp);
|
||||||
|
|
||||||
|
// Set custom properties
|
||||||
|
StringMap customProps = new StringMap();
|
||||||
|
customProps.put("key1", "value1");
|
||||||
|
customProps.put("key2", "value2");
|
||||||
|
input.setCustomProperties(customProps);
|
||||||
|
|
||||||
|
// Set hyper parameters
|
||||||
|
MLHyperParamArray hyperParams = new MLHyperParamArray();
|
||||||
|
MLHyperParam hyperParam1 = new MLHyperParam();
|
||||||
|
hyperParam1.setName("learning_rate");
|
||||||
|
hyperParam1.setValue("0.01");
|
||||||
|
hyperParams.add(hyperParam1);
|
||||||
|
input.setHyperParams(hyperParams);
|
||||||
|
|
||||||
|
// Set training metrics
|
||||||
|
MLMetricArray trainingMetrics = new MLMetricArray();
|
||||||
|
MLMetric metric1 = new MLMetric();
|
||||||
|
metric1.setName("accuracy");
|
||||||
|
metric1.setValue("0.95");
|
||||||
|
trainingMetrics.add(metric1);
|
||||||
|
input.setTrainingMetrics(trainingMetrics);
|
||||||
|
|
||||||
|
// Set ML features
|
||||||
|
MLFeatureUrnArray mlFeatures = new MLFeatureUrnArray();
|
||||||
|
MLFeatureUrn featureUrn = MLFeatureUrn.createFromString("urn:li:mlFeature:(dataset,feature)");
|
||||||
|
mlFeatures.add(featureUrn);
|
||||||
|
input.setMlFeatures(mlFeatures);
|
||||||
|
|
||||||
|
// Set tags
|
||||||
|
StringArray tags = new StringArray();
|
||||||
|
tags.add("tag1");
|
||||||
|
tags.add("tag2");
|
||||||
|
input.setTags(tags);
|
||||||
|
|
||||||
|
// Set training and downstream jobs
|
||||||
|
input.setTrainingJobs(
|
||||||
|
new com.linkedin.common.UrnArray(Urn.createFromString("urn:li:dataJob:train")));
|
||||||
|
input.setDownstreamJobs(
|
||||||
|
new com.linkedin.common.UrnArray(Urn.createFromString("urn:li:dataJob:predict")));
|
||||||
|
|
||||||
|
// Create ML Model URN
|
||||||
|
MLModelUrn modelUrn =
|
||||||
|
MLModelUrn.createFromString(
|
||||||
|
"urn:li:mlModel:(urn:li:dataPlatform:sagemaker,unittestmodel,PROD)");
|
||||||
|
|
||||||
|
// Map the properties
|
||||||
|
com.linkedin.datahub.graphql.generated.MLModelProperties result =
|
||||||
|
MLModelPropertiesMapper.map(null, input, modelUrn);
|
||||||
|
|
||||||
|
// Verify mapped properties
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(result.getName(), "TestModel");
|
||||||
|
assertEquals(result.getDescription(), "A test ML model");
|
||||||
|
assertEquals(result.getType(), "Classification");
|
||||||
|
assertEquals(result.getVersion(), "1.0.0");
|
||||||
|
assertEquals(result.getExternalUrl(), "https://example.com/model");
|
||||||
|
|
||||||
|
// Verify audit stamps
|
||||||
|
assertNotNull(result.getCreated());
|
||||||
|
assertEquals(result.getCreated().getTime().longValue(), 1000L);
|
||||||
|
assertEquals(result.getCreated().getActor(), userUrn.toString());
|
||||||
|
|
||||||
|
assertNotNull(result.getLastModified());
|
||||||
|
assertEquals(result.getLastModified().getTime().longValue(), 2000L);
|
||||||
|
assertEquals(result.getLastModified().getActor(), userUrn.toString());
|
||||||
|
|
||||||
|
// Verify custom properties
|
||||||
|
assertNotNull(result.getCustomProperties());
|
||||||
|
|
||||||
|
// Verify hyper parameters
|
||||||
|
assertNotNull(result.getHyperParams());
|
||||||
|
assertEquals(result.getHyperParams().size(), 1);
|
||||||
|
assertEquals(result.getHyperParams().get(0).getName(), "learning_rate");
|
||||||
|
assertEquals(result.getHyperParams().get(0).getValue(), "0.01");
|
||||||
|
|
||||||
|
// Verify training metrics
|
||||||
|
assertNotNull(result.getTrainingMetrics());
|
||||||
|
assertEquals(result.getTrainingMetrics().size(), 1);
|
||||||
|
assertEquals(result.getTrainingMetrics().get(0).getName(), "accuracy");
|
||||||
|
assertEquals(result.getTrainingMetrics().get(0).getValue(), "0.95");
|
||||||
|
|
||||||
|
// Verify ML features
|
||||||
|
assertNotNull(result.getMlFeatures());
|
||||||
|
assertEquals(result.getMlFeatures().size(), 1);
|
||||||
|
assertEquals(result.getMlFeatures().get(0), featureUrn.toString());
|
||||||
|
|
||||||
|
// Verify tags
|
||||||
|
assertNotNull(result.getTags());
|
||||||
|
assertEquals(result.getTags().get(0), "tag1");
|
||||||
|
assertEquals(result.getTags().get(1), "tag2");
|
||||||
|
|
||||||
|
// Verify lineage info
|
||||||
|
assertNotNull(result.getMlModelLineageInfo());
|
||||||
|
assertEquals(result.getMlModelLineageInfo().getTrainingJobs().size(), 1);
|
||||||
|
assertEquals(result.getMlModelLineageInfo().getTrainingJobs().get(0), "urn:li:dataJob:train");
|
||||||
|
assertEquals(result.getMlModelLineageInfo().getDownstreamJobs().size(), 1);
|
||||||
|
assertEquals(
|
||||||
|
result.getMlModelLineageInfo().getDownstreamJobs().get(0), "urn:li:dataJob:predict");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMapWithMissingName() throws URISyntaxException {
|
||||||
|
MLModelProperties input = new MLModelProperties();
|
||||||
|
MLModelUrn modelUrn =
|
||||||
|
MLModelUrn.createFromString(
|
||||||
|
"urn:li:mlModel:(urn:li:dataPlatform:sagemaker,missingnamemodel,PROD)");
|
||||||
|
|
||||||
|
com.linkedin.datahub.graphql.generated.MLModelProperties result =
|
||||||
|
MLModelPropertiesMapper.map(null, input, modelUrn);
|
||||||
|
|
||||||
|
// Verify that name is extracted from URN when not present in input
|
||||||
|
assertEquals(result.getName(), "missingnamemodel");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMapWithMinimalProperties() throws URISyntaxException {
|
||||||
|
MLModelProperties input = new MLModelProperties();
|
||||||
|
MLModelUrn modelUrn =
|
||||||
|
MLModelUrn.createFromString(
|
||||||
|
"urn:li:mlModel:(urn:li:dataPlatform:sagemaker,minimalmodel,PROD)");
|
||||||
|
|
||||||
|
com.linkedin.datahub.graphql.generated.MLModelProperties result =
|
||||||
|
MLModelPropertiesMapper.map(null, input, modelUrn);
|
||||||
|
|
||||||
|
// Verify basic mapping with minimal properties
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(result.getName(), "minimalmodel");
|
||||||
|
assertNull(result.getDescription());
|
||||||
|
assertNull(result.getType());
|
||||||
|
assertNull(result.getVersion());
|
||||||
|
}
|
||||||
|
}
|
@ -66,6 +66,7 @@ export const EntityPage = ({ entityType }: Props) => {
|
|||||||
entityType === EntityType.MlfeatureTable ||
|
entityType === EntityType.MlfeatureTable ||
|
||||||
entityType === EntityType.MlmodelGroup ||
|
entityType === EntityType.MlmodelGroup ||
|
||||||
entityType === EntityType.GlossaryTerm ||
|
entityType === EntityType.GlossaryTerm ||
|
||||||
|
entityType === EntityType.DataProcessInstance ||
|
||||||
entityType === EntityType.GlossaryNode;
|
entityType === EntityType.GlossaryNode;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ApiOutlined } from '@ant-design/icons';
|
import { ApiOutlined } from '@ant-design/icons';
|
||||||
import {
|
import { Entity as GraphQLEntity } from '@types';
|
||||||
DataProcessInstance,
|
import { DataProcessInstance, EntityType, OwnershipType, SearchResult } from '../../../types.generated';
|
||||||
Entity as GeneratedEntity,
|
|
||||||
EntityType,
|
|
||||||
OwnershipType,
|
|
||||||
SearchResult,
|
|
||||||
} from '../../../types.generated';
|
|
||||||
import { Preview } from './preview/Preview';
|
import { Preview } from './preview/Preview';
|
||||||
import { Entity, EntityCapabilityType, IconStyleType, PreviewType } from '../Entity';
|
import { Entity, EntityCapabilityType, IconStyleType, PreviewType } from '../Entity';
|
||||||
import { EntityProfile } from '../shared/containers/profile/EntityProfile';
|
import { EntityProfile } from '../shared/containers/profile/EntityProfile';
|
||||||
@ -23,32 +18,21 @@ import { EntityMenuItems } from '../shared/EntityDropdown/EntityDropdown';
|
|||||||
import { capitalizeFirstLetterOnly } from '../../shared/textUtil';
|
import { capitalizeFirstLetterOnly } from '../../shared/textUtil';
|
||||||
import DataProductSection from '../shared/containers/profile/sidebar/DataProduct/DataProductSection';
|
import DataProductSection from '../shared/containers/profile/sidebar/DataProduct/DataProductSection';
|
||||||
import { getDataProduct } from '../shared/utils';
|
import { getDataProduct } from '../shared/utils';
|
||||||
// import SummaryTab from './profile/DataProcessInstaceSummary';
|
import SummaryTab from './profile/DataProcessInstanceSummary';
|
||||||
|
|
||||||
// const getProcessPlatformName = (data?: DataProcessInstance): string => {
|
const getParentEntities = (data: DataProcessInstance): GraphQLEntity[] => {
|
||||||
// return (
|
|
||||||
// data?.dataPlatformInstance?.platform?.properties?.displayName ||
|
|
||||||
// capitalizeFirstLetterOnly(data?.dataPlatformInstance?.platform?.name) ||
|
|
||||||
// ''
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
const getParentEntities = (data: DataProcessInstance): GeneratedEntity[] => {
|
|
||||||
const parentEntity = data?.relationships?.relationships?.find(
|
const parentEntity = data?.relationships?.relationships?.find(
|
||||||
(rel) => rel.type === 'InstanceOf' && rel.entity?.type === EntityType.DataJob,
|
(rel) => rel.type === 'InstanceOf' && rel.entity?.type === EntityType.DataJob,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!parentEntity?.entity) return [];
|
if (!parentEntity || !parentEntity.entity) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
// Convert to GeneratedEntity
|
// First cast to unknown, then to Entity with proper type
|
||||||
return [
|
return [parentEntity.entity];
|
||||||
{
|
|
||||||
type: parentEntity.entity.type,
|
|
||||||
urn: (parentEntity.entity as any).urn, // Make sure urn exists
|
|
||||||
relationships: (parentEntity.entity as any).relationships,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Definition of the DataHub DataProcessInstance entity.
|
* Definition of the DataHub DataProcessInstance entity.
|
||||||
*/
|
*/
|
||||||
@ -97,18 +81,13 @@ export class DataProcessInstanceEntity implements Entity<DataProcessInstance> {
|
|||||||
urn={urn}
|
urn={urn}
|
||||||
entityType={EntityType.DataProcessInstance}
|
entityType={EntityType.DataProcessInstance}
|
||||||
useEntityQuery={this.useEntityQuery}
|
useEntityQuery={this.useEntityQuery}
|
||||||
// useUpdateQuery={useUpdateDataProcessInstanceMutation}
|
|
||||||
getOverrideProperties={this.getOverridePropertiesFromEntity}
|
getOverrideProperties={this.getOverridePropertiesFromEntity}
|
||||||
headerDropdownItems={new Set([EntityMenuItems.UPDATE_DEPRECATION, EntityMenuItems.RAISE_INCIDENT])}
|
headerDropdownItems={new Set([EntityMenuItems.UPDATE_DEPRECATION, EntityMenuItems.RAISE_INCIDENT])}
|
||||||
tabs={[
|
tabs={[
|
||||||
// {
|
{
|
||||||
// name: 'Documentation',
|
name: 'Summary',
|
||||||
// component: DocumentationTab,
|
component: SummaryTab,
|
||||||
// },
|
},
|
||||||
// {
|
|
||||||
// name: 'Summary',
|
|
||||||
// component: SummaryTab,
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
name: 'Lineage',
|
name: 'Lineage',
|
||||||
component: LineageTab,
|
component: LineageTab,
|
||||||
@ -117,14 +96,6 @@ export class DataProcessInstanceEntity implements Entity<DataProcessInstance> {
|
|||||||
name: 'Properties',
|
name: 'Properties',
|
||||||
component: PropertiesTab,
|
component: PropertiesTab,
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// name: 'Incidents',
|
|
||||||
// component: IncidentTab,
|
|
||||||
// getDynamicName: (_, processInstance) => {
|
|
||||||
// const activeIncidentCount = processInstance?.dataProcessInstance?.activeIncidents.total;
|
|
||||||
// return `Incidents${(activeIncidentCount && ` (${activeIncidentCount})`) || ''}`;
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
]}
|
]}
|
||||||
sidebarSections={this.getSidebarSections()}
|
sidebarSections={this.getSidebarSections()}
|
||||||
/>
|
/>
|
||||||
@ -181,13 +152,11 @@ export class DataProcessInstanceEntity implements Entity<DataProcessInstance> {
|
|||||||
platformLogo={data?.dataPlatformInstance?.platform?.properties?.logoUrl}
|
platformLogo={data?.dataPlatformInstance?.platform?.properties?.logoUrl}
|
||||||
owners={null}
|
owners={null}
|
||||||
globalTags={null}
|
globalTags={null}
|
||||||
// domain={data.domain?.domain}
|
|
||||||
dataProduct={getDataProduct(genericProperties?.dataProduct)}
|
dataProduct={getDataProduct(genericProperties?.dataProduct)}
|
||||||
externalUrl={data.properties?.externalUrl}
|
externalUrl={data.properties?.externalUrl}
|
||||||
parentContainers={data.parentContainers}
|
parentContainers={data.parentContainers}
|
||||||
parentEntities={parentEntities}
|
parentEntities={parentEntities}
|
||||||
container={data.container || undefined}
|
container={data.container || undefined}
|
||||||
// health={data.health}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -196,6 +165,9 @@ export class DataProcessInstanceEntity implements Entity<DataProcessInstance> {
|
|||||||
const data = result.entity as DataProcessInstance;
|
const data = result.entity as DataProcessInstance;
|
||||||
const genericProperties = this.getGenericEntityProperties(data);
|
const genericProperties = this.getGenericEntityProperties(data);
|
||||||
const parentEntities = getParentEntities(data);
|
const parentEntities = getParentEntities(data);
|
||||||
|
|
||||||
|
const firstState = data?.state && data.state.length > 0 ? data.state[0] : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Preview
|
<Preview
|
||||||
urn={data.urn}
|
urn={data.urn}
|
||||||
@ -210,9 +182,7 @@ export class DataProcessInstanceEntity implements Entity<DataProcessInstance> {
|
|||||||
platformInstanceId={data.dataPlatformInstance?.instanceId}
|
platformInstanceId={data.dataPlatformInstance?.instanceId}
|
||||||
owners={null}
|
owners={null}
|
||||||
globalTags={null}
|
globalTags={null}
|
||||||
// domain={data.domain?.domain}
|
|
||||||
dataProduct={getDataProduct(genericProperties?.dataProduct)}
|
dataProduct={getDataProduct(genericProperties?.dataProduct)}
|
||||||
// deprecation={data.deprecation}
|
|
||||||
insights={result.insights}
|
insights={result.insights}
|
||||||
externalUrl={data.properties?.externalUrl}
|
externalUrl={data.properties?.externalUrl}
|
||||||
degree={(result as any).degree}
|
degree={(result as any).degree}
|
||||||
@ -220,10 +190,9 @@ export class DataProcessInstanceEntity implements Entity<DataProcessInstance> {
|
|||||||
parentContainers={data.parentContainers}
|
parentContainers={data.parentContainers}
|
||||||
parentEntities={parentEntities}
|
parentEntities={parentEntities}
|
||||||
container={data.container || undefined}
|
container={data.container || undefined}
|
||||||
// duration={data?.state?.[0]?.durationMillis}
|
duration={firstState?.durationMillis}
|
||||||
// status={data?.state?.[0]?.result?.resultType}
|
status={firstState?.result?.resultType}
|
||||||
// startTime={data?.state?.[0]?.timestampMillis}
|
startTime={firstState?.timestampMillis}
|
||||||
// health={data.health}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -237,7 +206,6 @@ export class DataProcessInstanceEntity implements Entity<DataProcessInstance> {
|
|||||||
icon: entity?.dataPlatformInstance?.platform?.properties?.logoUrl || undefined,
|
icon: entity?.dataPlatformInstance?.platform?.properties?.logoUrl || undefined,
|
||||||
platform: entity?.dataPlatformInstance?.platform,
|
platform: entity?.dataPlatformInstance?.platform,
|
||||||
container: entity?.container,
|
container: entity?.container,
|
||||||
// health: entity?.health || undefined,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,10 +39,10 @@ export const Preview = ({
|
|||||||
health,
|
health,
|
||||||
parentEntities,
|
parentEntities,
|
||||||
parentContainers,
|
parentContainers,
|
||||||
}: // duration,
|
duration,
|
||||||
// status,
|
status,
|
||||||
// startTime,
|
startTime,
|
||||||
{
|
}: {
|
||||||
urn: string;
|
urn: string;
|
||||||
name: string;
|
name: string;
|
||||||
subType?: string | null;
|
subType?: string | null;
|
||||||
@ -64,9 +64,9 @@ export const Preview = ({
|
|||||||
health?: Health[] | null;
|
health?: Health[] | null;
|
||||||
parentEntities?: Array<GeneratedEntity> | null;
|
parentEntities?: Array<GeneratedEntity> | null;
|
||||||
parentContainers?: ParentContainersResult | null;
|
parentContainers?: ParentContainersResult | null;
|
||||||
// duration?: number | null;
|
duration?: number | null;
|
||||||
// status?: string | null;
|
status?: string | null;
|
||||||
// startTime?: number | null;
|
startTime?: number | null;
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const entityRegistry = useEntityRegistry();
|
const entityRegistry = useEntityRegistry();
|
||||||
return (
|
return (
|
||||||
@ -95,9 +95,9 @@ export const Preview = ({
|
|||||||
paths={paths}
|
paths={paths}
|
||||||
health={health || undefined}
|
health={health || undefined}
|
||||||
parentEntities={parentEntities}
|
parentEntities={parentEntities}
|
||||||
// duration={duration}
|
duration={duration}
|
||||||
// status={status}
|
status={status}
|
||||||
// startTime={startTime}
|
startTime={startTime}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,102 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Space, Table, Typography } from 'antd';
|
||||||
|
import { formatDetailedDuration } from '@src/app/shared/time/timeUtils';
|
||||||
|
import { capitalize } from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { MlHyperParam, MlMetric, DataProcessInstanceRunResultType } from '../../../../types.generated';
|
||||||
|
import { useBaseEntity } from '../../shared/EntityContext';
|
||||||
|
import { InfoItem } from '../../shared/components/styled/InfoItem';
|
||||||
|
import { GetDataProcessInstanceQuery } from '../../../../graphql/dataProcessInstance.generated';
|
||||||
|
import { Pill } from '../../../../alchemy-components/components/Pills';
|
||||||
|
|
||||||
|
const TabContent = styled.div`
|
||||||
|
padding: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InfoItemContainer = styled.div<{ justifyContent }>`
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
justify-content: ${(props) => props.justifyContent};
|
||||||
|
padding: 0px 2px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InfoItemContent = styled.div`
|
||||||
|
padding-top: 8px;
|
||||||
|
width: 100px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const propertyTableColumns = [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
width: 450,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Value',
|
||||||
|
dataIndex: 'value',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function MLModelSummary() {
|
||||||
|
const baseEntity = useBaseEntity<GetDataProcessInstanceQuery>();
|
||||||
|
const dpi = baseEntity?.dataProcessInstance;
|
||||||
|
|
||||||
|
const formatStatus = (state) => {
|
||||||
|
if (!state || state.length === 0) return '-';
|
||||||
|
const result = state[0]?.result?.resultType;
|
||||||
|
const statusColor = result === DataProcessInstanceRunResultType.Success ? 'green' : 'red';
|
||||||
|
return <Pill label={capitalize(result)} colorScheme={statusColor} clickable={false} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDuration = (state) => {
|
||||||
|
if (!state || state.length === 0) return '-';
|
||||||
|
return formatDetailedDuration(state[0]?.durationMillis);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TabContent>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
||||||
|
<Typography.Title level={3}>Details</Typography.Title>
|
||||||
|
<InfoItemContainer justifyContent="left">
|
||||||
|
<InfoItem title="Created At">
|
||||||
|
<InfoItemContent>
|
||||||
|
{dpi?.properties?.created?.time
|
||||||
|
? moment(dpi.properties.created.time).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: '-'}
|
||||||
|
</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
<InfoItem title="Status">
|
||||||
|
<InfoItemContent>{formatStatus(dpi?.state)}</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
<InfoItem title="Duration">
|
||||||
|
<InfoItemContent>{formatDuration(dpi?.state)}</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
<InfoItem title="Run ID">
|
||||||
|
<InfoItemContent>{dpi?.mlTrainingRunProperties?.id}</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
<InfoItem title="Created By">
|
||||||
|
<InfoItemContent>{dpi?.properties?.created?.actor}</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
</InfoItemContainer>
|
||||||
|
<InfoItemContainer justifyContent="left">
|
||||||
|
<InfoItem title="Artifacts Location">
|
||||||
|
<InfoItemContent>{dpi?.mlTrainingRunProperties?.outputUrls}</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
</InfoItemContainer>
|
||||||
|
<Typography.Title level={3}>Training Metrics</Typography.Title>
|
||||||
|
<Table
|
||||||
|
pagination={false}
|
||||||
|
columns={propertyTableColumns}
|
||||||
|
dataSource={dpi?.mlTrainingRunProperties?.trainingMetrics as MlMetric[]}
|
||||||
|
/>
|
||||||
|
<Typography.Title level={3}>Hyper Parameters</Typography.Title>
|
||||||
|
<Table
|
||||||
|
pagination={false}
|
||||||
|
columns={propertyTableColumns}
|
||||||
|
dataSource={dpi?.mlTrainingRunProperties?.hyperParams as MlHyperParam[]}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</TabContent>
|
||||||
|
);
|
||||||
|
}
|
@ -151,7 +151,7 @@ export class MLModelEntity implements Entity<MlModel> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
displayName = (data: MlModel) => {
|
displayName = (data: MlModel) => {
|
||||||
return data.name || data.urn;
|
return data.properties?.name || data.name || data.urn;
|
||||||
};
|
};
|
||||||
|
|
||||||
getGenericEntityProperties = (mlModel: MlModel) => {
|
getGenericEntityProperties = (mlModel: MlModel) => {
|
||||||
|
@ -21,7 +21,8 @@ export const Preview = ({
|
|||||||
return (
|
return (
|
||||||
<DefaultPreviewCard
|
<DefaultPreviewCard
|
||||||
url={entityRegistry.getEntityUrl(EntityType.Mlmodel, model.urn)}
|
url={entityRegistry.getEntityUrl(EntityType.Mlmodel, model.urn)}
|
||||||
name={model.name || ''}
|
// eslint-disable-next-line @typescript-eslint/dot-notation
|
||||||
|
name={model.properties?.['propertiesName'] || model.name || ''}
|
||||||
urn={model.urn}
|
urn={model.urn}
|
||||||
description={model.description || ''}
|
description={model.description || ''}
|
||||||
platformInstanceId={model.dataPlatformInstance?.instanceId}
|
platformInstanceId={model.dataPlatformInstance?.instanceId}
|
||||||
|
@ -1,18 +1,47 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Space, Table, Typography } from 'antd';
|
import { Space, Table, Typography } from 'antd';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import { MlHyperParam, MlMetric } from '../../../../types.generated';
|
import { colors } from '@src/alchemy-components/theme';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { useEntityRegistry } from '../../../useEntityRegistry';
|
||||||
|
import { MlHyperParam, MlMetric, EntityType } from '../../../../types.generated';
|
||||||
import { useBaseEntity } from '../../shared/EntityContext';
|
import { useBaseEntity } from '../../shared/EntityContext';
|
||||||
import { GetMlModelQuery } from '../../../../graphql/mlModel.generated';
|
import { GetMlModelQuery } from '../../../../graphql/mlModel.generated';
|
||||||
|
import { InfoItem } from '../../shared/components/styled/InfoItem';
|
||||||
|
import { notEmpty } from '../../shared/utils';
|
||||||
|
import { Pill } from '../../../../alchemy-components/components/Pills';
|
||||||
|
|
||||||
const TabContent = styled.div`
|
const TabContent = styled.div`
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const InfoItemContainer = styled.div<{ justifyContent }>`
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
justify-content: ${(props) => props.justifyContent};
|
||||||
|
padding: 0px 2px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InfoItemContent = styled.div`
|
||||||
|
padding-top: 8px;
|
||||||
|
width: 100px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const JobLink = styled(Link)`
|
||||||
|
color: ${colors.blue[700]};
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export default function MLModelSummary() {
|
export default function MLModelSummary() {
|
||||||
const baseEntity = useBaseEntity<GetMlModelQuery>();
|
const baseEntity = useBaseEntity<GetMlModelQuery>();
|
||||||
const model = baseEntity?.mlModel;
|
const model = baseEntity?.mlModel;
|
||||||
|
const entityRegistry = useEntityRegistry();
|
||||||
|
|
||||||
const propertyTableColumns = [
|
const propertyTableColumns = [
|
||||||
{
|
{
|
||||||
@ -26,9 +55,72 @@ export default function MLModelSummary() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const renderTrainingJobs = () => {
|
||||||
|
const trainingJobs =
|
||||||
|
model?.trainedBy?.relationships?.map((relationship) => relationship.entity).filter(notEmpty) || [];
|
||||||
|
|
||||||
|
if (trainingJobs.length === 0) return '-';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{trainingJobs.map((job, index) => {
|
||||||
|
const { urn, name } = job as { urn: string; name?: string };
|
||||||
|
return (
|
||||||
|
<span key={urn}>
|
||||||
|
<JobLink to={entityRegistry.getEntityUrl(EntityType.DataProcessInstance, urn)}>
|
||||||
|
{name || urn}
|
||||||
|
</JobLink>
|
||||||
|
{index < trainingJobs.length - 1 && ', '}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabContent>
|
<TabContent>
|
||||||
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
||||||
|
<Typography.Title level={3}>Model Details</Typography.Title>
|
||||||
|
<InfoItemContainer justifyContent="left">
|
||||||
|
<InfoItem title="Version">
|
||||||
|
<InfoItemContent>{model?.versionProperties?.version?.versionTag}</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
<InfoItem title="Registered At">
|
||||||
|
<InfoItemContent>
|
||||||
|
{model?.properties?.created?.time
|
||||||
|
? moment(model.properties.created.time).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: '-'}
|
||||||
|
</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
<InfoItem title="Last Modified At">
|
||||||
|
<InfoItemContent>
|
||||||
|
{model?.properties?.lastModified?.time
|
||||||
|
? moment(model.properties.lastModified.time).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: '-'}
|
||||||
|
</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
<InfoItem title="Created By">
|
||||||
|
<InfoItemContent>{model?.properties?.created?.actor}</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
</InfoItemContainer>
|
||||||
|
<InfoItemContainer justifyContent="left">
|
||||||
|
<InfoItem title="Aliases">
|
||||||
|
<InfoItemContent>
|
||||||
|
{model?.versionProperties?.aliases?.map((alias) => (
|
||||||
|
<Pill
|
||||||
|
label={alias.versionTag ?? '-'}
|
||||||
|
key={alias.versionTag}
|
||||||
|
colorScheme="blue"
|
||||||
|
clickable={false}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
<InfoItem title="Source Run">
|
||||||
|
<InfoItemContent>{renderTrainingJobs()}</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
</InfoItemContainer>
|
||||||
<Typography.Title level={3}>Training Metrics</Typography.Title>
|
<Typography.Title level={3}>Training Metrics</Typography.Title>
|
||||||
<Table
|
<Table
|
||||||
pagination={false}
|
pagination={false}
|
||||||
|
@ -139,7 +139,7 @@ export class MLModelGroupEntity implements Entity<MlModelGroup> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
displayName = (data: MlModelGroup) => {
|
displayName = (data: MlModelGroup) => {
|
||||||
return data.name || data.urn;
|
return data.properties?.name || data.name || data.urn;
|
||||||
};
|
};
|
||||||
|
|
||||||
getGenericEntityProperties = (mlModelGroup: MlModelGroup) => {
|
getGenericEntityProperties = (mlModelGroup: MlModelGroup) => {
|
||||||
|
@ -19,7 +19,8 @@ export const Preview = ({
|
|||||||
return (
|
return (
|
||||||
<DefaultPreviewCard
|
<DefaultPreviewCard
|
||||||
url={entityRegistry.getEntityUrl(EntityType.MlmodelGroup, group.urn)}
|
url={entityRegistry.getEntityUrl(EntityType.MlmodelGroup, group.urn)}
|
||||||
name={group?.name || ''}
|
// eslint-disable-next-line @typescript-eslint/dot-notation
|
||||||
|
name={group?.properties?.['propertiesName'] || group?.name || ''}
|
||||||
urn={group.urn}
|
urn={group.urn}
|
||||||
platformInstanceId={group.dataPlatformInstance?.instanceId}
|
platformInstanceId={group.dataPlatformInstance?.instanceId}
|
||||||
description={group?.description || ''}
|
description={group?.description || ''}
|
||||||
|
@ -1,32 +1,202 @@
|
|||||||
import { List, Space, Typography } from 'antd';
|
import { Typography, Table } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { colors } from '@src/alchemy-components/theme';
|
||||||
|
import { Pill } from '@src/alchemy-components/components/Pills';
|
||||||
|
import moment from 'moment';
|
||||||
import { GetMlModelGroupQuery } from '../../../../graphql/mlModelGroup.generated';
|
import { GetMlModelGroupQuery } from '../../../../graphql/mlModelGroup.generated';
|
||||||
import { EntityType } from '../../../../types.generated';
|
import { EntityType } from '../../../../types.generated';
|
||||||
import { useEntityRegistry } from '../../../useEntityRegistry';
|
import { useEntityRegistry } from '../../../useEntityRegistry';
|
||||||
import { PreviewType } from '../../Entity';
|
|
||||||
import { useBaseEntity } from '../../shared/EntityContext';
|
import { useBaseEntity } from '../../shared/EntityContext';
|
||||||
|
import { notEmpty } from '../../shared/utils';
|
||||||
|
import { EmptyTab } from '../../shared/components/styled/EmptyTab';
|
||||||
|
import { InfoItem } from '../../shared/components/styled/InfoItem';
|
||||||
|
|
||||||
|
const InfoItemContainer = styled.div<{ justifyContent }>`
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
justify-content: ${(props) => props.justifyContent};
|
||||||
|
padding: 12px 2px 20px 2px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InfoItemContent = styled.div`
|
||||||
|
padding-top: 8px;
|
||||||
|
width: 100px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const NameContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const NameLink = styled.a`
|
||||||
|
font-weight: 700;
|
||||||
|
color: inherit;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
&:hover {
|
||||||
|
color: ${colors.blue[400]} !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TagContainer = styled.div`
|
||||||
|
display: inline-flex;
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-top: 3px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-right: 8px;
|
||||||
|
backgroundcolor: white;
|
||||||
|
gap: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledTable = styled(Table)`
|
||||||
|
&&& .ant-table-cell {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
` as typeof Table;
|
||||||
|
|
||||||
|
const ModelsContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const VersionContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
export default function MLGroupModels() {
|
export default function MLGroupModels() {
|
||||||
const baseEntity = useBaseEntity<GetMlModelGroupQuery>();
|
const baseEntity = useBaseEntity<GetMlModelGroupQuery>();
|
||||||
const models = baseEntity?.mlModelGroup?.incoming?.relationships?.map((relationship) => relationship.entity) || [];
|
|
||||||
|
|
||||||
const entityRegistry = useEntityRegistry();
|
const entityRegistry = useEntityRegistry();
|
||||||
|
const modelGroup = baseEntity?.mlModelGroup;
|
||||||
|
|
||||||
|
const models =
|
||||||
|
baseEntity?.mlModelGroup?.incoming?.relationships
|
||||||
|
?.map((relationship) => relationship.entity)
|
||||||
|
.filter(notEmpty) || [];
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
width: 300,
|
||||||
|
render: (_: any, record) => (
|
||||||
|
<NameContainer>
|
||||||
|
<NameLink href={entityRegistry.getEntityUrl(EntityType.Mlmodel, record.urn)}>
|
||||||
|
{record?.properties?.propertiesName || record?.name}
|
||||||
|
</NameLink>
|
||||||
|
</NameContainer>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Version',
|
||||||
|
key: 'version',
|
||||||
|
width: 70,
|
||||||
|
render: (_: any, record: any) => (
|
||||||
|
<VersionContainer>{record.versionProperties?.version?.versionTag || '-'}</VersionContainer>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Created At',
|
||||||
|
key: 'createdAt',
|
||||||
|
width: 150,
|
||||||
|
render: (_: any, record: any) => (
|
||||||
|
<Typography.Text>
|
||||||
|
{record.properties?.createdTS?.time
|
||||||
|
? moment(record.properties.createdTS.time).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: '-'}
|
||||||
|
</Typography.Text>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Aliases',
|
||||||
|
key: 'aliases',
|
||||||
|
width: 200,
|
||||||
|
render: (_: any, record: any) => {
|
||||||
|
const aliases = record.versionProperties?.aliases || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TagContainer>
|
||||||
|
{aliases.map((alias) => (
|
||||||
|
<Pill
|
||||||
|
key={alias.versionTag}
|
||||||
|
label={alias.versionTag}
|
||||||
|
colorScheme="blue"
|
||||||
|
clickable={false}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TagContainer>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Tags',
|
||||||
|
key: 'tags',
|
||||||
|
width: 200,
|
||||||
|
render: (_: any, record: any) => {
|
||||||
|
const tags = record.properties?.tags || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TagContainer>
|
||||||
|
{tags.map((tag) => (
|
||||||
|
<Pill key={tag} label={tag} clickable={false} />
|
||||||
|
))}
|
||||||
|
</TagContainer>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Description',
|
||||||
|
dataIndex: 'description',
|
||||||
|
key: 'description',
|
||||||
|
width: 300,
|
||||||
|
render: (_: any, record: any) => {
|
||||||
|
const editableDesc = record.editableProperties?.description;
|
||||||
|
const originalDesc = record.description;
|
||||||
|
|
||||||
|
return <Typography.Text>{editableDesc || originalDesc || '-'}</Typography.Text>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ModelsContainer>
|
||||||
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
<Typography.Title level={3}>Model Group Details</Typography.Title>
|
||||||
<List
|
<InfoItemContainer justifyContent="left">
|
||||||
style={{ padding: '16px 16px' }}
|
<InfoItem title="Created At">
|
||||||
bordered
|
<InfoItemContent>
|
||||||
dataSource={models}
|
{modelGroup?.properties?.created?.time
|
||||||
header={<Typography.Title level={3}>Models</Typography.Title>}
|
? moment(modelGroup.properties.created.time).format('YYYY-MM-DD HH:mm:ss')
|
||||||
renderItem={(item) => (
|
: '-'}
|
||||||
<List.Item style={{ paddingTop: '20px' }}>
|
</InfoItemContent>
|
||||||
{entityRegistry.renderPreview(EntityType.Mlmodel, PreviewType.PREVIEW, item)}
|
</InfoItem>
|
||||||
</List.Item>
|
<InfoItem title="Last Modified At">
|
||||||
)}
|
<InfoItemContent>
|
||||||
/>
|
{modelGroup?.properties?.lastModified?.time
|
||||||
</Space>
|
? moment(modelGroup.properties.lastModified.time).format('YYYY-MM-DD HH:mm:ss')
|
||||||
</>
|
: '-'}
|
||||||
|
</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
{modelGroup?.properties?.created?.actor && (
|
||||||
|
<InfoItem title="Created By">
|
||||||
|
<InfoItemContent>{modelGroup.properties.created?.actor}</InfoItemContent>
|
||||||
|
</InfoItem>
|
||||||
|
)}
|
||||||
|
</InfoItemContainer>
|
||||||
|
<Typography.Title level={3}>Models</Typography.Title>
|
||||||
|
<StyledTable
|
||||||
|
columns={columns}
|
||||||
|
dataSource={models}
|
||||||
|
pagination={false}
|
||||||
|
rowKey="urn"
|
||||||
|
expandable={{
|
||||||
|
defaultExpandAllRows: true,
|
||||||
|
expandRowByClick: true,
|
||||||
|
}}
|
||||||
|
locale={{
|
||||||
|
emptyText: <EmptyTab tab="mlModel" />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ModelsContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import * as QueryString from 'query-string';
|
import * as QueryString from 'query-string';
|
||||||
import { useHistory, useLocation } from 'react-router';
|
import { useHistory, useLocation } from 'react-router';
|
||||||
import { ApolloError } from '@apollo/client';
|
import { ApolloError } from '@apollo/client';
|
||||||
import { FacetFilterInput } from '../../../../../../types.generated';
|
import { EntityType, FacetFilterInput } from '../../../../../../types.generated';
|
||||||
import useFilters from '../../../../../search/utils/useFilters';
|
import useFilters from '../../../../../search/utils/useFilters';
|
||||||
import { navigateToEntitySearchUrl } from './navigateToEntitySearchUrl';
|
import { navigateToEntitySearchUrl } from './navigateToEntitySearchUrl';
|
||||||
import { FilterSet, GetSearchResultsParams, SearchResultsInterface } from './types';
|
import { FilterSet, GetSearchResultsParams, SearchResultsInterface } from './types';
|
||||||
@ -16,6 +16,30 @@ import {
|
|||||||
} from '../../../../../search/utils/types';
|
} from '../../../../../search/utils/types';
|
||||||
|
|
||||||
const FILTER = 'filter';
|
const FILTER = 'filter';
|
||||||
|
const SEARCH_ENTITY_TYPES = [
|
||||||
|
EntityType.Dataset,
|
||||||
|
EntityType.Dashboard,
|
||||||
|
EntityType.Chart,
|
||||||
|
EntityType.Mlmodel,
|
||||||
|
EntityType.MlmodelGroup,
|
||||||
|
EntityType.MlfeatureTable,
|
||||||
|
EntityType.Mlfeature,
|
||||||
|
EntityType.MlprimaryKey,
|
||||||
|
EntityType.DataFlow,
|
||||||
|
EntityType.DataJob,
|
||||||
|
EntityType.GlossaryTerm,
|
||||||
|
EntityType.GlossaryNode,
|
||||||
|
EntityType.Tag,
|
||||||
|
EntityType.Role,
|
||||||
|
EntityType.CorpUser,
|
||||||
|
EntityType.CorpGroup,
|
||||||
|
EntityType.Container,
|
||||||
|
EntityType.Domain,
|
||||||
|
EntityType.DataProduct,
|
||||||
|
EntityType.Notebook,
|
||||||
|
EntityType.BusinessAttribute,
|
||||||
|
EntityType.DataProcessInstance,
|
||||||
|
];
|
||||||
|
|
||||||
function getParamsWithoutFilters(params: QueryString.ParsedQuery<string>) {
|
function getParamsWithoutFilters(params: QueryString.ParsedQuery<string>) {
|
||||||
const paramsCopy = { ...params };
|
const paramsCopy = { ...params };
|
||||||
@ -137,6 +161,7 @@ export const EmbeddedListSearchSection = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<EmbeddedListSearch
|
<EmbeddedListSearch
|
||||||
|
entityTypes={SEARCH_ENTITY_TYPES}
|
||||||
query={query || ''}
|
query={query || ''}
|
||||||
page={page}
|
page={page}
|
||||||
unionType={unionType}
|
unionType={unionType}
|
||||||
|
@ -82,6 +82,10 @@ export const EMPTY_MESSAGES = {
|
|||||||
title: 'No business attributes added yet',
|
title: 'No business attributes added yet',
|
||||||
description: 'Add business attributes to entities to classify their data.',
|
description: 'Add business attributes to entities to classify their data.',
|
||||||
},
|
},
|
||||||
|
mlModel: {
|
||||||
|
title: 'No ML models',
|
||||||
|
description: 'ML models will appear here if they are associated with this ML model group.',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ELASTIC_MAX_COUNT = 10000;
|
export const ELASTIC_MAX_COUNT = 10000;
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
import {
|
||||||
|
formatDetailedDuration,
|
||||||
|
formatDuration,
|
||||||
|
toLocalDateTimeString,
|
||||||
|
toRelativeTimeString,
|
||||||
|
} from '@app/shared/time/timeUtils';
|
||||||
|
import { Pill, Popover } from '@components';
|
||||||
|
import { Maybe } from 'graphql/jsutils/Maybe';
|
||||||
|
import { capitalize } from 'lodash';
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import colors from '@src/alchemy-components/theme/foundations/colors';
|
||||||
|
import { DataProcessInstanceRunResultType } from '../../types.generated';
|
||||||
|
|
||||||
|
const StatContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
margin-top: 40px;
|
||||||
|
height: 25px;
|
||||||
|
padding-left: 20px;
|
||||||
|
color: ${colors.gray[500]};
|
||||||
|
width: 150px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PopoverContent = styled.div`
|
||||||
|
color: ${colors.gray[500]};
|
||||||
|
font-size: 0.8rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Title = styled.div`
|
||||||
|
color: ${colors.gray[500]};
|
||||||
|
border-bottom: none;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const popoverStyles = {
|
||||||
|
overlayInnerStyle: {
|
||||||
|
borderRadius: '10px',
|
||||||
|
},
|
||||||
|
overlayStyle: {
|
||||||
|
margin: '5px',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
startTime: Maybe<number>;
|
||||||
|
duration: Maybe<number>;
|
||||||
|
status: Maybe<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DataProcessInstanceRightColumn({ startTime, duration, status }: Props) {
|
||||||
|
const statusPillColor = status === DataProcessInstanceRunResultType.Success ? 'green' : 'red';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{startTime && (
|
||||||
|
<Popover
|
||||||
|
content={<PopoverContent>{toLocalDateTimeString(startTime)}</PopoverContent>}
|
||||||
|
title={<Title>Start Time</Title>}
|
||||||
|
trigger="hover"
|
||||||
|
overlayInnerStyle={popoverStyles.overlayInnerStyle}
|
||||||
|
overlayStyle={popoverStyles.overlayStyle}
|
||||||
|
>
|
||||||
|
<StatContainer>{toRelativeTimeString(startTime)}</StatContainer>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
{duration && (
|
||||||
|
<Popover
|
||||||
|
content={<PopoverContent>{formatDetailedDuration(duration)}</PopoverContent>}
|
||||||
|
title={<Title>Duration</Title>}
|
||||||
|
trigger="hover"
|
||||||
|
overlayInnerStyle={popoverStyles.overlayInnerStyle}
|
||||||
|
overlayStyle={popoverStyles.overlayStyle}
|
||||||
|
>
|
||||||
|
<StatContainer>{formatDuration(duration)}</StatContainer>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
{status && (
|
||||||
|
<>
|
||||||
|
<StatContainer>
|
||||||
|
<Pill label={capitalize(status)} colorScheme={statusPillColor} clickable={false} />
|
||||||
|
</StatContainer>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
|
import DataProcessInstanceRightColumn from '@app/preview/DataProcessInstanceRightColumn';
|
||||||
import React, { ReactNode, useState } from 'react';
|
import React, { ReactNode, useState } from 'react';
|
||||||
import { Divider, Tooltip, Typography } from 'antd';
|
import { Divider, Tooltip, Typography } from 'antd';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GlobalTags,
|
GlobalTags,
|
||||||
Owner,
|
Owner,
|
||||||
@ -200,6 +200,9 @@ interface Props {
|
|||||||
paths?: EntityPath[];
|
paths?: EntityPath[];
|
||||||
health?: Health[];
|
health?: Health[];
|
||||||
parentDataset?: Dataset;
|
parentDataset?: Dataset;
|
||||||
|
startTime?: number | null;
|
||||||
|
duration?: number | null;
|
||||||
|
status?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DefaultPreviewCard({
|
export default function DefaultPreviewCard({
|
||||||
@ -243,6 +246,9 @@ export default function DefaultPreviewCard({
|
|||||||
paths,
|
paths,
|
||||||
health,
|
health,
|
||||||
parentDataset,
|
parentDataset,
|
||||||
|
startTime,
|
||||||
|
duration,
|
||||||
|
status,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
// sometimes these lists will be rendered inside an entity container (for example, in the case of impact analysis)
|
// sometimes these lists will be rendered inside an entity container (for example, in the case of impact analysis)
|
||||||
// in those cases, we may want to enrich the preview w/ context about the container entity
|
// in those cases, we may want to enrich the preview w/ context about the container entity
|
||||||
@ -270,7 +276,8 @@ export default function DefaultPreviewCard({
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldShowRightColumn = (topUsers && topUsers.length > 0) || (owners && owners.length > 0);
|
const shouldShowRightColumn =
|
||||||
|
(topUsers && topUsers.length > 0) || (owners && owners.length > 0) || startTime || duration || status;
|
||||||
const uniqueOwners = getUniqueOwners(owners);
|
const uniqueOwners = getUniqueOwners(owners);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -380,6 +387,7 @@ export default function DefaultPreviewCard({
|
|||||||
</LeftColumn>
|
</LeftColumn>
|
||||||
{shouldShowRightColumn && (
|
{shouldShowRightColumn && (
|
||||||
<RightColumn key="right-column">
|
<RightColumn key="right-column">
|
||||||
|
<DataProcessInstanceRightColumn startTime={startTime} duration={duration} status={status} />
|
||||||
{topUsers && topUsers?.length > 0 && (
|
{topUsers && topUsers?.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<UserListContainer>
|
<UserListContainer>
|
||||||
|
@ -206,3 +206,41 @@ export function getTimeRangeDescription(startDate: moment.Moment | null, endDate
|
|||||||
|
|
||||||
return 'Unknown time range';
|
return 'Unknown time range';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatDuration(durationMs: number): string {
|
||||||
|
const duration = moment.duration(durationMs);
|
||||||
|
const hours = Math.floor(duration.asHours());
|
||||||
|
const minutes = duration.minutes();
|
||||||
|
const seconds = duration.seconds();
|
||||||
|
|
||||||
|
if (hours === 0 && minutes === 0) {
|
||||||
|
return `${seconds} secs`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hours === 0) {
|
||||||
|
return minutes === 1 ? `${minutes} min` : `${minutes} mins`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minuteStr = minutes > 0 ? ` ${minutes} mins` : '';
|
||||||
|
return hours === 1 ? `${hours} hr${minuteStr}` : `${hours} hrs${minuteStr}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatDetailedDuration(durationMs: number): string {
|
||||||
|
const duration = moment.duration(durationMs);
|
||||||
|
const hours = Math.floor(duration.asHours());
|
||||||
|
const minutes = duration.minutes();
|
||||||
|
const seconds = duration.seconds();
|
||||||
|
|
||||||
|
const parts: string[] = [];
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
parts.push(hours === 1 ? `${hours} hr` : `${hours} hrs`);
|
||||||
|
}
|
||||||
|
if (minutes > 0) {
|
||||||
|
parts.push(minutes === 1 ? `${minutes} min` : `${minutes} mins`);
|
||||||
|
}
|
||||||
|
if (seconds > 0) {
|
||||||
|
parts.push(`${seconds} secs`);
|
||||||
|
}
|
||||||
|
return parts.join(' ');
|
||||||
|
}
|
||||||
|
@ -897,6 +897,10 @@ fragment nonRecursiveMLModel on MLModel {
|
|||||||
key
|
key
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
mlModelLineageInfo {
|
||||||
|
trainingJobs
|
||||||
|
downstreamJobs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
globalTags {
|
globalTags {
|
||||||
...globalTagsFields
|
...globalTagsFields
|
||||||
@ -971,6 +975,14 @@ fragment nonRecursiveMLModelGroupFields on MLModelGroup {
|
|||||||
time
|
time
|
||||||
actor
|
actor
|
||||||
}
|
}
|
||||||
|
lastModified {
|
||||||
|
time
|
||||||
|
actor
|
||||||
|
}
|
||||||
|
mlModelLineageInfo {
|
||||||
|
trainingJobs
|
||||||
|
downstreamJobs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
browsePathV2 {
|
browsePathV2 {
|
||||||
...browsePathV2Fields
|
...browsePathV2Fields
|
||||||
|
@ -272,6 +272,7 @@ fragment lineageNodeProperties on EntityWithRelationships {
|
|||||||
removed
|
removed
|
||||||
}
|
}
|
||||||
properties {
|
properties {
|
||||||
|
propertiesName: name
|
||||||
createdTS: created {
|
createdTS: created {
|
||||||
time
|
time
|
||||||
actor
|
actor
|
||||||
@ -296,6 +297,9 @@ fragment lineageNodeProperties on EntityWithRelationships {
|
|||||||
name
|
name
|
||||||
description
|
description
|
||||||
origin
|
origin
|
||||||
|
tags {
|
||||||
|
...globalTagsFields
|
||||||
|
}
|
||||||
platform {
|
platform {
|
||||||
...platformFields
|
...platformFields
|
||||||
}
|
}
|
||||||
@ -305,6 +309,34 @@ fragment lineageNodeProperties on EntityWithRelationships {
|
|||||||
status {
|
status {
|
||||||
removed
|
removed
|
||||||
}
|
}
|
||||||
|
versionProperties {
|
||||||
|
versionSet {
|
||||||
|
urn
|
||||||
|
type
|
||||||
|
}
|
||||||
|
version {
|
||||||
|
versionTag
|
||||||
|
}
|
||||||
|
aliases {
|
||||||
|
versionTag
|
||||||
|
}
|
||||||
|
comment
|
||||||
|
}
|
||||||
|
properties {
|
||||||
|
propertiesName: name
|
||||||
|
createdTS: created {
|
||||||
|
time
|
||||||
|
actor
|
||||||
|
}
|
||||||
|
tags
|
||||||
|
customProperties {
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editableProperties {
|
||||||
|
description
|
||||||
|
}
|
||||||
structuredProperties {
|
structuredProperties {
|
||||||
properties {
|
properties {
|
||||||
...structuredPropertiesFields
|
...structuredPropertiesFields
|
||||||
|
@ -20,6 +20,23 @@ query getMLModel($urn: String!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
trainedBy: relationships(input: { types: ["TrainedBy"], direction: OUTGOING, start: 0, count: 100 }) {
|
||||||
|
start
|
||||||
|
count
|
||||||
|
total
|
||||||
|
relationships {
|
||||||
|
type
|
||||||
|
direction
|
||||||
|
entity {
|
||||||
|
... on DataProcessInstance {
|
||||||
|
urn
|
||||||
|
name
|
||||||
|
type
|
||||||
|
...dataProcessInstanceFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
privileges {
|
privileges {
|
||||||
...entityPrivileges
|
...entityPrivileges
|
||||||
}
|
}
|
||||||
|
@ -886,6 +886,9 @@ fragment searchResultsWithoutSchemaField on Entity {
|
|||||||
...structuredPropertiesFields
|
...structuredPropertiesFields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
properties {
|
||||||
|
propertiesName: name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
... on MLModelGroup {
|
... on MLModelGroup {
|
||||||
name
|
name
|
||||||
@ -908,6 +911,9 @@ fragment searchResultsWithoutSchemaField on Entity {
|
|||||||
...structuredPropertiesFields
|
...structuredPropertiesFields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
properties {
|
||||||
|
propertiesName: name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
... on Tag {
|
... on Tag {
|
||||||
name
|
name
|
||||||
@ -954,6 +960,9 @@ fragment searchResultsWithoutSchemaField on Entity {
|
|||||||
...versionProperties
|
...versionProperties
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
... on DataProcessInstance {
|
||||||
|
...dataProcessInstanceFields
|
||||||
|
}
|
||||||
... on DataPlatformInstance {
|
... on DataPlatformInstance {
|
||||||
...dataPlatformInstanceFields
|
...dataPlatformInstanceFields
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user