Skip to content

Commit e4b6895

Browse files
committed
View files task attachments
Resolves: #1929 Functional changes: 1. document to be viewed is selected from the list (first action above the editor) or via URL 2. display language toggle based on languages supported by the document. For attachments the list consists of plain text and optionally a second language discovered based on file extension (YAML or JSON). 3. replace "merged" checkbox with an option in the select Related refactorings: 1. split getTaskById() query into specialized queries: a) getTaskById() returning Promise<Task> for working with the object b) getTaskByIdAndFormat() returning Promise<string> for displaying the task as a formatted text 2. configure AnalysisDetails component to respond to 2 routes: existing details route and newly added /applications/:applicationId/analysis-details/:taskId/attachments/:attachmentId Signed-off-by: Radoslaw Szwajkowski <[email protected]>
1 parent c2f4f06 commit e4b6895

File tree

12 files changed

+381
-111
lines changed

12 files changed

+381
-111
lines changed

client/public/locales/en/translation.json

+1
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@
285285
"assessmentQuestionnaires": "Assessment questionnaires",
286286
"assessmentNotes": "Assessment notes",
287287
"assessmentSummary": "Assessment summary",
288+
"attachments": "Attachments",
288289
"autoTagging": "Automated Tagging",
289290
"binary": "Binary",
290291
"branch": "Branch",

client/src/app/Paths.ts

