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

[Kedro-viz-890]/run-export-modal #898

Merged
merged 28 commits into from
Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
dccf5f2
run-export-modal component
Huongg Jun 9, 2022
1163132
pass state to RunExportModal
Huongg Jun 9, 2022
d0af3d5
move callback handle export to its own component
Huongg Jun 9, 2022
905caa9
import RunExportModal in Details component
Huongg Jun 9, 2022
8ee6f7c
remove console.log
Huongg Jun 9, 2022
f71d3aa
set fix width for primary button
Huongg Jun 9, 2022
364d90f
update timing
Huongg Jun 9, 2022
02fde8d
test for RunExportModal
Huongg Jun 9, 2022
146916e
run-export-modal component
Huongg Jun 9, 2022
bbab40a
pass state to RunExportModal
Huongg Jun 9, 2022
cb69bd2
move callback handle export to its own component
Huongg Jun 9, 2022
269731d
import RunExportModal in Details component
Huongg Jun 9, 2022
c6d733f
remove console.log
Huongg Jun 9, 2022
f968c68
set fix width for primary button
Huongg Jun 9, 2022
b12dc20
update timing
Huongg Jun 9, 2022
78ced62
test for RunExportModal
Huongg Jun 9, 2022
6339920
Merge branch 'kedro-viz-890/run-export-modal' of github.com:kedro-org…
Huongg Jun 10, 2022
b69ab00
update classname and label
Huongg Jun 13, 2022
c300702
create context for button timeout
Huongg Jun 13, 2022
86bca44
use ButtonTimeoutContext in RunExportModal
Huongg Jun 13, 2022
28e0ea8
use ButtonTimeoutContext in RunDetailsModal
Huongg Jun 13, 2022
f4a61d3
update test with mock test for Context
Huongg Jun 13, 2022
25f73af
Merge branch 'main'
Huongg Jun 13, 2022
a525385
use visible props instead
Huongg Jun 14, 2022
e025dd0
change hasInteracted to hasNotInteracted
Huongg Jun 14, 2022
f9a8993
update test to reflect with new props
Huongg Jun 14, 2022
4a0d649
sorting a-z
Huongg Jun 14, 2022
a4caf9e
update description for ButtonTimeoutContext
Huongg Jun 14, 2022
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
Binary file modified demo-project/data/session_store.db
Binary file not shown.
27 changes: 20 additions & 7 deletions src/components/experiment-tracking/details/details.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import classnames from 'classnames';
import RunMetadata from '../run-metadata';
import RunDataset from '../run-dataset';
import RunDetailsModal from '../run-details-modal';
import RunExportModal from '../run-export-modal.js';
import { ButtonTimeoutContextProvider } from '../../../utils/button-timeout-context';

import './details.css';

