diff options
author | James E. Blair <jim@acmegating.com> | 2022-07-26 19:11:02 -0700 |
---|---|---|
committer | James E. Blair <jim@acmegating.com> | 2022-08-02 08:03:30 -0700 |
commit | 97376adc21787494f5e544b271c7b435950d6256 (patch) | |
tree | fb3e15acb506fc190ab5332710401781d5cd02b4 /web | |
parent | 042d01ebbb69616911b3458eed5d27cd907a4c34 (diff) | |
download | zuul-97376adc21787494f5e544b271c7b435950d6256.tar.gz |
Add job graph support to web UI
This adds the ability to display the frozen job graph for a project.
It adds a toolbar to the Project page that allows a user to enter
a pipeline and branch. Hit the button and it will use the API
to freeze the job graph and then display it with graphviz (there
is a webassembly build of the graphviz libray).
Change-Id: Ieb5899a63a4c85eb5d445fa69dd1e85ddc11575d
Diffstat (limited to 'web')
-rw-r--r-- | web/package.json | 3 | ||||
-rw-r--r-- | web/src/actions/jobgraph.js | 83 | ||||
-rw-r--r-- | web/src/api.js | 8 | ||||
-rw-r--r-- | web/src/containers/jobgraph/JobGraph.jsx | 78 | ||||
-rw-r--r-- | web/src/containers/jobgraph/JobGraphDisplay.jsx | 120 | ||||
-rw-r--r-- | web/src/containers/jobgraph/JobGraphToolbar.jsx | 145 | ||||
-rw-r--r-- | web/src/index.css | 6 | ||||
-rw-r--r-- | web/src/pages/Project.jsx | 7 | ||||
-rw-r--r-- | web/src/reducers/index.js | 2 | ||||
-rw-r--r-- | web/src/reducers/jobgraph.js | 55 | ||||
-rw-r--r-- | web/yarn.lock | 71 |
11 files changed, 571 insertions, 7 deletions
diff --git a/web/package.json b/web/package.json index 7b6eef9cd..3122e8839 100644 --- a/web/package.json +++ b/web/package.json @@ -13,6 +13,7 @@ "@softwarefactory-project/re-ansi": "^0.5.0", "axios": "^0.26.0", "broadcast-channel": "^4.5.0", + "d3-graphviz": "2.6.1", "js-yaml": "^3.13.0", "lodash": "^4.17.10", "moment": "^2.22.2", @@ -57,7 +58,7 @@ "start:openstack": "REACT_APP_ZUUL_API='https://zuul.openstack.org/api/' react-scripts start", "start:multi": "REACT_APP_ZUUL_API='https://softwarefactory-project.io/zuul/api/' react-scripts start", "start": "react-scripts start", - "build": "react-scripts build", + "build": "react-scripts --max_old_space_size=4096 build", "test": "react-scripts test --env=jsdom --watchAll=false", "eject": "react-scripts eject", "lint": "eslint --ext .js --ext .jsx src", diff --git a/web/src/actions/jobgraph.js b/web/src/actions/jobgraph.js new file mode 100644 index 000000000..44ca2cb07 --- /dev/null +++ b/web/src/actions/jobgraph.js @@ -0,0 +1,83 @@ +// Copyright 2018 Red Hat, Inc +// Copyright 2022 Acme Gating, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +import * as API from '../api' + +export const JOB_GRAPH_FETCH_REQUEST = 'JOB_GRAPH_FETCH_REQUEST' +export const JOB_GRAPH_FETCH_SUCCESS = 'JOB_GRAPH_FETCH_SUCCESS' +export const JOB_GRAPH_FETCH_FAIL = 'JOB_GRAPH_FETCH_FAIL' + +export const requestJobGraph = () => ({ + type: JOB_GRAPH_FETCH_REQUEST +}) + +export function makeJobGraphKey(project, pipeline, branch) { + return JSON.stringify({ + project: project, pipeline: pipeline, branch: branch + }) +} + +export const receiveJobGraph = (tenant, jobGraphKey, jobGraph) => { + return { + type: JOB_GRAPH_FETCH_SUCCESS, + tenant: tenant, + jobGraphKey: jobGraphKey, + jobGraph: jobGraph, + receivedAt: Date.now(), + } +} + +const failedJobGraph = error => ({ + type: JOB_GRAPH_FETCH_FAIL, + error +}) + +const fetchJobGraph = (tenant, project, pipeline, branch) => dispatch => { + dispatch(requestJobGraph()) + const jobGraphKey = makeJobGraphKey(project, pipeline, branch) + return API.fetchJobGraph(tenant.apiPrefix, + project, + pipeline, + branch) + .then(response => dispatch(receiveJobGraph( + tenant.name, jobGraphKey, response.data))) + .catch(error => dispatch(failedJobGraph(error))) +} + +const shouldFetchJobGraph = (tenant, project, pipeline, branch, state) => { + const jobGraphKey = makeJobGraphKey(project, pipeline, branch) + const tenantJobGraphs = state.jobgraph.jobGraphs[tenant.name] + if (tenantJobGraphs) { + const jobGraph = tenantJobGraphs[jobGraphKey] + if (!jobGraph) { + return true + } + if (jobGraph.isFetching) { + return false + } + return false + } + return true +} + +export const fetchJobGraphIfNeeded = (tenant, project, pipeline, branch, + force) => ( + dispatch, getState) => { + if (force || shouldFetchJobGraph(tenant, project, pipeline, branch, + getState())) { + return dispatch(fetchJobGraph(tenant, project, pipeline, branch)) + } + return Promise.resolve() +} diff --git a/web/src/api.js b/web/src/api.js index 7b574a6b6..8fcb2ec18 100644 --- a/web/src/api.js +++ b/web/src/api.js @@ -159,6 +159,13 @@ function fetchProjects(apiPrefix) { function fetchJob(apiPrefix, jobName) { return Axios.get(apiUrl + apiPrefix + 'job/' + jobName) } +function fetchJobGraph(apiPrefix, projectName, pipelineName, branchName) { + return Axios.get(apiUrl + apiPrefix + + 'pipeline/' + pipelineName + + '/project/' + projectName + + '/branch/' + branchName + + '/freeze-jobs') +} function fetchJobs(apiPrefix) { return Axios.get(apiUrl + apiPrefix + 'jobs') } @@ -308,6 +315,7 @@ export { fetchProject, fetchProjects, fetchJob, + fetchJobGraph, fetchJobs, fetchLabels, fetchNodes, diff --git a/web/src/containers/jobgraph/JobGraph.jsx b/web/src/containers/jobgraph/JobGraph.jsx new file mode 100644 index 000000000..75d77161d --- /dev/null +++ b/web/src/containers/jobgraph/JobGraph.jsx @@ -0,0 +1,78 @@ +// Copyright 2022 Acme Gating, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +import React, { useState } from 'react' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import { useHistory, useLocation } from 'react-router-dom' + +import JobGraphToolbar from './JobGraphToolbar' +import JobGraphDisplay from './JobGraphDisplay' + +function JobGraph(props) { + const [currentPipeline, setCurrentPipeline] = useState() + const [currentBranch, setCurrentBranch] = useState() + const history = useHistory() + const location = useLocation() + + if (!currentBranch) { + const urlParams = new URLSearchParams(location.search) + const branch = urlParams.get('branch') + const pipeline = urlParams.get('pipeline') + if (pipeline && branch) { + setCurrentPipeline(pipeline) + setCurrentBranch(branch) + } + } + + function onChange(pipeline, branch) { + setCurrentPipeline(pipeline) + setCurrentBranch(branch) + + const searchParams = new URLSearchParams('') + searchParams.append('branch', branch) + searchParams.append('pipeline', pipeline) + history.push({ + pathname: location.pathname, + search: searchParams.toString(), + }) + } + + return ( + <> + <JobGraphToolbar + project={props.project} + onChange={onChange} + defaultBranch={currentBranch} + defaultPipeline={currentPipeline} + /> + {currentPipeline && currentBranch && + <JobGraphDisplay + project={props.project} + pipeline={currentPipeline} + branch={currentBranch} + />} + </> + ) +} + +JobGraph.propTypes = { + project: PropTypes.object.isRequired, + tenant: PropTypes.object, + dispatch: PropTypes.func, +} + +export default connect((state) => ({ + tenant: state.tenant, +}))(JobGraph) diff --git a/web/src/containers/jobgraph/JobGraphDisplay.jsx b/web/src/containers/jobgraph/JobGraphDisplay.jsx new file mode 100644 index 000000000..1fbcef332 --- /dev/null +++ b/web/src/containers/jobgraph/JobGraphDisplay.jsx @@ -0,0 +1,120 @@ +// Copyright 2022 Acme Gating, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +import React, { useState, useEffect} from 'react' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import * as d3 from 'd3' + +import { makeJobGraphKey, fetchJobGraphIfNeeded } from '../../actions/jobgraph' +import { graphviz } from 'd3-graphviz' + +function makeDot(jobGraph) { + let ret = 'digraph job_graph {' + ret += ' rankdir=LR;\n' + ret += ' node [shape=box];\n' + jobGraph.forEach((job) => { + if (job.dependencies.length) { + job.dependencies.forEach((dep) => { + let soft = ' [dir=back]' + if (dep.soft) { + soft = ' [style=dashed dir=back]' + } + ret += ' "' + dep.name + '" -> "' + job.name + '"' + soft + ';\n' + }) + } else { + ret += ' "' + job.name + '";\n' + } + }) + ret += '}\n' + return ret +} + +function GraphViz(props) { + useEffect(() => { + const gv = graphviz('#graphviz') + .options({ + fit: false, + zoom: true, + tweenPaths: false, + scale: 0.75, + }).renderDot(props.dot) + + // Fix up the initial values of the internal transform data; + // without this the first time we pan the graph jumps. + const element = d3.select('.zuul-job-graph > svg') + const transform = element[0][0].firstElementChild.attributes.transform.value + const match = transform.match(/translate\(\d+,(\d+)\).*/) + if (match && match.length > 0) { + const val = parseInt(match[1]) + gv._translation.y = val + gv._originalTransform.y = val + } + }, [props.dot]) + + return ( + <div className="zuul-job-graph" id="graphviz"/> + ) +} + +GraphViz.propTypes = { + dot: PropTypes.string.isRequired, +} + +function JobGraphDisplay(props) { + const [dot, setDot] = useState() + const {fetchJobGraphIfNeeded, tenant, project, pipeline, branch} = props + + useEffect(() => { + fetchJobGraphIfNeeded(tenant, project.name, pipeline, branch) + }, [fetchJobGraphIfNeeded, tenant, project, pipeline, branch]) + + const tenantJobGraph = props.jobgraph.jobGraphs[props.tenant.name] + const jobGraphKey = makeJobGraphKey(props.project.name, + props.pipeline, + props.branch) + const jobGraph = tenantJobGraph ? tenantJobGraph[jobGraphKey] : undefined + useEffect(() => { + if (jobGraph) { + setDot(makeDot(jobGraph)) + } + }, [jobGraph]) + return ( + <> + {dot && <GraphViz dot={dot}/>} + </> + ) +} + +JobGraphDisplay.propTypes = { + fetchJobGraphIfNeeded: PropTypes.func, + tenant: PropTypes.object, + project: PropTypes.object.isRequired, + pipeline: PropTypes.string.isRequired, + branch: PropTypes.string.isRequired, + jobgraph: PropTypes.object, + dispatch: PropTypes.func, + state: PropTypes.object, +} +function mapStateToProps(state) { + return { + tenant: state.tenant, + jobgraph: state.jobgraph, + state: state, + } +} + +const mapDispatchToProps = { fetchJobGraphIfNeeded } + +export default connect(mapStateToProps, mapDispatchToProps)(JobGraphDisplay) diff --git a/web/src/containers/jobgraph/JobGraphToolbar.jsx b/web/src/containers/jobgraph/JobGraphToolbar.jsx new file mode 100644 index 000000000..0e293d56c --- /dev/null +++ b/web/src/containers/jobgraph/JobGraphToolbar.jsx @@ -0,0 +1,145 @@ +// Copyright 2020 BMW Group +// Copyright 2022 Acme Gating, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +import React, { useState } from 'react' +import PropTypes from 'prop-types' +import { + Button, + TextInput, + Dropdown, + DropdownItem, + DropdownPosition, + DropdownToggle, + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem, + ToolbarToggleGroup, +} from '@patternfly/react-core' + + +function JobGraphToolbar(props) { + const pipelineSet = new Set() + props.project.configs.forEach((config) => { + config.pipelines.forEach((pipeline) => { + pipelineSet.add(pipeline.name) + }) + }) + const pipelines = Array.from(pipelineSet) + + const [isPipelineOpen, setIsPipelineOpen] = useState(false) + const [currentPipeline, setCurrentPipeline] = useState(props.defaultPipeline || pipelines[0]) + const [currentBranch, setCurrentBranch] = useState(props.defaultBranch || '') + + function handlePipelineSelect(event) { + setCurrentPipeline(event.target.innerText) + setIsPipelineOpen(false) + } + + function handlePipelineToggle(isOpen) { + setIsPipelineOpen(isOpen) + } + + function handleBranchChange(newValue) { + setCurrentBranch(newValue) + } + + function handleInputSend(event) { + // In case the event comes from a key press, only accept "Enter" + if (event.key && event.key !== 'Enter') { + return + } + + // Ignore empty values + if (!currentBranch) { + return + } + + // Clear the input field + setCurrentBranch('') + // Notify the parent component about the filter change + props.onChange(currentPipeline, currentBranch) + } + + function renderJobGraphToolbar () { + return <> + <Toolbar collapseListedFiltersBreakpoint="md"> + <ToolbarContent> + <ToolbarToggleGroup breakpoint="md"> + <ToolbarGroup variant="filter-group"> + + <ToolbarItem key="pipeline"> + <Dropdown + onSelect={handlePipelineSelect} + position={DropdownPosition.left} + toggle={ + <DropdownToggle + onToggle={handlePipelineToggle} + style={{ width: '100%' }} + > + Pipeline: {currentPipeline} + </DropdownToggle> + } + isOpen={isPipelineOpen} + dropdownItems={pipelines.map((pipeline) => ( + <DropdownItem key={pipeline}>{pipeline}</DropdownItem> + ))} + style={{ width: '100%' }} + menuAppendTo={document.body} + /> + </ToolbarItem> + + <ToolbarItem key="branch"> + <TextInput + name="branch" + id="branch-input" + type="search" + placeholder="Branch" + defaultValue={props.defaultBranch} + onChange={handleBranchChange} + onKeyDown={(event) => handleInputSend(event)} + /> + </ToolbarItem> + + <ToolbarItem key="button"> + <Button + onClick={(event) => handleInputSend(event)} + > + View Job Graph + </Button> + </ToolbarItem> + + </ToolbarGroup> + </ToolbarToggleGroup> + </ToolbarContent> + </Toolbar> + </> + } + + return ( + <div> + {renderJobGraphToolbar()} + </div> + ) +} + +JobGraphToolbar.propTypes = { + project: PropTypes.object.isRequired, + onChange: PropTypes.func.isRequired, + defaultBranch: PropTypes.string, + defaultPipeline: PropTypes.string, +} + +export default JobGraphToolbar diff --git a/web/src/index.css b/web/src/index.css index eddbca673..e186eeeda 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -443,3 +443,9 @@ details.foldable[open] summary::before { ); } } + +/* The box size calculation compared to the text size seems off, but + this looks better */ +.zuul-job-graph text { + font-size: 12px; +} diff --git a/web/src/pages/Project.jsx b/web/src/pages/Project.jsx index 8f76e9ff1..1ef757cd2 100644 --- a/web/src/pages/Project.jsx +++ b/web/src/pages/Project.jsx @@ -18,6 +18,7 @@ import PropTypes from 'prop-types' import { PageSection, PageSectionVariants } from '@patternfly/react-core' import Project from '../containers/project/Project' +import JobGraph from '../containers/jobgraph/JobGraph' import { fetchProjectIfNeeded } from '../actions/project' import { Fetchable } from '../containers/Fetching' @@ -61,7 +62,11 @@ class ProjectPage extends React.Component { /> </PageSection> {tenantProjects && tenantProjects[projectName] && - <Project project={tenantProjects[projectName]} />} + <> + <Project project={tenantProjects[projectName]} /> + <JobGraph project={tenantProjects[projectName]} /> + </> + } </PageSection> ) } diff --git a/web/src/reducers/index.js b/web/src/reducers/index.js index 325e4f3dd..5e18dbbea 100644 --- a/web/src/reducers/index.js +++ b/web/src/reducers/index.js @@ -23,6 +23,7 @@ import notifications from './notifications' import build from './build' import info from './info' import job from './job' +import jobgraph from './jobgraph' import jobs from './jobs' import labels from './labels' import logfile from './logfile' @@ -47,6 +48,7 @@ const reducers = { notifications, info, job, + jobgraph, jobs, labels, logfile, diff --git a/web/src/reducers/jobgraph.js b/web/src/reducers/jobgraph.js new file mode 100644 index 000000000..0a015fb45 --- /dev/null +++ b/web/src/reducers/jobgraph.js @@ -0,0 +1,55 @@ +// Copyright 2018 Red Hat, Inc +// Copyright 2022 Acme Gating, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +import { + JOB_GRAPH_FETCH_FAIL, + JOB_GRAPH_FETCH_REQUEST, + JOB_GRAPH_FETCH_SUCCESS +} from '../actions/jobgraph' + +export default (state = { + isFetching: false, + jobGraphs: {}, +}, action) => { + let stateJobGraphs + switch (action.type) { + case JOB_GRAPH_FETCH_REQUEST: + return { + isFetching: true, + jobGraphs: state.jobGraphs, + } + case JOB_GRAPH_FETCH_SUCCESS: + stateJobGraphs = !state.jobGraphs[action.tenant] ? + { ...state.jobGraphs, [action.tenant]: {} } : + { ...state.jobGraphs } + return { + isFetching: false, + jobGraphs: { + ...stateJobGraphs, + [action.tenant]: { + ...stateJobGraphs[action.tenant], + [action.jobGraphKey]: action.jobGraph + } + } + } + case JOB_GRAPH_FETCH_FAIL: + return { + isFetching: false, + jobGraphs: state.jobGraphs, + } + default: + return state + } +} diff --git a/web/yarn.lock b/web/yarn.lock index ff3dd3bf8..fc1675a0a 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -4711,24 +4711,52 @@ d3-color@1: resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.1.tgz#c52002bf8846ada4424d55d97982fef26eb3bc8a" integrity sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q== -d3-ease@^1.0.0: +d3-dispatch@1, d3-dispatch@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58" + integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA== + +d3-drag@1: + version "1.2.5" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.5.tgz#2537f451acd39d31406677b7dc77c82f7d988f70" + integrity sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w== + dependencies: + d3-dispatch "1" + d3-selection "1" + +d3-ease@1, d3-ease@^1.0.0: version "1.0.7" resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.7.tgz#9a834890ef8b8ae8c558b2fe55bd57f5993b85e2" integrity sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ== -d3-format@1: +d3-format@1, d3-format@^1.2.0: version "1.4.5" resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4" integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ== -d3-interpolate@1, d3-interpolate@^1.1.1: +d3-graphviz@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/d3-graphviz/-/d3-graphviz-2.6.1.tgz#61b93fe330e6339198fd2090f8080d7d4282c514" + integrity sha512-878AFSagQyr5tTOrM7YiVYeUC2/NoFcOB3/oew+LAML0xekyJSw9j3WOCUMBsc95KYe9XBYZ+SKKuObVya1tJQ== + dependencies: + d3-dispatch "^1.0.3" + d3-format "^1.2.0" + d3-interpolate "^1.1.5" + d3-path "^1.0.5" + d3-selection "^1.1.0" + d3-timer "^1.0.6" + d3-transition "^1.1.1" + d3-zoom "^1.5.0" + viz.js "^1.8.2" + +d3-interpolate@1, d3-interpolate@^1.1.1, d3-interpolate@^1.1.5: version "1.4.0" resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987" integrity sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA== dependencies: d3-color "1" -d3-path@1: +d3-path@1, d3-path@^1.0.5: version "1.0.9" resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== @@ -4746,6 +4774,11 @@ d3-scale@^1.0.0: d3-time "1" d3-time-format "2" +d3-selection@1, d3-selection@^1.1.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.4.2.tgz#dcaa49522c0dbf32d6c1858afc26b6094555bc5c" + integrity sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg== + d3-shape@^1.0.0, d3-shape@^1.2.0: version "1.3.7" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" @@ -4765,11 +4798,34 @@ d3-time@1: resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1" integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA== -d3-timer@^1.0.0: +d3-timer@1, d3-timer@^1.0.0, d3-timer@^1.0.6: version "1.0.10" resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5" integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw== +d3-transition@1, d3-transition@^1.1.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.3.2.tgz#a98ef2151be8d8600543434c1ca80140ae23b398" + integrity sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA== + dependencies: + d3-color "1" + d3-dispatch "1" + d3-ease "1" + d3-interpolate "1" + d3-selection "^1.1.0" + d3-timer "1" + +d3-zoom@^1.5.0: + version "1.8.3" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.8.3.tgz#b6a3dbe738c7763121cd05b8a7795ffe17f4fc0a" + integrity sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ== + dependencies: + d3-dispatch "1" + d3-drag "1" + d3-interpolate "1" + d3-selection "1" + d3-transition "1" + d3@~3.5.0, d3@~3.5.17: version "3.5.17" resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8" @@ -15208,6 +15264,11 @@ victory-zoom-container@^36.2.1, victory-zoom-container@^36.3.0: prop-types "^15.5.8" victory-core "^36.3.0" +viz.js@^1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/viz.js/-/viz.js-1.8.2.tgz#d9cc04cd99f98ec986bf9054db76a6cbcdc5d97a" + integrity sha512-W+1+N/hdzLpQZEcvz79n2IgUE9pfx6JLdHh3Kh8RGvLL8P1LdJVQmi2OsDcLdY4QVID4OUy+FPelyerX0nJxIQ== + vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" |