Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show spinner while ticket is being generated #560

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/560.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Visual feedback via spinner in the _Create ticket_-button that ticket is being automatically generated.
1 change: 1 addition & 0 deletions changelog.d/560.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Bug with inconsistent text and background coloring of the custom made buttons on the _Incidents_ page.
2 changes: 1 addition & 1 deletion src/components/centercontainer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type CenterContainerPropsType = { children: React.Props<{}>["children"] };
const CenterContainer: React.FC<CenterContainerPropsType> = ({ children }: CenterContainerPropsType) => {
return (
<Grid container alignItems="center" justify="space-evenly" direction="row">
<Grid item>{children}</Grid>
{children}
</Grid>
);
};
Expand Down
33 changes: 24 additions & 9 deletions src/components/incident/IncidentDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo } from "react";
import React, {useEffect, useMemo, useState} from "react";
import "react-table/react-table.css";

import Grid from "@material-ui/core/Grid";
Expand Down Expand Up @@ -48,6 +48,7 @@ import { globalConfig } from "../../config";
import "./IncidentDetails.css";
import {Hidden} from "@material-ui/core";
import {ModifyTicketButton, TicketModifiableField} from "./ModifyTicketAction";
import Spinning from "../spinning";
type IncidentDetailsListItemPropsType = {
title: string;
detail: string | React.ReactNode;
Expand Down Expand Up @@ -174,6 +175,7 @@ const IncidentDetails: React.FC<IncidentDetailsPropsType> = ({

const [{ result: acks, isLoading: isAcksLoading }, setAcksPromise] = useApiIncidentAcks();
const [{ result: events, isLoading: isEventsLoading }, setEventsPromise] = useApiIncidentEvents();
const [isGenerateTicketLoading, setIsGenerateTicketLoading] = useState<boolean>(false);

useEffect(() => {
setAcksPromise(api.getIncidentAcks(incident.pk));
Expand Down Expand Up @@ -242,15 +244,19 @@ const IncidentDetails: React.FC<IncidentDetailsPropsType> = ({
};

const handleCreateTicket = () => {
setIsGenerateTicketLoading(true);
api
.putCreateTicketEvent(incident.pk)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.then(({ ticket_url }: IncidentTicketUrlBody) => {
displayAlert(`Created ticket from incident ${incident.pk}`, "success");
onIncidentChange({ ...incident, ticket_url });
setIsGenerateTicketLoading(false);
window.open(ticket_url, '_blank', 'noopener,noreferrer');
})
.catch((error) => {});
.catch((error) => {
setIsGenerateTicketLoading(false);
});
};

const handleSaveTicket = (url?: string) => {
Expand Down Expand Up @@ -395,13 +401,17 @@ const IncidentDetails: React.FC<IncidentDetailsPropsType> = ({
onManualOpen={handleManualOpen}
isBulk={false}
/>
</CenterContainer>
<CenterContainer>

<ModifyTicketButton
onCreateTicket={handleCreateTicket}
onSaveTicket={handleSaveTicket}
ticketUrl={incident.ticket_url}
isBulk={false}>
onCreateTicket={handleCreateTicket}
onSaveTicket={handleSaveTicket}
ticketUrl={incident.ticket_url}
isBulk={false}
modifyTicketButtonProps={{
startIcon: isGenerateTicketLoading ? <Spinning shouldSpin /> : null,
variant: "contained",
}}
>

</ModifyTicketButton>
</CenterContainer>
Expand Down Expand Up @@ -566,7 +576,12 @@ const IncidentDetails: React.FC<IncidentDetailsPropsType> = ({
onCreateTicket={handleCreateTicket}
onSaveTicket={handleSaveTicket}
ticketUrl={incident.ticket_url}
isBulk={false}>
isBulk={false}
modifyTicketButtonProps={{
startIcon: isGenerateTicketLoading ? <Spinning shouldSpin /> : null,
variant: "contained",
}}
>
</ModifyTicketButton>
</Grid>
<Grid item className="close-button-container" data-testid={"details-button-sm-interactive-item"}>
Expand Down
8 changes: 4 additions & 4 deletions src/components/incident/styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ export const useStyles = makeStyles((theme: Theme) =>
"flex-grow": 0,
},
dangerousButton: {
background: theme.palette.warning.main,
color: "#FFFFFF !important",
background: `${theme.palette.warning.main} !important`,
color: `${WHITE} !important`,
'&:hover': {
backgroundColor: `${theme.palette.warning.main} !important`,
opacity: 0.7
},
},
safeButton: {
background: theme.palette.primary.main,
color: "#FFFFFF !important",
background: `${theme.palette.primary.main} !important`,
color: `${WHITE} !important`,
'&:hover': {
backgroundColor: `${theme.palette.primary.main} !important`,
opacity: 0.7
Expand Down
132 changes: 131 additions & 1 deletion src/components/incident/test/IncidentDetails.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** * @jest-environment jsdom */

import React from "react";
import {render, screen, waitFor, within} from "@testing-library/react";
import {render, screen, waitFor, waitForElementToBeRemoved, within} from "@testing-library/react";

import IncidentDetails from "../IncidentDetails";
import {Incident, IncidentTag, IncidentTicketUrlBody, SourceSystem} from "../../../api/types";
Expand Down Expand Up @@ -319,6 +319,136 @@ describe('Incident Details: Create Ticket button',() => {
// Create ticket endpoint was not called
expect(createTicketSpy).toHaveBeenCalledTimes(0);
});

it("should not display spinner within create ticket button by default in the incident details view", async () => {
await waitFor(() => {
render(
<IncidentDetails
incident={incidentWithoutTicketMock}
onIncidentChange={onIncidentChange}
/>
)
});

// Create ticket button is rendered
const createTicketButton = screen.getByRole('button', {name: /create ticket/i});
expect(createTicketButton).toBeInTheDocument();

// Spinner is not present within create ticket button
expect(within(createTicketButton).queryByRole('progressbar')).not.toBeInTheDocument();
});

it("should display spinner within create ticket button after user confirms create ticket action in the dialog", async () => {
await waitFor(() => {
render(
<IncidentDetails
incident={incidentWithoutTicketMock}
onIncidentChange={onIncidentChange}
/>
)
});

// User clicks on the Create Ticket button
const createTicketButton = screen.getByRole('button', {name: /create ticket/i});
userEvent.click(createTicketButton);

// User confirms create ticket action in the dialog
const dialogSubmitButton = screen.getByRole('button', {name: /yes/i});
expect(dialogSubmitButton).toBeInTheDocument();
userEvent.click(dialogSubmitButton);

// Spinner appears within create ticket button
const createTicketProgress = within(createTicketButton).getByRole('progressbar');
expect(createTicketProgress).toBeInTheDocument();
});

it("should not display spinner within create ticket button after user cancels create ticket action in the dialog", async () => {
await waitFor(() => {
render(
<IncidentDetails
incident={incidentWithoutTicketMock}
onIncidentChange={onIncidentChange}
/>
)
});

// User clicks on the Create Ticket button
const createTicketButton = screen.getByRole('button', {name: /create ticket/i});
userEvent.click(createTicketButton);

// User cancels create ticket action in the dialog
const dialogCancelButton = screen.getByRole('button', {name: /no/i});
expect(dialogCancelButton).toBeInTheDocument();
userEvent.click(dialogCancelButton);

// Spinner is not present within create ticket button
expect(within(createTicketButton).queryByRole('progressbar')).not.toBeInTheDocument();
});

it("should stop displaying spinner within create ticket button after error on create ticket operation", async () => {
createTicketSpy.mockRejectedValue(new Error("Create ticket test error"));

await waitFor(() => {
render(
<IncidentDetails
incident={incidentWithoutTicketMock}
onIncidentChange={onIncidentChange}
/>
)
});

// User clicks on the Create Ticket button
const createTicketButton = screen.getByRole('button', {name: /create ticket/i});
userEvent.click(createTicketButton);

// User confirms create ticket action
const dialogSubmitButton = screen.getByRole('button', {name: /yes/i});
expect(dialogSubmitButton).toBeInTheDocument();
userEvent.click(dialogSubmitButton);

// Spinner appears within create ticket button
const createTicketProgress = within(createTicketButton).getByRole('progressbar');
expect(createTicketProgress).toBeInTheDocument();

// Create ticket endpoint was called with correct incident pk
expect(createTicketSpy).toHaveBeenCalledTimes(1)
expect(createTicketSpy).toHaveBeenCalledWith(4000)

// Spinner within create ticket button disappears
await waitForElementToBeRemoved(createTicketProgress);
});

it("should stop displaying spinner within create ticket button after success on create ticket operation", async () => {
await waitFor(() => {
render(
<IncidentDetails
incident={incidentWithoutTicketMock}
onIncidentChange={onIncidentChange}
/>
)
});

// User clicks on the Create Ticket button
const createTicketButton = screen.getByRole('button', {name: /create ticket/i});
userEvent.click(createTicketButton);

// User confirms create ticket action
const dialogSubmitButton = screen.getByRole('button', {name: /yes/i});
expect(dialogSubmitButton).toBeInTheDocument();
userEvent.click(dialogSubmitButton);

// Spinner appears within create ticket button
const createTicketProgress = within(createTicketButton).getByRole('progressbar');
expect(createTicketProgress).toBeInTheDocument();

// Create ticket endpoint was called with correct incident pk
expect(createTicketSpy).toHaveBeenCalledTimes(1)
expect(createTicketSpy).toHaveBeenCalledWith(4000)
expect(createTicketSpy).toHaveReturnedWith(Promise.resolve({ticket_url: generatedTicketUrlMockValue}))

// Spinner within create ticket button disappears
await waitForElementToBeRemoved(createTicketProgress);
});
});

describe('Primary Details section: Source internal incident ID',() => {
Expand Down
Loading