Skip to content

Commit

Permalink
ART-10508: add Jira and PR creation logic with submitRequest button (#…
Browse files Browse the repository at this point in the history
…132)

* add Jira and PR creation logic with submitRequest button

* Use latest branch as default for image selector

* set isSubmitted sooner, put spinner; use BranchObject type, ditch dead code

* update imageReleaseVersion before setting inputs, propagate error details

* make Errata Writer optional; fix generated yaml formatting
  • Loading branch information
DennisPeriquet authored Nov 18, 2024
1 parent f8efc5e commit 8984020
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 51 deletions.
12 changes: 11 additions & 1 deletion components/api_calls/api_calls.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,17 @@ function makeApiCall(urlPath, method, data = {}, headers = {}, params = {}, req
})
.catch(error => {
console.error("API call failed:", error);
return { detail: 'Request failed' };

// If an error occurred, attempt to strigify the response data and
// include it in the error message so that it can be displayed in the UI.
let errorDetails = error.message;
try {
const errorResponseData = JSON.stringify(error.response?.data);
errorDetails += `, ${errorResponseData}`;
} catch (error) {
console.error("Error parsing response data:", error);
}
return { detail: 'Request failed', 'message': `${errorDetails}` };
});
}

Expand Down
9 changes: 6 additions & 3 deletions components/release/openshift_version_select.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {getReleaseBranchesFromOcpBuildData} from "../api_calls/release_calls";

const {Option} = Select;

