Bug 1837998 - File regression bugs using the API (#8048)

This commit is contained in:
MyeongJun Go 2024-07-26 21:36:19 +09:00 коммит произвёл GitHub
Родитель 706b739118
Коммит 13c4c50b83
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
4 изменённых файлов: 186 добавлений и 79 удалений

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

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