Bug 1480166 - Use class fields to avoid bind() calls in the rest (#4370)

This commit is contained in:
Cameron Dawson 2018-12-13 10:31:30 -08:00 коммит произвёл GitHub
Родитель 5e8377a3ff
Коммит 2885a9a1e3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 137 добавлений и 216 удалений

Просмотреть файл

@ -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 (