From dc490a3254b263d67d93047d87f57f8dd5957d6d Mon Sep 17 00:00:00 2001 From: Stephanie Roy Date: Tue, 9 Aug 2022 11:04:33 -0400 Subject: [PATCH 1/5] Merge branch 'main' into large-drop-target --- .eslintrc.js | 3 +- extension/package.json | 4 +- extension/src/plots/paths/model.test.ts | 147 ++++++++++ extension/src/plots/paths/model.ts | 23 +- extension/src/test/e2e/wdio.conf.ts | 2 +- package.json | 8 +- webview/package.json | 7 +- webview/src/plots/components/App.test.tsx | 273 ++++++++++++------ webview/src/plots/components/Plots.tsx | 6 +- .../src/plots/components/PlotsContainer.tsx | 22 +- .../checkpointPlots/CheckpointPlots.tsx | 6 +- .../CheckpointPlotsWrapper.tsx | 15 +- .../comparisonTable/ComparisonTable.tsx | 5 + webview/src/plots/components/plotDataStore.ts | 2 +- .../src/plots/components/styles.module.scss | 4 + .../components/templatePlots/AddedSection.tsx | 21 +- .../templatePlots/TemplatePlots.tsx | 20 +- .../templatePlots/TemplatePlotsGrid.tsx | 5 +- .../components/dragDrop/DragDropContainer.tsx | 65 +++-- .../components/dragDrop/dragDropSlice.ts | 11 +- yarn.lock | 239 ++++++++------- 21 files changed, 624 insertions(+), 264 deletions(-) create mode 100644 extension/src/plots/paths/model.test.ts diff --git a/.eslintrc.js b/.eslintrc.js index cfcb12dafd..18ecf002ff 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -27,7 +27,8 @@ module.exports = { '@typescript-eslint/no-unsafe-call': 'off', '@typescript-eslint/no-unsafe-return': 'off', 'no-undef': 'off', - 'sonarjs/no-duplicate-string': 'off' + 'sonarjs/no-duplicate-string': 'off', + 'testing-library/no-render-in-setup': 'off' } }, { diff --git a/extension/package.json b/extension/package.json index 928068548b..5066a799ac 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1531,7 +1531,7 @@ "@types/lodash.omit": "4.5.7", "@types/mocha": "9.1.1", "@types/mock-require": "2.0.1", - "@types/node": "16.11.45", + "@types/node": "16.11.47", "@types/react-vega": "7.0.0", "@types/sinon-chai": "3.2.8", "@types/uuid": "8.3.4", @@ -1558,7 +1558,7 @@ "ts-loader": "9.3.1", "vsce": "2.10.0", "vscode-uri": "3.0.3", - "wdio-vscode-service": "4.0.3", + "wdio-vscode-service": "4.0.4", "webdriverio": "7.20.9", "webpack": "5.74.0", "webpack-cli": "4.10.0" diff --git a/extension/src/plots/paths/model.test.ts b/extension/src/plots/paths/model.test.ts new file mode 100644 index 0000000000..12cff1ce82 --- /dev/null +++ b/extension/src/plots/paths/model.test.ts @@ -0,0 +1,147 @@ +import { join } from 'path' +import { PathsModel } from './model' +import { PathType } from './collect' +import plotsDiffFixture from '../../test/fixtures/plotsDiff/output' +import { buildMockMemento } from '../../test/util' +import { TemplatePlotGroup } from '../webview/contract' + +describe('PathsModel', () => { + const mockDvcRoot = 'test' + + const logsAcc = join('logs', 'acc.tsv') + const logsLoss = join('logs', 'loss.tsv') + const plotsAcc = join('plots', 'acc.png') + + it('should return the expected columns when given the default output fixture', () => { + const comparisonType = new Set([PathType.COMPARISON]) + const singleType = new Set([PathType.TEMPLATE_SINGLE]) + const multiType = new Set([PathType.TEMPLATE_MULTI]) + + const model = new PathsModel(mockDvcRoot, buildMockMemento()) + model.transformAndSet(plotsDiffFixture) + expect(model.getTerminalNodes()).toStrictEqual([ + { + hasChildren: false, + label: 'acc.png', + parentPath: 'plots', + path: plotsAcc, + selected: true, + type: comparisonType + }, + { + hasChildren: false, + label: 'heatmap.png', + parentPath: 'plots', + path: join('plots', 'heatmap.png'), + selected: true, + type: comparisonType + }, + { + hasChildren: false, + label: 'loss.png', + parentPath: 'plots', + path: join('plots', 'loss.png'), + selected: true, + type: comparisonType + }, + { + hasChildren: false, + label: 'loss.tsv', + parentPath: 'logs', + path: logsLoss, + selected: true, + type: singleType + }, + { + hasChildren: false, + label: 'acc.tsv', + parentPath: 'logs', + path: logsAcc, + selected: true, + type: singleType + }, + { + hasChildren: false, + label: 'predictions.json', + parentPath: undefined, + path: 'predictions.json', + selected: true, + type: multiType + } + ]) + }) + + const multiViewGroup = { + group: TemplatePlotGroup.MULTI_VIEW, + paths: ['predictions.json'] + } + const originalSingleViewGroup = { + group: TemplatePlotGroup.SINGLE_VIEW, + paths: [logsLoss, logsAcc] + } + + const logsAccGroup = { + group: TemplatePlotGroup.SINGLE_VIEW, + paths: [logsAcc] + } + + const logsLossGroup = { + group: TemplatePlotGroup.SINGLE_VIEW, + paths: [logsLoss] + } + + const originalTemplateOrder = [originalSingleViewGroup, multiViewGroup] + + it('should retain the order of template paths when they are unselected', () => { + const model = new PathsModel(mockDvcRoot, buildMockMemento()) + model.transformAndSet(plotsDiffFixture) + + expect(model.getTemplateOrder()).toStrictEqual(originalTemplateOrder) + + model.toggleStatus(logsAcc) + + const newOrder = model.getTemplateOrder() + + expect(newOrder).toStrictEqual([logsLossGroup, multiViewGroup]) + + model.toggleStatus(logsAcc) + + expect(model.getTemplateOrder()).toStrictEqual(originalTemplateOrder) + }) + + it('should move unselected plots to the end when a reordering occurs', () => { + const model = new PathsModel(mockDvcRoot, buildMockMemento()) + model.transformAndSet(plotsDiffFixture) + + expect(model.getTemplateOrder()).toStrictEqual(originalTemplateOrder) + + model.toggleStatus(logsAcc) + + const newOrder = model.getTemplateOrder() + + expect(newOrder).toStrictEqual([logsLossGroup, multiViewGroup]) + + model.setTemplateOrder([multiViewGroup, logsLossGroup]) + + model.toggleStatus(logsAcc) + + expect(model.getTemplateOrder()).toStrictEqual([ + multiViewGroup, + { + group: TemplatePlotGroup.SINGLE_VIEW, + paths: [logsLoss, logsAcc] + } + ]) + }) + + it('should merge template plots groups when a path is unselected', () => { + const model = new PathsModel(mockDvcRoot, buildMockMemento()) + model.transformAndSet(plotsDiffFixture) + + model.setTemplateOrder([logsLossGroup, logsAccGroup, multiViewGroup]) + + model.toggleStatus('predictions.json') + + expect(model.getTemplateOrder()).toStrictEqual([originalSingleViewGroup]) + }) +}) diff --git a/extension/src/plots/paths/model.ts b/extension/src/plots/paths/model.ts index a16a3af1b4..c2496457f2 100644 --- a/extension/src/plots/paths/model.ts +++ b/extension/src/plots/paths/model.ts @@ -32,9 +32,12 @@ export class PathsModel extends PathSelectionModel { } public setTemplateOrder(templateOrder?: TemplateOrder) { + const filter = (type: PathType, plotPath: PlotPath) => + !!plotPath.type?.has(type) + this.templateOrder = collectTemplateOrder( - this.getPathsByType(PathType.TEMPLATE_SINGLE), - this.getPathsByType(PathType.TEMPLATE_MULTI), + this.getPathsByType(PathType.TEMPLATE_SINGLE, filter), + this.getPathsByType(PathType.TEMPLATE_MULTI, filter), templateOrder || this.templateOrder ) @@ -51,7 +54,11 @@ export class PathsModel extends PathSelectionModel { } public getTemplateOrder(): TemplateOrder { - return this.templateOrder + return collectTemplateOrder( + this.getPathsByType(PathType.TEMPLATE_SINGLE), + this.getPathsByType(PathType.TEMPLATE_MULTI), + this.templateOrder + ) } public getComparisonPaths() { @@ -62,11 +69,13 @@ export class PathsModel extends PathSelectionModel { return this.data.length > 0 } - private getPathsByType(type: PathType) { + private getPathsByType( + type: PathType, + filter = (type: PathType, plotPath: PlotPath) => + !!(plotPath.type?.has(type) && this.status[plotPath.path]) + ) { return this.data - .filter( - plotPath => plotPath.type?.has(type) && this.status[plotPath.path] - ) + .filter(plotPath => filter(type, plotPath)) .map(({ path }) => path) } } diff --git a/extension/src/test/e2e/wdio.conf.ts b/extension/src/test/e2e/wdio.conf.ts index 00f7b66530..e334c47ce4 100644 --- a/extension/src/test/e2e/wdio.conf.ts +++ b/extension/src/test/e2e/wdio.conf.ts @@ -30,7 +30,7 @@ export const config: Options.Testrunner = { capabilities: [ { browserName: 'vscode', - browserVersion: 'insiders', + browserVersion: 'stable', 'wdio:vscodeOptions': { extensionPath: resolve(__dirname, '..', '..', '..'), userSettings: { diff --git a/package.json b/package.json index faad52a268..bc11dc6ced 100644 --- a/package.json +++ b/package.json @@ -36,14 +36,14 @@ "@typescript-eslint/eslint-plugin": "5.31.0", "@typescript-eslint/parser": "5.31.0", "@vscode/codicons": "0.0.31", - "eslint": "8.20.0", + "eslint": "8.21.0", "eslint-config-prettier": "8.5.0", "eslint-config-prettier-standard": "4.0.1", "eslint-config-standard": "17.0.0", "eslint-plugin-check-file": "1.2.2", "eslint-plugin-etc": "2.0.2", "eslint-plugin-import": "2.26.0", - "eslint-plugin-jest": "26.6.0", + "eslint-plugin-jest": "26.7.0", "eslint-plugin-jsx-a11y": "6.6.1", "eslint-plugin-n": "15.2.4", "eslint-plugin-node": "11.1.0", @@ -53,7 +53,7 @@ "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-sonarjs": "0.14.0", "eslint-plugin-sort-keys-fix": "1.1.2", - "eslint-plugin-testing-library": "5.5.1", + "eslint-plugin-testing-library": "5.6.0", "eslint-plugin-unicorn": "43.0.2", "husky": "8.0.1", "jest": "28.1.3", @@ -63,7 +63,7 @@ "prettier": "2.7.1", "prettier-config-standard": "5.0.0", "ts-node": "10.9.1", - "turbo": "1.3.4", + "turbo": "1.4.0", "typescript": "4.7.4" }, "resolutions": { diff --git a/webview/package.json b/webview/package.json index bec4ef6731..3c1b04ef9c 100644 --- a/webview/package.json +++ b/webview/package.json @@ -39,7 +39,7 @@ "vega-lite": "^5.2.0" }, "devDependencies": { - "@storybook/testing-library": "^0.0.13", + "@storybook/testing-library": "0.0.13", "@storybook/addon-essentials": "6.5.9", "@storybook/addon-interactions": "6.5.9", "@storybook/addons": "6.5.9", @@ -47,14 +47,13 @@ "@storybook/manager-webpack5": "6.5.9", "@storybook/preset-scss": "1.0.3", "@storybook/react": "6.5.9", - "@storybook/testing-library": "0.0.13", "@svgr/cli": "6.3.1", "@testing-library/jest-dom": "5.16.4", "@testing-library/react": "13.3.0", "@types/classnames": "2.3.1", "@types/jest": "28.1.6", "@types/jsdom": "20.0.0", - "@types/node": "16.11.45", + "@types/node": "16.11.47", "@types/react": "18.0.15", "@types/react-dom": "18.0.6", "@types/react-measure": "2.0.8", @@ -62,7 +61,7 @@ "@types/react-virtualized": "9.21.21", "@types/webpack": "5.28.0", "@welldone-software/why-did-you-render": "7.0.1", - "chromatic": "6.7.1", + "chromatic": "6.7.2", "clean-webpack-plugin": "4.0.0", "css-loader": "6.7.1", "file-loader": "6.2.0", diff --git a/webview/src/plots/components/App.test.tsx b/webview/src/plots/components/App.test.tsx index f9a7358b33..381a07a18f 100644 --- a/webview/src/plots/components/App.test.tsx +++ b/webview/src/plots/components/App.test.tsx @@ -3,7 +3,7 @@ */ import { join } from 'dvc/src/test/util/path' import { configureStore } from '@reduxjs/toolkit' -import React from 'react' +import React, { ReactElement } from 'react' import { Provider } from 'react-redux' import { cleanup, @@ -25,7 +25,8 @@ import { PlotSize, Section, TemplatePlotGroup, - TemplatePlotsData + TemplatePlotsData, + TemplatePlotSection } from 'dvc/src/plots/webview/contract' import { MessageFromWebviewType, @@ -36,6 +37,7 @@ import { act } from 'react-dom/test-utils' import { App } from './App' import { NewSectionBlock } from './templatePlots/TemplatePlots' import { SectionDescription } from './PlotsContainer' +import { CheckpointPlotsById, plotDataStore } from './plotDataStore' import { plotsReducers } from '../store' import { vsCodeApi } from '../../shared/api' import { createBubbledEvent, dragAndDrop, dragEnter } from '../../test/dragDrop' @@ -68,40 +70,13 @@ const originalOffsetWidth = Object.getOwnPropertyDescriptor( 'offsetWidth' )?.value -beforeAll(() => { - Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { - configurable: true, - value: 50 - }) - Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { - configurable: true, - value: 50 - }) -}) - -beforeEach(() => { - jest.clearAllMocks() - jest - .spyOn(HTMLElement.prototype, 'clientHeight', 'get') - .mockImplementation(() => heightToSuppressVegaError) -}) - -afterEach(() => { - cleanup() -}) - -afterAll(() => { - Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { - configurable: true, - value: originalOffsetHeight - }) - Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { - configurable: true, - value: originalOffsetWidth - }) -}) - describe('App', () => { + const sectionPosition = { + [Section.CHECKPOINT_PLOTS]: 2, + [Section.TEMPLATE_PLOTS]: 0, + [Section.COMPARISON_TABLE]: 1 + } + const sendSetDataMessage = (data: PlotsData) => { const message = new MessageEvent('message', { data: { @@ -113,7 +88,8 @@ describe('App', () => { } const renderAppWithOptionalData = (data?: PlotsData) => { - const store = configureStore({ reducer: plotsReducers }) + const store = configureStore({ reducer: { ...plotsReducers } }) + render( @@ -140,6 +116,50 @@ describe('App', () => { ] } as TemplatePlotsData + const getCheckpointMenuItem = (position: number) => + within( + screen.getAllByTestId('plots-container')[ + sectionPosition[Section.CHECKPOINT_PLOTS] + ] + ).getAllByTestId('icon-menu-item')[position] + + const getCheckpointSizePickerButton = () => getCheckpointMenuItem(1) + + beforeAll(() => { + Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { + configurable: true, + value: 50 + }) + Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { + configurable: true, + value: 50 + }) + }) + + beforeEach(() => { + jest.clearAllMocks() + jest + .spyOn(HTMLElement.prototype, 'clientHeight', 'get') + .mockImplementation(() => heightToSuppressVegaError) + plotDataStore.checkpoint = {} as CheckpointPlotsById + plotDataStore.template = [] as TemplatePlotSection[] + }) + + afterEach(() => { + cleanup() + }) + + afterAll(() => { + Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { + configurable: true, + value: originalOffsetHeight + }) + Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { + configurable: true, + value: originalOffsetWidth + }) + }) + it('should send the initialized message on first render', () => { renderAppWithOptionalData() expect(mockPostMessage).toHaveBeenCalledWith({ @@ -192,29 +212,27 @@ describe('App', () => { }) }) - it('should render only checkpoint plots when given a message with only checkpoint plots data', () => { + it('should render other sections given a message with only checkpoint plots data', () => { renderAppWithOptionalData({ checkpoint: checkpointPlotsFixture }) expect(screen.queryByText('Loading Plots...')).not.toBeInTheDocument() expect(screen.getByText('Trends')).toBeInTheDocument() - expect(screen.queryByText('Data Series')).not.toBeInTheDocument() - expect(screen.queryByText('Images')).not.toBeInTheDocument() + expect(screen.getByText('Data Series')).toBeInTheDocument() + expect(screen.getByText('Images')).toBeInTheDocument() + expect(screen.getByText('No Plots to Display')).toBeInTheDocument() + expect(screen.getByText('No Images to Compare')).toBeInTheDocument() }) - it('should render checkpoint and template plots when given messages with both types of plots data', () => { + it('should render checkpoint even when there is no checkpoint plots data', () => { renderAppWithOptionalData({ - checkpoint: checkpointPlotsFixture - }) - - sendSetDataMessage({ template: templatePlotsFixture }) expect(screen.queryByText('Loading Plots...')).not.toBeInTheDocument() expect(screen.getByText('Trends')).toBeInTheDocument() - expect(screen.getByText('Data Series')).toBeInTheDocument() + expect(screen.getByText('No Plots to Display')).toBeInTheDocument() }) it('should render the comparison table when given a message with comparison plots data', () => { @@ -281,8 +299,7 @@ describe('App', () => { it('should toggle the visibility of plots when clicking the metrics in the metrics picker', async () => { renderAppWithOptionalData({ - checkpoint: checkpointPlotsFixture, - template: null + checkpoint: checkpointPlotsFixture }) const summaryElement = await screen.findByText('Trends') @@ -293,7 +310,7 @@ describe('App', () => { expect(screen.getByTestId('plot-summary.json:loss')).toBeInTheDocument() - const [pickerButton] = screen.queryAllByTestId('icon-menu-item') + const pickerButton = getCheckpointMenuItem(0) fireEvent.mouseEnter(pickerButton) fireEvent.click(pickerButton) @@ -326,7 +343,7 @@ describe('App', () => { checkpoint: checkpointPlotsFixture }) - const [pickerButton] = screen.getAllByTestId('icon-menu-item') + const pickerButton = getCheckpointMenuItem(0) fireEvent.mouseEnter(pickerButton) fireEvent.click(pickerButton) @@ -366,8 +383,13 @@ describe('App', () => { renderAppWithOptionalData({ checkpoint: checkpointPlotsFixture }) + const position = sectionPosition[Section.CHECKPOINT_PLOTS] + const getWrapper = async () => { + const wrappers = await screen.findAllByTestId('plots-wrapper') + return wrappers[position] + } - const sizePickerButton = screen.getAllByTestId('icon-menu-item')[1] + const sizePickerButton = getCheckpointSizePickerButton() fireEvent.mouseEnter(sizePickerButton) fireEvent.click(sizePickerButton) @@ -376,15 +398,15 @@ describe('App', () => { const largeButton = screen.getByText('Large') fireEvent.click(smallButton) - let wrapper = await screen.findByTestId('plots-wrapper') + let wrapper = await getWrapper() expect(wrapper).toHaveClass('smallPlots') fireEvent.click(regularButton) - wrapper = await screen.findByTestId('plots-wrapper') + wrapper = await getWrapper() expect(wrapper).toHaveClass('regularPlots') fireEvent.click(largeButton) - wrapper = await screen.findByTestId('plots-wrapper') + wrapper = await getWrapper() expect(wrapper).toHaveClass('largePlots') }) @@ -393,7 +415,7 @@ describe('App', () => { checkpoint: checkpointPlotsFixture }) - const sizeButton = screen.getAllByTestId('icon-menu-item')[1] + const sizeButton = getCheckpointSizePickerButton() fireEvent.mouseEnter(sizeButton) fireEvent.click(sizeButton) @@ -419,7 +441,7 @@ describe('App', () => { checkpoint: checkpointPlotsFixture }) - const sizeButton = screen.getAllByTestId('icon-menu-item')[1] + const sizeButton = getCheckpointSizePickerButton() fireEvent.mouseEnter(sizeButton) fireEvent.click(sizeButton) @@ -527,7 +549,12 @@ describe('App', () => { checkpoint: checkpointPlotsFixture }) - const [pickerButton] = screen.queryAllByTestId('icon-menu-item') + const [pickerButton] = within( + screen.getAllByTestId('plots-container')[ + sectionPosition[Section.CHECKPOINT_PLOTS] + ] + ).queryAllByTestId('icon-menu-item') + fireEvent.mouseEnter(pickerButton) fireEvent.click(pickerButton) @@ -867,6 +894,43 @@ describe('App', () => { expect(plots[1].style.display).toBe('none') }) + it('should remove the drop target after exiting a section after dragging in and out of it', () => { + renderAppWithOptionalData({ + template: complexTemplatePlotsFixture + }) + + const movingPlotId = join('plot_other', 'plot.tsv') + + const bottomSection = screen.getByTestId(NewSectionBlock.BOTTOM) + const aSingleViewPlot = screen.getByTestId(movingPlotId) + + dragAndDrop(aSingleViewPlot, bottomSection) + + const movedPlot = screen.getByTestId(movingPlotId) + const otherSingleSection = screen.getByTestId(join('plot_logs', 'loss.tsv')) + + dragEnter(movedPlot, otherSingleSection.id, DragEnterDirection.LEFT) + + const topSection = screen.getByTestId('plots-section_template-single_0') + + let topSectionPlots = within(topSection) + .getAllByTestId(/^plot_/) + .map(plot => plot.id) + expect(topSectionPlots.includes('plot-drop-target')).toBe(true) + + const previousSection = screen.getByTestId( + 'plots-section_template-single_2' + ) + act(() => { + previousSection.dispatchEvent(createBubbledEvent('dragenter')) + }) + + topSectionPlots = within(topSection) + .getAllByTestId(/^plot_/) + .map(plot => plot.id) + expect(topSectionPlots.includes('plot-drop-target')).toBe(false) + }) + it('should open a modal with the plot zoomed in when clicking a template plot', () => { renderAppWithOptionalData({ template: complexTemplatePlotsFixture @@ -953,49 +1017,70 @@ describe('App', () => { const [templateInfo, comparisonInfo, checkpointInfo] = screen.getAllByTestId('info-tooltip-toggle') + const getSectionText = (sectionNode: ReactElement) => + // eslint-disable-next-line testing-library/no-node-access + sectionNode.props.children || '' + fireEvent.mouseEnter(templateInfo, { bubbles: true }) expect( - screen.getByText(SectionDescription[Section.TEMPLATE_PLOTS]) + screen.getByText( + getSectionText(SectionDescription[Section.TEMPLATE_PLOTS]) + ) ).toBeInTheDocument() fireEvent.mouseEnter(comparisonInfo, { bubbles: true }) expect( - screen.getByText(SectionDescription[Section.COMPARISON_TABLE]) + screen.getByText( + getSectionText(SectionDescription[Section.COMPARISON_TABLE]) + ) ).toBeInTheDocument() fireEvent.mouseEnter(checkpointInfo, { bubbles: true }) expect( - screen.getByText(SectionDescription[Section.CHECKPOINT_PLOTS]) + screen.getByText( + getSectionText(SectionDescription[Section.CHECKPOINT_PLOTS]) + ) ).toBeInTheDocument() }) describe('Virtualization', () => { - const changeSize = async (size: string, buttonPosition: number) => { + const changeSize = async ( + size: string, + buttonPosition: number, + wrapper: HTMLElement + ) => { const sizePickerButton = - screen.getAllByTestId('icon-menu-item')[buttonPosition] + within(wrapper).getAllByTestId('icon-menu-item')[buttonPosition] fireEvent.mouseEnter(sizePickerButton) fireEvent.click(sizePickerButton) - const sizeButton = await screen.findByText(size) + const sizeButton = await within(wrapper).findByText(size) fireEvent.click(sizeButton) - await screen.findByTestId('plots-wrapper') + await screen.findAllByTestId('plots-wrapper') fireEvent.click(sizePickerButton) - await screen.findByTestId('plots-wrapper') + await screen.findAllByTestId('plots-wrapper') } const renderAppAndChangeSize = async ( data: PlotsData, size: string, - buttonPosition: number + section: Section ) => { renderAppWithOptionalData({ ...data, sectionCollapsed: DEFAULT_SECTION_COLLAPSED }) - await screen.findByTestId('plots-wrapper') - await changeSize(size, buttonPosition) + const sectionButtonPosition = { + [Section.CHECKPOINT_PLOTS]: 1, + [Section.TEMPLATE_PLOTS]: 0, + [Section.COMPARISON_TABLE]: 0 + } + const wrappers = await screen.findAllByTestId('plots-container') + const wrapper = wrappers[sectionPosition[section]] + + await changeSize(size, sectionButtonPosition[section], wrapper) } const createCheckpointPlots = (nbOfPlots: number) => { @@ -1025,7 +1110,7 @@ describe('App', () => { await renderAppAndChangeSize( { checkpoint: createCheckpointPlots(11) }, 'Large', - 1 + Section.CHECKPOINT_PLOTS ) expect(screen.getByRole('grid')).toBeInTheDocument() @@ -1034,7 +1119,7 @@ describe('App', () => { checkpoint: createCheckpointPlots(50) }) - await screen.findByTestId('plots-wrapper') + await screen.findAllByTestId('plots-wrapper') expect(screen.getByRole('grid')).toBeInTheDocument() }) @@ -1043,7 +1128,7 @@ describe('App', () => { await renderAppAndChangeSize( { checkpoint: createCheckpointPlots(10) }, 'Large', - 1 + Section.CHECKPOINT_PLOTS ) expect(screen.queryByRole('grid')).not.toBeInTheDocument() @@ -1052,7 +1137,7 @@ describe('App', () => { checkpoint: createCheckpointPlots(1) }) - await screen.findByTestId('plots-wrapper') + await screen.findAllByTestId('plots-wrapper') expect(screen.queryByRole('grid')).not.toBeInTheDocument() }) @@ -1061,7 +1146,7 @@ describe('App', () => { await renderAppAndChangeSize( { template: manyTemplatePlots(11) }, 'Large', - 0 + Section.TEMPLATE_PLOTS ) expect(screen.getByRole('grid')).toBeInTheDocument() @@ -1070,7 +1155,7 @@ describe('App', () => { template: manyTemplatePlots(50) }) - await screen.findByTestId('plots-wrapper') + await screen.findAllByTestId('plots-wrapper') expect(screen.getByRole('grid')).toBeInTheDocument() }) @@ -1079,7 +1164,7 @@ describe('App', () => { await renderAppAndChangeSize( { template: manyTemplatePlots(10) }, 'Large', - 0 + Section.TEMPLATE_PLOTS ) expect(screen.queryByRole('grid')).not.toBeInTheDocument() @@ -1088,7 +1173,7 @@ describe('App', () => { template: manyTemplatePlots(1) }) - await screen.findByTestId('plots-wrapper') + await screen.findAllByTestId('plots-wrapper') expect(screen.queryByRole('grid')).not.toBeInTheDocument() }) @@ -1097,8 +1182,11 @@ describe('App', () => { const checkpoint = createCheckpointPlots(25) beforeEach(async () => { - // eslint-disable-next-line testing-library/no-render-in-setup - await renderAppAndChangeSize({ checkpoint }, 'Large', 1) + await renderAppAndChangeSize( + { checkpoint }, + 'Large', + Section.CHECKPOINT_PLOTS + ) }) it('should render one large plot per row per 1000px of screen when the screen is larger than 2000px', () => { @@ -1150,7 +1238,7 @@ describe('App', () => { await renderAppAndChangeSize( { checkpoint: createCheckpointPlots(16) }, 'Regular', - 1 + Section.CHECKPOINT_PLOTS ) expect(screen.getByRole('grid')).toBeInTheDocument() @@ -1160,7 +1248,7 @@ describe('App', () => { await renderAppAndChangeSize( { checkpoint: createCheckpointPlots(15) }, 'Regular', - 1 + Section.CHECKPOINT_PLOTS ) expect(screen.queryByRole('grid')).not.toBeInTheDocument() @@ -1170,7 +1258,7 @@ describe('App', () => { await renderAppAndChangeSize( { template: manyTemplatePlots(16) }, 'Regular', - 0 + Section.TEMPLATE_PLOTS ) expect(screen.getByRole('grid')).toBeInTheDocument() @@ -1180,7 +1268,7 @@ describe('App', () => { await renderAppAndChangeSize( { template: manyTemplatePlots(15) }, 'Regular', - 0 + Section.TEMPLATE_PLOTS ) expect(screen.queryByRole('grid')).not.toBeInTheDocument() @@ -1190,8 +1278,11 @@ describe('App', () => { const checkpoint = createCheckpointPlots(25) beforeEach(async () => { - // eslint-disable-next-line testing-library/no-render-in-setup - await renderAppAndChangeSize({ checkpoint }, 'Regular', 1) + await renderAppAndChangeSize( + { checkpoint }, + 'Regular', + Section.CHECKPOINT_PLOTS + ) }) it('should render one regular plot per row per 800px of screen when the screen is larger than 2000px', () => { @@ -1243,7 +1334,7 @@ describe('App', () => { await renderAppAndChangeSize( { checkpoint: createCheckpointPlots(21) }, 'Small', - 1 + Section.CHECKPOINT_PLOTS ) expect(screen.getByRole('grid')).toBeInTheDocument() @@ -1253,7 +1344,7 @@ describe('App', () => { await renderAppAndChangeSize( { checkpoint: createCheckpointPlots(20) }, 'Small', - 1 + Section.CHECKPOINT_PLOTS ) expect(screen.queryByRole('grid')).not.toBeInTheDocument() @@ -1263,7 +1354,7 @@ describe('App', () => { await renderAppAndChangeSize( { template: manyTemplatePlots(21) }, 'Small', - 0 + Section.TEMPLATE_PLOTS ) expect(screen.getByRole('grid')).toBeInTheDocument() @@ -1273,7 +1364,7 @@ describe('App', () => { await renderAppAndChangeSize( { template: manyTemplatePlots(20) }, 'Small', - 0 + Section.TEMPLATE_PLOTS ) expect(screen.queryByRole('grid')).not.toBeInTheDocument() @@ -1283,8 +1374,11 @@ describe('App', () => { const checkpoint = createCheckpointPlots(25) beforeEach(async () => { - // eslint-disable-next-line testing-library/no-render-in-setup - await renderAppAndChangeSize({ checkpoint }, 'Small', 1) + await renderAppAndChangeSize( + { checkpoint }, + 'Small', + Section.CHECKPOINT_PLOTS + ) }) it('should render one small plot per row per 500px of screen when the screen is larger than 2000px', () => { @@ -1458,7 +1552,6 @@ describe('App', () => { it('should not reorder the ribbon when comparison plots are reordered', () => { renderAppWithOptionalData({ comparison: comparisonTableFixture, - selectedRevisions: plotsRevisionsFixture }) diff --git a/webview/src/plots/components/Plots.tsx b/webview/src/plots/components/Plots.tsx index e2f1f8e8e5..4476e4998d 100644 --- a/webview/src/plots/components/Plots.tsx +++ b/webview/src/plots/components/Plots.tsx @@ -55,9 +55,9 @@ const PlotsContent = () => { return ( <> - {hasTemplateData && } - {hasComparisonData && } - {hasCheckpointData && } + + + {zoomedInPlot?.plot && ( Real-time plots based on metrics from the Experiments Table + ), // "Images" - [Section.COMPARISON_TABLE]: - 'Displays image plots side by side across experiments.', + [Section.COMPARISON_TABLE]: ( + Displays image plots side by side across experiments. + ), // "Data Series" - [Section.TEMPLATE_PLOTS]: - 'Plots of JSON, YAML, CSV, or TSV files, visualized using `dvc plots` templates' + [Section.TEMPLATE_PLOTS]: ( + + Plots of JSON, YAML, CSV, or TSV files, visualized using `dvc plots` + templates + + ) } const InfoIcon = () => ( @@ -108,7 +114,7 @@ export const PlotsContainer: React.FC = ({ ) return ( -
+
{ @@ -130,7 +136,7 @@ export const PlotsContainer: React.FC = ({ /> {title} - +
= ({ colors }) => { const [order, setOrder] = useState(plotsIds) - const { size } = useSelector((state: PlotsState) => state.checkpoint) + const { size, hasData } = useSelector((state: PlotsState) => state.checkpoint) const nbItemsPerRow = useNbItemsPerRow(size) useEffect(() => { @@ -43,6 +43,10 @@ export const CheckpointPlots: React.FC = ({ }) } + if (!hasData) { + return No Plots to Display + } + const items = order.map(plot => (
diff --git a/webview/src/plots/components/checkpointPlots/CheckpointPlotsWrapper.tsx b/webview/src/plots/components/checkpointPlots/CheckpointPlotsWrapper.tsx index 5f6e824997..d8764504bb 100644 --- a/webview/src/plots/components/checkpointPlots/CheckpointPlotsWrapper.tsx +++ b/webview/src/plots/components/checkpointPlots/CheckpointPlotsWrapper.tsx @@ -33,15 +33,20 @@ export const CheckpointPlotsWrapper: React.FC = () => { dispatch(changeSize(size)) } + const menu = + plotsIds.length > 0 + ? { + plots: metrics, + selectedPlots, + setSelectedPlots: setSelectedMetrics + } + : undefined + return ( { const { plots } = useSelector((state: PlotsState) => state.comparison) @@ -54,6 +55,10 @@ export const ComparisonTable: React.FC = () => { useEffect(() => setComparisonPlots(plots), [plots]) + if (!plots || plots.length === 0) { + return No Images to Compare + } + const setColumnsOrder = (order: string[]) => { const newOrder = reorderObjectList(order, columns, 'revision') setColumns(newOrder) diff --git a/webview/src/plots/components/plotDataStore.ts b/webview/src/plots/components/plotDataStore.ts index 4de4bac82d..9150be9662 100644 --- a/webview/src/plots/components/plotDataStore.ts +++ b/webview/src/plots/components/plotDataStore.ts @@ -3,7 +3,7 @@ import { TemplatePlotSection } from 'dvc/src/plots/webview/contract' -type CheckpointPlotsById = { [key: string]: CheckpointPlotData } +export type CheckpointPlotsById = { [key: string]: CheckpointPlotData } export const plotDataStore = { checkpoint: {} as CheckpointPlotsById, diff --git a/webview/src/plots/components/styles.module.scss b/webview/src/plots/components/styles.module.scss index 745b2033a8..4238db3f0b 100644 --- a/webview/src/plots/components/styles.module.scss +++ b/webview/src/plots/components/styles.module.scss @@ -262,6 +262,10 @@ $gap: 20px; } } +.dropSectionWrapper { + height: min-content; +} + .dropSection { height: 10px; } diff --git a/webview/src/plots/components/templatePlots/AddedSection.tsx b/webview/src/plots/components/templatePlots/AddedSection.tsx index cf904696f0..be7ab1c63b 100644 --- a/webview/src/plots/components/templatePlots/AddedSection.tsx +++ b/webview/src/plots/components/templatePlots/AddedSection.tsx @@ -43,14 +43,21 @@ export const AddedSection: React.FC = ({ const isHovered = hoveredSection === id return ( -
+
) => e.preventDefault()} + onDrop={onDrop} + draggable + className={cx( + styles.singleViewPlotsGrid, + styles.noBigGrid, + styles.dropSectionWrapper + )} + >
) => e.preventDefault()} - onDrop={onDrop} className={cx(styles.dropSection, { [styles.dropSectionMaximized]: isHovered, [styles.plot]: isHovered diff --git a/webview/src/plots/components/templatePlots/TemplatePlots.tsx b/webview/src/plots/components/templatePlots/TemplatePlots.tsx index 1e3d64bf63..b4beaa1f3c 100644 --- a/webview/src/plots/components/templatePlots/TemplatePlots.tsx +++ b/webview/src/plots/components/templatePlots/TemplatePlots.tsx @@ -5,7 +5,7 @@ import { } from 'dvc/src/plots/webview/contract' import React, { DragEvent, useState, useEffect, useRef } from 'react' import cx from 'classnames' -import { useSelector } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import { MessageFromWebviewType } from 'dvc/src/webview/contract' import { AddedSection } from './AddedSection' import { TemplatePlotsGrid } from './TemplatePlotsGrid' @@ -17,6 +17,8 @@ import { shouldUseVirtualizedGrid } from '../util' import { useNbItemsPerRow } from '../../hooks/useNbItemsPerRow' import { PlotsState } from '../../store' import { plotDataStore } from '../plotDataStore' +import { setDraggedOverGroup } from '../../../shared/components/dragDrop/dragDropSlice' +import { EmptyState } from '../../../shared/components/emptyState/EmptyState' export enum NewSectionBlock { TOP = 'drop-section-top', @@ -27,10 +29,14 @@ export const TemplatePlots: React.FC = () => { const { plotsSnapshot, size } = useSelector( (state: PlotsState) => state.template ) + const draggedOverGroup = useSelector( + (state: PlotsState) => state.dragAndDrop.draggedOverGroup + ) const [sections, setSections] = useState([]) const [hoveredSection, setHoveredSection] = useState('') const nbItemsPerRow = useNbItemsPerRow(size) const shouldSendMessage = useRef(true) + const dispatch = useDispatch() useEffect(() => { shouldSendMessage.current = false @@ -38,7 +44,7 @@ export const TemplatePlots: React.FC = () => { }, [plotsSnapshot, setSections]) useEffect(() => { - if (shouldSendMessage.current) { + if (sections && shouldSendMessage.current) { sendMessage({ payload: sections.map(section => ({ group: section.group, @@ -50,6 +56,10 @@ export const TemplatePlots: React.FC = () => { shouldSendMessage.current = true }, [sections]) + if (!sections || sections.length === 0) { + return No Plots to Display + } + const setSectionEntries = (index: number, entries: TemplatePlotEntry[]) => { setSections(sections => { const updatedSections = [...sections] @@ -126,6 +136,10 @@ export const TemplatePlots: React.FC = () => { setSections(updatedSections) } + const handleEnteringSection = (groupId: string) => { + dispatch(setDraggedOverGroup(groupId)) + } + const newDropSection = { acceptedGroups: Object.values(TemplatePlotGroup), hoveredSection, @@ -162,6 +176,7 @@ export const TemplatePlots: React.FC = () => { id={groupId} data-testid={`plots-section_${groupId}`} className={classes} + onDragEnter={() => handleEnteringSection(groupId)} > { setSectionEntries={setSectionEntries} useVirtualizedGrid={useVirtualizedGrid} nbItemsPerRow={nbItemsPerRow} + parentDraggedOver={draggedOverGroup === groupId} />
) diff --git a/webview/src/plots/components/templatePlots/TemplatePlotsGrid.tsx b/webview/src/plots/components/templatePlots/TemplatePlotsGrid.tsx index 6559e68b95..18f8a4fe32 100644 --- a/webview/src/plots/components/templatePlots/TemplatePlotsGrid.tsx +++ b/webview/src/plots/components/templatePlots/TemplatePlotsGrid.tsx @@ -23,6 +23,7 @@ interface TemplatePlotsGridProps { setSectionEntries: (groupIndex: number, entries: TemplatePlotEntry[]) => void useVirtualizedGrid?: boolean nbItemsPerRow: number + parentDraggedOver?: boolean } const autoSize = { @@ -38,7 +39,8 @@ export const TemplatePlotsGrid: React.FC = ({ multiView, setSectionEntries, useVirtualizedGrid, - nbItemsPerRow + nbItemsPerRow, + parentDraggedOver }) => { const [order, setOrder] = useState([]) @@ -101,6 +103,7 @@ export const TemplatePlotsGrid: React.FC = ({ } : undefined } + parentDraggedOver={parentDraggedOver} /> ) } diff --git a/webview/src/shared/components/dragDrop/DragDropContainer.tsx b/webview/src/shared/components/dragDrop/DragDropContainer.tsx index 8f78f95fb6..cdc9f11559 100644 --- a/webview/src/shared/components/dragDrop/DragDropContainer.tsx +++ b/webview/src/shared/components/dragDrop/DragDropContainer.tsx @@ -8,7 +8,7 @@ import React, { } from 'react' import { useDispatch, useSelector } from 'react-redux' import { DragEnterDirection, getDragEnterDirection } from './util' -import { changeRef } from './dragDropSlice' +import { changeRef, setDraggedOverGroup } from './dragDropSlice' import styles from './styles.module.scss' import { DropTarget } from './DropTarget' import { getIDIndex, getIDWithoutIndex } from '../../../util/ids' @@ -54,6 +54,7 @@ interface DragDropContainerProps { } shouldShowOnDrag?: boolean ghostElemStyle?: CSSProperties + parentDraggedOver?: boolean } export const DragDropContainer: React.FC = ({ @@ -66,13 +67,16 @@ export const DragDropContainer: React.FC = ({ dropTarget, wrapperComponent, shouldShowOnDrag, - ghostElemStyle + ghostElemStyle, + parentDraggedOver // eslint-disable-next-line sonarjs/cognitive-complexity }) => { const [draggedOverId, setDraggedOverId] = useState('') const [draggedId, setDraggedId] = useState('') const [direction, setDirection] = useState(DragEnterDirection.LEFT) - const { draggedRef } = useSelector((state: PlotsState) => state.dragAndDrop) + const { draggedRef, draggedOverGroup } = useSelector( + (state: PlotsState) => state.dragAndDrop + ) const draggedOverIdTimeout = useRef(0) const dispatch = useDispatch() @@ -126,16 +130,16 @@ export const DragDropContainer: React.FC = ({ e.dataTransfer.setData('group', group) e.dataTransfer.effectAllowed = 'move' e.dataTransfer.dropEffect = 'move' - dispatch( - changeRef({ - group, - itemId: id, - itemIndex - }) - ) createGhostStyle(e) draggedOverIdTimeout.current = window.setTimeout(() => { + dispatch( + changeRef({ + group, + itemId: id, + itemIndex + }) + ) setDraggedId(id) setDraggedOverId(order[toIdx]) resetDraggedStyle(id) @@ -198,6 +202,7 @@ export const DragDropContainer: React.FC = ({ ) { setDraggedOverId(id) setDirection(getDragEnterDirection(e)) + dispatch(setDraggedOverGroup(group)) } } } @@ -242,19 +247,25 @@ export const DragDropContainer: React.FC = ({ }) : undefined + const getTarget = (id: string, isEnteringRight: boolean) => ( + + {dropTarget} + + ) + const createItemWithDropTarget = (id: string, item: JSX.Element) => { const isEnteringRight = direction === DragEnterDirection.RIGHT - const target = ( - - {dropTarget} - - ) + const target = + draggedOverGroup === group || draggedRef?.group === group + ? getTarget(id, isEnteringRight) + : null + const itemWithTag = shouldShowOnDrag ? (
) : ( @@ -280,6 +291,18 @@ export const DragDropContainer: React.FC = ({ return id === draggedOverId ? createItemWithDropTarget(id, item) : item }) + if ( + draggedRef && + isSameGroup(draggedRef.group, group) && + draggedRef.itemId !== draggedId && + !draggedOverId && + parentDraggedOver && + wrappedItems.length > 0 + ) { + const lastId = wrappedItems[wrappedItems.length - 1].id + wrappedItems.push(getTarget(lastId, false)) + } + const Wrapper = wrapperComponent?.component return Wrapper ? ( diff --git a/webview/src/shared/components/dragDrop/dragDropSlice.ts b/webview/src/shared/components/dragDrop/dragDropSlice.ts index 95596f5874..d9f40ac296 100644 --- a/webview/src/shared/components/dragDrop/dragDropSlice.ts +++ b/webview/src/shared/components/dragDrop/dragDropSlice.ts @@ -18,9 +18,11 @@ export type GroupStates = { export interface DragDropState { draggedRef: DraggedInfo groups: GroupStates + draggedOverGroup: string } export const dragDropInitialState: DragDropState = { + draggedOverGroup: '', draggedRef: undefined, groups: {} } @@ -35,6 +37,12 @@ export const dragDropSlice = createSlice({ draggedRef: action.payload } }, + setDraggedOverGroup: (state, action: PayloadAction) => { + return { + ...state, + draggedOverGroup: action.payload + } + }, setGroup: ( state, action: PayloadAction<{ id: string; group: DragDropGroupState }> @@ -47,6 +55,7 @@ export const dragDropSlice = createSlice({ } }) -export const { changeRef, setGroup } = dragDropSlice.actions +export const { changeRef, setGroup, setDraggedOverGroup } = + dragDropSlice.actions export default dragDropSlice.reducer diff --git a/yarn.lock b/yarn.lock index 73517597e2..5e03453812 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1563,6 +1563,11 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f" integrity sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA== +"@discoveryjs/json-ext@^0.5.7": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + "@eslint/eslintrc@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" @@ -1642,15 +1647,20 @@ mobx "^5.10.0" sinon "^7.2.7" -"@humanwhocodes/config-array@^0.9.2": - version "0.9.5" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" - integrity sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw== +"@humanwhocodes/config-array@^0.10.4": + version "0.10.4" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.4.tgz#01e7366e57d2ad104feea63e72248f22015c520c" + integrity sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" minimatch "^3.0.4" +"@humanwhocodes/gitignore-to-minimatch@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz#316b0a63b91c10e53f242efb4ace5c3b34e8728d" + integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA== + "@humanwhocodes/object-schema@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" @@ -3844,10 +3854,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.21.tgz#864b987c0c68d07b4345845c3e63b75edd143644" integrity sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ== -"@types/node@16.11.45": - version "16.11.45" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.45.tgz#155b13a33c665ef2b136f7f245fa525da419e810" - integrity sha512-3rKg/L5x0rofKuuUt5zlXzOnKyIHXmIu5R8A0TuNDMF2062/AOIDBciFIjToLEJ/9F9DzkHNot+BpNsMI1OLdQ== +"@types/node@16.11.47": + version "16.11.47" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.47.tgz#efa9e3e0f72e7aa6a138055dace7437a83d9f91c" + integrity sha512-fpP+jk2zJ4VW66+wAMFoBJlx1bxmBKx4DUFf68UHgdGCOuyUTDlLWqsaNPJh7xhNDykyJ9eIzAygilP/4WoN8g== "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0": version "16.11.39" @@ -4951,6 +4961,11 @@ acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== +acorn@^8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + address@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -6492,11 +6507,12 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -chromatic@6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-6.7.1.tgz#6548e51121269832709a203a55ce09e3bcfdda6d" - integrity sha512-iDVYnB24zPrMROvVBblIxlOaj5+vO1uDI0wRXObi5DVrkvCjDvHzsFfPwUoIHGiXE7f6tmw38ZZRI+LGarM9Ow== +chromatic@6.7.2: + version "6.7.2" + resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-6.7.2.tgz#367734e8430c2ee34c585f0a7cb26cfa302d782c" + integrity sha512-0CCAbZSPi+QMXyDtlBasxz0Htc0oM76IztQZn3xCP6IIT53RwxOiQ1++Zd/zCBEyC+UTodKQ/G4fUOX0Y1ERvw== dependencies: + "@discoveryjs/json-ext" "^0.5.7" "@types/webpack-env" "^1.17.0" chrome-launcher@^0.15.0: @@ -8454,10 +8470,10 @@ eslint-plugin-import@2.26.0: resolve "^1.22.0" tsconfig-paths "^3.14.1" -eslint-plugin-jest@26.6.0: - version "26.6.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-26.6.0.tgz#546804fa42da75d7d58d4d3b278d5186abd3f6c0" - integrity sha512-f8n46/97ZFdU4KqeQYqO8AEVGIhHWvkpgNBWHH3jrM28/y8llnbf3IjfIKv6p2pZIMinK1PCqbbROxs9Eud02Q== +eslint-plugin-jest@26.7.0: + version "26.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-26.7.0.tgz#41d405ac9143e1284a3401282db47ed459436778" + integrity sha512-/YNitdfG3o3cC6juZziAdkk6nfJt01jXVfj4AgaYVLs7bupHzRDL5K+eipdzhDXtQsiqaX1TzfwSuRlEgeln1A== dependencies: "@typescript-eslint/utils" "^5.10.0" @@ -8558,10 +8574,10 @@ eslint-plugin-sort-keys-fix@1.1.2: natural-compare "^1.4.0" requireindex "~1.2.0" -eslint-plugin-testing-library@5.5.1: - version "5.5.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.5.1.tgz#6fe602f9082a421b471bbae8aed692e26fe981b3" - integrity sha512-plLEkkbAKBjPxsLj7x4jNapcHAg2ernkQlKKrN2I8NrQwPISZHyCUNvg5Hv3EDqOQReToQb5bnqXYbkijJPE/g== +eslint-plugin-testing-library@5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.6.0.tgz#91e810ecb838f86decc9b5202876c87e42d73ea7" + integrity sha512-y63TRzPhGCMNsnUwMGJU1MFWc/3GvYw+nzobp9QiyNTTKsgAt5RKAOT1I34+XqVBpX1lC8bScoOjCkP7iRv0Mw== dependencies: "@typescript-eslint/utils" "^5.13.0" @@ -8638,13 +8654,14 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@8.20.0: - version "8.20.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.20.0.tgz#048ac56aa18529967da8354a478be4ec0a2bc81b" - integrity sha512-d4ixhz5SKCa1D6SCPrivP7yYVi7nyD6A4vs6HIAul9ujBzcEmZVM3/0NN/yu5nKhmO1wjp5xQ46iRfmDGlOviA== +eslint@8.21.0: + version "8.21.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.21.0.tgz#1940a68d7e0573cef6f50037addee295ff9be9ef" + integrity sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA== dependencies: "@eslint/eslintrc" "^1.3.0" - "@humanwhocodes/config-array" "^0.9.2" + "@humanwhocodes/config-array" "^0.10.4" + "@humanwhocodes/gitignore-to-minimatch" "^1.0.2" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -8654,14 +8671,17 @@ eslint@8.20.0: eslint-scope "^7.1.1" eslint-utils "^3.0.0" eslint-visitor-keys "^3.3.0" - espree "^9.3.2" + espree "^9.3.3" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" + find-up "^5.0.0" functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" globals "^13.15.0" + globby "^11.1.0" + grapheme-splitter "^1.0.4" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -8697,6 +8717,15 @@ espree@^9.3.2: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.3.0" +espree@^9.3.3: + version "9.3.3" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.3.tgz#2dd37c4162bb05f433ad3c1a52ddf8a49dc08e9d" + integrity sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng== + dependencies: + acorn "^8.8.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.3.0" + esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -9999,7 +10028,7 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== -grapheme-splitter@^1.0.2: +grapheme-splitter@^1.0.2, grapheme-splitter@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== @@ -17059,95 +17088,95 @@ tunnel@0.0.6: resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== -turbo-android-arm64@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-android-arm64/-/turbo-android-arm64-1.3.4.tgz#0a23a9feb49f1b1b3062756038c3d3c6059febf3" - integrity sha512-rAbfiw5dT2rKV7L8XCL6nKwBxSz0TNknUT8F64pE+h3ESiT4y6Ow/hCdNxlb+hcee6lvZ8tB0cynXCVM5bthAA== +turbo-android-arm64@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-android-arm64/-/turbo-android-arm64-1.4.0.tgz#dc6d3815013d5ec0022e4470c35a3b9d70964f14" + integrity sha512-k03ztiuVpRqFiXl452HUsDgns0KrDtKL+e19h3eVJZFlr0lXtMBAcjh6qkh9lSmBW99NGSESGbsdaL9cp6F/vw== -turbo-darwin-64@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.3.4.tgz#656c7ec3b2c936c8bebf9751f64ca70dc7f8e7bb" - integrity sha512-DZbRwVHH3nKOzVtijKWzkiKLLY+pBjawK90po7VRKMwdN2Db+JkWdu9+6wIqaxQ4WEYnYpwnTm0Aiyua0U/dNg== +turbo-darwin-64@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.4.0.tgz#d1d35fca2c76666192aa63d8de51dae201bf985b" + integrity sha512-uj6p1marrEIFBX1nv4+LRg4e1vTYXTsv2DUB0e/LeAf9G2dRzh/MtSwBWuUaFLCcDvMSsnOeComgEkKuYyeVfw== -turbo-darwin-arm64@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.3.4.tgz#c4e3ffa22ade349c993773021654f7f9efabcf86" - integrity sha512-Qfe7iBad/XM4G22G0XAnEQnHiSUO1WR4MgvyHV022WABIf7CgGDsW6DUu/4DOWFlfTO+xCC0Qgu93w6Kli/9uw== +turbo-darwin-arm64@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.4.0.tgz#554772f42aac0762e9441f001b3897aab5c831bd" + integrity sha512-O6xBDY3LUJVctQBkbPoHHDsUIhuJTdIgIY/w4ZPRgdv51fj6uBJRolj9lLx1jGioLqjUaj/sXIizlaPar6tm5A== -turbo-freebsd-64@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-freebsd-64/-/turbo-freebsd-64-1.3.4.tgz#563abe19c30244402944e1f2b68f66db126b2cfb" - integrity sha512-lXFViR0fnoTRtnRtkeSA/10Q41h+fLgnYC62wzHNzPsq/kCYmiaXBg+gWRJom8Ka2nh+xWQdLG2Dh/uxK/+1Og== +turbo-freebsd-64@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-freebsd-64/-/turbo-freebsd-64-1.4.0.tgz#84f1852cba9eb14495992fd92f7a85ccab91fb73" + integrity sha512-103LbqCHxDHCz0xmpWis5JHkti2Irlq7n7vAk43+Kkxrz8UqhbrSfe0qUhkYltvxQ/R5cKAOKmBT5eZCO85+Xw== -turbo-freebsd-arm64@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-freebsd-arm64/-/turbo-freebsd-arm64-1.3.4.tgz#59ed33d17c96b9ee71ae1c7926c3d49a4762966d" - integrity sha512-VN9gPZcRaYhQOIo+NlIDIDNlJjhnyM7i+3WvWAK8y5p5GoZoDAahM36GDX05JNjVdPq95WJPnWcxoiQvwnctxg== +turbo-freebsd-arm64@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-freebsd-arm64/-/turbo-freebsd-arm64-1.4.0.tgz#9efa07236ad4a0fc05a874fc952b28c300c57d68" + integrity sha512-Cf+TGpQTpogSd/SsGhz1uQMhNVYYT1GeGg9iJ99rjjzbdC1zjdmeCbgUwyUUx5wVVM832o5fWaCJai3My3PDDQ== -turbo-linux-32@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-linux-32/-/turbo-linux-32-1.3.4.tgz#2a096280cf6e46e604bb8e04440c94439c461304" - integrity sha512-h1oVx85jovYnAaP+KxSmFIPhlKFQmBwVkJBngALnHNU7HTN9+1t/VJ1WKjHEeLXrQ7ujeCMd/+TBm9XRd73RdA== +turbo-linux-32@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-linux-32/-/turbo-linux-32-1.4.0.tgz#c4f930e3036bb543a9af00c15960b6ed8a6b414d" + integrity sha512-bJn78F+mH45g6xPFTdn7PLzixMuqugGz4Db9dPLijWQzeKzVz52qpN7WGUZwfPZtQOs0HUI8woH6eqAZMdT2ZA== -turbo-linux-64@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.3.4.tgz#cbcc2d9628c4c6235166d3fa8d1907deadcaf17d" - integrity sha512-QJJeksggK9/s3VzS+iMTQh8gO6JLDxKBSc3qnpP1Kaq8hF+M1upfP9KhDFNguz8UEFlOvTblplNdqZdk/wtkXQ== +turbo-linux-64@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.4.0.tgz#9688b4c118755834c03ef02dbc0c16fc80d42544" + integrity sha512-1+WyeJ1CBOnXNQl+Qke4NWvy3Zymp0NoxmJcZAmVBhwqAqGyERypeDznMfFmFtpG99/ZhmT7xwilz3Bw3Eyukw== -turbo-linux-arm64@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.3.4.tgz#6256b0e672f9a4a9ce704fb09e9b020a0bb61cee" - integrity sha512-SSyUvBxZmlS44LQ2hzX5gfDlQueH1Hx5/rFS9mJZKFdgMnAB2g7btxxEvnpm0lgpfP/c3LH0VRks3xYEoQvILg== +turbo-linux-arm64@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.4.0.tgz#7619e13118ed9c282903fe950539fbb64f568e04" + integrity sha512-DUtBhN0+o4XULQ9hsnzp6nI4mycMH7PpjKtG2HdmiZIhSYipSKWIMZkvgJpnpVJoRarLe7PmcDJIBp2aXvw2xw== -turbo-linux-arm@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-linux-arm/-/turbo-linux-arm-1.3.4.tgz#80d5a3439c89daad48fb0908a5914ea8107bf503" - integrity sha512-vCVDcO4KNJak//UKsss/TnJw0ywYc8OyV88ZlSWyP3WtA+9D8DJNPOKodcR8IbKuV7Tqpr+f7cP+J9jGDkHjQw== +turbo-linux-arm@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-linux-arm/-/turbo-linux-arm-1.4.0.tgz#04fd2eba485438f244d09d5f52c7938f40895b2a" + integrity sha512-9DEUP1pKWCwMDuriYCuTf+73oBIxTU4VLdkP89dtPoQVXW6mviES4gRXm8jAs6AOzzMI4DHgNKPxk7UKUvuExQ== -turbo-linux-mips64le@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-linux-mips64le/-/turbo-linux-mips64le-1.3.4.tgz#e78c2984d188c3bef86af8d8080498b2666ff05f" - integrity sha512-w7Ib7i/GZhyJRdvQSLA1Be7AOIPgJpuLLjrtT2gUl3wXd4JuiHDvhOS7E0B9izaxeD/2iygU2kQjqD4o9tejlw== +turbo-linux-mips64le@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-linux-mips64le/-/turbo-linux-mips64le-1.4.0.tgz#f4c91875b58b22ab1c74110365ad351d0f2ad250" + integrity sha512-ZJcQvUgmCJ4TFosVL6hEmECP5ysSubF3wMOq5hkBxzRMBjL0djW+BK4aIXutBTpjAz9OyM10S4KeEGeGRS/e3Q== -turbo-linux-ppc64le@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-linux-ppc64le/-/turbo-linux-ppc64le-1.3.4.tgz#a42b61e78f370659b43d0ae0dd80b46e3d193c4e" - integrity sha512-xATyouJSGmfgQM6lLk4Da5HrsAw8qoPUArrBuDK1p3Rw1gcRnmlJL10mr6ZmZyRu+3o4M8UlThfus5q8eETmBw== +turbo-linux-ppc64le@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-linux-ppc64le/-/turbo-linux-ppc64le-1.4.0.tgz#237fd4262d79851d745ae0952a47629e8d320a90" + integrity sha512-sk/rOrcGnYRcwNP8yJwmeOOzInv7YzMfWBomVf/TwrpsGL8QrD05zjsMR+lGxYNoNOJIHbroracjHaLvKWZN7A== -turbo-windows-32@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-windows-32/-/turbo-windows-32-1.3.4.tgz#65e08a51ae58a844739fac7b60aefc6221a23faa" - integrity sha512-1oXgiGxkWuC/7rlBZTng7qC2zjAZ7lYLDDh3ePAycZzp6BprlxqT5+xA0kFJo+WqGRdAQVhanlET2bLGOdBQBQ== +turbo-windows-32@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-windows-32/-/turbo-windows-32-1.4.0.tgz#88dd0930b4d7db66565a17e0f389d1f2e05b1938" + integrity sha512-5fmS6J3ZvhWRss+sFw2edOElVeKtk0l9yCoCYltPeFSy3+ArXws06ik3Z318SuKlbFu/+VTbM/NyBleKru+0ag== -turbo-windows-64@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.3.4.tgz#ed7951faa59a7a00ac645bc8a66a69524f539afe" - integrity sha512-k7K/oC+399Gtwol42ALvt0espWmZZR7qlRwfgFS3BF4pEetxjGFJMXKNWUMDkOqsHhMxvLIiDbWPmY3fTIWL7g== +turbo-windows-64@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.4.0.tgz#5ef6aaf3b47fcfc868fcc85623fcc1cebc2ae332" + integrity sha512-FZZBHjb8BYpEGbVOpJEEFAjS3xxUVg76MBPXqRb5IUPQeXiCd8VfO4UApn9syjUuTLORBoWLmSf3xp7vd1cIgA== -turbo-windows-arm64@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.3.4.tgz#796cab3fa28e5c8412712d7eedb736ead2030526" - integrity sha512-jHBuTvQ3t/OElxn//kwHZR2mlPdmpcV7BZy+n18+wwx6ydGj5QDOrer7Dwk81YbA5KtOkp086Xpgr75T73wsSA== +turbo-windows-arm64@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.4.0.tgz#c6862aaea9c1d5a5694b9524a9e5fca94a82820a" + integrity sha512-mJAlDf5/qBVMVv+q+0/2PF5tRHKz4mgtl/DnRn5n/rIfRvElGkjlPbQwJdXuMCEQVE9FMOAGRCstu0dkcdTluQ== -turbo@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.3.4.tgz#94464209e6e648f6c3ea8e94a1e6518d36f33963" - integrity sha512-MsjlfAL29leQaIMdHGnIpK6IKZA4HwSAwDSIoBAs9EAKfAXIsnjLoF50dKDnBlaq5d4aVmiHsT6RYVcTKhSgBQ== +turbo@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.4.0.tgz#6ff8e53f624303533c22bbcaca93b2557cf392b7" + integrity sha512-k2x8QqunK/96tqzB7aRCQ601H7N2PnxTcrEH9NoJjN5sJeymnfn/dQ8l6HrSyuYrW259W3N/AWAAUhpXnUyitA== optionalDependencies: - turbo-android-arm64 "1.3.4" - turbo-darwin-64 "1.3.4" - turbo-darwin-arm64 "1.3.4" - turbo-freebsd-64 "1.3.4" - turbo-freebsd-arm64 "1.3.4" - turbo-linux-32 "1.3.4" - turbo-linux-64 "1.3.4" - turbo-linux-arm "1.3.4" - turbo-linux-arm64 "1.3.4" - turbo-linux-mips64le "1.3.4" - turbo-linux-ppc64le "1.3.4" - turbo-windows-32 "1.3.4" - turbo-windows-64 "1.3.4" - turbo-windows-arm64 "1.3.4" + turbo-android-arm64 "1.4.0" + turbo-darwin-64 "1.4.0" + turbo-darwin-arm64 "1.4.0" + turbo-freebsd-64 "1.4.0" + turbo-freebsd-arm64 "1.4.0" + turbo-linux-32 "1.4.0" + turbo-linux-64 "1.4.0" + turbo-linux-arm "1.4.0" + turbo-linux-arm64 "1.4.0" + turbo-linux-mips64le "1.4.0" + turbo-linux-ppc64le "1.4.0" + turbo-windows-32 "1.4.0" + turbo-windows-64 "1.4.0" + turbo-windows-arm64 "1.4.0" type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" @@ -18117,10 +18146,10 @@ wdio-chromedriver-service@^7.3.2: split2 "^3.2.2" tcp-port-used "^1.0.1" -wdio-vscode-service@4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/wdio-vscode-service/-/wdio-vscode-service-4.0.3.tgz#17e09806e8eeec5404c89fd68b6efa9a29a54833" - integrity sha512-o82f+yyTSJN3KS77zDb5VoMrDQOP0mtWlLATlr3K3uAF7ZSR+ay6GSmjdeBEoQd2rMoBu8ckzhfGQu+Fo1Zr2Q== +wdio-vscode-service@4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/wdio-vscode-service/-/wdio-vscode-service-4.0.4.tgz#3da87d21d895cfe0383e98dee407ae99755eef44" + integrity sha512-Cx/JEpS7dXV1HHl+sRxgsrwOIDO8U9ZvpBwAen3gJlaH4bEXamNmZLRDZ57xPLq37DpI1qpqf1vKNvGsPMOroA== dependencies: "@fastify/cors" "^7.0.0" "@fastify/static" "^5.0.2" From af7ab220044de95d55e419883d8cac04345554f6 Mon Sep 17 00:00:00 2001 From: Stephanie Roy Date: Fri, 12 Aug 2022 12:40:03 -0400 Subject: [PATCH 2/5] Cleanup drag and drop to use draggedRef and fix tests --- webview/src/plots/components/App.test.tsx | 8 +------ .../templatePlots/TemplatePlots.tsx | 10 ++++++-- .../components/dragDrop/DragDropContainer.tsx | 24 +++++++++---------- .../components/dragDrop/dragDropSlice.ts | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/webview/src/plots/components/App.test.tsx b/webview/src/plots/components/App.test.tsx index 381a07a18f..127503996e 100644 --- a/webview/src/plots/components/App.test.tsx +++ b/webview/src/plots/components/App.test.tsx @@ -797,13 +797,7 @@ describe('App', () => { expect(topDropIcon).not.toBeInTheDocument() - act(() => { - multiViewPlot.dispatchEvent(createBubbledEvent('dragstart')) - }) - - act(() => { - topSection.dispatchEvent(createBubbledEvent('dragenter')) - }) + dragEnter(multiViewPlot, topSection.id, DragEnterDirection.LEFT) topDropIcon = screen.queryByTestId(`${NewSectionBlock.TOP}_drop-icon`) diff --git a/webview/src/plots/components/templatePlots/TemplatePlots.tsx b/webview/src/plots/components/templatePlots/TemplatePlots.tsx index b4beaa1f3c..10d5368868 100644 --- a/webview/src/plots/components/templatePlots/TemplatePlots.tsx +++ b/webview/src/plots/components/templatePlots/TemplatePlots.tsx @@ -32,6 +32,9 @@ export const TemplatePlots: React.FC = () => { const draggedOverGroup = useSelector( (state: PlotsState) => state.dragAndDrop.draggedOverGroup ) + const draggedRef = useSelector( + (state: PlotsState) => state.dragAndDrop.draggedRef + ) const [sections, setSections] = useState([]) const [hoveredSection, setHoveredSection] = useState('') const nbItemsPerRow = useNbItemsPerRow(size) @@ -79,8 +82,11 @@ export const TemplatePlots: React.FC = () => { } const handleDropInNewSection = (e: DragEvent) => { - const draggedSectionId = getIDIndex(e.dataTransfer.getData('group')) - const draggedId = e.dataTransfer.getData('itemId') + if (!draggedRef) { + return + } + const draggedSectionId = getIDIndex(draggedRef.group) + const draggedId = draggedRef.itemId const updatedSections = removeFromPreviousAndAddToNewSection( sections, diff --git a/webview/src/shared/components/dragDrop/DragDropContainer.tsx b/webview/src/shared/components/dragDrop/DragDropContainer.tsx index cdc9f11559..e75d846aa4 100644 --- a/webview/src/shared/components/dragDrop/DragDropContainer.tsx +++ b/webview/src/shared/components/dragDrop/DragDropContainer.tsx @@ -125,9 +125,6 @@ export const DragDropContainer: React.FC = ({ } } const itemIndex = idx.toString() - e.dataTransfer.setData('itemIndex', itemIndex) - e.dataTransfer.setData('itemId', id) - e.dataTransfer.setData('group', group) e.dataTransfer.effectAllowed = 'move' e.dataTransfer.dropEffect = 'move' @@ -147,7 +144,6 @@ export const DragDropContainer: React.FC = ({ } const applyDrop = ( - e: DragEvent, droppedIndex: number, draggedIndex: number, newOrder: string[], @@ -161,34 +157,38 @@ export const DragDropContainer: React.FC = ({ setOrder(newOrder) dispatch(changeRef(undefined)) - onDrop?.(oldDraggedId, e.dataTransfer.getData('group'), group, droppedIndex) + onDrop?.(oldDraggedId, draggedRef?.group || '', group, droppedIndex) } const handleOnDrop = (e: DragEvent) => { + if (!draggedRef) { + return + } + const dragged = draggedRef.itemId + draggedOverIdTimeout.current = window.setTimeout(() => { setDraggedId('') }, 0) - if (e.dataTransfer.getData('itemId') === draggedOverId) { + if (dragged === draggedOverId) { dispatch(changeRef(undefined)) return } const newOrder = [...order] - const oldDraggedId = e.dataTransfer.getData('itemId') - const isNew = !order.includes(draggedId) + const isNew = !order.includes(dragged) if (isNew) { - newOrder.push(draggedId) + newOrder.push(dragged) } const draggedIndex = isNew ? newOrder.length - 1 - : getIDIndex(e.dataTransfer.getData('itemIndex')) + : getIDIndex(draggedRef.itemIndex) const droppedIndex = order.indexOf(e.currentTarget.id.split('__')[0]) const orderIdxChange = orderIdxTune(direction, droppedIndex > draggedIndex) const orderIdxChanged = droppedIndex + orderIdxChange const isEnabled = !disabledDropIds.includes(order[orderIdxChanged]) - if (isEnabled && isSameGroup(e.dataTransfer.getData('group'), group)) { - applyDrop(e, orderIdxChanged, draggedIndex, newOrder, oldDraggedId) + if (isEnabled && isSameGroup(draggedRef.group, group)) { + applyDrop(orderIdxChanged, draggedIndex, newOrder, dragged) } } diff --git a/webview/src/shared/components/dragDrop/dragDropSlice.ts b/webview/src/shared/components/dragDrop/dragDropSlice.ts index d9f40ac296..876d9fada5 100644 --- a/webview/src/shared/components/dragDrop/dragDropSlice.ts +++ b/webview/src/shared/components/dragDrop/dragDropSlice.ts @@ -4,7 +4,7 @@ export type DraggedInfo = | { itemIndex: string itemId: string - group?: string + group: string } | undefined export interface DragDropGroupState { From 3a5b998e362bf473ed696a41a19d50e664d8fc14 Mon Sep 17 00:00:00 2001 From: Stephanie Roy Date: Fri, 12 Aug 2022 12:55:37 -0400 Subject: [PATCH 3/5] Try to simplify complex expression --- webview/src/shared/components/dragDrop/DragDropContainer.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/webview/src/shared/components/dragDrop/DragDropContainer.tsx b/webview/src/shared/components/dragDrop/DragDropContainer.tsx index e75d846aa4..c170460c45 100644 --- a/webview/src/shared/components/dragDrop/DragDropContainer.tsx +++ b/webview/src/shared/components/dragDrop/DragDropContainer.tsx @@ -292,9 +292,8 @@ export const DragDropContainer: React.FC = ({ }) if ( - draggedRef && - isSameGroup(draggedRef.group, group) && - draggedRef.itemId !== draggedId && + isSameGroup(draggedRef?.group, group) && + draggedRef?.itemId !== draggedId && !draggedOverId && parentDraggedOver && wrappedItems.length > 0 From d0439acc7f60fdc981d6309f7fdb4efd47330a92 Mon Sep 17 00:00:00 2001 From: Stephanie Roy Date: Fri, 12 Aug 2022 13:07:46 -0400 Subject: [PATCH 4/5] Revert file --- extension/src/test/e2e/wdio.conf.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src/test/e2e/wdio.conf.ts b/extension/src/test/e2e/wdio.conf.ts index b40ba1daa8..a9c14490a6 100644 --- a/extension/src/test/e2e/wdio.conf.ts +++ b/extension/src/test/e2e/wdio.conf.ts @@ -33,7 +33,7 @@ export const config: Options.Testrunner = { capabilities: [ { browserName: 'vscode', - browserVersion: 'stable', + browserVersion: 'insiders', 'wdio:vscodeOptions': { extensionPath, userSettings: { From 893f08c921b2333ecc1f775b10370b8174e3935c Mon Sep 17 00:00:00 2001 From: Stephanie Roy Date: Fri, 12 Aug 2022 13:13:55 -0400 Subject: [PATCH 5/5] Add test --- webview/src/plots/components/App.test.tsx | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/webview/src/plots/components/App.test.tsx b/webview/src/plots/components/App.test.tsx index 127503996e..8157ad06c6 100644 --- a/webview/src/plots/components/App.test.tsx +++ b/webview/src/plots/components/App.test.tsx @@ -784,6 +784,30 @@ describe('App', () => { ]) }) + it('should show a drop target at the end of the section when moving a plot from one section to another but not over any other plot', async () => { + renderAppWithOptionalData({ + template: complexTemplatePlotsFixture + }) + + const bottomSection = screen.getByTestId(NewSectionBlock.BOTTOM) + const aSingleViewPlot = screen.getByTestId(join('plot_other', 'plot.tsv')) + + dragAndDrop(aSingleViewPlot, bottomSection) + + await screen.findByTestId('plots-section_template-single_2') + const anotherSingleViewPlot = screen.getByTestId( + join('plot_logs', 'loss.tsv') + ) + + dragEnter( + anotherSingleViewPlot, + 'plots-section_template-single_0', + DragEnterDirection.RIGHT + ) + + expect(screen.getByTestId('plot_drop-target')).toBeInTheDocument() + }) + it('should show a drop zone when hovering a new section', () => { renderAppWithOptionalData({ template: complexTemplatePlotsFixture