// Copyright 2019 Red Hat, Inc // // 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, useDispatch } from 'react-redux' import { Link } from 'react-router-dom' import { Button, Flex, FlexItem, List, ListItem, Title, Modal, ModalVariant, } from '@patternfly/react-core' import { CodeIcon, CodeBranchIcon, OutlinedCommentDotsIcon, CubeIcon, FingerprintIcon, StreamIcon, OutlinedCalendarAltIcon, OutlinedClockIcon, RedoAltIcon, } from '@patternfly/react-icons' import * as moment from 'moment' import 'moment-duration-format' import { buildExternalLink, IconProperty } from '../../Misc' import { BuildResultBadge, BuildResultWithIcon } from './Misc' import { enqueue, enqueue_ref } from '../../api' import { addNotification, addApiError } from '../../actions/notifications' import { ChartModal } from '../charts/ChartModal' import BuildsetGanttChart from '../charts/GanttChart' function Buildset({ buildset, timezone, tenant, user }) { const buildset_link = buildExternalLink(buildset) const [isGanttChartModalOpen, setIsGanttChartModalOpen] = useState(false) function renderBuildTimes() { const firstStartBuild = buildset.builds.reduce((prev, cur) => !cur.start_time || prev.start_time < cur.start_time ? prev : cur ) const lastEndBuild = buildset.builds.reduce((prev, cur) => !cur.end_time || prev.end_time > cur.end_time ? prev : cur ) const totalDuration = (moment.utc(lastEndBuild.end_time).tz(timezone) - moment.utc(firstStartBuild.start_time).tz(timezone)) / 1000 const overallDuration = (moment.utc(lastEndBuild.end_time).tz(timezone) - moment.utc( buildset.event_timestamp != null ? buildset.event_timestamp : firstStartBuild.start_time ).tz(timezone) ) / 1000 const buildLink = (build) => ( {build.job_name} ) const firstStartLink = buildLink(firstStartBuild) const lastEndLink = buildLink(lastEndBuild) return ( } value={ Starting build {firstStartLink}
(started{' '} {moment .utc(firstStartBuild.start_time) .tz(timezone) .format('YYYY-MM-DD HH:mm:ss')} )
Ending build {lastEndLink}
(ended{' '} {moment .utc(lastEndBuild.end_time) .tz(timezone) .format('YYYY-MM-DD HH:mm:ss')} )
} /> } value={ <> Buildset duration {moment .duration(totalDuration, 'seconds') .format('h [hr] m [min] s [sec]')}{' '}   } /> } value={ <> Overall duration {moment .duration(overallDuration, 'seconds') .format('h [hr] m [min] s [sec]')} } />
) } const [showEnqueueModal, setShowEnqueueModal] = useState(false) const dispatch = useDispatch() function renderEnqueueButton() { const value = ( { event.preventDefault() setShowEnqueueModal(true) }} > Re-enqueue buildset ) return ( } value={value} /> ) } function enqueueConfirm() { setShowEnqueueModal(false) if (buildset.change === null) { const oldrev = '0000000000000000000000000000000000000000' const newrev = buildset.newrev ? buildset.newrev : '0000000000000000000000000000000000000000' enqueue_ref(tenant.apiPrefix, buildset.project, buildset.pipeline, buildset.ref, oldrev, newrev) .then(() => { dispatch(addNotification( { text: 'Enqueue successful.', type: 'success', status: '', url: '', })) }) .catch(error => { dispatch(addApiError(error)) }) } else { const changeId = buildset.change + ',' + buildset.patchset enqueue(tenant.apiPrefix, buildset.project, buildset.pipeline, changeId) .then(() => { dispatch(addNotification( { text: 'Change queued successfully.', type: 'success', status: '', url: '', })) }) .catch(error => { dispatch(addApiError(error)) }) } } function renderEnqueueModal() { let changeId = buildset.change ? buildset.change + ',' + buildset.patchset : buildset.newrev let changeInfo = changeId ? <>for change {changeId} : <>for ref {buildset.ref} const title = 'You are about to re-enqueue a change' return ( { setShowEnqueueModal(false) }} actions={[ , , ]}>

Please confirm that you want to re-enqueue all jobs {changeInfo} on project {buildset.project} on pipeline {buildset.pipeline}.

) } return ( <> <BuildResultWithIcon result={buildset.result} size="md"> Buildset result </BuildResultWithIcon> <BuildResultBadge result={buildset.result} />   {/* We handle the spacing for the body and the flex items by ourselves so they go hand in hand. By default, the flex items' spacing only affects left/right margin, but not top or bottom (which looks awkward when the items are stacked at certain breakpoints) */} {/* TODO (felix): It would be cool if we could differentiate between the SVC system (Github, Gitlab, Gerrit), so we could show the respective icon here (GithubIcon, GitlabIcon, GitIcon - AFAIK the Gerrit icon is not very popular among icon frameworks like fontawesome */} {buildset_link && ( } value={buildset_link} /> )} {/* TODO (felix): Link to project page in Zuul */} } value={ <> Project {buildset.project} } /> } value={ buildset.branch ? ( <> Branch {buildset.branch} ) : ( <> Ref {buildset.ref} ) } /> } value={ <> Pipeline {buildset.pipeline} } /> } value={ UUID {buildset.uuid}
Event ID {buildset.event_id}
} />
{buildset.builds && renderBuildTimes()} } value={ <> Message:
{buildset.message}
} /> {(user.isAdmin && user.scope.indexOf(tenant.name) !== -1) && <> {renderEnqueueButton()} }
} isOpen={isGanttChartModalOpen} title="Builds Timeline" onClose={() => { setIsGanttChartModalOpen(false) }} /> {renderEnqueueModal()} ) } Buildset.propTypes = { buildset: PropTypes.object, tenant: PropTypes.object, timezone: PropTypes.string, user: PropTypes.object, } export default connect((state) => ({ tenant: state.tenant, timezone: state.timezone, user: state.user, }))(Buildset)