Skip to content

Commit

Permalink
feat(frontend): Support v2 HTML/Markdown visualization. Fix #3970 (#6367
Browse files Browse the repository at this point in the history
)

* feat(frontend): Support v2 HTML/Markdown visualization.

* improve

* shorter name
  • Loading branch information
zijianjoy authored Aug 18, 2021
1 parent 61dedb1 commit 3e83f32
Showing 1 changed file with 186 additions and 14 deletions.
200 changes: 186 additions & 14 deletions frontend/src/components/viewers/MetricsVisualizations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import { useQuery } from 'react-query';
import { Array as ArrayRunType, Failure, Number, Record, String, ValidationError } from 'runtypes';
import IconWithTooltip from 'src/atoms/IconWithTooltip';
import { color, padding } from 'src/Css';
import { Apis } from 'src/lib/Apis';
import { OutputArtifactLoader } from 'src/lib/OutputArtifactLoader';
import WorkflowParser from 'src/lib/WorkflowParser';
import WorkflowParser, { StoragePath } from 'src/lib/WorkflowParser';
import { getMetadataValue } from 'src/mlmd/library';
import {
filterArtifactsByType,
Expand All @@ -33,6 +34,8 @@ import { Artifact, ArtifactType, Execution } from 'src/third_party/mlmd';
import Banner from '../Banner';
import PlotCard from '../PlotCard';
import ConfusionMatrix, { ConfusionMatrixConfig } from './ConfusionMatrix';
import { HTMLViewerConfig } from './HTMLViewer';
import { MarkdownViewerConfig } from './MarkdownViewer';
import PagedTable from './PagedTable';
import ROCCurve, { ROCCurveConfig } from './ROCCurve';
import { PlotType, ViewerConfig } from './Viewer';
Expand Down Expand Up @@ -60,11 +63,13 @@ export function MetricsVisualizations({
// If there is no available metrics, show banner to notify users.
// Otherwise, Visualize all available metrics per artifact.
const artifacts = linkedArtifacts.map(x => x.artifact);
const verifiedClassificationMetricsArtifacts = getVerifiedClassificationMetricsArtifacts(
const classificationMetricsArtifacts = getVerifiedClassificationMetricsArtifacts(
artifacts,
artifactTypes,
);
const verifiedMetricsArtifacts = getVerifiedMetricsArtifacts(artifacts, artifactTypes);
const metricsArtifacts = getVerifiedMetricsArtifacts(artifacts, artifactTypes);
const htmlArtifacts = getVertifiedHtmlArtifacts(linkedArtifacts, artifactTypes);
const mdArtifacts = getVertifiedMarkdownArtifacts(linkedArtifacts, artifactTypes);
const v1VisualizationArtifact = getV1VisualizationArtifacts(linkedArtifacts, artifactTypes);

const {
Expand All @@ -84,34 +89,110 @@ export function MetricsVisualizations({
{ staleTime: Infinity },
);

const { isSuccess: isHtmlDownloaded, error: htmlError, data: htmlViewerConfigs } = useQuery<
HTMLViewerConfig[],
Error
>(
[
'htmlViewerConfig',
{
artifacts: htmlArtifacts.map(linkedArtifact => {
return linkedArtifact.artifact.getId();
}),
state: execution.getLastKnownState(),
namespace: namespace,
},
],
() => getHtmlViewerConfig(htmlArtifacts, namespace),
{ staleTime: Infinity },
);

const {
isSuccess: isMarkdownDownloaded,
error: markdownError,
data: markdownViewerConfigs,
} = useQuery<MarkdownViewerConfig[], Error>(
[
'markdownViewerConfig',
{
artifacts: mdArtifacts.map(linkedArtifact => {
return linkedArtifact.artifact.getId();
}),
state: execution.getLastKnownState(),
namespace: namespace,
},
],
() => getMarkdownViewerConfig(mdArtifacts, namespace),
{ staleTime: Infinity },
);

if (
verifiedClassificationMetricsArtifacts.length === 0 &&
verifiedMetricsArtifacts.length === 0 &&
classificationMetricsArtifacts.length === 0 &&
metricsArtifacts.length === 0 &&
htmlArtifacts.length === 0 &&
mdArtifacts.length === 0 &&
!v1VisualizationArtifact
) {
return <Banner message='There is no metrics artifact available in this step.' mode='info' />;
}

return (
<>
{v1ViewerConfigError && (
<Banner
message='Error in retrieving v1 metrics information.'
mode='error'
additionalInfo={v1ViewerConfigError.message}
/>
)}
{verifiedClassificationMetricsArtifacts.map(artifact => {
{/* Shows first encountered issue on Banner */}

{(() => {
if (v1ViewerConfigError) {
return (
<Banner
message='Error in retrieving v1 metrics information.'
mode='error'
additionalInfo={v1ViewerConfigError.message}
/>
);
}
if (htmlError) {
return (
<Banner
message='Error in retrieving HTML visualization information.'
mode='error'
additionalInfo={htmlError.message}
/>
);
}
if (markdownError) {
return (
<Banner
message='Error in retrieving Markdown visualization information.'
mode='error'
additionalInfo={markdownError.message}
/>
);
}
return null;
})()}

{/* Shows visualizations of all kinds */}
{classificationMetricsArtifacts.map(artifact => {
return (
<React.Fragment key={artifact.getId()}>
<ConfidenceMetricsSection artifact={artifact} />
<ConfusionMatrixSection artifact={artifact} />
</React.Fragment>
);
})}
{verifiedMetricsArtifacts.map(artifact => (
{metricsArtifacts.map(artifact => (
<ScalarMetricsSection artifact={artifact} key={artifact.getId()} />
))}
{isHtmlDownloaded && htmlViewerConfigs && (
<div key={'html'} className={padding(20, 'lrt')}>
<PlotCard configs={htmlViewerConfigs} title={'Static HTML'} />
</div>
)}
{isMarkdownDownloaded && markdownViewerConfigs && (
<div key={'markdown'} className={padding(20, 'lrt')}>
<PlotCard configs={markdownViewerConfigs} title={'Static Markdown'} />
</div>
)}
{isV1ViewerConfigsSuccess &&
v1ViewerConfigs &&
v1ViewerConfigs.map((config, i) => {
Expand Down Expand Up @@ -185,6 +266,44 @@ function getVerifiedMetricsArtifacts(
);
}

function getVertifiedHtmlArtifacts(
linkedArtifacts: LinkedArtifact[],
artifactTypes: ArtifactType[],
): LinkedArtifact[] {
if (!linkedArtifacts || !artifactTypes) {
return [];
}
const htmlArtifacts = filterLinkedArtifactsByType('system.HTML', artifactTypes, linkedArtifacts);

return htmlArtifacts.filter(x =>
x.artifact
.getCustomPropertiesMap()
.get('display_name')
?.getStringValue(),
);
}

function getVertifiedMarkdownArtifacts(
linkedArtifacts: LinkedArtifact[],
artifactTypes: ArtifactType[],
): LinkedArtifact[] {
if (!linkedArtifacts || !artifactTypes) {
return [];
}
const htmlArtifacts = filterLinkedArtifactsByType(
'system.Markdown',
artifactTypes,
linkedArtifacts,
);

return htmlArtifacts.filter(x =>
x.artifact
.getCustomPropertiesMap()
.get('display_name')
?.getStringValue(),
);
}

function getV1VisualizationArtifacts(
linkedArtifacts: LinkedArtifact[],
artifactTypes: ArtifactType[],
Expand Down Expand Up @@ -432,6 +551,7 @@ function ScalarMetricsSection({ artifact }: ScalarMetricsSectionProps) {
</div>
);
}

async function getViewConfig(
v1VisualizationArtifact: LinkedArtifact | undefined,
namespace: string | undefined,
Expand All @@ -444,3 +564,55 @@ async function getViewConfig(
}
return [];
}

async function getHtmlViewerConfig(
htmlArtifacts: LinkedArtifact[] | undefined,
namespace: string | undefined,
): Promise<HTMLViewerConfig[]> {
if (!htmlArtifacts) {
return [];
}
const htmlViewerConfigs = htmlArtifacts.map(async linkedArtifact => {
const uri = linkedArtifact.artifact.getUri();
let storagePath: StoragePath | undefined;
if (!uri) {
throw new Error('HTML Artifact URI unknown');
}

storagePath = WorkflowParser.parseStoragePath(uri);
if (!storagePath) {
throw new Error('HTML Artifact storagePath unknown');
}

// TODO(zijianjoy): Limit the size of HTML file fetching to prevent UI frozen.
let data = await Apis.readFile(storagePath, namespace);
return { htmlContent: data, type: PlotType.WEB_APP } as HTMLViewerConfig;
});
return Promise.all(htmlViewerConfigs);
}

async function getMarkdownViewerConfig(
markdownArtifacts: LinkedArtifact[] | undefined,
namespace: string | undefined,
): Promise<MarkdownViewerConfig[]> {
if (!markdownArtifacts) {
return [];
}
const markdownViewerConfigs = markdownArtifacts.map(async linkedArtifact => {
const uri = linkedArtifact.artifact.getUri();
let storagePath: StoragePath | undefined;
if (!uri) {
throw new Error('Markdown Artifact URI unknown');
}

storagePath = WorkflowParser.parseStoragePath(uri);
if (!storagePath) {
throw new Error('Markdown Artifact storagePath unknown');
}

// TODO(zijianjoy): Limit the size of Markdown file fetching to prevent UI frozen.
let data = await Apis.readFile(storagePath, namespace);
return { markdownContent: data, type: PlotType.MARKDOWN } as MarkdownViewerConfig;
});
return Promise.all(markdownViewerConfigs);
}

0 comments on commit 3e83f32

Please sign in to comment.