зеркало из https://github.com/mozilla/treeherder.git
Bug 1837998 - File regression bugs using the API (#8048)
This commit is contained in:
Родитель
706b739118
Коммит
13c4c50b83
|
@ -6,7 +6,14 @@ import { getByText } from '@testing-library/dom';
|
|||
import FileBugModal from '../../../../ui/perfherder/alerts/FileBugModal';
|
||||
import testRegressions from '../../mock/performance_regressions';
|
||||
|
||||
const testFileBugModal = (handleClose) => {
|
||||
const testUser = {
|
||||
username: 'mozilla-ldap/test_user@mozilla.com',
|
||||
isLoggedIn: true,
|
||||
isStaff: true,
|
||||
email: 'test_user@mozilla.com',
|
||||
};
|
||||
|
||||
const testFileBugModal = (user, handleClose) => {
|
||||
const toggle = () => {};
|
||||
|
||||
return render(
|
||||
|
@ -17,6 +24,7 @@ const testFileBugModal = (handleClose) => {
|
|||
header="File Regression for"
|
||||
title="Bug Number"
|
||||
submitButtonText="File Bug"
|
||||
user={user || testUser}
|
||||
/>,
|
||||
{ legacyRoot: true },
|
||||
);
|
||||
|
@ -80,3 +88,25 @@ test('Entering a bug number with leading zero(es) File bug button should be disa
|
|||
const submitButton = getByText('File Bug');
|
||||
expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
test('Submit button should be disabled when user is not logged in', async () => {
|
||||
const notLoggedInUser = {
|
||||
...testUser,
|
||||
isLoggedIn: false,
|
||||
};
|
||||
|
||||
const { getByText, queryByText } = testFileBugModal(notLoggedInUser);
|
||||
|
||||
expect(queryByText('File Bug')).toBeNull();
|
||||
expect(
|
||||
getByText('You need to log in to access this feature.'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Submit button should be active when user is logged in', async () => {
|
||||
const { getByText } = testFileBugModal(testUser);
|
||||
|
||||
expect(await waitFor(() => getByText('File Bug'))).toBeInTheDocument();
|
||||
const submitButton = getByText('File Bug');
|
||||
expect(submitButton).toBeEnabled();
|
||||
});
|
||||
|
|
|
@ -34,7 +34,10 @@ class BugzillaViewSet(viewsets.ViewSet):
|
|||
).encode("utf-8")
|
||||
summary = params.get("summary").encode("utf-8").strip()
|
||||
url = settings.BUGFILER_API_URL + "/rest/bug"
|
||||
headers = {"x-bugzilla-api-key": settings.BUGFILER_API_KEY, "Accept": "application/json"}
|
||||
headers = {
|
||||
"x-bugzilla-api-key": settings.BUGFILER_API_KEY,
|
||||
"Accept": "application/json",
|
||||
}
|
||||
data = {
|
||||
"type": "defect",
|
||||
"product": params.get("product"),
|
||||
|
@ -51,6 +54,19 @@ class BugzillaViewSet(viewsets.ViewSet):
|
|||
"description": description,
|
||||
"comment_tags": "treeherder",
|
||||
}
|
||||
if params.get("by_treeherder"):
|
||||
data["type"] = params.get("type")
|
||||
data["description"] = params.get("description").encode("utf-8")
|
||||
data["cc"] = params.get("cc")
|
||||
if params.get("needinfo_from"):
|
||||
data["flags"] = [
|
||||
{
|
||||
"name": "needinfo",
|
||||
"status": "?",
|
||||
"requestee": params.get("needinfo_from"),
|
||||
}
|
||||
]
|
||||
|
||||
if params.get("is_security_issue"):
|
||||
security_group_list = list(
|
||||
BugzillaSecurityGroup.objects.filter(product=data.get("product")).values_list(
|
||||
|
|
|
@ -58,7 +58,15 @@ export default class FileBugModal extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { showModal, toggle, header, title, submitButtonText } = this.props;
|
||||
const {
|
||||
showModal,
|
||||
toggle,
|
||||
header,
|
||||
title,
|
||||
submitButtonText,
|
||||
user,
|
||||
errorMessage,
|
||||
} = this.props;
|
||||
|
||||
const { inputValue, invalidInput, validated } = this.state;
|
||||
|
||||
|
@ -72,46 +80,65 @@ export default class FileBugModal extends React.Component {
|
|||
<ModalHeader toggle={toggle}>{header}</ModalHeader>
|
||||
<Form>
|
||||
<ModalBody>
|
||||
<FormGroup>
|
||||
<Row className="justify-content-left">
|
||||
<Col className="col-6">
|
||||
<Label for="culpritBugId">
|
||||
{title} <i>(optional): </i>
|
||||
<span className="text-secondary">
|
||||
<FontAwesomeIcon icon={faInfoCircle} title={infoText} />
|
||||
</span>
|
||||
</Label>
|
||||
<Input
|
||||
value={inputValue}
|
||||
onChange={this.updateInput}
|
||||
name="culpritBugId"
|
||||
placeholder="123456"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="justify-content-left">
|
||||
<Col className="text-left">
|
||||
{invalidInput && !validated && (
|
||||
<p className="text-danger pt-2 text-wrap">
|
||||
Input should only contain numbers and not start with 0
|
||||
</p>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</FormGroup>
|
||||
{errorMessage && (
|
||||
<div className="alert alert-danger">{errorMessage}</div>
|
||||
)}
|
||||
{user.isLoggedIn ? (
|
||||
<FormGroup>
|
||||
<Row className="justify-content-left">
|
||||
<Col className="col-6">
|
||||
<Label for="culpritBugId">
|
||||
{title} <i>(optional): </i>
|
||||
<span className="text-secondary">
|
||||
<FontAwesomeIcon icon={faInfoCircle} title={infoText} />
|
||||
</span>
|
||||
</Label>
|
||||
<Input
|
||||
value={inputValue}
|
||||
onChange={this.updateInput}
|
||||
name="culpritBugId"
|
||||
placeholder="123456"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="justify-content-left">
|
||||
<Col className="text-left">
|
||||
{invalidInput && !validated && (
|
||||
<p className="text-danger pt-2 text-wrap">
|
||||
Input should only contain numbers and not start with 0
|
||||
</p>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</FormGroup>
|
||||
) : (
|
||||
<div>
|
||||
<p>You need to log in to access this feature.</p>
|
||||
</div>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
className="btn-outline-darker-info active"
|
||||
onClick={(event) => this.handleSubmit(event, inputValue)}
|
||||
disabled={invalidInput && !validated}
|
||||
type="submit"
|
||||
>
|
||||
{(inputValue.length &&
|
||||
!invalidInput &&
|
||||
`${submitButtonText} for ${inputValue}`) ||
|
||||
((!inputValue.length || invalidInput) && `${submitButtonText}`)}
|
||||
</Button>
|
||||
{user.isLoggedIn ? (
|
||||
<Button
|
||||
className="btn-outline-darker-info active"
|
||||
onClick={(event) => this.handleSubmit(event, inputValue)}
|
||||
disabled={invalidInput && !validated}
|
||||
type="submit"
|
||||
>
|
||||
{(inputValue.length &&
|
||||
!invalidInput &&
|
||||
`${submitButtonText} for ${inputValue}`) ||
|
||||
((!inputValue.length || invalidInput) &&
|
||||
`${submitButtonText}`)}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
className="btn-outline-darker-info active"
|
||||
onClick={toggle}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
</ModalFooter>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
@ -126,4 +153,5 @@ FileBugModal.propTypes = {
|
|||
header: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
submitButtonText: PropTypes.string.isRequired,
|
||||
user: PropTypes.shape({}).isRequired,
|
||||
};
|
||||
|
|
|
@ -17,14 +17,9 @@ import {
|
|||
getStatus,
|
||||
updateAlertSummary,
|
||||
} from '../perf-helpers/helpers';
|
||||
import { getData } from '../../helpers/http';
|
||||
import { getData, create } from '../../helpers/http';
|
||||
import TextualSummary from '../perf-helpers/textualSummary';
|
||||
import {
|
||||
getApiUrl,
|
||||
bzBaseUrl,
|
||||
createQueryParams,
|
||||
bugzillaBugsApi,
|
||||
} from '../../helpers/url';
|
||||
import { getApiUrl, bzBaseUrl, bugzillaBugsApi } from '../../helpers/url';
|
||||
import { summaryStatusMap } from '../perf-helpers/constants';
|
||||
import DropdownMenuItems from '../../shared/DropdownMenuItems';
|
||||
import BrowsertimeAlertsExtraData from '../../models/browsertimeAlertsExtraData';
|
||||
|
@ -50,6 +45,7 @@ export default class StatusDropdown extends React.Component {
|
|||
this.props.frameworks,
|
||||
),
|
||||
isWeekend: isWeekend(),
|
||||
fileBugErrorMessage: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -58,13 +54,22 @@ export default class StatusDropdown extends React.Component {
|
|||
if (bugDetails.failureStatus) {
|
||||
return bugDetails;
|
||||
}
|
||||
const bugData = bugDetails.data.bugs[0];
|
||||
|
||||
const bugData = bugDetails.data.bugs[0];
|
||||
const bugVersion = 'Default';
|
||||
const needinfoFrom = bugData.assigned_to;
|
||||
|
||||
let needinfoFrom = '';
|
||||
if (bugData.assigned_to !== 'nobody@mozilla.org') {
|
||||
needinfoFrom = bugData.assigned_to;
|
||||
} else {
|
||||
const componentInfo = await getData(
|
||||
bugzillaBugsApi(`component/${bugData.product}/${bugData.component}`),
|
||||
);
|
||||
needinfoFrom = componentInfo.data.triage_owner;
|
||||
}
|
||||
|
||||
// Using set because it doesn't keep duplicates by default
|
||||
const ccList = new Set();
|
||||
ccList.add(bugData.creator);
|
||||
const ccList = new Set([bugData.creator]);
|
||||
|
||||
return {
|
||||
bug_version: bugVersion,
|
||||
|
@ -122,42 +127,55 @@ export default class StatusDropdown extends React.Component {
|
|||
templateSettings.interpolate = /{{([\s\S]+?)}}/g;
|
||||
const fillTemplate = template(result.text);
|
||||
const commentText = fillTemplate(templateArgs);
|
||||
|
||||
const bugTitle = `${getFilledBugSummary(alertSummary)}`;
|
||||
|
||||
const culpritDetails = await this.getCulpritDetails(culpritId);
|
||||
const defaultParams = {
|
||||
bug_type: templateArgs.bugType,
|
||||
const componentInfo = await getData(
|
||||
bugzillaBugsApi(
|
||||
`component/${result.default_product}/${result.default_component}`,
|
||||
),
|
||||
);
|
||||
|
||||
let defaultParams = {
|
||||
type: templateArgs.bugType,
|
||||
version: 'unspecified',
|
||||
// result.cc_list is a string, not array
|
||||
cc: result.cc_list,
|
||||
comment: commentText,
|
||||
cc: [result.cc_list],
|
||||
description: commentText,
|
||||
component: result.default_component,
|
||||
product: result.default_product,
|
||||
keywords: result.keywords,
|
||||
short_desc: bugTitle,
|
||||
status_whiteboard: result.status_whiteboard,
|
||||
summary: bugTitle,
|
||||
whiteboard: result.status_whiteboard,
|
||||
needinfo_from: componentInfo.data.triage_owner,
|
||||
by_treeherder: true,
|
||||
};
|
||||
|
||||
if (culpritDetails.failureStatus) {
|
||||
window.open(
|
||||
`${bzBaseUrl}enter_bug.cgi${createQueryParams(defaultParams)}`,
|
||||
);
|
||||
} else {
|
||||
if (!culpritDetails.failureStatus) {
|
||||
let cc = culpritDetails.ccList.add(result.cc_list);
|
||||
cc = Array.from(cc);
|
||||
window.open(
|
||||
`${bzBaseUrl}enter_bug.cgi${createQueryParams({
|
||||
...defaultParams,
|
||||
cc,
|
||||
needinfo_from: culpritDetails.needinfoFrom,
|
||||
component: culpritDetails.component,
|
||||
product: culpritDetails.product,
|
||||
regressed_by: culpritId,
|
||||
cf_has_regression_range: 'yes',
|
||||
})}`,
|
||||
);
|
||||
defaultParams = {
|
||||
...defaultParams,
|
||||
cc,
|
||||
needinfo_from: culpritDetails.needinfoFrom,
|
||||
component: culpritDetails.component,
|
||||
product: culpritDetails.product,
|
||||
regressed_by: culpritId,
|
||||
};
|
||||
}
|
||||
|
||||
const createResult = await create(
|
||||
getApiUrl('/bugzilla/create_bug/'),
|
||||
defaultParams,
|
||||
);
|
||||
if (createResult.failureStatus) {
|
||||
return {
|
||||
failureStatus: createResult.failureStatus,
|
||||
data: createResult.data,
|
||||
};
|
||||
}
|
||||
window.open(`${bzBaseUrl}show_bug.cgi?id=${createResult.data.id}`);
|
||||
return {
|
||||
failureStatus: null,
|
||||
};
|
||||
};
|
||||
|
||||
copySummary = async () => {
|
||||
|
@ -200,6 +218,12 @@ export default class StatusDropdown extends React.Component {
|
|||
this.setState((prevState) => ({
|
||||
[state]: !prevState[state],
|
||||
}));
|
||||
|
||||
if (this.state.showFileBugModal) {
|
||||
this.setState({
|
||||
fileBugErrorMessage: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateAndClose = async (event, params, state) => {
|
||||
|
@ -211,8 +235,15 @@ export default class StatusDropdown extends React.Component {
|
|||
fileBugAndClose = async (event, params, state) => {
|
||||
event.preventDefault();
|
||||
const culpritId = params.bug_number;
|
||||
await this.fileBug(culpritId);
|
||||
this.toggle(state);
|
||||
const createResult = await this.fileBug(culpritId);
|
||||
|
||||
if (createResult.failureStatus) {
|
||||
this.setState({
|
||||
fileBugErrorMessage: `Failure: ${createResult.data}`,
|
||||
});
|
||||
} else {
|
||||
this.toggle(state);
|
||||
}
|
||||
};
|
||||
|
||||
changeAlertSummary = async (params) => {
|
||||
|
@ -314,6 +345,8 @@ export default class StatusDropdown extends React.Component {
|
|||
header="File Regression Bug for"
|
||||
title="Enter Bug Number"
|
||||
submitButtonText="File Bug"
|
||||
user={user}
|
||||
errorMessage={this.state.fileBugErrorMessage}
|
||||
/>
|
||||
<NotesModal
|
||||
showModal={showNotesModal}
|
||||
|
|
Загрузка…
Ссылка в новой задаче