@qoretechnologies/reqraft 0.10.27-pr.74.gc5f6ca3 → 0.10.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +8 -1
- package/dist/components/form/engine/CompactRow.js.map +1 -1
- package/dist/components/searchFilterBar/SearchFilterBar.d.ts +33 -0
- package/dist/components/searchFilterBar/SearchFilterBar.d.ts.map +1 -0
- package/dist/components/searchFilterBar/SearchFilterBar.js +76 -0
- package/dist/components/searchFilterBar/SearchFilterBar.js.map +1 -0
- package/dist/components/searchFilterBar/index.d.ts +2 -0
- package/dist/components/searchFilterBar/index.d.ts.map +1 -0
- package/dist/components/searchFilterBar/index.js +18 -0
- package/dist/components/searchFilterBar/index.js.map +1 -0
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts +9 -1
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts.map +1 -1
- package/dist/components/supportTicket/InterfaceReferenceTags.js +2 -2
- package/dist/components/supportTicket/InterfaceReferenceTags.js.map +1 -1
- package/dist/components/supportTicket/ReferencePicker.d.ts +52 -6
- package/dist/components/supportTicket/ReferencePicker.d.ts.map +1 -1
- package/dist/components/supportTicket/ReferencePicker.js +240 -31
- package/dist/components/supportTicket/ReferencePicker.js.map +1 -1
- package/dist/components/supportTicket/attachmentAccess.d.ts +37 -0
- package/dist/components/supportTicket/attachmentAccess.d.ts.map +1 -0
- package/dist/components/supportTicket/attachmentAccess.js +132 -0
- package/dist/components/supportTicket/attachmentAccess.js.map +1 -0
- package/dist/components/supportTicket/index.d.ts +2 -0
- package/dist/components/supportTicket/index.d.ts.map +1 -1
- package/dist/components/supportTicket/index.js +2 -0
- package/dist/components/supportTicket/index.js.map +1 -1
- package/dist/components/supportTicket/meta.d.ts +8 -0
- package/dist/components/supportTicket/meta.d.ts.map +1 -1
- package/dist/components/supportTicket/meta.js +37 -1
- package/dist/components/supportTicket/meta.js.map +1 -1
- package/dist/components/supportTicket/ticketModel.d.ts +102 -0
- package/dist/components/supportTicket/ticketModel.d.ts.map +1 -0
- package/dist/components/supportTicket/ticketModel.js +3 -0
- package/dist/components/supportTicket/ticketModel.js.map +1 -0
- package/dist/components/ticketReferences/TicketReferences.d.ts.map +1 -1
- package/dist/components/ticketReferences/TicketReferences.js +109 -78
- package/dist/components/ticketReferences/TicketReferences.js.map +1 -1
- package/dist/components/ticketThread/TicketThread.d.ts.map +1 -1
- package/dist/components/ticketThread/TicketThread.js +19 -2
- package/dist/components/ticketThread/TicketThread.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/form/engine/CompactRow.tsx +7 -0
- package/src/components/form/engine/FormEngine.stories.tsx +90 -0
- package/src/components/searchFilterBar/SearchFilterBar.tsx +131 -0
- package/src/components/searchFilterBar/index.ts +1 -0
- package/src/components/supportTicket/InterfaceReferenceTags.stories.tsx +37 -0
- package/src/components/supportTicket/InterfaceReferenceTags.tsx +11 -0
- package/src/components/supportTicket/ReferencePicker.stories.tsx +257 -34
- package/src/components/supportTicket/ReferencePicker.tsx +421 -95
- package/src/components/supportTicket/TicketConversation.stories.tsx +0 -1
- package/src/components/supportTicket/TicketReplyBox.stories.tsx +0 -4
- package/src/components/supportTicket/attachmentAccess.ts +97 -0
- package/src/components/supportTicket/index.ts +2 -0
- package/src/components/supportTicket/meta.ts +40 -0
- package/src/components/supportTicket/ticketModel.ts +131 -0
- package/src/components/ticketReferences/TicketReferences.tsx +124 -145
- package/src/components/ticketThread/TicketThread.stories.tsx +24 -2
- package/src/components/ticketThread/TicketThread.tsx +23 -3
- package/src/index.tsx +3 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Authed attachment byte-fetching, shared by every ticket surface.
|
|
3
|
+
*
|
|
4
|
+
* Attachment bytes can't go through the JSON fetch helpers, so each app grew its
|
|
5
|
+
* own raw-`fetch` copy: qorus-ide in `views/support/api.ts`, admin-portal in
|
|
6
|
+
* `pages/SupportTickets/helpers.ts`. The two were the same function down to the
|
|
7
|
+
* error text ("Could not download the attachment") and the anchor-click dance —
|
|
8
|
+
* they differed only in how the URL is built, which auth header is sent, and
|
|
9
|
+
* which Error subclass is thrown.
|
|
10
|
+
*
|
|
11
|
+
* Those three differences are the parameters below. Everything else — the
|
|
12
|
+
* non-OK check, `URL.createObjectURL`, the temporary anchor, revoking the object
|
|
13
|
+
* URL — lives here once.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface ITicketAttachmentAccessConfig {
|
|
17
|
+
/** Absolute URL for one attachment's bytes. */
|
|
18
|
+
attachmentUrl: (ticketId: string, attachmentId: string) => string;
|
|
19
|
+
/**
|
|
20
|
+
* Headers for the byte request — typically auth. A function rather than a
|
|
21
|
+
* value because tokens are resolved per request, not at wire-up time.
|
|
22
|
+
*/
|
|
23
|
+
headers?: () => Record<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* Error to throw on a non-OK response. Defaults to a plain `Error`; apps with
|
|
26
|
+
* their own API error type (carrying the status) pass a factory.
|
|
27
|
+
*/
|
|
28
|
+
onError?: (message: string, status: number) => Error;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ITicketAttachmentAccess {
|
|
32
|
+
/** Raw bytes. `failure` is the message used if the response isn't OK. */
|
|
33
|
+
fetchBlob: (ticketId: string, attachmentId: string, failure?: string) => Promise<Blob>;
|
|
34
|
+
/**
|
|
35
|
+
* An object URL for the bytes — for inline thumbnails and the lightbox.
|
|
36
|
+
*
|
|
37
|
+
* The caller owns the returned URL and must `URL.revokeObjectURL` it when the
|
|
38
|
+
* element goes away; this cannot do it for them without breaking the img.
|
|
39
|
+
*/
|
|
40
|
+
fetchBlobUrl: (ticketId: string, attachmentId: string) => Promise<string>;
|
|
41
|
+
/** Save the attachment to disk under its original filename. */
|
|
42
|
+
download: (ticketId: string, attachmentId: string, filename: string) => Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const DEFAULT_DOWNLOAD_FAILURE = 'Could not download the attachment';
|
|
46
|
+
const DEFAULT_LOAD_FAILURE = 'Could not load the attachment';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Save a blob to disk under `filename`, via a temporary anchor.
|
|
50
|
+
*
|
|
51
|
+
* Exported because it is the half of `download` that has nothing to do with
|
|
52
|
+
* tickets — a surface that already holds the bytes shouldn't have to re-fetch
|
|
53
|
+
* them just to reuse the save behaviour.
|
|
54
|
+
*/
|
|
55
|
+
export const saveBlobAs = (blob: Blob, filename: string): void => {
|
|
56
|
+
const url = URL.createObjectURL(blob);
|
|
57
|
+
const anchor = document.createElement('a');
|
|
58
|
+
|
|
59
|
+
anchor.href = url;
|
|
60
|
+
anchor.download = filename;
|
|
61
|
+
document.body.appendChild(anchor);
|
|
62
|
+
anchor.click();
|
|
63
|
+
anchor.remove();
|
|
64
|
+
// Safari needs the URL alive until the click is processed; a task boundary is
|
|
65
|
+
// enough and avoids leaking the blob for the life of the document.
|
|
66
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const createTicketAttachmentAccess = ({
|
|
70
|
+
attachmentUrl,
|
|
71
|
+
headers,
|
|
72
|
+
onError,
|
|
73
|
+
}: ITicketAttachmentAccessConfig): ITicketAttachmentAccess => {
|
|
74
|
+
const fetchBlob = async (
|
|
75
|
+
ticketId: string,
|
|
76
|
+
attachmentId: string,
|
|
77
|
+
failure: string = DEFAULT_LOAD_FAILURE
|
|
78
|
+
): Promise<Blob> => {
|
|
79
|
+
const response = await fetch(attachmentUrl(ticketId, attachmentId), {
|
|
80
|
+
headers: headers?.() ?? {},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw onError ? onError(failure, response.status) : new Error(failure);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return response.blob();
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
fetchBlob,
|
|
92
|
+
fetchBlobUrl: async (ticketId, attachmentId) =>
|
|
93
|
+
URL.createObjectURL(await fetchBlob(ticketId, attachmentId, DEFAULT_LOAD_FAILURE)),
|
|
94
|
+
download: async (ticketId, attachmentId, filename) =>
|
|
95
|
+
saveBlobAs(await fetchBlob(ticketId, attachmentId, DEFAULT_DOWNLOAD_FAILURE), filename),
|
|
96
|
+
};
|
|
97
|
+
};
|
|
@@ -143,3 +143,43 @@ const DEFAULT_INTERFACE_ICONS: Record<string, IReqoreIconName> = {
|
|
|
143
143
|
/** Default per-kind icon for a referenced interface, with a generic fallback. */
|
|
144
144
|
export const defaultInterfaceIcon = (kind: string): IReqoreIconName =>
|
|
145
145
|
DEFAULT_INTERFACE_ICONS[kind] ?? 'CodeBoxLine';
|
|
146
|
+
|
|
147
|
+
// The reading name for each interface kind. The kind id (`value-map`, `ai-collection`)
|
|
148
|
+
// is a wire value — it's what a host fetches with and what gets stored on the reference
|
|
149
|
+
// — so it must never be what the user reads. These are plural because they name a
|
|
150
|
+
// category the user picks from, not a single interface. `fsm` maps to "Qogs" like every
|
|
151
|
+
// other user-facing surface; the id stays `fsm` wherever a host still uses it.
|
|
152
|
+
const DEFAULT_INTERFACE_KIND_LABELS: Record<string, string> = {
|
|
153
|
+
workflow: 'Workflows',
|
|
154
|
+
service: 'Services',
|
|
155
|
+
job: 'Jobs',
|
|
156
|
+
fsm: 'Qogs',
|
|
157
|
+
qog: 'Qogs',
|
|
158
|
+
connection: 'Connections',
|
|
159
|
+
mapper: 'Mappers',
|
|
160
|
+
class: 'Classes',
|
|
161
|
+
'value-map': 'Value maps',
|
|
162
|
+
'ai-collection': 'AI collections',
|
|
163
|
+
'ai-endpoint': 'AI endpoints',
|
|
164
|
+
'ai-guardrail': 'AI guardrails',
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Default reading name for an interface kind — "Value maps" for `value-map`. An
|
|
169
|
+
* unknown kind is humanised generically (separators to spaces, sentence case, a
|
|
170
|
+
* plural `s`) so a kind this map hasn't caught up with still reads as a word
|
|
171
|
+
* rather than an identifier. Consumers with their own vocabulary override the
|
|
172
|
+
* whole thing via the picker's `resolveKindLabel`.
|
|
173
|
+
*/
|
|
174
|
+
export const defaultInterfaceKindLabel = (kind: string): string => {
|
|
175
|
+
const known = DEFAULT_INTERFACE_KIND_LABELS[kind];
|
|
176
|
+
|
|
177
|
+
if (known) {
|
|
178
|
+
return known;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const words = kind.replace(/[-_]+/g, ' ').trim();
|
|
182
|
+
const sentenceCase = words.charAt(0).toUpperCase() + words.slice(1);
|
|
183
|
+
|
|
184
|
+
return sentenceCase.endsWith('s') ? sentenceCase : `${sentenceCase}s`;
|
|
185
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { IInterfaceReference, TTicketCategory, TTicketMessageAuthor, TTicketPriority, TTicketStatus } from './meta';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* The support-ticket wire model, shared by every surface that speaks it.
|
|
5
|
+
*
|
|
6
|
+
* These shapes mirror the `SupportTicket` / `SupportMessage` schemas served by
|
|
7
|
+
* the SaaS plane — `/qorus-saas/support/*` for the customer (qorus-api
|
|
8
|
+
* `QorusSaasSupportRestClass`) and `/qorus-saas-admin/support/*` for staff
|
|
9
|
+
* (`QorusSaasAdminRestHandler`). Both planes serve the SAME ticket row; the
|
|
10
|
+
* staff plane simply projects more of it.
|
|
11
|
+
*
|
|
12
|
+
* They live here because they were previously declared twice — once in
|
|
13
|
+
* qorus-ide's `views/support/types.ts` and once in admin-portal's
|
|
14
|
+
* `types/supportTickets.ts` — and had already started to drift. The enum
|
|
15
|
+
* vocabularies (`meta.ts`) were shared from the start; the record shapes that
|
|
16
|
+
* carry them were not, which is the wrong way round: a field added to the wire
|
|
17
|
+
* schema had to be remembered in two repos, and nothing failed if it wasn't.
|
|
18
|
+
*
|
|
19
|
+
* The split below is deliberate. `ITicket` is what BOTH planes return;
|
|
20
|
+
* `IStaffTicket` is the extra projection only the admin plane serves.
|
|
21
|
+
* That keeps "customer must never see the assignee" a property of the type
|
|
22
|
+
* rather than a convention, while still giving one place to add a field.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** Attachment metadata as embedded in a message (no content). */
|
|
26
|
+
export interface ITicketAttachment {
|
|
27
|
+
attachment_id: string;
|
|
28
|
+
filename: string;
|
|
29
|
+
content_type: string;
|
|
30
|
+
size_bytes: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Attachment metadata from the ticket-level list endpoint.
|
|
35
|
+
*
|
|
36
|
+
* Richer than the per-message copy: it carries the provenance the embedded
|
|
37
|
+
* metadata omits — who uploaded the file, when, and which reply it came from
|
|
38
|
+
* (null for a file attached when the ticket was filed). A staff uploader arrives
|
|
39
|
+
* already masked by the server, so this never exposes a real staff identity.
|
|
40
|
+
*/
|
|
41
|
+
export interface ITicketAttachmentDetail extends ITicketAttachment {
|
|
42
|
+
message_id?: string | null;
|
|
43
|
+
uploaded_by?: string;
|
|
44
|
+
uploader_type?: 'customer' | 'staff';
|
|
45
|
+
created?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** One message in a ticket thread. */
|
|
49
|
+
export interface ITicketMessage {
|
|
50
|
+
message_id: string;
|
|
51
|
+
ticket_id: string;
|
|
52
|
+
author_type: TTicketMessageAuthor;
|
|
53
|
+
/** author identity; staff identities are masked from the customer */
|
|
54
|
+
author_id: string;
|
|
55
|
+
/** message body (markdown) */
|
|
56
|
+
body: string;
|
|
57
|
+
/** staff-only internal note — never present in the customer thread */
|
|
58
|
+
internal: boolean;
|
|
59
|
+
created: string;
|
|
60
|
+
/** files attached to this message */
|
|
61
|
+
attachments?: ITicketAttachment[];
|
|
62
|
+
/** interfaces referenced in this message, as `{kind, name}` snapshots */
|
|
63
|
+
referenced_interfaces?: IInterfaceReference[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* One support ticket, in the projection BOTH planes serve.
|
|
68
|
+
*
|
|
69
|
+
* Staff-only fields are not optional-here-required-there; they live in
|
|
70
|
+
* `IStaffTicket` so a customer surface cannot read them by accident.
|
|
71
|
+
*/
|
|
72
|
+
export interface ITicket {
|
|
73
|
+
/** external ticket UUID, used in URLs */
|
|
74
|
+
ticket_id: string;
|
|
75
|
+
/** human-visible ticket number */
|
|
76
|
+
ticket_number: number;
|
|
77
|
+
subject: string;
|
|
78
|
+
/** initial description (markdown) */
|
|
79
|
+
description: string;
|
|
80
|
+
status: TTicketStatus;
|
|
81
|
+
priority: TTicketPriority;
|
|
82
|
+
category: TTicketCategory;
|
|
83
|
+
subscription_code: string;
|
|
84
|
+
/** account name (email) of the filer */
|
|
85
|
+
created_by: string;
|
|
86
|
+
/** parent ticket UUID, when this continues a closed ticket */
|
|
87
|
+
parent_ticket_id?: string;
|
|
88
|
+
/** first staff-reply timestamp */
|
|
89
|
+
first_response_at?: string;
|
|
90
|
+
resolved_at?: string;
|
|
91
|
+
closed_at?: string;
|
|
92
|
+
/** most-recent-message timestamp */
|
|
93
|
+
last_message_at: string;
|
|
94
|
+
created: string;
|
|
95
|
+
modified?: string;
|
|
96
|
+
/** interfaces this ticket references (ticket-level), as `{kind, name}` snapshots */
|
|
97
|
+
referenced_interfaces?: IInterfaceReference[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The extra projection the admin plane adds on top of {@link ITicket}. */
|
|
101
|
+
export interface IStaffTicket extends ITicket {
|
|
102
|
+
/** owner scope key (staff view) */
|
|
103
|
+
scope_key: string;
|
|
104
|
+
/** organization / account */
|
|
105
|
+
scope_type: string;
|
|
106
|
+
account_id: number;
|
|
107
|
+
groupid?: number;
|
|
108
|
+
/** the staff assignee */
|
|
109
|
+
assignee?: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The offset pagination every support list endpoint returns. */
|
|
113
|
+
export interface ITicketPagedResult {
|
|
114
|
+
limit: number;
|
|
115
|
+
offset: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A ticket-list response. Generic over the ticket projection so the staff plane
|
|
120
|
+
* gets `IStaffTicket[]` without redeclaring the envelope; each plane extends this
|
|
121
|
+
* with the scope fields it actually serves.
|
|
122
|
+
*/
|
|
123
|
+
export interface ITicketListResult<T extends ITicket = ITicket> extends ITicketPagedResult {
|
|
124
|
+
tickets: T[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A message-list response for one ticket. */
|
|
128
|
+
export interface ITicketMessageListResult extends ITicketPagedResult {
|
|
129
|
+
ticket_id: string;
|
|
130
|
+
messages: ITicketMessage[];
|
|
131
|
+
}
|