Expand All @@ -21,6 +23,8 @@ const Details = ({
sidebarVisible,
theme,
trackingDataError,
showRunExportModal,
setShowRunExportModal,
}) => {
const [runMetadataToEdit, setRunMetadataToEdit] = useState(null);

Expand All @@ -40,13 +44,22 @@ const Details = ({

return (
<>
<RunDetailsModal
runMetadataToEdit={runMetadataToEdit}
runs={runMetadata}
setShowRunDetailsModal={setShowRunDetailsModal}
theme={theme}
visible={showRunDetailsModal}
/>
<ButtonTimeoutContextProvider>
<RunDetailsModal
runMetadataToEdit={runMetadataToEdit}
runs={runMetadata}
setShowRunDetailsModal={setShowRunDetailsModal}
theme={theme}
visible={showRunDetailsModal}
/>
<RunExportModal
runMetadata={runMetadata}
runTrackingData={runTrackingData}
setShowRunExportModal={setShowRunExportModal}
theme={theme}
visible={showRunExportModal}
/>
</ButtonTimeoutContextProvider>
<div
className={classnames('kedro', 'details-mainframe', {
'details-mainframe--sidebar-visible': sidebarVisible,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useCallback, useState } from 'react';
import { CSVLink } from 'react-csv';
import React from 'react';
import { useUpdateRunDetails } from '../../../apollo/mutations';
import IconButton from '../../ui/icon-button';
import PencilIcon from '../../icons/pencil';
Expand All @@ -8,23 +7,20 @@ import ExportIcon from '../../icons/export';
import BookmarkStrokeIcon from '../../icons/bookmark-stroke';
import PrimaryToolbar from '../../primary-toolbar';
import ShowChangesIcon from '../../icons/show-changes';
import { constructExportData } from '../../../utils/experiment-tracking-utils';

export const ExperimentPrimaryToolbar = ({
displaySidebar,
enableComparisonView,
enableShowChanges,
runMetadata,
runTrackingData,
selectedRunData,
setEnableShowChanges,
setSidebarVisible,
showChangesIconDisabled,
showRunDetailsModal,
sidebarVisible,
setShowRunExportModal,
}) => {
const { updateRunDetails } = useUpdateRunDetails();
const [exportData, setExportData] = useState([]);

const toggleBookmark = () => {
updateRunDetails({
Expand All @@ -33,10 +29,6 @@ export const ExperimentPrimaryToolbar = ({
});
};

const updateExportData = useCallback(() => {
setExportData(constructExportData(runMetadata, runTrackingData));
}, [runMetadata, runTrackingData]);

return (
<PrimaryToolbar
displaySidebar={displaySidebar}
Expand Down Expand Up @@ -72,19 +64,13 @@ export const ExperimentPrimaryToolbar = ({
visible={enableComparisonView}
disabled={showChangesIconDisabled}
/>
<CSVLink
data={exportData}
asyncOnClick={true}
onClick={updateExportData}
filename="run-data.csv"
>
<IconButton
ariaLabel="Export graph as SVG or PNG"
className={'pipeline-menu-button--export'}
icon={ExportIcon}
labelText="Export run data"
/>
</CSVLink>
<IconButton
ariaLabel="Export Run Data"
className={'pipeline-menu-button--export-runs'}
icon={ExportIcon}
labelText="Export run data"
onClick={() => setShowRunExportModal(true)}
/>
</PrimaryToolbar>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useContext } from 'react';
import { useUpdateRunDetails } from '../../../apollo/mutations';

import { ButtonTimeoutContext } from '../../../utils/button-timeout-context';

import Button from '../../ui/button';
import Modal from '../../ui/modal';
import Input from '../../ui/input';
Expand All @@ -15,18 +17,26 @@ const RunDetailsModal = ({
visible,
}) => {
const [valuesToUpdate, setValuesToUpdate] = useState({});
const [hasNotInteracted, setHasNotInteracted] = useState(true);
const [editsAreSuccessful, setEditsAreSuccessful] = useState(false);
const { updateRunDetails, error, reset } = useUpdateRunDetails();
const {
handleClick,
hasNotInteracted,
isSuccessful,
setHasNotInteracted,
setIsSuccessful,
showModal,
} = useContext(ButtonTimeoutContext);

const onApplyChanges = () => {
updateRunDetails({
runId: runMetadataToEdit.id,
runInput: { notes: valuesToUpdate.notes, title: valuesToUpdate.title },
});

handleClick();

if (!error) {
setEditsAreSuccessful(true);
setIsSuccessful(true);
}
};

Expand All @@ -39,30 +49,12 @@ const RunDetailsModal = ({
setHasNotInteracted(false);
};

const resetState = () => {
setHasNotInteracted(true);
setEditsAreSuccessful(false);
};

// only if the component is visible first, then apply isSuccessful to show or hide modal
useEffect(() => {
let modalTimeout, resetTimeout;

if (editsAreSuccessful) {
modalTimeout = setTimeout(() => {
setShowRunDetailsModal(false);
}, 1500);

// Delay the reset so the user can't see the button text change.
resetTimeout = setTimeout(() => {
resetState();
}, 2000);
if (visible && isSuccessful) {
setShowRunDetailsModal(showModal);
}

return () => {
clearTimeout(modalTimeout);
clearTimeout(resetTimeout);
};
}, [editsAreSuccessful, setShowRunDetailsModal]);
}, [showModal, setShowRunDetailsModal, isSuccessful, visible]);

useEffect(() => {
setValuesToUpdate({
Expand All @@ -78,8 +70,7 @@ const RunDetailsModal = ({
* the next time the modal opens.
*/
reset();
setHasNotInteracted(true);
}, [reset, runMetadataToEdit, visible]);
}, [runMetadataToEdit, visible, setHasNotInteracted, reset]);

return (
<div className="pipeline-settings-modal pipeline-settings-modal--experiment-tracking">
Expand Down Expand Up @@ -124,10 +115,10 @@ const RunDetailsModal = ({
<Button
disabled={hasNotInteracted}
onClick={onApplyChanges}
mode={editsAreSuccessful ? 'success' : 'primary'}
mode={isSuccessful ? 'success' : 'primary'}
size="small"
>
{editsAreSuccessful ? (
{isSuccessful ? (
<>
Changes applied <span className="success-check-mark">✅</span>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';
import RunDetailsModal from './index';
import Adapter from 'enzyme-adapter-react-16';
import { configure, mount, shallow } from 'enzyme';
import { configure, mount } from 'enzyme';
import { render } from '@testing-library/react';
import { ButtonTimeoutContext } from '../../../utils/button-timeout-context';

configure({ adapter: new Adapter() });

Expand All @@ -22,27 +23,46 @@ jest.mock('../../../apollo/mutations', () => {
};
});

const mockValue = {
handleClick: jest.fn(),
hasNotInteracted: true,
isSuccessful: false,
setHasNotInteracted: jest.fn(),
setIsSuccessful: jest.fn(),
showModal: false,
};

// Tests

describe('RunDetailsModal', () => {
it('renders without crashing', () => {
const wrapper = shallow(<RunDetailsModal visible />);
const wrapper = mount(
<ButtonTimeoutContext.Provider value={mockValue}>
<RunDetailsModal visible />
</ButtonTimeoutContext.Provider>
);

expect(
wrapper.find('.pipeline-settings-modal--experiment-tracking').length
).toBe(1);
});

it('renders with a disabled primary button', () => {
const { getByText } = render(<RunDetailsModal visible />);
const { getByText } = render(
<ButtonTimeoutContext.Provider value={mockValue}>
<RunDetailsModal visible />
</ButtonTimeoutContext.Provider>
);

expect(getByText(/Apply changes and close/i)).toBeDisabled();
});

it('modal closes when cancel button is clicked', () => {
const setVisible = jest.fn();
const wrapper = mount(
<RunDetailsModal setShowRunDetailsModal={() => setVisible(true)} />
<ButtonTimeoutContext.Provider value={mockValue}>
<RunDetailsModal setShowRunDetailsModal={() => setVisible(true)} />
</ButtonTimeoutContext.Provider>
);
const onClick = jest.spyOn(React, 'useState');
const closeButton = wrapper.find(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import RunExportModal from './run-export-modal';

export default RunExportModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useState, useCallback, useContext, useEffect } from 'react';
import { CSVLink } from 'react-csv';

import { constructExportData } from '../../../utils/experiment-tracking-utils';
import { ButtonTimeoutContext } from '../../../utils/button-timeout-context';

import Button from '../../ui/button';
import Modal from '../../ui/modal';

import './run-export-modal.css';

const RunExportModal = ({
runMetadata,
runTrackingData,
setShowRunExportModal,
theme,
visible,
}) => {
const [exportData, setExportData] = useState([]);
const { isSuccessful, showModal, handleClick } =
useContext(ButtonTimeoutContext);

const updateExportData = useCallback(() => {
Copy link
Contributor Author

@Huongg Huongg Jun 9, 2022

Choose a reason for hiding this comment

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

I decided to leave
this function here locally in the component itself, rather than in the parent component experiment-primary-toolbar, or experiment-wrapper, as it's easier to manage local state, rather than passing it down through props

setExportData(constructExportData(runMetadata, runTrackingData));
}, [runMetadata, runTrackingData]);

// only if the component is visible first, then apply isSuccessful to show or hide modal
useEffect(() => {
if (visible && isSuccessful) {
setShowRunExportModal(showModal);
}
}, [showModal, setShowRunExportModal, isSuccessful, visible]);

return (
<div className="pipeline-run-export-modal pipeline-run-export-modal--experiment-tracking">
<Modal
closeModal={() => setShowRunExportModal(false)}
theme={theme}
title="Export experiment run"
visible={visible}
>
<div className="run-export-modal-button-wrapper">
<Button
mode="secondary"
onClick={() => setShowRunExportModal(false)}
size="small"
>
Cancel
</Button>
<CSVLink
asyncOnClick={true}
data={exportData}
filename="run-data.csv"
onClick={updateExportData}
>
<Button
mode={isSuccessful ? 'success' : 'primary'}
onClick={handleClick}
size="small"
>
{isSuccessful ? (
<>
Done <span className="success-check-mark">✅</span>
</>
) : (
'Export all and close'
)}
</Button>
</CSVLink>
</div>
</Modal>
</div>
);
};

export default RunExportModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.pipeline-run-export-modal--experiment-tracking .modal__title {
text-align: left;
margin-left: 30px;
}

.run-export-modal-button-wrapper {
display: flex;
justify-content: space-around;

Huongg marked this conversation as resolved.
Show resolved Hide resolved
width: 100%;
}

// set fix width for export button for when the text is shorter, eg "Done"
.pipeline-run-export-modal--experiment-tracking .button__btn--primary {
width: 160px;
}
Loading