зеркало из https://github.com/mozilla/treeherder.git
Bug 1492270 - Convert list of pinned jobs to a Context
This commit is contained in:
Родитель
8d5972d01d
Коммит
8aae70a82c
|
@ -7,19 +7,20 @@ const DIST = require('./base').DIST;
|
|||
module.exports = neutrino => {
|
||||
basePreset(neutrino);
|
||||
|
||||
neutrino.config.plugin('minify')
|
||||
.inject(BabiliPlugin => new BabiliPlugin({
|
||||
evaluate: false, // prevents some minification errors
|
||||
// Prevents a minification error in react-dom that manifests as
|
||||
// `ReferenceError: Hp is not defined` when loading the main jobs view (bug 1426902).
|
||||
// TODO: Either remove this workaround or file upstream if this persists
|
||||
// after the Neutrino upgrade (which comes with latest babel-plugin-minify-mangle-names).
|
||||
mangle: {
|
||||
keepFnName: true,
|
||||
},
|
||||
}
|
||||
));
|
||||
|
||||
// neutrino.config.plugin('minify')
|
||||
// .inject(BabiliPlugin => new BabiliPlugin({
|
||||
// evaluate: false, // prevents some minification errors
|
||||
// // Prevents a minification error in react-dom that manifests as
|
||||
// // `ReferenceError: Hp is not defined` when loading the main jobs view (bug 1426902).
|
||||
// // TODO: Either remove this workaround or file upstream if this persists
|
||||
// // after the Neutrino upgrade (which comes with latest babel-plugin-minify-mangle-names).
|
||||
// mangle: {
|
||||
// keepFnName: true,
|
||||
// },
|
||||
// }
|
||||
// ));
|
||||
neutrino.config.plugins.delete('minify');
|
||||
neutrino.config.devtool(false);
|
||||
neutrino.config.plugin('clean')
|
||||
.use(CleanPlugin, [DIST], { root: CWD } );
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ testpaths = tests
|
|||
norecursedirs = __pycache__ ui
|
||||
DJANGO_SETTINGS_MODULE=tests.settings
|
||||
# Disable unused auto-loaded plugins.
|
||||
addopts = --driver Firefox -p no:mozlog -p no:metadata -p no:html
|
||||
addopts = --driver Firefox -p no:mozlog -p no:metadata
|
||||
# Make most warnings fatal (including the hidden by default DeprecationWarning):
|
||||
# https://docs.pytest.org/en/latest/warnings.html
|
||||
# https://docs.python.org/2.7/library/warnings.html#warning-categories
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
font-size: 12px;
|
||||
}
|
||||
|
||||
.pin-count-group {
|
||||
#pin-count-group {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: -14px;
|
||||
|
|
|
@ -5,6 +5,7 @@ import SplitPane from 'react-split-pane';
|
|||
|
||||
import treeherder from '../js/treeherder';
|
||||
import { thEvents, thFavicons } from '../js/constants';
|
||||
import { PinnedJobs } from './context/PinnedJobs';
|
||||
import { matchesDefaults } from '../helpers/filter';
|
||||
import { deployedRevisionUrl } from '../helpers/url';
|
||||
import { getRepo } from '../helpers/location';
|
||||
|
@ -74,9 +75,9 @@ class JobView extends React.Component {
|
|||
|
||||
this.toggleFieldFilterVisible = this.toggleFieldFilterVisible.bind(this);
|
||||
this.updateDimensions = this.updateDimensions.bind(this);
|
||||
this.pinJobs = this.pinJobs.bind(this);
|
||||
this.setCurrentRepoTreeStatus = this.setCurrentRepoTreeStatus.bind(this);
|
||||
this.handleUrlChanges = this.handleUrlChanges.bind(this);
|
||||
this.selectFirstJob = this.selectFirstJob.bind(this);
|
||||
|
||||
RepositoryModel.getList().then((repos) => {
|
||||
const currentRepo = repos.find(repo => repo.name === repoName) || this.state.currentRepo;
|
||||
|
@ -95,7 +96,7 @@ class JobView extends React.Component {
|
|||
window.addEventListener('resize', this.updateDimensions, false);
|
||||
window.addEventListener('hashchange', this.handleUrlChanges, false);
|
||||
|
||||
this.$rootScope.$on(thEvents.toggleFieldFilterVisible, () => {
|
||||
this.toggleFieldFilterVisibleUnlisten = this.$rootScope.$on(thEvents.toggleFieldFilterVisible, () => {
|
||||
this.toggleFieldFilterVisible();
|
||||
});
|
||||
|
||||
|
@ -128,6 +129,7 @@ class JobView extends React.Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.toggleFieldFilterVisibleUnlisten();
|
||||
window.removeEventListener('resize', this.updateDimensions, false);
|
||||
window.removeEventListener('hashchange', this.handleUrlChanges, false);
|
||||
}
|
||||
|
@ -178,10 +180,6 @@ class JobView extends React.Component {
|
|||
this.setState({ isFieldFilterVisible: !this.state.isFieldFilterVisible });
|
||||
}
|
||||
|
||||
pinJobs() {
|
||||
this.$rootScope.$emit(thEvents.pinJobs, this.ThResultSetStore.getAllShownJobs());
|
||||
}
|
||||
|
||||
updateDimensions() {
|
||||
this.setState(JobView.getSplitterDimensions(this.props));
|
||||
}
|
||||
|
@ -192,6 +190,14 @@ class JobView extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
selectFirstJob(jobs) {
|
||||
const { selectedJob } = this.props;
|
||||
|
||||
if (!selectedJob) {
|
||||
this.$rootScope.$emit(thEvents.jobClick, jobs[0]);
|
||||
}
|
||||
}
|
||||
|
||||
clearIfEligibleTarget(target) {
|
||||
if (target.hasAttribute('data-job-clear-on-click')) {
|
||||
this.$rootScope.$emit(thEvents.clearSelectedJob, target);
|
||||
|
@ -230,64 +236,70 @@ class JobView extends React.Component {
|
|||
), []);
|
||||
|
||||
return (
|
||||
<KeyboardShortcuts
|
||||
filterModel={filterModel}
|
||||
<PinnedJobs
|
||||
notify={this.thNotify}
|
||||
selectedJob={selectedJob}
|
||||
$injector={$injector}
|
||||
selectFirstJob={this.selectFirstJob}
|
||||
>
|
||||
<PrimaryNavBar
|
||||
repos={repos}
|
||||
updateButtonClick={this.updateButtonClick}
|
||||
pinJobs={this.pinJobs}
|
||||
serverChanged={serverChanged}
|
||||
<KeyboardShortcuts
|
||||
filterModel={filterModel}
|
||||
setUser={this.setUser}
|
||||
user={user}
|
||||
setCurrentRepoTreeStatus={this.setCurrentRepoTreeStatus}
|
||||
selectedJob={selectedJob}
|
||||
$injector={$injector}
|
||||
/>
|
||||
<SplitPane
|
||||
split="horizontal"
|
||||
size={`${pushListPct}%`}
|
||||
onChange={size => this.handleSplitChange(size)}
|
||||
>
|
||||
<div className="d-flex flex-column w-100" onClick={evt => this.clearIfEligibleTarget(evt.target)}>
|
||||
{(isFieldFilterVisible || !!filterBarFilters.length) && <ActiveFilters
|
||||
$injector={$injector}
|
||||
classificationTypes={classificationTypes}
|
||||
filterModel={filterModel}
|
||||
filterBarFilters={filterBarFilters}
|
||||
isFieldFilterVisible={isFieldFilterVisible}
|
||||
toggleFieldFilterVisible={this.toggleFieldFilterVisible}
|
||||
/>}
|
||||
{serverChangedDelayed && <UpdateAvailable
|
||||
updateButtonClick={this.updateButtonClick}
|
||||
/>}
|
||||
<div id="th-global-content" className="th-global-content" data-job-clear-on-click>
|
||||
<span className="th-view-content">
|
||||
<PushList
|
||||
user={user}
|
||||
repoName={repoName}
|
||||
revision={revision}
|
||||
currentRepo={currentRepo}
|
||||
filterModel={filterModel}
|
||||
$injector={$injector}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<DetailsPanel
|
||||
resizedHeight={detailsHeight}
|
||||
currentRepo={currentRepo}
|
||||
repoName={repoName}
|
||||
selectedJob={selectedJob}
|
||||
<PrimaryNavBar
|
||||
repos={repos}
|
||||
updateButtonClick={this.updateButtonClick}
|
||||
serverChanged={serverChanged}
|
||||
filterModel={filterModel}
|
||||
setUser={this.setUser}
|
||||
user={user}
|
||||
classificationTypes={classificationTypes}
|
||||
classificationMap={classificationMap}
|
||||
setCurrentRepoTreeStatus={this.setCurrentRepoTreeStatus}
|
||||
$injector={$injector}
|
||||
resultSetStore={this.ThResultSetStore}
|
||||
/>
|
||||
</SplitPane>
|
||||
</KeyboardShortcuts>
|
||||
<SplitPane
|
||||
split="horizontal"
|
||||
size={`${pushListPct}%`}
|
||||
onChange={size => this.handleSplitChange(size)}
|
||||
>
|
||||
<div className="d-flex flex-column w-100" onClick={evt => this.clearIfEligibleTarget(evt.target)}>
|
||||
{(isFieldFilterVisible || !!filterBarFilters.length) && <ActiveFilters
|
||||
$injector={$injector}
|
||||
classificationTypes={classificationTypes}
|
||||
filterModel={filterModel}
|
||||
filterBarFilters={filterBarFilters}
|
||||
isFieldFilterVisible={isFieldFilterVisible}
|
||||
toggleFieldFilterVisible={this.toggleFieldFilterVisible}
|
||||
/>}
|
||||
{serverChangedDelayed && <UpdateAvailable
|
||||
updateButtonClick={this.updateButtonClick}
|
||||
/>}
|
||||
<div id="th-global-content" className="th-global-content" data-job-clear-on-click>
|
||||
<span className="th-view-content">
|
||||
<PushList
|
||||
user={user}
|
||||
repoName={repoName}
|
||||
revision={revision}
|
||||
currentRepo={currentRepo}
|
||||
filterModel={filterModel}
|
||||
$injector={$injector}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<DetailsPanel
|
||||
resizedHeight={detailsHeight}
|
||||
currentRepo={currentRepo}
|
||||
repoName={repoName}
|
||||
selectedJob={selectedJob}
|
||||
user={user}
|
||||
classificationTypes={classificationTypes}
|
||||
classificationMap={classificationMap}
|
||||
$injector={$injector}
|
||||
/>
|
||||
</SplitPane>
|
||||
</KeyboardShortcuts>
|
||||
</PinnedJobs>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import { thEvents, thJobNavSelectors } from '../js/constants';
|
||||
import { withPinnedJobs } from './context/PinnedJobs';
|
||||
|
||||
const keyMap = {
|
||||
addRelatedBug: 'b',
|
||||
|
@ -27,7 +28,7 @@ const keyMap = {
|
|||
deleteClassification: 'ctrl+backspace',
|
||||
};
|
||||
|
||||
export default class KeyboardShortcuts extends React.Component {
|
||||
class KeyboardShortcuts extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
@ -108,36 +109,37 @@ export default class KeyboardShortcuts extends React.Component {
|
|||
|
||||
// pin selected job to pinboard
|
||||
pinJob() {
|
||||
const { selectedJob } = this.props;
|
||||
const { selectedJob, pinJob } = this.props;
|
||||
|
||||
if (selectedJob) {
|
||||
this.$rootScope.$emit(thEvents.jobPin, selectedJob);
|
||||
pinJob(selectedJob);
|
||||
}
|
||||
}
|
||||
|
||||
// pin selected job to pinboard and add a related bug
|
||||
addRelatedBug() {
|
||||
const { selectedJob } = this.$rootScope;
|
||||
const { selectedJob, pinJob } = this.props;
|
||||
|
||||
if (selectedJob) {
|
||||
this.$rootScope.$emit(thEvents.addRelatedBug, selectedJob);
|
||||
pinJob(selectedJob);
|
||||
document.getElementById('add-related-bug-button').click();
|
||||
document.getElementById('related-bug-input').focus();
|
||||
}
|
||||
}
|
||||
|
||||
// pin selected job to pinboard and enter classification
|
||||
pinEditComment() {
|
||||
const { selectedJob } = this.$rootScope;
|
||||
const { selectedJob, pinJob } = this.props;
|
||||
|
||||
if (selectedJob) {
|
||||
this.$rootScope.$emit(thEvents.jobPin, selectedJob);
|
||||
pinJob(selectedJob);
|
||||
document.getElementById('classification-comment').focus();
|
||||
}
|
||||
}
|
||||
|
||||
// clear the PinBoard
|
||||
clearPinboard() {
|
||||
this.$rootScope.$emit(thEvents.clearPinboard);
|
||||
this.props.unPinAll();
|
||||
}
|
||||
|
||||
saveClassification() {
|
||||
|
@ -261,6 +263,8 @@ export default class KeyboardShortcuts extends React.Component {
|
|||
KeyboardShortcuts.propTypes = {
|
||||
filterModel: PropTypes.object.isRequired,
|
||||
$injector: PropTypes.object.isRequired,
|
||||
pinJob: PropTypes.func.isRequired,
|
||||
unPinAll: PropTypes.func.isRequired,
|
||||
children: PropTypes.array.isRequired,
|
||||
selectedJob: PropTypes.object,
|
||||
};
|
||||
|
@ -268,3 +272,5 @@ KeyboardShortcuts.propTypes = {
|
|||
KeyboardShortcuts.defaultProps = {
|
||||
selectedJob: null,
|
||||
};
|
||||
|
||||
export default withPinnedJobs(KeyboardShortcuts);
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const COUNT_ERROR = 'Max pinboard size of 500 reached.';
|
||||
const MAX_SIZE = 500;
|
||||
const PinnedJobsContext = React.createContext({});
|
||||
|
||||
export class PinnedJobs extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
pinnedJobs: {},
|
||||
pinnedJobBugs: {},
|
||||
isPinBoardVisible: false,
|
||||
};
|
||||
this.value = {
|
||||
...this.state,
|
||||
setPinBoardVisible: this.setPinBoardVisible,
|
||||
pinJob: this.pinJob,
|
||||
unPinJob: this.unPinJob,
|
||||
togglePinJob: this.togglePinJob,
|
||||
pinJobs: this.pinJobs,
|
||||
unPinAll: this.unPinAll,
|
||||
addBug: this.addBug,
|
||||
removeBug: this.removeBug,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.pinJob = this.pinJob.bind(this);
|
||||
this.unPinJob = this.unPinJob.bind(this);
|
||||
this.pinJobs = this.pinJobs.bind(this);
|
||||
this.addBug = this.addBug.bind(this);
|
||||
this.removeBug = this.removeBug.bind(this);
|
||||
this.unPinAll = this.unPinAll.bind(this);
|
||||
this.togglePinJob = this.togglePinJob.bind(this);
|
||||
this.setPinBoardVisible = this.setPinBoardVisible.bind(this);
|
||||
|
||||
// TODO: this.value needs to now get the bound versions of the functions.
|
||||
// But when we move the function binding to the constructors, we won't
|
||||
// have to re-do this in componentDidMount.
|
||||
this.value = {
|
||||
...this.state,
|
||||
setPinBoardVisible: this.setPinBoardVisible,
|
||||
togglePinJob: this.togglePinJob,
|
||||
pinJob: this.pinJob,
|
||||
unPinJob: this.unPinJob,
|
||||
pinJobs: this.pinJobs,
|
||||
unPinAll: this.unPinAll,
|
||||
addBug: this.addBug,
|
||||
removeBug: this.removeBug,
|
||||
};
|
||||
}
|
||||
|
||||
setValue(newState, callback) {
|
||||
this.value = { ...this.value, ...newState };
|
||||
this.setState(newState, callback);
|
||||
}
|
||||
|
||||
setPinBoardVisible(isPinBoardVisible) {
|
||||
this.setValue({ isPinBoardVisible });
|
||||
}
|
||||
|
||||
pinJob(job, callback) {
|
||||
const { pinnedJobs } = this.state;
|
||||
const { notify } = this.props;
|
||||
|
||||
if (MAX_SIZE - Object.keys(pinnedJobs).length > 0) {
|
||||
this.setValue({
|
||||
pinnedJobs: { ...pinnedJobs, [job.id]: job },
|
||||
isPinBoardVisible: true,
|
||||
}, () => { if (callback) callback(); });
|
||||
this.pulsePinCount();
|
||||
} else {
|
||||
notify.send(COUNT_ERROR, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
unPinJob(id) {
|
||||
const { pinnedJobs } = this.state;
|
||||
|
||||
delete pinnedJobs[id];
|
||||
this.setValue({ pinnedJobs: { ...pinnedJobs } });
|
||||
}
|
||||
|
||||
pinJobs(jobsToPin) {
|
||||
const { pinnedJobs } = this.state;
|
||||
const { notify, selectFirstJob } = this.props;
|
||||
const spaceRemaining = MAX_SIZE - Object.keys(pinnedJobs).length;
|
||||
const showError = jobsToPin.length > spaceRemaining;
|
||||
const newPinnedJobs = jobsToPin.slice(0, spaceRemaining).reduce((acc, job) => ({ ...acc, [job.id]: job }), {});
|
||||
|
||||
if (!spaceRemaining) {
|
||||
notify.send(COUNT_ERROR, 'danger', { sticky: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setValue({
|
||||
pinnedJobs: { ...pinnedJobs, ...newPinnedJobs },
|
||||
isPinBoardVisible: true,
|
||||
}, () => {
|
||||
selectFirstJob(Object.values(newPinnedJobs));
|
||||
if (showError) {
|
||||
notify.send(COUNT_ERROR, 'danger', { sticky: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addBug(bug, job) {
|
||||
const { pinnedJobBugs } = this.state;
|
||||
|
||||
pinnedJobBugs[bug.id] = bug;
|
||||
this.setValue({ pinnedJobBugs: { ...pinnedJobBugs } });
|
||||
if (job) {
|
||||
this.pinJob(job);
|
||||
}
|
||||
}
|
||||
|
||||
removeBug(id) {
|
||||
const { pinnedJobBugs } = this.state;
|
||||
|
||||
delete pinnedJobBugs[id];
|
||||
this.setValue({ pinnedJobBugs: { ...pinnedJobBugs } });
|
||||
}
|
||||
|
||||
unPinAll() {
|
||||
this.setValue({
|
||||
pinnedJobs: {},
|
||||
pinnedJobBugs: {},
|
||||
});
|
||||
}
|
||||
|
||||
togglePinJob(job) {
|
||||
const { pinnedJobs } = this.state;
|
||||
|
||||
if (pinnedJobs[job.id]) {
|
||||
this.unPinJob(job.id);
|
||||
} else {
|
||||
this.pinJob(job);
|
||||
}
|
||||
}
|
||||
|
||||
pulsePinCount() {
|
||||
const jobEl = document.getElementById('pin-count-group');
|
||||
|
||||
if (jobEl) {
|
||||
jobEl.classList.add('pin-count-pulse');
|
||||
window.setTimeout(() => {
|
||||
jobEl.classList.remove('pin-count-pulse');
|
||||
}, 700);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<PinnedJobsContext.Provider value={this.value}>
|
||||
{this.props.children}
|
||||
</PinnedJobsContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function withPinnedJobs(Component) {
|
||||
return function PinBoardComponent(props) {
|
||||
return (
|
||||
<PinnedJobsContext.Consumer>
|
||||
{context => (
|
||||
<Component
|
||||
{...props}
|
||||
pinnedJobs={context.pinnedJobs}
|
||||
pinnedJobBugs={context.pinnedJobBugs}
|
||||
isPinBoardVisible={context.isPinBoardVisible}
|
||||
setPinBoardVisible={context.setPinBoardVisible}
|
||||
pinJob={context.pinJob}
|
||||
unPinJob={context.unPinJob}
|
||||
pinJobs={context.pinJobs}
|
||||
unPinAll={context.unPinAll}
|
||||
togglePinJob={context.togglePinJob}
|
||||
addBug={context.addBug}
|
||||
removeBug={context.removeBug}
|
||||
/>
|
||||
)}
|
||||
</PinnedJobsContext.Consumer>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
PinnedJobs.propTypes = {
|
||||
notify: PropTypes.object.isRequired,
|
||||
selectFirstJob: PropTypes.func.isRequired,
|
||||
children: PropTypes.object.isRequired,
|
||||
};
|
|
@ -1,14 +1,9 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { chunk } from 'lodash';
|
||||
import $ from 'jquery';
|
||||
|
||||
import {
|
||||
thEvents,
|
||||
thBugSuggestionLimit,
|
||||
thPinboardCountError,
|
||||
thPinboardMaxSize,
|
||||
} from '../../js/constants';
|
||||
import { thEvents, thBugSuggestionLimit } from '../../js/constants';
|
||||
import { withPinnedJobs } from '../context/PinnedJobs';
|
||||
import { getLogViewerUrl, getReftestUrl } from '../../helpers/url';
|
||||
import BugJobMapModel from '../../models/bugJobMap';
|
||||
import BugSuggestionsModel from '../../models/bugSuggestions';
|
||||
|
@ -17,7 +12,6 @@ import JobModel from '../../models/job';
|
|||
import JobDetailModel from '../../models/jobDetail';
|
||||
import JobLogUrlModel from '../../models/jobLogUrl';
|
||||
import TextLogStepModel from '../../models/textLogStep';
|
||||
|
||||
import PinBoard from './PinBoard';
|
||||
import SummaryPanel from './summary/SummaryPanel';
|
||||
import TabsPanel from './tabs/TabsPanel';
|
||||
|
@ -25,7 +19,7 @@ import { setUrlParam } from '../../helpers/location';
|
|||
|
||||
export const pinboardHeight = 100;
|
||||
|
||||
export default class DetailsPanel extends React.Component {
|
||||
class DetailsPanel extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
@ -33,7 +27,6 @@ export default class DetailsPanel extends React.Component {
|
|||
|
||||
this.PhSeries = $injector.get('PhSeries');
|
||||
this.ThResultSetStore = $injector.get('ThResultSetStore');
|
||||
this.thNotify = $injector.get('thNotify');
|
||||
this.$rootScope = $injector.get('$rootScope');
|
||||
|
||||
// used to cancel all the ajax requests triggered by selectJob
|
||||
|
@ -41,7 +34,6 @@ export default class DetailsPanel extends React.Component {
|
|||
|
||||
this.state = {
|
||||
job: null,
|
||||
isPinBoardVisible: false,
|
||||
jobDetails: [],
|
||||
jobLogUrls: [],
|
||||
jobDetailLoading: false,
|
||||
|
@ -57,8 +49,6 @@ export default class DetailsPanel extends React.Component {
|
|||
suggestions: [],
|
||||
errors: [],
|
||||
bugSuggestionsLoading: false,
|
||||
pinnedJobs: {},
|
||||
pinnedJobBugs: {},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -70,23 +60,13 @@ export default class DetailsPanel extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.pinJob = this.pinJob.bind(this);
|
||||
this.unPinJob = this.unPinJob.bind(this);
|
||||
this.unPinAll = this.unPinAll.bind(this);
|
||||
this.addBug = this.addBug.bind(this);
|
||||
this.removeBug = this.removeBug.bind(this);
|
||||
this.closeJob = this.closeJob.bind(this);
|
||||
this.countPinnedJobs = this.countPinnedJobs.bind(this);
|
||||
// give access to this count to components that don't have a common ancestor in React
|
||||
// TODO: remove this once pinnedJobs is converted to a model or Context
|
||||
this.$rootScope.countPinnedJobs = this.countPinnedJobs;
|
||||
|
||||
this.jobClickUnlisten = this.$rootScope.$on(thEvents.jobClick, (evt, job) => {
|
||||
this.setState({
|
||||
jobDetailLoading: true,
|
||||
jobDetails: [],
|
||||
suggestions: [],
|
||||
isPinBoardVisible: !!this.countPinnedJobs(),
|
||||
}, () => this.selectJob(job));
|
||||
});
|
||||
|
||||
|
@ -94,47 +74,20 @@ export default class DetailsPanel extends React.Component {
|
|||
if (this.selectJobController !== null) {
|
||||
this.selectJobController.abort();
|
||||
}
|
||||
if (!this.countPinnedJobs()) {
|
||||
if (!Object.keys(this.props.pinnedJobs).length) {
|
||||
this.closeJob();
|
||||
}
|
||||
});
|
||||
|
||||
this.toggleJobPinUnlisten = this.$rootScope.$on(thEvents.toggleJobPin, (event, job) => {
|
||||
this.toggleJobPin(job);
|
||||
});
|
||||
|
||||
this.jobPinUnlisten = this.$rootScope.$on(thEvents.jobPin, (event, job) => {
|
||||
this.pinJob(job);
|
||||
});
|
||||
|
||||
this.jobsClassifiedUnlisten = this.$rootScope.$on(thEvents.jobsClassified, () => {
|
||||
this.updateClassifications(this.props.selectedJob);
|
||||
});
|
||||
|
||||
this.pinAllShownJobsUnlisten = this.$rootScope.$on(thEvents.pinJobs, (event, jobs) => {
|
||||
this.pinJobs(jobs);
|
||||
});
|
||||
|
||||
this.clearPinboardUnlisten = this.$rootScope.$on(thEvents.clearPinboard, () => {
|
||||
if (this.state.isPinBoardVisible) {
|
||||
this.unPinAll();
|
||||
}
|
||||
});
|
||||
|
||||
this.pulsePinCountUnlisten = this.$rootScope.$on(thEvents.pulsePinCount, () => {
|
||||
this.pulsePinCount();
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.jobClickUnlisten();
|
||||
this.clearSelectedJobUnlisten();
|
||||
this.toggleJobPinUnlisten();
|
||||
this.jobPinUnlisten();
|
||||
this.jobsClassifiedUnlisten();
|
||||
this.clearPinboardUnlisten();
|
||||
this.pulsePinCountUnlisten();
|
||||
this.pinAllShownJobsUnlisten();
|
||||
}
|
||||
|
||||
getRevisionTips() {
|
||||
|
@ -146,7 +99,9 @@ export default class DetailsPanel extends React.Component {
|
|||
}
|
||||
|
||||
togglePinBoardVisibility() {
|
||||
this.setState({ isPinBoardVisible: !this.state.isPinBoardVisible });
|
||||
const { setPinBoardVisible, isPinBoardVisible } = this.props;
|
||||
|
||||
setPinBoardVisible(!isPinBoardVisible);
|
||||
}
|
||||
|
||||
loadBugSuggestions(job) {
|
||||
|
@ -319,108 +274,15 @@ export default class DetailsPanel extends React.Component {
|
|||
this.setState({ isPinboardVisible: false });
|
||||
}
|
||||
|
||||
toggleJobPin(job) {
|
||||
const { pinnedJobs } = this.state;
|
||||
|
||||
if (pinnedJobs[job.id]) {
|
||||
this.unPinJob(job.id);
|
||||
} else {
|
||||
this.pinJob(job);
|
||||
}
|
||||
}
|
||||
|
||||
pulsePinCount() {
|
||||
$('.pin-count-group').addClass('pin-count-pulse');
|
||||
window.setTimeout(() => {
|
||||
$('.pin-count-group').removeClass('pin-count-pulse');
|
||||
}, 700);
|
||||
}
|
||||
|
||||
pinJob(job) {
|
||||
const { pinnedJobs } = this.state;
|
||||
|
||||
if (thPinboardMaxSize - this.countPinnedJobs() > 0) {
|
||||
this.setState({
|
||||
pinnedJobs: { ...pinnedJobs, [job.id]: job },
|
||||
isPinBoardVisible: true,
|
||||
});
|
||||
this.pulsePinCount();
|
||||
} else {
|
||||
this.thNotify.send(thPinboardCountError, 'danger');
|
||||
}
|
||||
if (!this.state.selectedJob) {
|
||||
this.selectJob(job);
|
||||
}
|
||||
}
|
||||
|
||||
unPinJob(id) {
|
||||
const { pinnedJobs } = this.state;
|
||||
|
||||
delete pinnedJobs[id];
|
||||
this.setState({ pinnedJobs: { ...pinnedJobs } });
|
||||
}
|
||||
|
||||
pinJobs(jobsToPin) {
|
||||
const { pinnedJobs } = this.state;
|
||||
const spaceRemaining = thPinboardMaxSize - this.countPinnedJobs();
|
||||
const showError = jobsToPin.length > spaceRemaining;
|
||||
const newPinnedJobs = jobsToPin.slice(0, spaceRemaining).reduce((acc, job) => ({ ...acc, [job.id]: job }), {});
|
||||
|
||||
if (!spaceRemaining) {
|
||||
this.thNotify.send(thPinboardCountError, 'danger', { sticky: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
pinnedJobs: { ...pinnedJobs, ...newPinnedJobs },
|
||||
isPinBoardVisible: true,
|
||||
}, () => {
|
||||
if (!this.props.selectedJob) {
|
||||
this.$rootScope.$emit(thEvents.jobClick, jobsToPin[0]);
|
||||
}
|
||||
if (showError) {
|
||||
this.thNotify.send(thPinboardCountError, 'danger', { sticky: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
countPinnedJobs() {
|
||||
return Object.keys(this.state.pinnedJobs).length;
|
||||
}
|
||||
|
||||
addBug(bug, job) {
|
||||
const { pinnedJobBugs } = this.state;
|
||||
|
||||
pinnedJobBugs[bug.id] = bug;
|
||||
this.setState({ pinnedJobBugs: { ...pinnedJobBugs } });
|
||||
if (job) {
|
||||
this.pinJob(job);
|
||||
}
|
||||
}
|
||||
|
||||
removeBug(id) {
|
||||
const { pinnedJobBugs } = this.state;
|
||||
|
||||
delete pinnedJobBugs[id];
|
||||
this.setState({ pinnedJobBugs: { ...pinnedJobBugs } });
|
||||
}
|
||||
|
||||
unPinAll() {
|
||||
this.setState({
|
||||
pinnedJobs: {},
|
||||
pinnedJobBugs: {},
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
repoName, $injector, user, currentRepo, resizedHeight, classificationMap,
|
||||
classificationTypes,
|
||||
classificationTypes, isPinBoardVisible,
|
||||
} = this.props;
|
||||
const {
|
||||
job, isPinBoardVisible, jobDetails, jobRevision, jobLogUrls, jobDetailLoading,
|
||||
job, jobDetails, jobRevision, jobLogUrls, jobDetailLoading,
|
||||
perfJobDetail, suggestions, errors, bugSuggestionsLoading, logParseStatus,
|
||||
classifications, logViewerUrl, logViewerFullUrl, pinnedJobs, pinnedJobBugs, bugs, reftestUrl,
|
||||
classifications, logViewerUrl, logViewerFullUrl, bugs, reftestUrl,
|
||||
} = this.state;
|
||||
const detailsPanelHeight = isPinBoardVisible ? resizedHeight - pinboardHeight : resizedHeight;
|
||||
|
||||
|
@ -431,18 +293,10 @@ export default class DetailsPanel extends React.Component {
|
|||
className={job ? 'details-panel-slide' : 'hidden'}
|
||||
>
|
||||
<PinBoard
|
||||
isVisible={isPinBoardVisible}
|
||||
selectedJob={job}
|
||||
isLoggedIn={user.isLoggedIn || false}
|
||||
classificationTypes={classificationTypes}
|
||||
revisionList={this.getRevisionTips()}
|
||||
pinnedJobs={pinnedJobs}
|
||||
pinnedJobBugs={pinnedJobBugs}
|
||||
addBug={this.addBug}
|
||||
removeBug={this.removeBug}
|
||||
pinJob={this.pinJob}
|
||||
unPinJob={this.unPinJob}
|
||||
unPinAll={this.unPinAll}
|
||||
$injector={$injector}
|
||||
/>
|
||||
{!!job && <div id="details-panel-content">
|
||||
|
@ -457,7 +311,6 @@ export default class DetailsPanel extends React.Component {
|
|||
latestClassification={classifications.length ? classifications[0] : null}
|
||||
logViewerUrl={logViewerUrl}
|
||||
logViewerFullUrl={logViewerFullUrl}
|
||||
pinJob={this.pinJob}
|
||||
bugs={bugs}
|
||||
user={user}
|
||||
$injector={$injector}
|
||||
|
@ -476,11 +329,7 @@ export default class DetailsPanel extends React.Component {
|
|||
classifications={classifications}
|
||||
classificationMap={classificationMap}
|
||||
jobLogUrls={jobLogUrls}
|
||||
isPinBoardVisible={isPinBoardVisible}
|
||||
pinnedJobs={pinnedJobs}
|
||||
bugs={bugs}
|
||||
addBug={this.addBug}
|
||||
pinJob={this.pinJob}
|
||||
togglePinBoardVisibility={() => this.togglePinBoardVisibility()}
|
||||
logViewerFullUrl={logViewerFullUrl}
|
||||
reftestUrl={reftestUrl}
|
||||
|
@ -502,9 +351,14 @@ DetailsPanel.propTypes = {
|
|||
resizedHeight: PropTypes.number.isRequired,
|
||||
classificationTypes: PropTypes.array.isRequired,
|
||||
classificationMap: PropTypes.object.isRequired,
|
||||
setPinBoardVisible: PropTypes.func.isRequired,
|
||||
isPinBoardVisible: PropTypes.bool.isRequired,
|
||||
pinnedJobs: PropTypes.object.isRequired,
|
||||
selectedJob: PropTypes.object,
|
||||
};
|
||||
|
||||
DetailsPanel.defaultProps = {
|
||||
selectedJob: null,
|
||||
};
|
||||
|
||||
export default withPinnedJobs(DetailsPanel);
|
||||
|
|
|
@ -10,8 +10,9 @@ import { getBugUrl } from '../../helpers/url';
|
|||
import BugJobMapModel from '../../models/bugJobMap';
|
||||
import JobClassificationModel from '../../models/classification';
|
||||
import JobModel from '../../models/job';
|
||||
import { withPinnedJobs } from '../context/PinnedJobs';
|
||||
|
||||
export default class PinBoard extends React.Component {
|
||||
class PinBoard extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
@ -37,18 +38,12 @@ export default class PinBoard extends React.Component {
|
|||
this.retriggerAllPinnedJobs = this.retriggerAllPinnedJobs.bind(this);
|
||||
this.pasteSHA = this.pasteSHA.bind(this);
|
||||
|
||||
this.addRelatedBugUnlisten = this.$rootScope.$on(thEvents.addRelatedBug, (event, job) => {
|
||||
this.props.pinJob(job);
|
||||
this.toggleEnterBugNumber(true);
|
||||
});
|
||||
|
||||
this.saveClassificationUnlisten = this.$rootScope.$on(thEvents.saveClassification, () => {
|
||||
this.save();
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.addRelatedBugUnlisten();
|
||||
this.saveClassificationUnlisten();
|
||||
}
|
||||
|
||||
|
@ -347,7 +342,7 @@ export default class PinBoard extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
selectedJob, revisionList, isLoggedIn, isVisible, classificationTypes,
|
||||
selectedJob, revisionList, isLoggedIn, isPinBoardVisible, classificationTypes,
|
||||
pinnedJobs, pinnedJobBugs, removeBug, unPinJob,
|
||||
} = this.props;
|
||||
const {
|
||||
|
@ -359,7 +354,7 @@ export default class PinBoard extends React.Component {
|
|||
return (
|
||||
<div
|
||||
id="pinboard-panel"
|
||||
className={isVisible ? '' : 'hidden'}
|
||||
className={isPinBoardVisible ? '' : 'hidden'}
|
||||
>
|
||||
<div id="pinboard-contents">
|
||||
<div id="pinned-job-list">
|
||||
|
@ -389,6 +384,7 @@ export default class PinBoard extends React.Component {
|
|||
<div id="pinboard-related-bugs">
|
||||
<div className="content">
|
||||
<span
|
||||
id="add-related-bug-button"
|
||||
onClick={() => this.toggleEnterBugNumber(!enteringBugNumber)}
|
||||
className="pointable"
|
||||
title="Add a related bug"
|
||||
|
@ -541,13 +537,12 @@ PinBoard.propTypes = {
|
|||
$injector: PropTypes.object.isRequired,
|
||||
classificationTypes: PropTypes.array.isRequired,
|
||||
isLoggedIn: PropTypes.bool.isRequired,
|
||||
isVisible: PropTypes.bool.isRequired,
|
||||
isPinBoardVisible: PropTypes.bool.isRequired,
|
||||
pinnedJobs: PropTypes.object.isRequired,
|
||||
pinnedJobBugs: PropTypes.object.isRequired,
|
||||
addBug: PropTypes.func.isRequired,
|
||||
removeBug: PropTypes.func.isRequired,
|
||||
unPinJob: PropTypes.func.isRequired,
|
||||
pinJob: PropTypes.func.isRequired,
|
||||
unPinAll: PropTypes.func.isRequired,
|
||||
selectedJob: PropTypes.object,
|
||||
email: PropTypes.string,
|
||||
|
@ -559,3 +554,5 @@ PinBoard.defaultProps = {
|
|||
email: null,
|
||||
revisionList: [],
|
||||
};
|
||||
|
||||
export default withPinnedJobs(PinBoard);
|
||||
|
|
|
@ -10,8 +10,9 @@ import JobModel from '../../../models/job';
|
|||
import TaskclusterModel from '../../../models/taskcluster';
|
||||
import CustomJobActions from '../../CustomJobActions';
|
||||
import LogUrls from './LogUrls';
|
||||
import { withPinnedJobs } from '../../context/PinnedJobs';
|
||||
|
||||
export default class ActionBar extends React.Component {
|
||||
class ActionBar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
@ -350,3 +351,5 @@ ActionBar.defaultProps = {
|
|||
logViewerFullUrl: null,
|
||||
jobLogUrls: [],
|
||||
};
|
||||
|
||||
export default withPinnedJobs(ActionBar);
|
||||
|
|
|
@ -51,7 +51,7 @@ export default class SummaryPanel extends React.Component {
|
|||
const {
|
||||
repoName, selectedJob, latestClassification, bugs, jobLogUrls,
|
||||
jobDetailLoading, buildUrl, logViewerUrl, logViewerFullUrl,
|
||||
logParseStatus, pinJob, $injector, user, currentRepo, classificationMap,
|
||||
logParseStatus, $injector, user, currentRepo, classificationMap,
|
||||
} = this.props;
|
||||
const { machineUrl, machineUrlStatus } = this.state;
|
||||
|
||||
|
@ -77,7 +77,6 @@ export default class SummaryPanel extends React.Component {
|
|||
logViewerUrl={logViewerUrl}
|
||||
logViewerFullUrl={logViewerFullUrl}
|
||||
jobLogUrls={jobLogUrls}
|
||||
pinJob={pinJob}
|
||||
$injector={$injector}
|
||||
user={user}
|
||||
/>
|
||||
|
@ -189,7 +188,6 @@ export default class SummaryPanel extends React.Component {
|
|||
|
||||
SummaryPanel.propTypes = {
|
||||
repoName: PropTypes.string.isRequired,
|
||||
pinJob: PropTypes.func.isRequired,
|
||||
bugs: PropTypes.array.isRequired,
|
||||
$injector: PropTypes.object.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
|
|
|
@ -12,8 +12,9 @@ import PerformanceTab from './PerformanceTab';
|
|||
import AutoclassifyTab from './autoclassify/AutoclassifyTab';
|
||||
import AnnotationsTab from './AnnotationsTab';
|
||||
import SimilarJobsTab from './SimilarJobsTab';
|
||||
import { withPinnedJobs } from '../../context/PinnedJobs';
|
||||
|
||||
export default class TabsPanel extends React.Component {
|
||||
class TabsPanel extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
@ -92,9 +93,9 @@ export default class TabsPanel extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
jobDetails, jobLogUrls, logParseStatus, suggestions, errors, pinJob, user, bugs,
|
||||
jobDetails, jobLogUrls, logParseStatus, suggestions, errors, user, bugs,
|
||||
bugSuggestionsLoading, selectedJob, perfJobDetail, repoName, jobRevision,
|
||||
classifications, togglePinBoardVisibility, isPinBoardVisible, pinnedJobs, addBug,
|
||||
classifications, togglePinBoardVisibility, isPinBoardVisible, pinnedJobs,
|
||||
classificationMap, logViewerFullUrl, reftestUrl, $injector,
|
||||
} = this.props;
|
||||
const { showAutoclassifyTab, tabIndex } = this.state;
|
||||
|
@ -124,8 +125,9 @@ export default class TabsPanel extends React.Component {
|
|||
title={isPinBoardVisible ? 'Close the pinboard' : 'Open the pinboard'}
|
||||
>PinBoard
|
||||
{!!countPinnedJobs && <div
|
||||
id="pin-count-group"
|
||||
title={`You have ${countPinnedJobs} job${countPinnedJobs > 1 ? 's' : ''} pinned`}
|
||||
className={`pin-count-group ${countPinnedJobs > 99 ? 'pin-count-group-3-digit' : ''}`}
|
||||
className={`${countPinnedJobs > 99 ? 'pin-count-group-3-digit' : ''}`}
|
||||
>
|
||||
<div
|
||||
className={`pin-count-text ${countPinnedJobs > 99 ? 'pin-count-group-3-digit' : ''}`}
|
||||
|
@ -152,8 +154,6 @@ export default class TabsPanel extends React.Component {
|
|||
bugSuggestionsLoading={bugSuggestionsLoading}
|
||||
jobLogUrls={jobLogUrls}
|
||||
logParseStatus={logParseStatus}
|
||||
addBug={addBug}
|
||||
pinJob={pinJob}
|
||||
logViewerFullUrl={logViewerFullUrl}
|
||||
reftestUrl={reftestUrl}
|
||||
$injector={$injector}
|
||||
|
@ -165,9 +165,6 @@ export default class TabsPanel extends React.Component {
|
|||
hasLogs={!!jobLogUrls.length}
|
||||
logsParsed={logParseStatus !== 'pending'}
|
||||
logParseStatus={logParseStatus}
|
||||
addBug={addBug}
|
||||
pinJob={pinJob}
|
||||
pinnedJobs={pinnedJobs}
|
||||
user={user}
|
||||
$injector={$injector}
|
||||
/>
|
||||
|
@ -212,8 +209,6 @@ TabsPanel.propTypes = {
|
|||
isPinBoardVisible: PropTypes.bool.isRequired,
|
||||
pinnedJobs: PropTypes.object.isRequired,
|
||||
bugs: PropTypes.array.isRequired,
|
||||
addBug: PropTypes.func.isRequired,
|
||||
pinJob: PropTypes.func.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
perfJobDetail: PropTypes.array,
|
||||
suggestions: PropTypes.array,
|
||||
|
@ -239,3 +234,5 @@ TabsPanel.defaultProps = {
|
|||
logViewerFullUrl: null,
|
||||
reftestUrl: null,
|
||||
};
|
||||
|
||||
export default withPinnedJobs(TabsPanel);
|
||||
|
|
|
@ -9,8 +9,9 @@ import TextLogErrorsModel from '../../../../models/textLogErrors';
|
|||
import AutoclassifyToolbar from './AutoclassifyToolbar';
|
||||
import ErrorLine from './ErrorLine';
|
||||
import ErrorLineData from './ErrorLineModel';
|
||||
import { withPinnedJobs } from '../../../context/PinnedJobs';
|
||||
|
||||
export default class AutoclassifyTab extends React.Component {
|
||||
class AutoclassifyTab extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
@ -459,7 +460,7 @@ export default class AutoclassifyTab extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { job, autoclassifyStatus, user, $injector, addBug, pinnedJobs } = this.props;
|
||||
const { job, autoclassifyStatus, user, $injector } = this.props;
|
||||
const {
|
||||
errorLines,
|
||||
loadStatus,
|
||||
|
@ -507,8 +508,6 @@ export default class AutoclassifyTab extends React.Component {
|
|||
errorLine={errorLine}
|
||||
prevErrorLine={errorLines[idx - 1]}
|
||||
canClassify={canClassify}
|
||||
addBug={addBug}
|
||||
pinnedJobs={pinnedJobs}
|
||||
$injector={$injector}
|
||||
isSelected={selectedLineIds.has(errorLine.id)}
|
||||
isEditable={editableLineIds.has(errorLine.id)}
|
||||
|
@ -530,8 +529,6 @@ AutoclassifyTab.propTypes = {
|
|||
job: PropTypes.object.isRequired,
|
||||
hasLogs: PropTypes.bool.isRequired,
|
||||
pinJob: PropTypes.func.isRequired,
|
||||
addBug: PropTypes.func.isRequired,
|
||||
pinnedJobs: PropTypes.object.isRequired,
|
||||
autoclassifyStatus: PropTypes.string,
|
||||
logsParsed: PropTypes.bool,
|
||||
logParseStatus: PropTypes.string,
|
||||
|
@ -542,3 +539,5 @@ AutoclassifyTab.defaultProps = {
|
|||
logsParsed: false,
|
||||
logParseStatus: 'pending',
|
||||
};
|
||||
|
||||
export default withPinnedJobs(AutoclassifyTab);
|
||||
|
|
|
@ -489,7 +489,7 @@ export default class ErrorLine extends React.Component {
|
|||
render() {
|
||||
const {
|
||||
errorLine, job, canClassify, isSelected, isEditable, setEditable,
|
||||
$injector, toggleSelect, pinnedJobs, addBug,
|
||||
$injector, toggleSelect,
|
||||
} = this.props;
|
||||
const {
|
||||
messageExpanded, showHidden, selectedOption, options, extraOptions,
|
||||
|
@ -592,8 +592,6 @@ export default class ErrorLine extends React.Component {
|
|||
canClassify={canClassify}
|
||||
onOptionChange={this.onOptionChange}
|
||||
ignoreAlways={option.ignoreAlways}
|
||||
pinnedJobs={pinnedJobs}
|
||||
addBug={addBug}
|
||||
$injector={$injector}
|
||||
/>
|
||||
</li>))}
|
||||
|
@ -620,8 +618,6 @@ export default class ErrorLine extends React.Component {
|
|||
manualBugNumber={option.manualBugNumber}
|
||||
ignoreAlways={option.ignoreAlways}
|
||||
$injector={$injector}
|
||||
pinnedJobs={pinnedJobs}
|
||||
addBug={addBug}
|
||||
/>
|
||||
</li>))}
|
||||
</ul>}
|
||||
|
@ -638,8 +634,6 @@ export default class ErrorLine extends React.Component {
|
|||
setEditable={setEditable}
|
||||
ignoreAlways={selectedOption.ignoreAlways}
|
||||
manualBugNumber={selectedOption.manualBugNumber}
|
||||
pinnedJobs={pinnedJobs}
|
||||
addBug={addBug}
|
||||
/>
|
||||
</div>}
|
||||
</div>
|
||||
|
@ -658,8 +652,6 @@ ErrorLine.propTypes = {
|
|||
setEditable: PropTypes.func.isRequired,
|
||||
canClassify: PropTypes.bool.isRequired,
|
||||
$injector: PropTypes.object.isRequired,
|
||||
pinnedJobs: PropTypes.object.isRequired,
|
||||
addBug: PropTypes.func.isRequired,
|
||||
errorMatchers: PropTypes.object,
|
||||
prevErrorLine: PropTypes.object,
|
||||
};
|
||||
|
|
|
@ -10,11 +10,12 @@ import { getBugUrl, getLogViewerUrl, getReftestUrl } from '../../../../helpers/u
|
|||
import BugFiler from '../../BugFiler';
|
||||
import { thEvents } from '../../../../js/constants';
|
||||
import { getAllUrlParams } from '../../../../helpers/location';
|
||||
import { withPinnedJobs } from '../../../context/PinnedJobs';
|
||||
|
||||
/**
|
||||
* Editable option
|
||||
*/
|
||||
export default class LineOption extends React.Component {
|
||||
class LineOption extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { $injector } = props;
|
||||
|
@ -212,3 +213,5 @@ LineOption.defaultProps = {
|
|||
onIgnoreAlwaysChange: null,
|
||||
manualBugNumber: undefined,
|
||||
};
|
||||
|
||||
export default withPinnedJobs(LineOption);
|
||||
|
|
|
@ -4,11 +4,12 @@ import Highlighter from 'react-highlight-words';
|
|||
|
||||
import { getSearchWords } from '../../../../helpers/display';
|
||||
import { getBugUrl } from '../../../../helpers/url';
|
||||
import { withPinnedJobs } from '../../../context/PinnedJobs';
|
||||
|
||||
/**
|
||||
* Non-editable best option
|
||||
*/
|
||||
export default function StaticLineOption(props) {
|
||||
function StaticLineOption(props) {
|
||||
const {
|
||||
job, canClassify, errorLine, option, numOptions, setEditable, ignoreAlways,
|
||||
manualBugNumber, pinnedJobs, addBug,
|
||||
|
@ -91,3 +92,5 @@ StaticLineOption.propTypes = {
|
|||
StaticLineOption.defaultProps = {
|
||||
manualBugNumber: undefined,
|
||||
};
|
||||
|
||||
export default withPinnedJobs(StaticLineOption);
|
||||
|
|
|
@ -4,9 +4,10 @@ import Highlighter from 'react-highlight-words';
|
|||
|
||||
import { getSearchWords } from '../../../../helpers/display';
|
||||
import { getBugUrl } from '../../../../helpers/url';
|
||||
import { withPinnedJobs } from '../../../context/PinnedJobs';
|
||||
|
||||
|
||||
export default function BugListItem(props) {
|
||||
function BugListItem(props) {
|
||||
const {
|
||||
bug, suggestion, bugClassName, title, selectedJob, addBug,
|
||||
} = props;
|
||||
|
@ -53,3 +54,5 @@ BugListItem.defaultProps = {
|
|||
bugClassName: '',
|
||||
title: null,
|
||||
};
|
||||
|
||||
export default withPinnedJobs(BugListItem);
|
||||
|
|
|
@ -9,8 +9,9 @@ import ErrorsList from './ErrorsList';
|
|||
import ListItem from './ListItem';
|
||||
import SuggestionsListItem from './SuggestionsListItem';
|
||||
import BugFiler from '../../BugFiler';
|
||||
import { withPinnedJobs } from '../../../context/PinnedJobs';
|
||||
|
||||
export default class FailureSummaryTab extends React.Component {
|
||||
class FailureSummaryTab extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
@ -54,7 +55,7 @@ export default class FailureSummaryTab extends React.Component {
|
|||
render() {
|
||||
const {
|
||||
jobLogUrls, logParseStatus, suggestions, errors, logViewerFullUrl,
|
||||
bugSuggestionsLoading, selectedJob, addBug, reftestUrl,
|
||||
bugSuggestionsLoading, selectedJob, reftestUrl,
|
||||
} = this.props;
|
||||
const { isBugFilerOpen, suggestion } = this.state;
|
||||
const logs = jobLogUrls;
|
||||
|
@ -70,7 +71,6 @@ export default class FailureSummaryTab extends React.Component {
|
|||
suggestion={suggestion}
|
||||
toggleBugFiler={() => this.fileBug(suggestion)}
|
||||
selectedJob={selectedJob}
|
||||
addBug={addBug}
|
||||
/>))}
|
||||
|
||||
{!!errors.length &&
|
||||
|
@ -153,3 +153,5 @@ FailureSummaryTab.defaultProps = {
|
|||
logParseStatus: 'pending',
|
||||
logViewerFullUrl: null,
|
||||
};
|
||||
|
||||
export default withPinnedJobs(FailureSummaryTab);
|
||||
|
|
|
@ -20,7 +20,7 @@ export default class SuggestionsListItem extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
suggestion, selectedJob, toggleBugFiler, addBug,
|
||||
suggestion, selectedJob, toggleBugFiler,
|
||||
} = this.props;
|
||||
const { suggestionShowMore } = this.state;
|
||||
|
||||
|
@ -46,7 +46,6 @@ export default class SuggestionsListItem extends React.Component {
|
|||
bug={bug}
|
||||
selectedJob={selectedJob}
|
||||
suggestion={suggestion}
|
||||
addBug={addBug}
|
||||
/>))}
|
||||
|
||||
</ul>}
|
||||
|
@ -70,7 +69,6 @@ export default class SuggestionsListItem extends React.Component {
|
|||
suggestion={suggestion}
|
||||
bugClassName={bug.resolution !== '' ? 'deleted' : ''}
|
||||
title={bug.resolution !== '' ? bug.resolution : ''}
|
||||
addBug={addBug}
|
||||
/>))}
|
||||
</ul>}
|
||||
|
||||
|
@ -85,6 +83,5 @@ export default class SuggestionsListItem extends React.Component {
|
|||
SuggestionsListItem.propTypes = {
|
||||
suggestion: PropTypes.object.isRequired,
|
||||
selectedJob: PropTypes.object.isRequired,
|
||||
addBug: PropTypes.func.isRequired,
|
||||
toggleBugFiler: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
|
@ -2,11 +2,12 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { thAllResultStatuses } from '../../js/constants';
|
||||
import { withPinnedJobs } from '../context/PinnedJobs';
|
||||
|
||||
const resultStatusMenuItems = thAllResultStatuses.filter(rs => rs !== 'runnable');
|
||||
|
||||
export default function FiltersMenu(props) {
|
||||
const { filterModel, pinJobs } = props;
|
||||
function FiltersMenu(props) {
|
||||
const { filterModel, pinJobs, resultSetStore } = props;
|
||||
const { urlParams: { resultStatus, classifiedState } } = filterModel;
|
||||
|
||||
return (
|
||||
|
@ -62,7 +63,7 @@ export default function FiltersMenu(props) {
|
|||
<li
|
||||
title="Pin all jobs that pass the global filters"
|
||||
className="dropdown-item"
|
||||
onClick={pinJobs}
|
||||
onClick={() => pinJobs(resultSetStore.getAllShownJobs())}
|
||||
>Pin all showing</li>
|
||||
<li
|
||||
title="Show only superseded jobs"
|
||||
|
@ -83,4 +84,7 @@ export default function FiltersMenu(props) {
|
|||
FiltersMenu.propTypes = {
|
||||
filterModel: PropTypes.object.isRequired,
|
||||
pinJobs: PropTypes.func.isRequired,
|
||||
resultSetStore: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withPinnedJobs(FiltersMenu);
|
||||
|
|
|
@ -13,8 +13,8 @@ import SecondaryNavBar from './SecondaryNavBar';
|
|||
|
||||
export default function PrimaryNavBar(props) {
|
||||
const {
|
||||
user, setUser, repos, pinJobs, updateButtonClick, serverChanged,
|
||||
filterModel, $injector, setCurrentRepoTreeStatus,
|
||||
user, setUser, repos, updateButtonClick, serverChanged,
|
||||
filterModel, $injector, setCurrentRepoTreeStatus, resultSetStore,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
|
@ -31,8 +31,8 @@ export default function PrimaryNavBar(props) {
|
|||
filterModel={filterModel}
|
||||
/>
|
||||
<FiltersMenu
|
||||
pinJobs={pinJobs}
|
||||
filterModel={filterModel}
|
||||
resultSetStore={resultSetStore}
|
||||
/>
|
||||
<HelpMenu />
|
||||
<Login
|
||||
|
@ -61,9 +61,9 @@ PrimaryNavBar.propTypes = {
|
|||
filterModel: PropTypes.object.isRequired,
|
||||
repos: PropTypes.array.isRequired,
|
||||
updateButtonClick: PropTypes.func.isRequired,
|
||||
pinJobs: PropTypes.func.isRequired,
|
||||
serverChanged: PropTypes.bool.isRequired,
|
||||
setUser: PropTypes.func.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
setCurrentRepoTreeStatus: PropTypes.func.isRequired,
|
||||
resultSetStore: PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ import { thEvents } from '../../js/constants';
|
|||
import { getJobsUrl } from '../../helpers/url';
|
||||
import PushModel from '../../models/push';
|
||||
import JobModel from '../../models/job';
|
||||
import { withPinnedJobs } from '../context/PinnedJobs';
|
||||
|
||||
// url params we don't want added from the current querystring to the revision
|
||||
// and author links.
|
||||
|
@ -58,7 +59,7 @@ PushCounts.propTypes = {
|
|||
completed: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export default class PushHeader extends React.PureComponent {
|
||||
class PushHeader extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { $injector, pushTimestamp } = this.props;
|
||||
|
@ -69,9 +70,6 @@ export default class PushHeader extends React.PureComponent {
|
|||
|
||||
this.pushDateStr = toDateStr(pushTimestamp);
|
||||
|
||||
this.pinAllShownJobs = this.pinAllShownJobs.bind(this);
|
||||
this.cancelAllJobs = this.cancelAllJobs.bind(this);
|
||||
|
||||
this.state = {
|
||||
runnableJobsSelected: false,
|
||||
};
|
||||
|
@ -89,6 +87,11 @@ export default class PushHeader extends React.PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.pinAllShownJobs = this.pinAllShownJobs.bind(this);
|
||||
this.cancelAllJobs = this.cancelAllJobs.bind(this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.toggleRunnableJobUnlisten();
|
||||
}
|
||||
|
@ -148,12 +151,10 @@ export default class PushHeader extends React.PureComponent {
|
|||
}
|
||||
|
||||
pinAllShownJobs() {
|
||||
const { pinJobs } = this.props;
|
||||
const shownJobs = this.ThResultSetStore.getAllShownJobs(this.props.pushId);
|
||||
this.$rootScope.$emit(thEvents.pinJobs, shownJobs);
|
||||
|
||||
if (!this.$rootScope.selectedJob) {
|
||||
this.$rootScope.$emit(thEvents.jobClick, shownJobs[0]);
|
||||
}
|
||||
pinJobs(shownJobs);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -270,6 +271,7 @@ PushHeader.propTypes = {
|
|||
hideRunnableJobsCb: PropTypes.func.isRequired,
|
||||
cycleWatchState: PropTypes.func.isRequired,
|
||||
isLoggedIn: PropTypes.bool.isRequired,
|
||||
pinJobs: PropTypes.func.isRequired,
|
||||
notificationSupported: PropTypes.bool.isRequired,
|
||||
jobCounts: PropTypes.object,
|
||||
watchState: PropTypes.string,
|
||||
|
@ -279,3 +281,5 @@ PushHeader.defaultProps = {
|
|||
jobCounts: null,
|
||||
watchState: 'none',
|
||||
};
|
||||
|
||||
export default withPinnedJobs(PushHeader);
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { thPlatformMap, thSimplePlatforms, thEvents } from '../../js/constants';
|
||||
import { withPinnedJobs } from '../context/PinnedJobs';
|
||||
import { getPlatformRowId, getPushTableId } from '../../helpers/aggregateId';
|
||||
import { findInstance, findSelectedInstance, findJobInstance } from '../../helpers/job';
|
||||
import { getUrlParam } from '../../helpers/location';
|
||||
|
@ -9,7 +10,7 @@ import { getLogViewerUrl } from '../../helpers/url';
|
|||
import JobModel from '../../models/job';
|
||||
import Platform from './Platform';
|
||||
|
||||
export default class PushJobs extends React.Component {
|
||||
class PushJobs extends React.Component {
|
||||
static getDerivedStateFromProps(nextProps, state) {
|
||||
const { filterModel, push } = nextProps;
|
||||
const { platforms } = state;
|
||||
|
@ -104,6 +105,7 @@ export default class PushJobs extends React.Component {
|
|||
}
|
||||
|
||||
onMouseDown(ev) {
|
||||
const { togglePinJob } = this.props;
|
||||
const jobElem = ev.target.attributes.getNamedItem('data-job-id');
|
||||
|
||||
if (jobElem) {
|
||||
|
@ -116,7 +118,7 @@ export default class PushJobs extends React.Component {
|
|||
this.ThResultSetStore.setSelectedJob(job);
|
||||
this.selectJob(job, ev.target);
|
||||
}
|
||||
this.$rootScope.$emit(thEvents.toggleJobPin, job);
|
||||
togglePinJob(job);
|
||||
} else if (job.state === 'runnable') { // Toggle runnable
|
||||
this.handleRunnableClick(job);
|
||||
} else {
|
||||
|
@ -234,5 +236,8 @@ PushJobs.propTypes = {
|
|||
push: PropTypes.object.isRequired,
|
||||
repoName: PropTypes.string.isRequired,
|
||||
filterModel: PropTypes.object.isRequired,
|
||||
togglePinJob: PropTypes.func.isRequired,
|
||||
$injector: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withPinnedJobs(PushJobs);
|
||||
|
|
|
@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
|
|||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
import { thDefaultRepo, thEvents, thMaxPushFetchSize } from '../../js/constants';
|
||||
import { withPinnedJobs } from '../context/PinnedJobs';
|
||||
import { reloadOnChangeParameters } from '../../helpers/filter';
|
||||
import {
|
||||
findInstance,
|
||||
|
@ -25,7 +26,7 @@ import ErrorBoundary from '../../shared/ErrorBoundary';
|
|||
import Push from './Push';
|
||||
import PushLoadErrors from './PushLoadErrors';
|
||||
|
||||
export default class PushList extends React.Component {
|
||||
class PushList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { $injector, repoName } = this.props;
|
||||
|
@ -313,12 +314,15 @@ export default class PushList extends React.Component {
|
|||
|
||||
// Clear the selectedJob
|
||||
closeJob() {
|
||||
const { pinnedJobs } = this.props;
|
||||
|
||||
// TODO: Should block clearing the selected job if there are pinned jobs
|
||||
// But can't get the pinned jobs at this time. When we're completely on React,
|
||||
// or at least have a shared parent between PushList and DetailsPanel, we can share
|
||||
// a PinBoardModel or Context so they both have access.
|
||||
if (!this.$rootScope.countPinnedJobs()) {
|
||||
if (!Object.keys(pinnedJobs).length) {
|
||||
const selected = findSelectedInstance();
|
||||
|
||||
if (selected) {
|
||||
selected.setSelected(false);
|
||||
}
|
||||
|
@ -386,6 +390,7 @@ PushList.propTypes = {
|
|||
repoName: PropTypes.string.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
filterModel: PropTypes.object.isRequired,
|
||||
pinnedJobs: PropTypes.object.isRequired,
|
||||
revision: PropTypes.string,
|
||||
currentRepo: PropTypes.object,
|
||||
};
|
||||
|
@ -394,3 +399,5 @@ PushList.defaultProps = {
|
|||
revision: null,
|
||||
currentRepo: {},
|
||||
};
|
||||
|
||||
export default withPinnedJobs(PushList);
|
||||
|
|
|
@ -240,10 +240,6 @@ export const thJobNavSelectors = {
|
|||
},
|
||||
};
|
||||
|
||||
export const thPinboardCountError = 'Max pinboard size of 500 reached.';
|
||||
|
||||
export const thPinboardMaxSize = 500;
|
||||
|
||||
export const thPerformanceBranches = ['autoland', 'mozilla-inbound'];
|
||||
|
||||
/**
|
||||
|
@ -254,12 +250,6 @@ export const thEvents = {
|
|||
jobClick: 'job-click-EVT',
|
||||
// fired with a selected job on 't'
|
||||
selectNextTab: 'select-next-tab-EVT',
|
||||
// fired with a selected job on spacebar
|
||||
jobPin: 'job-pin-EVT',
|
||||
// fired with a selected job on ctrl/cmd-click
|
||||
toggleJobPin: 'job-togglepin-EVT',
|
||||
// fired with api call to increment the pinned jobs
|
||||
pulsePinCount: 'pulse-pin-count-EVT',
|
||||
// fired with a selected job on 'r'
|
||||
jobRetrigger: 'job-retrigger-EVT',
|
||||
// fired when jobs are classified locally
|
||||
|
@ -277,11 +267,8 @@ export const thEvents = {
|
|||
showRunnableJobs: 'show-runnable-jobs-EVT',
|
||||
deleteRunnableJobs: 'delete-runnable-jobs-EVT',
|
||||
changeSelection: 'next-previous-job-EVT',
|
||||
addRelatedBug: 'add-related-bug-EVT',
|
||||
saveClassification: 'save-classification-EVT',
|
||||
deleteClassification: 'delete-classification-EVT',
|
||||
clearPinboard: 'clear-pinboard-EVT',
|
||||
pinJobs: 'pin-jobs-EVT',
|
||||
selectJob: 'select-job-EVT',
|
||||
applyNewJobs: 'apply-new-jobs-EVT',
|
||||
openLogviewer: 'open-logviewer-EVT',
|
||||
|
|
|
@ -22,9 +22,6 @@ treeherderApp.controller('MainCtrl', [
|
|||
$rootScope.repoName = thDefaultRepo;
|
||||
}
|
||||
|
||||
// TODO: Remove this when pinnedJobs is converted to a model or Context
|
||||
$rootScope.countPinnedJobs = () => 0;
|
||||
|
||||
const getSingleRevisionTitleString = function () {
|
||||
let revisions = [];
|
||||
let percentComplete;
|
||||
|
|
Загрузка…
Ссылка в новой задаче