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
This commit is contained in:
+2
-1
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+66
-5
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user