function OpenshiftVersionSelect({ onVersionChange, initialVersion, redirectOnSelect=false }) {
function OpenshiftVersionSelect({ onVersionChange, initialVersion, alignment='right', padding='30px', redirectOnSelect=false, useDefaultValue=true }) {
const [data, setData] = useState([]);

const setDataFunc = () => {
Expand Down Expand Up @@ -47,8 +47,11 @@ function OpenshiftVersionSelect({ onVersionChange, initialVersion, redirectOnSel

// Simplify the render function by removing the if-else
return (
<div align={"right"} style={{padding: "30px"}}>
<Select defaultValue={initialVersion} placeholder={<div style={{color: "black"}}>Openshift Version</div>} onChange={onChangeFunc}>
<div align={alignment} style={{ padding: { padding } }}>
<Select
{...(useDefaultValue ? { defaultValue: initialVersion } : { value: initialVersion })}
placeholder={<div style={{ color: "black" }}>Openshift Version</div>} onChange={onChangeFunc}
>
{generateSelectOptionFromStateDate(data)}
</Select>
</div>
Expand Down
184 changes: 142 additions & 42 deletions components/self-service/new-content-done.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import Box from '@mui/material/Box'
import { Button, Typography } from '@mui/material'
import { Button, Typography, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from '@mui/material';
import * as React from 'react';
import { useState } from 'react';
import { useNewContentState } from './new-content-state'
import { message } from 'antd';
import YAML from 'yaml'
import { makeApiCall } from '../api_calls/api_calls';

export default function NewContentDone() {
const { activeStep, handleBack, handleReset, inputs } = useNewContentState();

const [dialogOpen, setDialogOpen] = useState(false);
const [dialogContent, setDialogContent] = useState([]);
const [dialogTitle, setDialogTitle] = useState('');
const [isSubmitted, setIsSubmitted] = useState(false);

const distgit_ns = inputs.componentType == 'rpm' ? 'rpms' : 'containers';
let web_url = inputs.sourceRepo;
if (web_url?.endsWith('.git'))
Expand Down Expand Up @@ -95,6 +104,7 @@ export default function NewContentDone() {
const generateYaml = () => {
const result: Record<string, any> = {
meta: {
release: inputs.imageReleaseVersion,
payload_name: inputs.payloadName,
component_type: inputs.componentType,
distgit_repo: `${distgit_ns}/${distgitName}`,
Expand All @@ -112,52 +122,111 @@ export default function NewContentDone() {
return YAML.stringify(result);
};

// safeEncodeURIComponent wraps the library function, and additionally encodes
// square brackets. Square brackets are NOT unsafe per RFC1738, but Google and
// others mishandle them.
const safeEncodeURIComponent = (value) => {
return encodeURIComponent(value)
.replace('[', '%5B')
.replace(']', '%5D')
.replace('{', '%7B')
.replace('}', '%7D')
}

// Create jira summary.
const jiraSummary = `[BuildAuto] Add OCP component - ${distgit_ns}/${distgitName}`
const jiraSummaryEncoded = safeEncodeURIComponent(jiraSummary);

// Combine YAMLs as Jira description
const jiraDescription = `${generateYaml()}\n\n${generateHBYaml()}`;
const jiraDescriptionEncoded = safeEncodeURIComponent(jiraDescription);
const handleSubmitRequest = async () => {

const releaseWork = safeEncodeURIComponent("Release work")
// Use this to remove the button immediately to avoid the user being able to
// click while a request is being processed.
setIsSubmitted(true);

const ARTProjectID = "12323120"
const ARTStoryTypeID = 17
const imageName = web_url?.substring(web_url.lastIndexOf('/')+ 1)
const jiraDescription = `${generateYaml()}\n\n${generateHBYaml()}`;
const ARTProjectID = "ART"
const ARTStoryTypeID = "Story"
const component = "Release work";
const priority = "Normal";

// Build the Jira URL
let jiraUrl = `https://issues.redhat.com/secure/CreateIssueDetails!init.jspa?priority=10200`
jiraUrl += `&pid=${ARTProjectID}`
jiraUrl += `&issuetype=${ARTStoryTypeID}`
jiraUrl += `&summary=${jiraSummaryEncoded}`
jiraUrl += `&description=${jiraDescriptionEncoded}`
jiraUrl += `&component=${releaseWork}`
const fileContent = `content:
source:
dockerfile: Dockerfile.openshift
git:
branch:
target: release-{MAJOR}.{MINOR}
url: ${repo_url}
web: ${web_url}
ci_alignment:
streams_prs:
ci_build_root:
stream: rhel-9-golang-ci-build-root
distgit:
branch: rhaos-{MAJOR}.{MINOR}-rhel-9
component: ${imageName}-container
enabled_repos:
- rhel-9-appstream-rpms
- rhel-9-baseos-rpms
for_payload: false
from:
builder:
- stream: rhel-9-golang
member: openshift-enterprise-base-rhel9
name: openshift/${imageName}-rhel9
owners:
- ${inputs.deliveryRepoImageOwner}@redhat.com
`;

const handleCreateJira = () => {
const params = {
image_name: imageName,
release_for_image: inputs.imageReleaseVersion,
file_content: fileContent,
jira_summary: jiraSummary,
jira_description: jiraDescription,
jira_project_id: ARTProjectID,
jira_story_type_id: ARTStoryTypeID,
jira_component: component,
jira_priority: priority,

// Open the Jira creation page in a new tab with pre-filled data
window.open(jiraUrl, '_blank');
// the default mode is test mode (i.e., create fake PR and Jira) so you can easily
// test the UI and API and be intentional about actually creating the PR and Jira.
git_test_mode: "false",
jira_test_mode: "false"
}

try {
message.loading({
content: 'Creating PR and Jira ...',
duration: 0
});
const response = await makeApiCall('/api/v1/git_jira_api', 'GET', {}, {}, params, false);
setDialogTitle('Error occurred');
if (response?.status === "success") {
// Override the dialog title and content to show Jira and PR URLs.
setDialogTitle('Jira and PR created successfully:');
setDialogContent([response.jira_url, response.pr_url]);
} else {
setDialogContent([`ART UI server return status: ${response?.status}`,
`Error message: ${response?.error}`,
`Other error: ${response?.detail}`,
`Other message: ${response?.message}`]);
// If the call to ART UI server failed, we should allow the user to try again.
setIsSubmitted(false);
}
setDialogOpen(true);
} catch (error) {
setDialogContent(['Error in call to ART UI server', `ART UI server error: ${error}`]);
setDialogOpen(true);

// If the call to ART UI server failed, we should allow the user to try again.
setIsSubmitted(false);
} finally {

// Ensure we get rid of the loading message whether the and Jira and PR creation succeeded or not.
message.destroy();
}
};

const handleCloseDialog = () => {
setDialogOpen(false);
};

return (<Box
component="div"
sx={{
'& > :not(style)': { m: 2},
'& > :not(style)': { m: 2 },
}}
>
<Box>
<Typography sx={{ mb: 3 }}>About to make a Jira in the <strong>ART</strong> project using the <strong>Summary</strong> and <strong>Description</strong> below:</Typography>
<Typography sx={{ mb: 3 }}>About to make a Jira in the <strong>ART</strong> project using the <strong>Summary</strong> and <strong>Description</strong> below:</Typography>
<hr />
<Typography component="h6" sx={{ mt: 2 }}><b>Summary</b></Typography>
<Typography>{jiraSummary}</Typography>
Expand All @@ -174,19 +243,20 @@ export default function NewContentDone() {
Follow these steps:
</Typography>
<ul>
<li>If the above looks good, login to Jira <a href="https://issues.redhat.com/login.jsp?os_destination=%2Fdefault.jsp" target="_blank" rel="noopener noreferrer">here</a> (a separate tab will open) then click the "Create Jira" button below</li>
<li>In the "Create Issue" page, set the "Reporter" field as your UserId and click the "Create" button at the bottom</li>
<li>Share the ticket to <strong>@release-artists</strong> on <strong>#forum-ocp-art</strong> on Slack</li>
<li>If the above looks good, click the Submit Request button and a Jira and PR will be created</li>
<li>Inform <strong>@release-artists</strong> on <strong>#forum-ocp-art</strong> on Slack about the Jira and PR</li>
</ul>
</Box>
<Box sx={{ py: 2 }}>
<Button
variant="contained"
onClick={handleCreateJira}
sx={{ mt: 1, mr: 1 }}
>
Create Jira
</Button>
{!isSubmitted && (
<Button
variant="contained"
onClick={handleSubmitRequest}
sx={{ mt: 1, mr: 1 }}
>
Submit Request
</Button>
)}
<Button
variant="contained"
onClick={handleReset}
Expand All @@ -201,6 +271,36 @@ export default function NewContentDone() {
>
Back
</Button>
<Dialog
open={dialogOpen}
onClose={handleCloseDialog}
>
<DialogTitle>{dialogTitle}</DialogTitle>
<DialogContent>
<DialogContentText>
{dialogContent.map((text, index) => {
return (
<span key={index}>
{text.startsWith('https://') ? (
<a href={text} target="_blank" rel="noopener noreferrer">
{text}
</a>
) : (
text
)}
<br />
</span>
);
})}
</DialogContentText>
</DialogContent>
<DialogActions sx={{ justifyContent: 'center' }}>
<Button onClick={handleCloseDialog} color="primary">
OK
</Button>
</DialogActions>
</Dialog>

</Box>
</Box>)
}
Loading

0 comments on commit 8984020

Please sign in to comment.