Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show all run outputs in dedicated tab #496

Merged
merged 8 commits into from
Dec 7, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
pr comments
  • Loading branch information
Yasser Elsayed committed Dec 7, 2018
commit 77cafe83667e604632b1dc5341a77ac41e04159d
8 changes: 4 additions & 4 deletions frontend/src/lib/WorkflowParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,17 +163,17 @@ export default class WorkflowParser {
// Returns a list of output paths for the entire workflow, by searching all nodes in
// the workflow, and parsing outputs for each.
public static loadAllOutputPaths(workflow: Workflow): StoragePath[] {
return this.loadAllOutputPathsWithStepNames(workflow).map(entry => entry[1]);
return this.loadAllOutputPathsWithStepNames(workflow).map(entry => entry.path);
}

// Returns a list of object mapping a step name to output path for the entire workflow,
// by searching all nodes in the workflow, and parsing outputs for each.
public static loadAllOutputPathsWithStepNames(workflow: Workflow): Array<[string, StoragePath]> {
const outputPaths: Array<[string, StoragePath]> = [];
public static loadAllOutputPathsWithStepNames(workflow: Workflow): Array<{ stepName: string, path: StoragePath }> {
const outputPaths: Array<{ stepName: string, path: StoragePath }> = [];
if (workflow && workflow.status && workflow.status.nodes) {
Object.keys(workflow.status.nodes).forEach(n =>
this.loadNodeOutputPaths(workflow.status.nodes[n]).map(path =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't wait for a better way to iterate over values in a map like this 😷

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want an easier way. I found that lodash has map and iteratee methods that can be used here, but they don't actually look more readable.

outputPaths.push([workflow.status.nodes[n].displayName, path])));
outputPaths.push({ stepName: workflow.status.nodes[n].displayName, path })));
}

return outputPaths;
Expand Down
50 changes: 20 additions & 30 deletions frontend/src/pages/RunDetails.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ describe('RunDetails', () => {
const getRunSpy = jest.spyOn(Apis.runServiceApi, 'getRun');
const getExperimentSpy = jest.spyOn(Apis.experimentServiceApi, 'getExperiment');
const getPodLogsSpy = jest.spyOn(Apis, 'getPodLogs');
const pathsParser = jest.spyOn(WorkflowParser, 'loadAllOutputPathsWithStepNames');
const pathsWithStepsParser = jest.spyOn(WorkflowParser, 'loadNodeOutputPaths');
const pathsParser = jest.spyOn(WorkflowParser, 'loadNodeOutputPaths');
const pathsWithStepsParser = jest.spyOn(WorkflowParser, 'loadAllOutputPathsWithStepNames');
const loaderSpy = jest.spyOn(OutputArtifactLoader, 'load');

let testRun: ApiRunDetail = {};
Expand All @@ -60,10 +60,6 @@ describe('RunDetails', () => {
}

beforeAll(() => jest.spyOn(console, 'error').mockImplementation());
afterAll(() => {
jest.resetAllMocks();
tree.unmount();
});

beforeEach(() => {
testRun = {
Expand All @@ -82,23 +78,18 @@ describe('RunDetails', () => {
status: 'Succeeded',
},
};
historyPushSpy.mockClear();
updateBannerSpy.mockClear();
updateDialogSpy.mockClear();
updateSnackbarSpy.mockClear();
updateToolbarSpy.mockClear();
getRunSpy.mockReset();
getRunSpy.mockImplementation(() => Promise.resolve(testRun));
getExperimentSpy.mockClear();
getExperimentSpy.mockImplementation(() => Promise.resolve('{}'));
getPodLogsSpy.mockClear();
getPodLogsSpy.mockImplementation(() => 'test logs');
pathsParser.mockClear();
pathsParser.mockImplementation(() => []);
pathsWithStepsParser.mockClear();
pathsWithStepsParser.mockImplementation(() => []);
loaderSpy.mockClear();
loaderSpy.mockImplementation(() => Promise.resolve([]));
jest.clearAllMocks();
});

afterEach(() => {
jest.resetAllMocks();
tree.unmount();
});

it('shows success run status in page title', async () => {
Expand Down Expand Up @@ -221,21 +212,18 @@ describe('RunDetails', () => {
});

it('loads the run\'s outputs in the output tab', async () => {
jest.spyOn(WorkflowParser, 'loadAllOutputPathsWithStepNames').mockImplementation(() =>
[['step1', { source: 'gcs', bucket: 'somebucket', key: 'somekey' }]]);
jest.spyOn(WorkflowParser, 'loadNodeOutputPaths').mockImplementation(() =>
[{ source: 'gcs', bucket: 'somebucket', key: 'somekey' }]);
jest.spyOn(OutputArtifactLoader, 'load').mockImplementation(() =>
Promise.resolve([{ type: PlotType.TENSORBOARD, url: 'some url' }]));
pathsWithStepsParser.mockImplementation(() => [
{ stepName: 'step1', path: { source: 'gcs', bucket: 'somebucket', key: 'somekey' } }
]);
pathsParser.mockImplementation(() => [{ source: 'gcs', bucket: 'somebucket', key: 'somekey' }]);
loaderSpy.mockImplementation(() => Promise.resolve([{ type: PlotType.TENSORBOARD, url: 'some url' }]));
tree = shallow(<RunDetails {...generateProps()} />);
await getRunSpy;
await TestUtils.flushPromises();
tree.find('MD2Tabs').simulate('switch', 1);
expect(tree.state('selectedTab')).toBe(1);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();

jest.resetAllMocks();
});

it('switches to config tab', async () => {
Expand Down Expand Up @@ -360,19 +348,21 @@ describe('RunDetails', () => {
testRun.pipeline_runtime!.workflow_manifest = JSON.stringify({
status: { nodes: { node1: { id: 'node1', }, }, },
});
pathsParser.mockImplementation(() => [['step1', { source: 'gcs', bucket: 'somebucket', key: 'somekey' }]]);
pathsWithStepsParser.mockImplementation(() => [{ source: 'gcs', bucket: 'somebucket', key: 'somekey' }]);
pathsWithStepsParser.mockImplementation(() => [
{ stepName: 'step1', path: { source: 'gcs', bucket: 'somebucket', key: 'somekey' } }
]);
pathsParser.mockImplementation(() => [{ source: 'gcs', bucket: 'somebucket', key: 'somekey' }]);
loaderSpy.mockImplementation(() => Promise.resolve([{ type: PlotType.TENSORBOARD, url: 'some url' }]));
tree = shallow(<RunDetails {...generateProps()} />);
await getRunSpy;
await TestUtils.flushPromises();
expect(pathsParser).toHaveBeenCalledTimes(1); // Loading output list
tree.find('Graph').simulate('click', 'node1');
await pathsParser;
await pathsWithStepsParser;
await loaderSpy;
expect(pathsWithStepsParser).toHaveBeenCalledTimes(1);
expect(pathsWithStepsParser).toHaveBeenLastCalledWith({ id: 'node1' });
expect(pathsWithStepsParser).toHaveBeenCalledTimes(1); // Loading output list
expect(pathsParser).toHaveBeenCalledTimes(1);
expect(pathsParser).toHaveBeenLastCalledWith({ id: 'node1' });
expect(loaderSpy).toHaveBeenCalledTimes(2);
expect(loaderSpy).toHaveBeenLastCalledWith({ bucket: 'somebucket', key: 'somekey', source: 'gcs' });
expect(tree.state('selectedNodeDetails')).toMatchObject({ id: 'node1' });
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/RunDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ class RunDetails extends Page<RunDetailsProps, RunDetailsState> {
<div className={padding()}>
{!allArtifactConfigs.length && (
<span className={commonCss.absoluteCenter}>
Not output artifacts found for this run.
No output artifacts found for this run.
</span>
)}

Expand Down Expand Up @@ -321,7 +321,7 @@ class RunDetails extends Page<RunDetailsProps, RunDetailsState> {
const outputPathsList = WorkflowParser.loadAllOutputPathsWithStepNames(workflow);

const configLists = await Promise.all(outputPathsList.map(
([stepName, path]) => OutputArtifactLoader.load(path)
({ stepName, path }) => OutputArtifactLoader.load(path)
.then(configs => configs.map(config => ({ config, stepName })))));
const allArtifactConfigs = flatten(configLists);

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/__snapshots__/RunDetails.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1540,7 +1540,7 @@ exports[`RunDetails switches to run output tab, shows empty message 1`] = `
<span
className="absoluteCenter"
>
Not output artifacts found for this run.
No output artifacts found for this run.
</span>
</div>
</div>
Expand Down