Skip to content
Merged
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
3 changes: 3 additions & 0 deletions src/assets/info-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions src/pages/BugsList/Table/AISuggestion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Modal } from "@appquality/appquality-design-system";
import { useState } from "react";
import { useGetCampaignsByCampaignBugsAndBugIdAiReviewQuery } from "src/services/tryberApi";
import { Button as AppQualityButton } from "@appquality/appquality-design-system";
import { ReactComponent as InfoIcon } from "src/assets/info-icon.svg";
import styled from "styled-components";

const StyledSuggestion = styled.span<{ suggestion: string }>`
${({ suggestion }) => suggestion === "refused" && `color: #800208;`}
${({ suggestion }) => suggestion === "need_review" && `color: #C78430;`}
${({ suggestion }) => suggestion === "approved" && `color: #02807A;`}
text-transform: capitalize;
`;

const AiSuggestion = ({
campaignId,
bugId,
}: {
campaignId: string;
bugId: number;
}) => {
const [statusInfoModal, setInfoModal] = useState(false);
const { data, isError } = useGetCampaignsByCampaignBugsAndBugIdAiReviewQuery({
campaign: campaignId,
bugId: bugId.toString(),
});
const labelMap: Record<string, string> = {
approved: "Approved",
refused: "Refused",
need_review: "Need Review",
};

if (!data || isError) {
return null;
}
const label = labelMap[data.ai_status] || data.ai_status;

return (
<>
<StyledSuggestion suggestion={data.ai_status}>{label}</StyledSuggestion>
<AppQualityButton
onClick={() => setInfoModal(true)}
kind="transparent"
style={{ paddingLeft: 4 }}
>
<InfoIcon width={15} height={15} />
</AppQualityButton>
{statusInfoModal && (
<Modal
isOpen={statusInfoModal}
onClose={() => setInfoModal(false)}
title="AI Status Reason"
>
<p>{data.ai_reason}</p>
</Modal>
)}
</>
);
};

export default AiSuggestion;
34 changes: 34 additions & 0 deletions src/pages/BugsList/Table/AiScore.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useGetCampaignsByCampaignBugsAndBugIdAiReviewQuery } from "src/services/tryberApi";
import styled from "styled-components";

const StyledScore = styled.span<{ score: number }>`
${({ score }) => score < 40 && `color: #800208;`}
${({ score }) => score >= 40 && score < 60 && `color: #C78430;`}
${({ score }) => score >= 60 && `color: #02807A;`}
text-transform: capitalize;
`;

const AiScore = ({
campaignId,
bugId,
}: {
campaignId: string;
bugId: number;
}) => {
const { data, isError } = useGetCampaignsByCampaignBugsAndBugIdAiReviewQuery({
campaign: campaignId,
bugId: bugId.toString(),
});

if (!data || isError) {
return null;
}

return (
<StyledScore score={data?.score_percentage}>
{data?.score_percentage}%
</StyledScore>
);
};

export default AiScore;
82 changes: 82 additions & 0 deletions src/pages/BugsList/Table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,30 @@ import {
Card,
icons,
aqBootstrapTheme as theme,
Modal,
} from "@appquality/appquality-design-system";
import {
useGetCampaignsByCampaignBugsQuery,
GetCampaignsByCampaignBugsApiArg,
} from "src/services/tryberApi";
import { useFiltersCardContext } from "../FilterContext";
import { Button as AppQualityButton } from "@appquality/appquality-design-system";
import { ReactComponent as InfoIcon } from "src/assets/info-icon.svg";
import Button from "./TableButton";
import Severity from "./Severity";
import Type from "./Type";
import AiSuggestion from "./AISuggestion";
import AiScore from "./AiScore";
import { useState } from "react";

const LIMIT = 100;

const StarFill = icons.StarFill;

const BugsTable = ({ id }: { id: string }) => {
const { filters, page, setPage, order, setOrder } = useFiltersCardContext();
const [scoreModal, setScoreModal] = useState(false);
const [statusModal, setStatusModal] = useState(false);

let orderBy: GetCampaignsByCampaignBugsApiArg["orderBy"] = "id";
if (order.field === "internalId") orderBy = "id";
Expand Down Expand Up @@ -80,6 +88,12 @@ const BugsTable = ({ id }: { id: string }) => {
title: r.severity.name,
content: <Severity severity={r.severity} />,
},
ai_suggestion: {
content: <AiSuggestion campaignId={id} bugId={r.id} />,
},
score: {
content: <AiScore campaignId={id} bugId={r.id} />,
},
status: {
title: r.status.name,
content: r.status.name,
Expand Down Expand Up @@ -135,6 +149,40 @@ const BugsTable = ({ id }: { id: string }) => {
dataIndex: "title",
key: "title",
},
{
title: (
<div>
<span>AI Suggestion</span>
<AppQualityButton
onClick={() => setStatusModal(true)}
kind="transparent"
style={{ paddingLeft: 4 }}
>
<InfoIcon />
</AppQualityButton>
</div>
),
dataIndex: "ai_suggestion",
key: "ai_suggestion",
maxWidth: "25ch",
},
{
title: (
<div>
<span>Score</span>
<AppQualityButton
onClick={() => setScoreModal(true)}
kind="transparent"
style={{ paddingLeft: 4 }}
>
<InfoIcon />
</AppQualityButton>
</div>
),
dataIndex: "score",
key: "score",
maxWidth: "15ch",
},
{
title: "Status",
dataIndex: "status",
Expand Down Expand Up @@ -175,6 +223,40 @@ const BugsTable = ({ id }: { id: string }) => {
maxPages={Math.ceil(data.total / data.limit)}
/>
) : null}
{scoreModal && (
<Modal
isOpen={scoreModal}
onClose={() => setScoreModal(false)}
title="AI Score"
>
<p>
The AI Score is a percentage that reflects how accurately the tester
has evaluated the bug according to the AI based on several criteria
— including scope relevance, a well-formed title and aligned to the
usecase, a clear description, appropriate severity, correct type,
and attached media.
</p>
</Modal>
)}
{statusModal && (
<Modal
isOpen={statusModal}
onClose={() => setStatusModal(false)}
title="AI Suggestion"
>
<p>
The AI Suggestion is a status recommendation provided by the AI
based on the score percentage achieved by the bug based on the score
criteria. It can be:
<br />
Approved : 60% or more
<br />
Need Review : Between 40% and 60%
<br />
Refused : Less than 40%
</p>
</Modal>
)}
</Card>
);
};
Expand Down
21 changes: 21 additions & 0 deletions src/services/tryberApi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,14 @@ const injectedRtkApi = api.injectEndpoints({
url: `/campaigns/${queryArg.campaign}/bugs/${queryArg.bugId}`,
}),
}),
getCampaignsByCampaignBugsAndBugIdAiReview: build.query<
GetCampaignsByCampaignBugsAndBugIdAiReviewApiResponse,
GetCampaignsByCampaignBugsAndBugIdAiReviewApiArg
>({
query: (queryArg) => ({
url: `/campaigns/${queryArg.campaign}/bugs/${queryArg.bugId}/aiReview`,
}),
}),
getCampaignsByCampaignCandidates: build.query<
GetCampaignsByCampaignCandidatesApiResponse,
GetCampaignsByCampaignCandidatesApiArg
Expand Down Expand Up @@ -1380,6 +1388,18 @@ export type GetCampaignsByCampaignBugsAndBugIdApiArg = {
campaign: string;
bugId: string;
};
export type GetCampaignsByCampaignBugsAndBugIdAiReviewApiResponse =
/** status 200 OK */ {
ai_status: string;
ai_reason: string;
score_percentage: number;
ai_notes?: string;
};
export type GetCampaignsByCampaignBugsAndBugIdAiReviewApiArg = {
/** A campaign id */
campaign: string;
bugId: string;
};
export type GetCampaignsByCampaignCandidatesApiResponse = /** status 200 OK */ {
results?: {
age: number;
Expand Down Expand Up @@ -3241,6 +3261,7 @@ export const {
usePutCampaignsByCampaignMutation,
useGetCampaignsByCampaignBugsQuery,
useGetCampaignsByCampaignBugsAndBugIdQuery,
useGetCampaignsByCampaignBugsAndBugIdAiReviewQuery,
useGetCampaignsByCampaignCandidatesQuery,
usePostCampaignsByCampaignCandidatesMutation,
useGetCampaignsByCampaignClustersQuery,
Expand Down
Loading
Loading