+8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ export const DevPaths = {
33
applications: "/applications",
44
applicationsAnalysisDetails:
55
"/applications/:applicationId/analysis-details/:taskId",
6+
applicationsAnalysisDetailsAttachment:
7+
"/applications/:applicationId/analysis-details/:taskId/attachments/:attachmentId",
68
applicationsAnalysisTab: "/applications/analysis-tab",
79
applicationsAssessmentTab: "/applications/assessment-tab",
810
applicationsImports: "/applications/application-imports",
@@ -93,3 +95,9 @@ export interface AnalysisDetailsRoute {
9395
applicationId: string;
9496
taskId: string;
9597
}
98+
99+
export interface AnalysisDetailsAttachmentRoute {
100+
applicationId: string;
101+
taskId: string;
102+
attachmentId: string;
103+
}

client/src/app/Routes.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ export const devRoutes: IRoute<DevPathValues>[] = [
7979
{
8080
path: Paths.applicationsAnalysisDetails,
8181
comp: AnalysisDetails,
82+
exact: true,
83+
},
84+
{
85+
path: Paths.applicationsAnalysisDetailsAttachment,
86+
comp: AnalysisDetails,
8287
exact: false,
8388
},
8489
{

client/src/app/api/models.ts

+5
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,11 @@ export interface Task {
319319
state?: TaskState;
320320
job?: string;
321321
report?: TaskReport;
322+
attached?: TaskAttachment[];
323+
}
324+
325+
interface TaskAttachment extends Ref {
326+
activity?: number;
322327
}
323328

324329
export interface TaskData {

client/src/app/api/rest.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -319,13 +319,25 @@ export const getApplicationImports = (
319319
.get(`${APP_IMPORT}?importSummary.id=${importSummaryID}&isValid=${isValid}`)
320320
.then((response) => response.data);
321321

322-
export function getTaskById(
322+
export function getTaskById(id: number): Promise<Task> {
323+
return axios
324+
.get(`${TASKS}/${id}`, {
325+
headers: { ...jsonHeaders },
326+
responseType: "json",
327+
})
328+
.then((response) => {
329+
return response.data;
330+
});
331+
}
332+
333+
export function getTaskByIdAndFormat(
323334
id: number,
324335
format: string,
325336
merged: boolean = false
326-
): Promise<Task | string> {
327-
const headers = format === "yaml" ? { ...yamlHeaders } : { ...jsonHeaders };
328-
const responseType = format === "yaml" ? "text" : "json";
337+
): Promise<string> {
338+
const isYaml = format === "yaml";
339+
const headers = isYaml ? { ...yamlHeaders } : { ...jsonHeaders };
340+
const responseType = isYaml ? "text" : "json";
329341

330342
let url = `${TASKS}/${id}`;
331343
if (merged) {
@@ -338,7 +350,9 @@ export function getTaskById(
338350
responseType: responseType,
339351
})
340352
.then((response) => {
341-
return response.data;
353+
return isYaml
354+
? String(response.data ?? "")
355+
: JSON.stringify(response.data, undefined, 2);
342356
});
343357
}
344358

@@ -439,6 +453,11 @@ export const createFile = ({
439453
return response.data;
440454
});
441455

456+
export const getTextFile = (id: number): Promise<string> =>
457+
axios
458+
.get(`${FILES}/${id}`, { headers: { Accept: "text/plain" } })
459+
.then((response) => response.data);
460+
442461
export const getSettingById = <K extends keyof SettingTypes>(
443462
id: K
444463
): Promise<SettingTypes[K]> =>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React, { FC, useState } from "react";
2+
3+
import {
4+
Select,
5+
SelectOption,
6+
SelectList,
7+
MenuToggleElement,
8+
MenuToggle,
9+
} from "@patternfly/react-core";
10+
import { Document } from "./SimpleDocumentViewer";
11+
import "./SimpleDocumentViewer.css";
12+
13+
export const AttachmentToggle: FC<{
14+
onSelect: (doc: Document) => void;
15+
documents: Document[];
16+
}> = ({ onSelect, documents }) => {
17+
const [isOpen, setIsOpen] = useState(false);
18+
const onToggle = () => {
19+
setIsOpen(!isOpen);
20+
};
21+
22+
return (
23+
<div className="simple-task-viewer-attachment-toggle">
24+
<Select
25+
isOpen={isOpen}
26+
onSelect={(_event, value: string | number | undefined) => {
27+
const selected = documents.find((it) => it.id === value);
28+
if (selected) {
29+
onSelect(selected);
30+
}
31+
onToggle();
32+
}}
33+
onOpenChange={(isOpen) => setIsOpen(isOpen)}
34+
toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
35+
<MenuToggle ref={toggleRef} onClick={onToggle} isExpanded={isOpen}>
36+
{documents.find(({ isSelected }) => isSelected)?.name}
37+
</MenuToggle>
38+
)}
39+
shouldFocusToggleOnSelect
40+
>
41+
<SelectList>
42+
{documents.map(({ id, name, isSelected, description }) => (
43+
<SelectOption
44+
isSelected={isSelected}
45+
key={id}
46+
value={id}
47+
description={description}
48+
>
49+
{name}
50+
</SelectOption>
51+
))}
52+
</SelectList>
53+
</Select>
54+
</div>
55+
);
56+
};

client/src/app/components/simple-document-viewer/LanguageToggle.tsx

+42-44
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,47 @@ import CodeIcon from "@patternfly/react-icons/dist/esm/icons/code-icon";
77
import "./SimpleDocumentViewer.css";
88

99
export const LanguageToggle: React.FC<{
10-
currentLanguage: Language.yaml | Language.json;
10+
currentLanguage: Language;
1111
code?: string;
12-
setCurrentLanguage: (lang: Language.yaml | Language.json) => void;
13-
}> = ({ currentLanguage, code, setCurrentLanguage }) => (
14-
<div
15-
className={css(
16-
editorStyles.codeEditorTab,
17-
"language-toggle-group-container"
18-
)}
19-
key="code-language-toggle"
20-
>
21-
<ToggleGroup
22-
aria-label="code content type selection"
23-
className="language-toggle-group"
12+
supportedLanguages: Language[];
13+
setCurrentLanguage: (lang: Language) => void;
14+
}> = ({ currentLanguage, code, setCurrentLanguage, supportedLanguages }) => {
15+
if (supportedLanguages.length <= 1) {
16+
return <></>;
17+
}
18+
19+
return (
20+
<div
21+
className={css(
22+
editorStyles.codeEditorTab,
23+
"language-toggle-group-container"
24+
)}
25+
key="code-language-toggle"
2426
>
25-
<ToggleGroupItem
26-
text={
27-
<>
28-
<span className={editorStyles.codeEditorTabIcon}>
29-
<CodeIcon />
30-
</span>
31-
<span className={editorStyles.codeEditorTabText}>JSON</span>
32-
</>
33-
}
34-
buttonId="code-language-select-json"
35-
isSelected={currentLanguage === "json"}
36-
isDisabled={!code && currentLanguage !== "json"}
37-
onChange={() => setCurrentLanguage(Language.json)}
38-
/>
39-
<ToggleGroupItem
40-
text={
41-
<>
42-
<span className={editorStyles.codeEditorTabIcon}>
43-
<CodeIcon />
44-
</span>
45-
<span className={editorStyles.codeEditorTabText}>YAML</span>
46-
</>
47-
}
48-
buttonId="code-language-select-yaml"
49-
isSelected={currentLanguage === "yaml"}
50-
isDisabled={!code && currentLanguage !== "yaml"}
51-
onChange={() => setCurrentLanguage(Language.yaml)}
52-
/>
53-
</ToggleGroup>
54-
</div>
55-
);
27+
<ToggleGroup
28+
aria-label="code content type selection"
29+
className="language-toggle-group"
30+
>
31+
{supportedLanguages.map((lang) => (
32+
<ToggleGroupItem
33+
key={lang}
34+
text={
35+
<>
36+
<span className={editorStyles.codeEditorTabIcon}>
37+
<CodeIcon />
38+
</span>
39+
<span className={editorStyles.codeEditorTabText}>
40+
{lang === Language.plaintext ? "Text" : lang.toUpperCase()}
41+
</span>
42+
</>
43+
}
44+
buttonId={`code-language-select-${lang}`}
45+
isSelected={currentLanguage === lang}
46+
isDisabled={!code && currentLanguage !== lang}
47+
onChange={() => setCurrentLanguage(lang)}
48+
/>
49+
))}
50+
</ToggleGroup>
51+
</div>
52+
);
53+
};

client/src/app/components/simple-document-viewer/SimpleDocumentViewer.css

+6-2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@
5353
flex-grow: 1;
5454
}
5555

56+
.simple-task-viewer-code .pf-v5-c-code-editor__controls > * {
57+
display: flex;
58+
}
59+
5660
.simple-task-viewer-code .pf-v5-c-code-editor__header-main {
5761
display: none;
5862
}
@@ -67,6 +71,6 @@
6771
--pf-v5-c-toggle-group__button--FontSize: var(--pf-v5-global--FontSize--md);
6872
}
6973

70-
.merged-checkbox {
71-
margin: auto 0.5rem;
74+
.simple-task-viewer-attachment-toggle {
75+
order: -1;
7276
}

0 commit comments

Comments
 (0)