зеркало из https://github.com/mozilla/treeherder.git
Bug 1480166 - Use class fields to avoid bind() calls in the rest (#4370)
This commit is contained in:
Родитель
5e8377a3ff
Коммит
2885a9a1e3
|
@ -8,7 +8,6 @@ import BugDetailsView from './BugDetailsView';
|
|||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.updateAppState = this.updateAppState.bind(this);
|
||||
|
||||
// keep track of the mainviews graph and table data so the API won't be
|
||||
// called again when navigating back from bugdetailsview.
|
||||
|
@ -18,9 +17,9 @@ class App extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
updateAppState(state) {
|
||||
updateAppState = state => {
|
||||
this.setState(state);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
|
|
@ -8,26 +8,23 @@ export default class BugLogColumn extends React.Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.updateTarget = this.updateTarget.bind(this);
|
||||
this.toggle = this.toggle.bind(this);
|
||||
|
||||
this.state = {
|
||||
tooltipOpen: false,
|
||||
target: null,
|
||||
};
|
||||
}
|
||||
|
||||
updateTarget(target) {
|
||||
updateTarget = target => {
|
||||
if (!this.state.target) {
|
||||
this.setState({ target });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
toggle() {
|
||||
toggle = () => {
|
||||
this.setState({
|
||||
tooltipOpen: !this.state.tooltipOpen,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { value, original } = this.props;
|
||||
|
|
|
@ -14,17 +14,15 @@ export default class DateOptions extends React.Component {
|
|||
dropdownOpen: false,
|
||||
dateRange: '',
|
||||
};
|
||||
this.toggle = this.toggle.bind(this);
|
||||
this.updateDateRange = this.updateDateRange.bind(this);
|
||||
}
|
||||
|
||||
toggle() {
|
||||
toggle = () => {
|
||||
this.setState({
|
||||
dropdownOpen: !this.state.dropdownOpen,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateDateRange(dateRange) {
|
||||
updateDateRange = dateRange => {
|
||||
this.setState({ dateRange });
|
||||
if (dateRange === 'custom range') {
|
||||
return;
|
||||
|
@ -45,7 +43,7 @@ export default class DateOptions extends React.Component {
|
|||
);
|
||||
const endday = ISODate(moment().utc());
|
||||
this.props.updateState({ startday, endday });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { updateState } = this.props;
|
||||
|
|
|
@ -15,47 +15,44 @@ export default class DateRangePicker extends React.Component {
|
|||
from: undefined,
|
||||
to: undefined,
|
||||
};
|
||||
this.fromChange = this.fromChange.bind(this);
|
||||
this.toChange = this.toChange.bind(this);
|
||||
this.updateData = this.updateData.bind(this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
|
||||
focusTo() {
|
||||
focusTo = () => {
|
||||
this.timeout = setTimeout(() => this.to.getInput().focus(), 0);
|
||||
}
|
||||
};
|
||||
|
||||
showFromMonth() {
|
||||
showFromMonth = () => {
|
||||
const { from } = this.state;
|
||||
if (!from) {
|
||||
return;
|
||||
}
|
||||
this.to.getDayPicker().showMonth(from);
|
||||
}
|
||||
};
|
||||
|
||||
fromChange(from) {
|
||||
fromChange = from => {
|
||||
this.setState({ from }, () => {
|
||||
if (!this.state.to) {
|
||||
this.focusTo();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
toChange(to) {
|
||||
toChange = to => {
|
||||
this.setState({ to }, this.showFromMonth);
|
||||
}
|
||||
};
|
||||
|
||||
updateData() {
|
||||
updateData = () => {
|
||||
const { from, to } = this.state;
|
||||
|
||||
const startday = ISODate(moment(from));
|
||||
const endday = ISODate(moment(to));
|
||||
|
||||
this.props.updateState({ startday, endday });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { from, to } = this.state;
|
||||
|
|
|
@ -11,12 +11,11 @@ export default class GraphsContainer extends React.Component {
|
|||
this.state = {
|
||||
showGraphTwo: false,
|
||||
};
|
||||
this.toggleGraph = this.toggleGraph.bind(this);
|
||||
}
|
||||
|
||||
toggleGraph() {
|
||||
toggleGraph = () => {
|
||||
this.setState({ showGraphTwo: !this.state.showGraphTwo });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { graphOneData, graphTwoData, children } = this.props;
|
||||
|
|
|
@ -15,13 +15,11 @@ export default class Navigation extends React.Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { isOpen: false };
|
||||
|
||||
this.toggle = this.toggle.bind(this);
|
||||
}
|
||||
|
||||
toggle() {
|
||||
toggle = () => {
|
||||
this.setState({ isOpen: !this.state.isOpen });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { updateState, tree } = this.props;
|
||||
|
|
|
@ -22,14 +22,6 @@ const withView = defaultState => WrappedComponent =>
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.updateData = this.updateData.bind(this);
|
||||
this.setQueryParams = this.setQueryParams.bind(this);
|
||||
this.checkQueryValidation = this.checkQueryValidation.bind(this);
|
||||
this.getTableData = this.getTableData.bind(this);
|
||||
this.getGraphData = this.getGraphData.bind(this);
|
||||
this.updateState = this.updateState.bind(this);
|
||||
this.getBugDetails = this.getBugDetails.bind(this);
|
||||
|
||||
this.default = this.props.location.state || defaultState;
|
||||
this.state = {
|
||||
errorMessages: [],
|
||||
|
@ -64,7 +56,7 @@ const withView = defaultState => WrappedComponent =>
|
|||
}
|
||||
}
|
||||
|
||||
setQueryParams() {
|
||||
setQueryParams = () => {
|
||||
const { location, history } = this.props;
|
||||
const { startday, endday, tree, bug } = this.state;
|
||||
const params = { startday, endday, tree };
|
||||
|
@ -87,16 +79,16 @@ const withView = defaultState => WrappedComponent =>
|
|||
this.getGraphData(createApiUrl(graphsEndpoint, params));
|
||||
this.getTableData(createApiUrl(defaultState.endpoint, params));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async getBugDetails(url) {
|
||||
getBugDetails = async url => {
|
||||
const { data, failureStatus } = await getData(url);
|
||||
if (!failureStatus && data.bugs.length === 1) {
|
||||
this.setState({ summary: data.bugs[0].summary });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async getTableData(url) {
|
||||
getTableData = async url => {
|
||||
this.setState({ tableFailureStatus: null, isFetchingTable: true });
|
||||
const { data, failureStatus } = await getData(url);
|
||||
let mergedData = null;
|
||||
|
@ -112,9 +104,9 @@ const withView = defaultState => WrappedComponent =>
|
|||
tableFailureStatus: failureStatus,
|
||||
isFetchingTable: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
async getGraphData(url) {
|
||||
getGraphData = async url => {
|
||||
this.setState({ graphFailureStatus: null, isFetchingGraphs: true });
|
||||
const { data, failureStatus } = await getData(url);
|
||||
this.setState({
|
||||
|
@ -122,9 +114,9 @@ const withView = defaultState => WrappedComponent =>
|
|||
graphFailureStatus: failureStatus,
|
||||
isFetchingGraphs: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
async batchBugRequests(bugIds) {
|
||||
batchBugRequests = async bugIds => {
|
||||
const urlParams = {
|
||||
include_fields: 'id,status,summary,whiteboard',
|
||||
};
|
||||
|
@ -148,9 +140,9 @@ const withView = defaultState => WrappedComponent =>
|
|||
bugsList = [...bugsList, ...result.data.bugs];
|
||||
}
|
||||
return bugsList;
|
||||
}
|
||||
};
|
||||
|
||||
updateState(updatedObj) {
|
||||
updateState = updatedObj => {
|
||||
this.setState(updatedObj, () => {
|
||||
const { startday, endday, tree, bug } = this.state;
|
||||
const params = { startday, endday, tree };
|
||||
|
@ -171,9 +163,9 @@ const withView = defaultState => WrappedComponent =>
|
|||
this.props.location,
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateData(params, urlChanged = false) {
|
||||
updateData = (params, urlChanged = false) => {
|
||||
const { mainGraphData, mainTableData } = this.props;
|
||||
|
||||
if (mainGraphData && mainTableData && !urlChanged) {
|
||||
|
@ -188,9 +180,9 @@ const withView = defaultState => WrappedComponent =>
|
|||
bugzillaBugsApi('bug', { include_fields: 'summary', id: params.bug }),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkQueryValidation(params, urlChanged = false) {
|
||||
checkQueryValidation = (params, urlChanged = false) => {
|
||||
const { errorMessages, initialParamsSet, summary } = this.state;
|
||||
const messages = validateQueryParams(
|
||||
params,
|
||||
|
@ -215,7 +207,7 @@ const withView = defaultState => WrappedComponent =>
|
|||
this.setState({ ...updates, ...params });
|
||||
this.updateData(params, urlChanged);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const updateState = { updateState: this.updateState };
|
||||
|
|
|
@ -54,10 +54,6 @@ class App extends React.PureComponent {
|
|||
jobId: queryString.get('job_id'),
|
||||
jobUrl: null,
|
||||
};
|
||||
|
||||
this.setSelectedLine = this.setSelectedLine.bind(this);
|
||||
this.onHighlight = this.onHighlight.bind(this);
|
||||
this.scrollHighlightToTop = this.scrollHighlightToTop.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -130,7 +126,7 @@ class App extends React.PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
onHighlight(range) {
|
||||
onHighlight = range => {
|
||||
const { highlight } = this.state;
|
||||
const { _start, _end, size } = range;
|
||||
// We can't use null to represent "no highlight", due to:
|
||||
|
@ -145,24 +141,24 @@ class App extends React.PureComponent {
|
|||
if (!isEqual(newHighlight, highlight)) {
|
||||
this.setSelectedLine(newHighlight);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setSelectedLine(highlight, scrollToTop) {
|
||||
setSelectedLine = (highlight, scrollToTop) => {
|
||||
this.setState({ highlight }, () => {
|
||||
this.updateQuery({ highlight });
|
||||
if (highlight && scrollToTop) {
|
||||
this.scrollHighlightToTop(highlight);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
scrollHighlightToTop(highlight) {
|
||||
scrollHighlightToTop = highlight => {
|
||||
const lineAtTop = highlight && highlight[0] > 7 ? highlight[0] - 7 : 0;
|
||||
|
||||
this.scrollToLine(`a[id="${lineAtTop}"]`, 100);
|
||||
}
|
||||
};
|
||||
|
||||
scrollToLine(selector, time, iteration = 0) {
|
||||
scrollToLine = (selector, time, iteration = 0) => {
|
||||
const line = document.querySelector(selector);
|
||||
|
||||
if (line !== null) {
|
||||
|
@ -172,9 +168,9 @@ class App extends React.PureComponent {
|
|||
if (iteration < 10) {
|
||||
setTimeout(() => this.scrollToLine(selector, time, iteration + 1), time);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updateQuery() {
|
||||
updateQuery = () => {
|
||||
const { highlight } = this.state;
|
||||
|
||||
if (highlight < 1) {
|
||||
|
@ -184,7 +180,7 @@ class App extends React.PureComponent {
|
|||
} else {
|
||||
setUrlParam('lineNumber', highlight[0]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
|
|
@ -19,16 +19,6 @@ import { getAllUrlParams } from '../helpers/location';
|
|||
export default class FilterModel {
|
||||
constructor() {
|
||||
this.urlParams = FilterModel.getUrlParamsWithDefaults();
|
||||
|
||||
this.addFilter = this.addFilter.bind(this);
|
||||
this.removeFilter = this.removeFilter.bind(this);
|
||||
this.resetNonFieldFilters = this.resetNonFieldFilters.bind(this);
|
||||
this.setOnlySuperseded = this.setOnlySuperseded.bind(this);
|
||||
this.clearNonStatusFilters = this.clearNonStatusFilters.bind(this);
|
||||
this.toggleUnclassifiedFailures = this.toggleUnclassifiedFailures.bind(
|
||||
this,
|
||||
);
|
||||
this.toggleInProgress = this.toggleInProgress.bind(this);
|
||||
}
|
||||
|
||||
static getUrlParamsWithDefaults() {
|
||||
|
@ -53,7 +43,7 @@ export default class FilterModel {
|
|||
}
|
||||
|
||||
// If a param matches the defaults, then don't include it.
|
||||
getUrlParamsWithoutDefaults() {
|
||||
getUrlParamsWithoutDefaults = () => {
|
||||
// ensure the repo param is always set
|
||||
const params = { repo: thDefaultRepo, ...this.urlParams };
|
||||
|
||||
|
@ -64,9 +54,9 @@ export default class FilterModel {
|
|||
: acc,
|
||||
{},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
addFilter(field, value) {
|
||||
addFilter = (field, value) => {
|
||||
const currentValue = this.urlParams[field];
|
||||
|
||||
if (currentValue) {
|
||||
|
@ -80,10 +70,10 @@ export default class FilterModel {
|
|||
this.urlParams[field] = [value];
|
||||
}
|
||||
this.push();
|
||||
}
|
||||
};
|
||||
|
||||
// Also used for non-filter params
|
||||
removeFilter(field, value) {
|
||||
removeFilter = (field, value) => {
|
||||
if (value) {
|
||||
const currentValue = this.urlParams[field];
|
||||
|
||||
|
@ -96,42 +86,41 @@ export default class FilterModel {
|
|||
delete this.urlParams[field];
|
||||
}
|
||||
this.push();
|
||||
}
|
||||
};
|
||||
|
||||
getFilterQueryString() {
|
||||
return new URLSearchParams(this.getUrlParamsWithoutDefaults()).toString();
|
||||
}
|
||||
getFilterQueryString = () =>
|
||||
new URLSearchParams(this.getUrlParamsWithoutDefaults()).toString();
|
||||
|
||||
/**
|
||||
* Push all the url params to the url. Components listening for hashchange
|
||||
* will get updates.
|
||||
*/
|
||||
push() {
|
||||
push = () => {
|
||||
window.location.hash = `#/jobs?${this.getFilterQueryString()}`;
|
||||
}
|
||||
};
|
||||
|
||||
setOnlySuperseded() {
|
||||
setOnlySuperseded = () => {
|
||||
this.urlParams.resultStatus = 'superseded';
|
||||
this.urlParams.classifiedState = [...thFilterDefaults.classifiedState];
|
||||
this.push();
|
||||
}
|
||||
};
|
||||
|
||||
toggleFilter(field, value) {
|
||||
toggleFilter = (field, value) => {
|
||||
const action = !this.urlParams[field].includes(value)
|
||||
? this.addFilter
|
||||
: this.removeFilter;
|
||||
action(field, value);
|
||||
}
|
||||
};
|
||||
|
||||
toggleInProgress() {
|
||||
toggleInProgress = () => {
|
||||
this.toggleResultStatuses(['pending', 'running']);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If none or only some of the statuses here are on, then set them all to on.
|
||||
* If they ARE all on, then set them to off.
|
||||
*/
|
||||
toggleResultStatuses(resultStatuses) {
|
||||
toggleResultStatuses = resultStatuses => {
|
||||
const currentResultStatuses = this.urlParams.resultStatus;
|
||||
const allOn = resultStatuses.every(rs =>
|
||||
currentResultStatuses.includes(rs),
|
||||
|
@ -141,13 +130,13 @@ export default class FilterModel {
|
|||
: [...new Set([...resultStatuses, ...currentResultStatuses])];
|
||||
|
||||
this.push();
|
||||
}
|
||||
};
|
||||
|
||||
toggleClassifiedFilter(classifiedState) {
|
||||
toggleClassifiedFilter = classifiedState => {
|
||||
this.toggleFilter('classifiedState', classifiedState);
|
||||
}
|
||||
};
|
||||
|
||||
toggleUnclassifiedFailures() {
|
||||
toggleUnclassifiedFailures = () => {
|
||||
if (this._isUnclassifiedFailures()) {
|
||||
this.resetNonFieldFilters();
|
||||
} else {
|
||||
|
@ -155,39 +144,39 @@ export default class FilterModel {
|
|||
this.urlParams.classifiedState = ['unclassified'];
|
||||
this.push();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
replaceFilter(field, value) {
|
||||
replaceFilter = (field, value) => {
|
||||
this.urlParams[field] = !Array.isArray(value) ? [value] : value;
|
||||
this.push();
|
||||
}
|
||||
};
|
||||
|
||||
clearNonStatusFilters() {
|
||||
clearNonStatusFilters = () => {
|
||||
const { repo, resultStatus, classifiedState } = this.urlParams;
|
||||
|
||||
this.urlParams = { repo, resultStatus, classifiedState };
|
||||
this.push();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* reset the non-field (checkbox in the ui) filters to the default state
|
||||
* so the user sees everything. Doesn't affect the field filters. This
|
||||
* is used to undo the call to ``setOnlyUnclassifiedFailures``.
|
||||
*/
|
||||
resetNonFieldFilters() {
|
||||
resetNonFieldFilters = () => {
|
||||
const { resultStatus, classifiedState } = thFilterDefaults;
|
||||
|
||||
this.urlParams.resultStatus = [...resultStatus];
|
||||
this.urlParams.classifiedState = [...classifiedState];
|
||||
this.push();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether or not this job should be shown based on the current filters.
|
||||
*
|
||||
* @param job - the job we are checking against the filters
|
||||
*/
|
||||
showJob(job) {
|
||||
showJob = job => {
|
||||
// when runnable jobs have been added to a resultset, they should be
|
||||
// shown regardless of settings for classified or result state
|
||||
const status = getStatus(job);
|
||||
|
@ -203,9 +192,9 @@ export default class FilterModel {
|
|||
// runnable or not, we still want to apply the field filters like
|
||||
// for symbol, platform, search str, etc...
|
||||
return this._checkFieldFilters(job);
|
||||
}
|
||||
};
|
||||
|
||||
_checkClassifiedStateFilters(job) {
|
||||
_checkClassifiedStateFilters = job => {
|
||||
const { classifiedState } = this.urlParams;
|
||||
const isJobClassified = isClassified(job);
|
||||
|
||||
|
@ -215,10 +204,10 @@ export default class FilterModel {
|
|||
// If the filters say not to include classified, but it IS
|
||||
// classified, then return false, otherwise, true.
|
||||
return !(!classifiedState.includes('classified') && isJobClassified);
|
||||
}
|
||||
};
|
||||
|
||||
_checkFieldFilters(job) {
|
||||
return Object.entries(this.urlParams).every(([field, values]) => {
|
||||
_checkFieldFilters = job =>
|
||||
Object.entries(this.urlParams).every(([field, values]) => {
|
||||
let jobFieldValue = this._getJobFieldValue(job, field);
|
||||
|
||||
// If ``job`` does not have this field, then don't filter.
|
||||
|
@ -253,7 +242,6 @@ export default class FilterModel {
|
|||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field from the job. In most cases, this is very simple. But
|
||||
|
@ -261,7 +249,7 @@ export default class FilterModel {
|
|||
* shows to the user as a different string than what is stored in the job
|
||||
* object.
|
||||
*/
|
||||
_getJobFieldValue(job, field) {
|
||||
_getJobFieldValue = (job, field) => {
|
||||
if (field === 'platform') {
|
||||
return `${thPlatformMap[job.platform] || job.platform} ${
|
||||
job.platform_option
|
||||
|
@ -273,15 +261,12 @@ export default class FilterModel {
|
|||
return job.getSearchStr();
|
||||
}
|
||||
return job[field];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* check if we're in the state of showing only unclassified failures
|
||||
*/
|
||||
_isUnclassifiedFailures() {
|
||||
return (
|
||||
arraysEqual(this.urlParams.resultStatus, thFailureResults) &&
|
||||
arraysEqual(this.urlParams.classifiedState, ['unclassified'])
|
||||
);
|
||||
}
|
||||
_isUnclassifiedFailures = () =>
|
||||
arraysEqual(this.urlParams.resultStatus, thFailureResults) &&
|
||||
arraysEqual(this.urlParams.classifiedState, ['unclassified']);
|
||||
}
|
||||
|
|
|
@ -37,10 +37,6 @@ export default class CompareSelectorView extends React.Component {
|
|||
errorMessages: [],
|
||||
disableButton: true,
|
||||
};
|
||||
this.submitData = this.submitData.bind(this);
|
||||
this.validateQueryParams = this.validateQueryParams.bind(this);
|
||||
this.validateProject = this.validateProject.bind(this);
|
||||
this.validateRevision = this.validateRevision.bind(this);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
|
@ -49,7 +45,7 @@ export default class CompareSelectorView extends React.Component {
|
|||
this.validateQueryParams();
|
||||
}
|
||||
|
||||
validateQueryParams() {
|
||||
validateQueryParams = () => {
|
||||
const {
|
||||
originalProject,
|
||||
newProject,
|
||||
|
@ -80,9 +76,9 @@ export default class CompareSelectorView extends React.Component {
|
|||
originalProject || this.state.originalProject,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
validateProject(projectName, project) {
|
||||
validateProject = (projectName, project) => {
|
||||
const { projects, errorMessages } = this.state;
|
||||
let updates = {};
|
||||
const validProject = projects.find(item => item.name === project);
|
||||
|
@ -98,9 +94,9 @@ export default class CompareSelectorView extends React.Component {
|
|||
};
|
||||
}
|
||||
this.setState(updates);
|
||||
}
|
||||
};
|
||||
|
||||
async validateRevision(revisionName, revision, project) {
|
||||
validateRevision = async (revisionName, revision, project) => {
|
||||
const { errorMessages } = this.state;
|
||||
let updates = {};
|
||||
|
||||
|
@ -120,9 +116,9 @@ export default class CompareSelectorView extends React.Component {
|
|||
updates = { [revisionName]: revision };
|
||||
}
|
||||
this.setState(updates);
|
||||
}
|
||||
};
|
||||
|
||||
submitData() {
|
||||
submitData = () => {
|
||||
const {
|
||||
originalProject,
|
||||
newProject,
|
||||
|
@ -150,7 +146,7 @@ export default class CompareSelectorView extends React.Component {
|
|||
selectedTimeRange: compareDefaultTimeRange,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
|
|
@ -35,11 +35,6 @@ export default class SelectorCard extends React.Component {
|
|||
failureStatus: null,
|
||||
invalidInput: false,
|
||||
};
|
||||
this.toggle = this.toggle.bind(this);
|
||||
this.fetchRevisions = this.fetchRevisions.bind(this);
|
||||
this.validateInput = this.validateInput.bind(this);
|
||||
this.compareRevisions = this.compareRevisions.bind(this);
|
||||
this.updateRevision = this.updateRevision.bind(this);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
|
@ -50,7 +45,7 @@ export default class SelectorCard extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
async fetchRevisions(selectedRepo) {
|
||||
fetchRevisions = async selectedRepo => {
|
||||
const params = {
|
||||
full: true,
|
||||
count: 10,
|
||||
|
@ -66,28 +61,28 @@ export default class SelectorCard extends React.Component {
|
|||
} else {
|
||||
this.setState({ data, failureStatus });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
toggle(dropdown) {
|
||||
toggle = dropdown => {
|
||||
this.setState({
|
||||
[dropdown]: !this.state[dropdown],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateData(selectedRepo) {
|
||||
updateData = selectedRepo => {
|
||||
const { updateState, projectState } = this.props;
|
||||
this.fetchRevisions(selectedRepo);
|
||||
updateState({ [projectState]: selectedRepo });
|
||||
}
|
||||
};
|
||||
|
||||
compareRevisions() {
|
||||
compareRevisions = () => {
|
||||
this.toggle('checkboxSelected');
|
||||
if (!this.state.data.results) {
|
||||
this.fetchRevisions(this.props.selectedRepo);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async validateInput(value) {
|
||||
validateInput = async value => {
|
||||
const { updateState } = this.props;
|
||||
|
||||
if (value.length < 40 && value !== '') {
|
||||
|
@ -112,9 +107,9 @@ export default class SelectorCard extends React.Component {
|
|||
if (this.state.invalidInput) {
|
||||
this.setState({ invalidInput: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updateRevision(value) {
|
||||
updateRevision = value => {
|
||||
const { updateState, revisionState } = this.props;
|
||||
|
||||
this.setState({ invalidInput: false });
|
||||
|
@ -123,7 +118,7 @@ export default class SelectorCard extends React.Component {
|
|||
errorMessages: [],
|
||||
disableButton: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
|
|
@ -23,10 +23,6 @@ class Login extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.login = this.login.bind(this);
|
||||
this.logout = this.logout.bind(this);
|
||||
this.handleStorageEvent = this.handleStorageEvent.bind(this);
|
||||
|
||||
window.addEventListener('storage', this.handleStorageEvent);
|
||||
|
||||
// Ask the back-end if a user is logged in on page load
|
||||
|
@ -43,7 +39,7 @@ class Login extends React.Component {
|
|||
window.removeEventListener('storage', this.handleStorageEvent);
|
||||
}
|
||||
|
||||
setLoggedIn(newUser) {
|
||||
setLoggedIn = newUser => {
|
||||
const { setUser } = this.props;
|
||||
const userSession = JSON.parse(localStorage.getItem('userSession'));
|
||||
newUser.isLoggedIn = true;
|
||||
|
@ -54,18 +50,18 @@ class Login extends React.Component {
|
|||
if (userSession && userSession.renewAfter) {
|
||||
this.authService.resetRenewalTimer();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setLoggedOut() {
|
||||
setLoggedOut = () => {
|
||||
const { setUser } = this.props;
|
||||
|
||||
this.authService.logout();
|
||||
// logging out will not trigger a storage event since localStorage is being set by the same window
|
||||
taskcluster.updateAgent();
|
||||
setUser(loggedOutUser);
|
||||
}
|
||||
};
|
||||
|
||||
handleStorageEvent(e) {
|
||||
handleStorageEvent = e => {
|
||||
if (e.key === 'user') {
|
||||
const oldUser = JSON.parse(e.oldValue);
|
||||
const newUser = JSON.parse(e.newValue);
|
||||
|
@ -81,18 +77,18 @@ class Login extends React.Component {
|
|||
// used when a different tab updates userSession,
|
||||
taskcluster.updateAgent();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a new tab to handle authentication, which will get closed
|
||||
* if it's successful.
|
||||
*/
|
||||
login() {
|
||||
login = () => {
|
||||
// Intentionally not using `noopener` since `window.opener` used in LoginCallback.
|
||||
window.open(loginCallbackUrl, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
logout() {
|
||||
logout = () => {
|
||||
const { notify } = this.props;
|
||||
|
||||
fetch(getApiUrl('/auth/logout/')).then(async resp => {
|
||||
|
@ -103,7 +99,7 @@ class Login extends React.Component {
|
|||
notify(`Logout failed: ${msg}`, 'danger', { sticky: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { user } = this.props;
|
||||
|
|
|
@ -23,36 +23,19 @@ export class Notifications extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.notify = this.notify.bind(this);
|
||||
this.removeNotification = this.removeNotification.bind(this);
|
||||
this.shift = this.shift.bind(this);
|
||||
this.clearStoredNotifications = this.clearStoredNotifications.bind(this);
|
||||
this.clearOnScreenNotifications = this.clearOnScreenNotifications.bind(
|
||||
this,
|
||||
);
|
||||
this.handleStorageEvent = this.handleStorageEvent.bind(this);
|
||||
|
||||
window.addEventListener('storage', this.handleStorageEvent);
|
||||
|
||||
this.value = {
|
||||
...this.state,
|
||||
notify: this.notify,
|
||||
removeNotification: this.removeNotification,
|
||||
clearStoredNotifications: this.clearStoredNotifications,
|
||||
clearOnScreenNotifications: this.clearOnScreenNotifications,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('storage', this.handleStorageEvent);
|
||||
}
|
||||
|
||||
setValue(newState, callback) {
|
||||
setValue = (newState, callback) => {
|
||||
this.value = { ...this.value, ...newState };
|
||||
this.setState(newState, callback);
|
||||
}
|
||||
};
|
||||
|
||||
handleStorageEvent(e) {
|
||||
handleStorageEvent = e => {
|
||||
if (e.key === 'notifications') {
|
||||
this.setValue({
|
||||
storedNotifications: JSON.parse(
|
||||
|
@ -60,9 +43,9 @@ export class Notifications extends React.Component {
|
|||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
notify(message, severity, opts) {
|
||||
notify = (message, severity, opts) => {
|
||||
opts = opts || {};
|
||||
severity = severity || 'info';
|
||||
const { notifications, storedNotifications } = this.state;
|
||||
|
@ -89,12 +72,12 @@ export class Notifications extends React.Component {
|
|||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* remove an arbitrary element from the notifications queue
|
||||
*/
|
||||
removeNotification(index, delay = 0) {
|
||||
removeNotification = (index, delay = 0) => {
|
||||
const { notifications } = this.state;
|
||||
|
||||
notifications.splice(index, 1);
|
||||
|
@ -102,30 +85,30 @@ export class Notifications extends React.Component {
|
|||
() => this.setValue({ notifications: [...notifications] }),
|
||||
delay,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Delete the first non-sticky element from the notifications queue
|
||||
*/
|
||||
shift(delay) {
|
||||
shift = delay => {
|
||||
const { notifications } = this.state;
|
||||
|
||||
this.removeNotification(notifications.findIndex(n => !n.sticky), delay);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Clear the list of stored notifications
|
||||
*/
|
||||
clearStoredNotifications() {
|
||||
clearStoredNotifications = () => {
|
||||
const storedNotifications = [];
|
||||
|
||||
localStorage.setItem('notifications', storedNotifications);
|
||||
this.setValue({ storedNotifications });
|
||||
}
|
||||
};
|
||||
|
||||
clearOnScreenNotifications() {
|
||||
clearOnScreenNotifications = () => {
|
||||
this.setValue({ notifications: [] });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
|
|
@ -20,12 +20,7 @@ const mapStateToProps = ({ groups }) => ({
|
|||
});
|
||||
|
||||
class BugCountComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
onClick = () => {
|
||||
store.dispatch(
|
||||
actions.groups.fetchBugsSingleTest(
|
||||
this.props.test,
|
||||
|
@ -39,7 +34,7 @@ class BugCountComponent extends React.Component {
|
|||
this.props.expanded || {},
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -125,12 +120,7 @@ Platform.propTypes = {
|
|||
// TODO: Move `TestComponent` into its own file.
|
||||
// eslint-disable-next-line react/no-multi-comp
|
||||
class TestComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
onClick = () => {
|
||||
store.dispatch(
|
||||
actions.groups.fetchBugsSingleTest(
|
||||
this.props.test,
|
||||
|
@ -144,7 +134,7 @@ class TestComponent extends React.Component {
|
|||
this.props.expanded || {},
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderExpanded() {
|
||||
return (
|
||||
|
|
Загрузка…
Ссылка в новой задаче