Add freeze job to web UI
This adds a freeze-job page to the web UI. It can be navigated to by clicking a job in a job graph. Change-Id: Ibd07449314a9454295bc97f8d654347f09dc504c
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
// 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 FREEZE_JOB_FETCH_REQUEST = 'FREEZE_JOB_FETCH_REQUEST'
|
||||
export const FREEZE_JOB_FETCH_SUCCESS = 'FREEZE_JOB_FETCH_SUCCESS'
|
||||
export const FREEZE_JOB_FETCH_FAIL = 'FREEZE_JOB_FETCH_FAIL'
|
||||
|
||||
export const requestFreezeJob = () => ({
|
||||
type: FREEZE_JOB_FETCH_REQUEST
|
||||
})
|
||||
|
||||
export function makeFreezeJobKey(pipeline, project, branch, job) {
|
||||
return JSON.stringify({
|
||||
pipeline, project, branch, job
|
||||
})
|
||||
}
|
||||
|
||||
export const receiveFreezeJob = (tenant, freezeJobKey, freezeJob) => {
|
||||
return {
|
||||
type: FREEZE_JOB_FETCH_SUCCESS,
|
||||
tenant: tenant,
|
||||
freezeJobKey: freezeJobKey,
|
||||
freezeJob: freezeJob,
|
||||
receivedAt: Date.now(),
|
||||
}
|
||||
}
|
||||
|
||||
const failedFreezeJob = error => ({
|
||||
type: FREEZE_JOB_FETCH_FAIL,
|
||||
error
|
||||
})
|
||||
|
||||
const fetchFreezeJob = (tenant, pipeline, project, branch, job) => dispatch => {
|
||||
dispatch(requestFreezeJob())
|
||||
const freezeJobKey = makeFreezeJobKey(pipeline, project, branch, job)
|
||||
return API.fetchFreezeJob(tenant.apiPrefix,
|
||||
pipeline,
|
||||
project,
|
||||
branch,
|
||||
job)
|
||||
.then(response => dispatch(receiveFreezeJob(
|
||||
tenant.name, freezeJobKey, response.data)))
|
||||
.catch(error => dispatch(failedFreezeJob(error)))
|
||||
}
|
||||
|
||||
const shouldFetchFreezeJob = (tenant, pipeline, project, branch, job, state) => {
|
||||
const freezeJobKey = makeFreezeJobKey(pipeline, project, branch, job)
|
||||
const tenantFreezeJobs = state.freezejob.freezeJobs[tenant.name]
|
||||
if (tenantFreezeJobs) {
|
||||
const freezeJob = tenantFreezeJobs[freezeJobKey]
|
||||
if (!freezeJob) {
|
||||
return true
|
||||
}
|
||||
if (freezeJob.isFetching) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const fetchFreezeJobIfNeeded = (tenant, pipeline, project, branch, job,
|
||||
force) => (
|
||||
dispatch, getState) => {
|
||||
if (force || shouldFetchFreezeJob(tenant, pipeline, project, branch, job,
|
||||
getState())) {
|
||||
return dispatch(fetchFreezeJob(tenant, pipeline, project, branch, job))
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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 PIPELINES_FETCH_REQUEST = 'PIPELINES_FETCH_REQUEST'
|
||||
export const PIPELINES_FETCH_SUCCESS = 'PIPELINES_FETCH_SUCCESS'
|
||||
export const PIPELINES_FETCH_FAIL = 'PIPELINES_FETCH_FAIL'
|
||||
|
||||
export const requestPipelines = () => ({
|
||||
type: PIPELINES_FETCH_REQUEST
|
||||
})
|
||||
|
||||
export const receivePipelines = (tenant, json) => ({
|
||||
type: PIPELINES_FETCH_SUCCESS,
|
||||
tenant: tenant,
|
||||
pipelines: json,
|
||||
receivedAt: Date.now()
|
||||
})
|
||||
|
||||
const failedPipelines = error => ({
|
||||
type: PIPELINES_FETCH_FAIL,
|
||||
error
|
||||
})
|
||||
|
||||
const fetchPipelines = (tenant) => dispatch => {
|
||||
dispatch(requestPipelines())
|
||||
return API.fetchPipelines(tenant.apiPrefix)
|
||||
.then(response => dispatch(receivePipelines(tenant.name, response.data)))
|
||||
.catch(error => dispatch(failedPipelines(error)))
|
||||
}
|
||||
|
||||
const shouldFetchPipelines = (tenant, state) => {
|
||||
const pipelines = state.pipelines.pipelines[tenant.name]
|
||||
if (!pipelines || pipelines.length === 0) {
|
||||
return true
|
||||
}
|
||||
if (pipelines.isFetching) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export const fetchPipelinesIfNeeded = (tenant, force) => (
|
||||
dispatch, getState) => {
|
||||
if (force || shouldFetchPipelines(tenant, getState())) {
|
||||
return dispatch(fetchPipelines(tenant))
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
@@ -130,6 +130,13 @@ function fetchStatus(apiPrefix) {
|
||||
function fetchChangeStatus(apiPrefix, changeId) {
|
||||
return Axios.get(apiUrl + apiPrefix + 'status/change/' + changeId)
|
||||
}
|
||||
function fetchFreezeJob(apiPrefix, pipelineName, projectName, branchName, jobName) {
|
||||
return Axios.get(apiUrl + apiPrefix +
|
||||
'pipeline/' + pipelineName +
|
||||
'/project/' + projectName +
|
||||
'/branch/' + branchName +
|
||||
'/freeze-job/' + jobName)
|
||||
}
|
||||
function fetchBuild(apiPrefix, buildId) {
|
||||
return Axios.get(apiUrl + apiPrefix + 'build/' + buildId)
|
||||
}
|
||||
@@ -150,6 +157,9 @@ function fetchBuildsets(apiPrefix, queryString) {
|
||||
}
|
||||
return Axios.get(apiUrl + apiPrefix + path)
|
||||
}
|
||||
function fetchPipelines(apiPrefix) {
|
||||
return Axios.get(apiUrl + apiPrefix + 'pipelines')
|
||||
}
|
||||
function fetchProject(apiPrefix, projectName) {
|
||||
return Axios.get(apiUrl + apiPrefix + 'project/' + projectName)
|
||||
}
|
||||
@@ -312,6 +322,8 @@ export {
|
||||
fetchBuilds,
|
||||
fetchBuildset,
|
||||
fetchBuildsets,
|
||||
fetchFreezeJob,
|
||||
fetchPipelines,
|
||||
fetchProject,
|
||||
fetchProjects,
|
||||
fetchJob,
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
// 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, { useEffect, useState } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import {
|
||||
Button,
|
||||
TextInput,
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownPosition,
|
||||
DropdownToggle,
|
||||
Toolbar,
|
||||
ToolbarContent,
|
||||
ToolbarGroup,
|
||||
ToolbarItem,
|
||||
ToolbarToggleGroup,
|
||||
} from '@patternfly/react-core'
|
||||
|
||||
import { fetchPipelinesIfNeeded } from '../../actions/pipelines'
|
||||
|
||||
function FreezeJobToolbar(props) {
|
||||
const { tenant, fetchPipelinesIfNeeded } = props
|
||||
|
||||
useEffect(() => {
|
||||
fetchPipelinesIfNeeded(tenant)
|
||||
}, [fetchPipelinesIfNeeded, tenant])
|
||||
|
||||
const tenantPipelines = props.pipelines.pipelines[tenant.name]
|
||||
const pipelines = tenantPipelines ? tenantPipelines.map(p => { return p.name }) : []
|
||||
|
||||
const [isPipelineOpen, setIsPipelineOpen] = useState(false)
|
||||
const [currentPipeline, setCurrentPipeline] = useState(props.defaultPipeline || '')
|
||||
const [currentProject, setCurrentProject] = useState(props.defaultProject || '')
|
||||
const [currentBranch, setCurrentBranch] = useState(props.defaultBranch || '')
|
||||
const [currentJob, setCurrentJob] = useState(props.defaultJob || '')
|
||||
|
||||
if (!currentPipeline && pipelines.length) {
|
||||
// We may have gotten a list of pipelines after we loaded the page
|
||||
setCurrentPipeline(pipelines[0])
|
||||
}
|
||||
|
||||
function handlePipelineSelect(event) {
|
||||
setCurrentPipeline(event.target.innerText)
|
||||
setIsPipelineOpen(false)
|
||||
}
|
||||
|
||||
function handlePipelineToggle(isOpen) {
|
||||
setIsPipelineOpen(isOpen)
|
||||
}
|
||||
|
||||
function handleProjectChange(newValue) {
|
||||
setCurrentProject(newValue)
|
||||
}
|
||||
|
||||
function handleBranchChange(newValue) {
|
||||
setCurrentBranch(newValue)
|
||||
}
|
||||
|
||||
function handleJobChange(newValue) {
|
||||
setCurrentJob(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 || !currentProject || !currentJob) {
|
||||
return
|
||||
}
|
||||
|
||||
// Notify the parent component about the filter change
|
||||
props.onChange(currentPipeline, currentProject, currentBranch, currentJob)
|
||||
}
|
||||
|
||||
function renderFreezeJobToolbar () {
|
||||
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="project">
|
||||
<TextInput
|
||||
name="project"
|
||||
id="project-input"
|
||||
type="search"
|
||||
placeholder="Project"
|
||||
defaultValue={props.defaultProject}
|
||||
onChange={handleProjectChange}
|
||||
onKeyDown={(event) => handleInputSend(event)}
|
||||
/>
|
||||
</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="job">
|
||||
<TextInput
|
||||
name="job"
|
||||
id="job-input"
|
||||
type="search"
|
||||
placeholder="Job"
|
||||
defaultValue={props.defaultJob}
|
||||
onChange={handleJobChange}
|
||||
onKeyDown={(event) => handleInputSend(event)}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
|
||||
<ToolbarItem key="button">
|
||||
<Button
|
||||
onClick={(event) => handleInputSend(event)}
|
||||
>
|
||||
Freeze Job
|
||||
</Button>
|
||||
</ToolbarItem>
|
||||
|
||||
</ToolbarGroup>
|
||||
</ToolbarToggleGroup>
|
||||
</ToolbarContent>
|
||||
</Toolbar>
|
||||
</>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{renderFreezeJobToolbar()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
FreezeJobToolbar.propTypes = {
|
||||
fetchPipelinesIfNeeded: PropTypes.func,
|
||||
tenant: PropTypes.object,
|
||||
pipelines: PropTypes.object,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
defaultPipeline: PropTypes.string,
|
||||
defaultProject: PropTypes.string,
|
||||
defaultBranch: PropTypes.string,
|
||||
defaultJob: PropTypes.string,
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
tenant: state.tenant,
|
||||
pipelines: state.pipelines,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchPipelinesIfNeeded
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FreezeJobToolbar)
|
||||
@@ -20,11 +20,24 @@ import * as d3 from 'd3'
|
||||
import { makeJobGraphKey, fetchJobGraphIfNeeded } from '../../actions/jobgraph'
|
||||
import { graphviz } from 'd3-graphviz'
|
||||
|
||||
function makeDot(jobGraph) {
|
||||
let ret = 'digraph job_graph {'
|
||||
import { getHomepageUrl } from '../../api'
|
||||
|
||||
function makeDot(tenant, pipeline, project, branch, jobGraph) {
|
||||
let ret = 'digraph job_graph {\n'
|
||||
ret += ' rankdir=LR;\n'
|
||||
ret += ' node [shape=box];\n'
|
||||
jobGraph.forEach((job) => {
|
||||
const searchParams = new URLSearchParams('')
|
||||
searchParams.append('pipeline', pipeline)
|
||||
searchParams.append('project', project.name)
|
||||
searchParams.append('job', job.name)
|
||||
searchParams.append('branch', branch)
|
||||
const url = (getHomepageUrl() + tenant.linkPrefix +
|
||||
'freeze-job?' + searchParams.toString())
|
||||
// Escape ampersands to get it through graphviz and d3; these will
|
||||
// appear unescaped in the DOM.
|
||||
const escaped_url = url.replace(/&/g, '&')
|
||||
ret += ' "' + job.name + '" [URL="' + escaped_url + '"];\n'
|
||||
if (job.dependencies.length) {
|
||||
job.dependencies.forEach((dep) => {
|
||||
let soft = ' [dir=back]'
|
||||
@@ -33,8 +46,6 @@ function makeDot(jobGraph) {
|
||||
}
|
||||
ret += ' "' + dep.name + '" -> "' + job.name + '"' + soft + ';\n'
|
||||
})
|
||||
} else {
|
||||
ret += ' "' + job.name + '";\n'
|
||||
}
|
||||
})
|
||||
ret += '}\n'
|
||||
@@ -80,16 +91,16 @@ function JobGraphDisplay(props) {
|
||||
fetchJobGraphIfNeeded(tenant, project.name, pipeline, branch)
|
||||
}, [fetchJobGraphIfNeeded, tenant, project, pipeline, branch])
|
||||
|
||||
const tenantJobGraph = props.jobgraph.jobGraphs[props.tenant.name]
|
||||
const tenantJobGraph = props.jobgraph.jobGraphs[tenant.name]
|
||||
const jobGraphKey = makeJobGraphKey(props.project.name,
|
||||
props.pipeline,
|
||||
props.branch)
|
||||
const jobGraph = tenantJobGraph ? tenantJobGraph[jobGraphKey] : undefined
|
||||
useEffect(() => {
|
||||
if (jobGraph) {
|
||||
setDot(makeDot(jobGraph))
|
||||
setDot(makeDot(tenant, pipeline, project, branch, jobGraph))
|
||||
}
|
||||
}, [jobGraph])
|
||||
}, [tenant, pipeline, project, branch, jobGraph])
|
||||
return (
|
||||
<>
|
||||
{dot && <GraphViz dot={dot}/>}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
// 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, { useEffect, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { useHistory, useLocation } from 'react-router-dom'
|
||||
import {
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
Text,
|
||||
TextContent,
|
||||
} from '@patternfly/react-core'
|
||||
import ReactJson from 'react-json-view'
|
||||
|
||||
import FreezeJobToolbar from '../containers/freezejob/FreezeJobToolbar'
|
||||
import { makeFreezeJobKey, fetchFreezeJobIfNeeded } from '../actions/freezejob'
|
||||
|
||||
function FreezeJobPage(props) {
|
||||
const { tenant, fetchFreezeJobIfNeeded } = props
|
||||
|
||||
const [currentPipeline, setCurrentPipeline] = useState()
|
||||
const [currentProject, setCurrentProject] = useState()
|
||||
const [currentBranch, setCurrentBranch] = useState()
|
||||
const [currentJob, setCurrentJob] = useState()
|
||||
const history = useHistory()
|
||||
const location = useLocation()
|
||||
|
||||
if (!currentBranch) {
|
||||
const urlParams = new URLSearchParams(location.search)
|
||||
const pipeline = urlParams.get('pipeline')
|
||||
const project = urlParams.get('project')
|
||||
const branch = urlParams.get('branch')
|
||||
const job = urlParams.get('job')
|
||||
if (pipeline && branch && project && job) {
|
||||
setCurrentPipeline(pipeline)
|
||||
setCurrentProject(project)
|
||||
setCurrentBranch(branch)
|
||||
setCurrentJob(job)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'Zuul Frozen Job'
|
||||
if (currentPipeline && currentProject && currentBranch && currentJob) {
|
||||
fetchFreezeJobIfNeeded(tenant, currentPipeline, currentProject,
|
||||
currentBranch, currentJob)
|
||||
}
|
||||
}, [fetchFreezeJobIfNeeded, tenant, currentPipeline, currentProject,
|
||||
currentBranch, currentJob])
|
||||
|
||||
function onChange(pipeline, project, branch, job) {
|
||||
setCurrentPipeline(pipeline)
|
||||
setCurrentProject(project)
|
||||
setCurrentBranch(branch)
|
||||
setCurrentJob(job)
|
||||
|
||||
const searchParams = new URLSearchParams('')
|
||||
searchParams.append('pipeline', pipeline)
|
||||
searchParams.append('project', project)
|
||||
searchParams.append('branch', branch)
|
||||
searchParams.append('job', job)
|
||||
history.push({
|
||||
pathname: location.pathname,
|
||||
search: searchParams.toString(),
|
||||
})
|
||||
|
||||
if (currentPipeline && currentProject && currentBranch && currentJob) {
|
||||
fetchFreezeJobIfNeeded(tenant, currentPipeline, currentProject,
|
||||
currentBranch, currentJob)
|
||||
}
|
||||
}
|
||||
|
||||
const tenantJobs = props.freezejob.freezeJobs[tenant.name]
|
||||
const freezeJobKey = makeFreezeJobKey(currentPipeline,
|
||||
currentProject,
|
||||
currentBranch,
|
||||
currentJob)
|
||||
const job = tenantJobs ? tenantJobs[freezeJobKey] : undefined
|
||||
function renderFrozenJob() {
|
||||
return (
|
||||
<span style={{whiteSpace: 'pre-wrap'}}>
|
||||
<ReactJson
|
||||
src={job}
|
||||
name={null}
|
||||
collapsed={false}
|
||||
sortKeys={true}
|
||||
enableClipboard={false}
|
||||
displayDataTypes={false}/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
<TextContent>
|
||||
<Text component="h1">Freeze Job</Text>
|
||||
<Text component="p">
|
||||
Freezing a job asks Zuul to combine all the
|
||||
<i>project</i> and <i>job</i> configuration
|
||||
stanzas for a job as if a change for a given
|
||||
project and branch were to be enqueued into
|
||||
a specific pipeline. The resulting job
|
||||
configuration is displayed below.
|
||||
</Text>
|
||||
</TextContent>
|
||||
<FreezeJobToolbar
|
||||
onChange={onChange}
|
||||
defaultPipeline={currentPipeline}
|
||||
defaultProject={currentProject}
|
||||
defaultBranch={currentBranch}
|
||||
defaultJob={currentJob}
|
||||
/>
|
||||
{job && renderFrozenJob(job)}
|
||||
</PageSection>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
FreezeJobPage.propTypes = {
|
||||
fetchFreezeJobIfNeeded: PropTypes.func,
|
||||
tenant: PropTypes.object,
|
||||
freezejob: PropTypes.object,
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
tenant: state.tenant,
|
||||
freezejob: state.freezejob,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchFreezeJobIfNeeded
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FreezeJobPage)
|
||||
@@ -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 {
|
||||
FREEZE_JOB_FETCH_FAIL,
|
||||
FREEZE_JOB_FETCH_REQUEST,
|
||||
FREEZE_JOB_FETCH_SUCCESS
|
||||
} from '../actions/freezejob'
|
||||
|
||||
export default (state = {
|
||||
isFetching: false,
|
||||
freezeJobs: {},
|
||||
}, action) => {
|
||||
let stateFreezeJobs
|
||||
switch (action.type) {
|
||||
case FREEZE_JOB_FETCH_REQUEST:
|
||||
return {
|
||||
isFetching: true,
|
||||
freezeJobs: state.freezeJobs,
|
||||
}
|
||||
case FREEZE_JOB_FETCH_SUCCESS:
|
||||
stateFreezeJobs = !state.freezeJobs[action.tenant] ?
|
||||
{ ...state.freezeJobs, [action.tenant]: {} } :
|
||||
{ ...state.freezeJobs }
|
||||
return {
|
||||
isFetching: false,
|
||||
freezeJobs: {
|
||||
...stateFreezeJobs,
|
||||
[action.tenant]: {
|
||||
...stateFreezeJobs[action.tenant],
|
||||
[action.freezeJobKey]: action.freezeJob
|
||||
}
|
||||
}
|
||||
}
|
||||
case FREEZE_JOB_FETCH_FAIL:
|
||||
return {
|
||||
isFetching: false,
|
||||
freezeJobs: state.freezeJobs,
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import autoholds from './autoholds'
|
||||
import configErrors from './configErrors'
|
||||
import change from './change'
|
||||
import component from './component'
|
||||
import freezejob from './freezejob'
|
||||
import notifications from './notifications'
|
||||
import build from './build'
|
||||
import info from './info'
|
||||
@@ -30,6 +31,7 @@ import logfile from './logfile'
|
||||
import nodes from './nodes'
|
||||
import openapi from './openapi'
|
||||
import project from './project'
|
||||
import pipelines from './pipelines'
|
||||
import projects from './projects'
|
||||
import preferences from './preferences'
|
||||
import status from './status'
|
||||
@@ -45,6 +47,7 @@ const reducers = {
|
||||
change,
|
||||
component,
|
||||
configErrors,
|
||||
freezejob,
|
||||
notifications,
|
||||
info,
|
||||
job,
|
||||
@@ -54,6 +57,7 @@ const reducers = {
|
||||
logfile,
|
||||
nodes,
|
||||
openapi,
|
||||
pipelines,
|
||||
project,
|
||||
projects,
|
||||
status,
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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 {
|
||||
PIPELINES_FETCH_FAIL,
|
||||
PIPELINES_FETCH_REQUEST,
|
||||
PIPELINES_FETCH_SUCCESS
|
||||
} from '../actions/pipelines'
|
||||
|
||||
export default (state = {
|
||||
isFetching: false,
|
||||
pipelines: {},
|
||||
}, action) => {
|
||||
switch (action.type) {
|
||||
case PIPELINES_FETCH_REQUEST:
|
||||
return {
|
||||
isFetching: true,
|
||||
pipelines: state.pipelines,
|
||||
}
|
||||
case PIPELINES_FETCH_SUCCESS:
|
||||
return {
|
||||
isFetching: false,
|
||||
pipelines: { ...state.pipelines, [action.tenant]: action.pipelines },
|
||||
}
|
||||
case PIPELINES_FETCH_FAIL:
|
||||
return {
|
||||
isFetching: false,
|
||||
pipelines: state.pipelines,
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
// under the License.
|
||||
|
||||
import ComponentsPage from './pages/Components'
|
||||
import FreezeJobPage from './pages/FreezeJob'
|
||||
import StatusPage from './pages/Status'
|
||||
import ChangeStatusPage from './pages/ChangeStatus'
|
||||
import ProjectPage from './pages/Project'
|
||||
@@ -78,6 +79,10 @@ const routes = () => [
|
||||
to: '/buildsets',
|
||||
component: BuildsetsPage
|
||||
},
|
||||
{
|
||||
to: '/freeze-job',
|
||||
component: FreezeJobPage
|
||||
},
|
||||
{
|
||||
to: '/status/change/:changeId',
|
||||
component: ChangeStatusPage
|
||||
|
||||
Reference in New Issue
Block a user