summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2022-08-16 19:37:39 +0000
committerGerrit Code Review <review@openstack.org>2022-08-16 19:37:39 +0000
commita425f56d08dd577539ca34418a28f7c0c68e4162 (patch)
tree126b575ce294f8d361e1aa86f8ac146a7d2bb412
parent598e7a9632b01e31e6611ed47198214128f7394d (diff)
parent97376adc21787494f5e544b271c7b435950d6256 (diff)
downloadzuul-a425f56d08dd577539ca34418a28f7c0c68e4162.tar.gz
Merge "Add job graph support to web UI"
-rw-r--r--web/package.json3
-rw-r--r--web/src/actions/jobgraph.js83
-rw-r--r--web/src/api.js8
-rw-r--r--web/src/containers/jobgraph/JobGraph.jsx78
-rw-r--r--web/src/containers/jobgraph/JobGraphDisplay.jsx120
-rw-r--r--web/src/containers/jobgraph/JobGraphToolbar.jsx145
-rw-r--r--web/src/index.css6
-rw-r--r--web/src/pages/Project.jsx7
-rw-r--r--web/src/reducers/index.js2
-rw-r--r--web/src/reducers/jobgraph.js55
-rw-r--r--web/yarn.lock71
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"