@ikatec/digisac-api-sdk 4.1.0 → 4.3.0
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/README.md +7 -4
- package/dist/apis/authHistory/AuthHistoryApi.cjs +12 -12
- package/dist/apis/authHistory/AuthHistoryApi.cjs.map +1 -1
- package/dist/apis/authHistory/AuthHistoryApi.d.ts +12 -19
- package/dist/apis/authHistory/AuthHistoryApi.d.ts.map +1 -1
- package/dist/apis/authHistory/AuthHistoryApi.mjs +12 -12
- package/dist/apis/authHistory/AuthHistoryApi.mjs.map +1 -1
- package/dist/apis/authHistory/types.d.ts +4 -3
- package/dist/apis/authHistory/types.d.ts.map +1 -1
- package/dist/apis/customFields/types.d.ts +21 -19
- package/dist/apis/customFields/types.d.ts.map +1 -1
- package/dist/apis/dashboard/DashboardApi.cjs +54 -0
- package/dist/apis/dashboard/DashboardApi.cjs.map +1 -0
- package/dist/apis/dashboard/DashboardApi.d.ts +65 -0
- package/dist/apis/dashboard/DashboardApi.d.ts.map +1 -0
- package/dist/apis/dashboard/DashboardApi.mjs +53 -0
- package/dist/apis/dashboard/DashboardApi.mjs.map +1 -0
- package/dist/apis/dashboard/index.cjs +4 -0
- package/dist/apis/dashboard/index.d.ts +3 -0
- package/dist/apis/dashboard/index.d.ts.map +1 -0
- package/dist/apis/dashboard/index.mjs +3 -0
- package/dist/apis/dashboard/types.cjs +0 -0
- package/dist/apis/dashboard/types.d.ts +124 -0
- package/dist/apis/dashboard/types.d.ts.map +1 -0
- package/dist/apis/dashboard/types.mjs +0 -0
- package/dist/apis/holiday/types.d.ts +34 -8
- package/dist/apis/holiday/types.d.ts.map +1 -1
- package/dist/apis/index.cjs +6 -0
- package/dist/apis/index.d.ts +2 -0
- package/dist/apis/index.d.ts.map +1 -1
- package/dist/apis/index.mjs +5 -1
- package/dist/apis/now/NowApi.cjs +37 -0
- package/dist/apis/now/NowApi.cjs.map +1 -0
- package/dist/apis/now/NowApi.d.ts +26 -0
- package/dist/apis/now/NowApi.d.ts.map +1 -0
- package/dist/apis/now/NowApi.mjs +36 -0
- package/dist/apis/now/NowApi.mjs.map +1 -0
- package/dist/apis/now/index.cjs +4 -0
- package/dist/apis/now/index.d.ts +3 -0
- package/dist/apis/now/index.d.ts.map +1 -0
- package/dist/apis/now/index.mjs +3 -0
- package/dist/apis/now/types.cjs +0 -0
- package/dist/apis/now/types.d.ts +45 -0
- package/dist/apis/now/types.d.ts.map +1 -0
- package/dist/apis/now/types.mjs +0 -0
- package/dist/apis/people/types.d.ts +7 -2
- package/dist/apis/people/types.d.ts.map +1 -1
- package/dist/apis/permissions/PermissionsApi.cjs +6 -2
- package/dist/apis/permissions/PermissionsApi.cjs.map +1 -1
- package/dist/apis/permissions/PermissionsApi.d.ts +7 -3
- package/dist/apis/permissions/PermissionsApi.d.ts.map +1 -1
- package/dist/apis/permissions/PermissionsApi.mjs +6 -2
- package/dist/apis/permissions/PermissionsApi.mjs.map +1 -1
- package/dist/apis/permissions/types.d.ts +4 -14
- package/dist/apis/permissions/types.d.ts.map +1 -1
- package/dist/apis/personalAccessTokens/PersonalAccessTokensApi.cjs +5 -0
- package/dist/apis/personalAccessTokens/PersonalAccessTokensApi.cjs.map +1 -1
- package/dist/apis/personalAccessTokens/PersonalAccessTokensApi.d.ts +5 -0
- package/dist/apis/personalAccessTokens/PersonalAccessTokensApi.d.ts.map +1 -1
- package/dist/apis/personalAccessTokens/PersonalAccessTokensApi.mjs +5 -0
- package/dist/apis/personalAccessTokens/PersonalAccessTokensApi.mjs.map +1 -1
- package/dist/apis/personalAccessTokens/types.d.ts +23 -4
- package/dist/apis/personalAccessTokens/types.d.ts.map +1 -1
- package/dist/apis/roles/RolesApi.cjs.map +1 -1
- package/dist/apis/roles/RolesApi.d.ts +2 -2
- package/dist/apis/roles/RolesApi.d.ts.map +1 -1
- package/dist/apis/roles/RolesApi.mjs.map +1 -1
- package/dist/apis/roles/types.d.ts +27 -4
- package/dist/apis/roles/types.d.ts.map +1 -1
- package/dist/apis/terms/TermsApi.cjs +4 -1
- package/dist/apis/terms/TermsApi.cjs.map +1 -1
- package/dist/apis/terms/TermsApi.d.ts +4 -1
- package/dist/apis/terms/TermsApi.d.ts.map +1 -1
- package/dist/apis/terms/TermsApi.mjs +4 -1
- package/dist/apis/terms/TermsApi.mjs.map +1 -1
- package/dist/apis/timetable/types.d.ts +12 -7
- package/dist/apis/timetable/types.d.ts.map +1 -1
- package/dist/core/BaseCrudApi.cjs +3 -30
- package/dist/core/BaseCrudApi.cjs.map +1 -1
- package/dist/core/BaseCrudApi.d.ts +4 -24
- package/dist/core/BaseCrudApi.d.ts.map +1 -1
- package/dist/core/BaseCrudApi.mjs +3 -30
- package/dist/core/BaseCrudApi.mjs.map +1 -1
- package/dist/core/BaseReadApi.cjs +44 -0
- package/dist/core/BaseReadApi.cjs.map +1 -0
- package/dist/core/BaseReadApi.d.ts +32 -0
- package/dist/core/BaseReadApi.d.ts.map +1 -0
- package/dist/core/BaseReadApi.mjs +43 -0
- package/dist/core/BaseReadApi.mjs.map +1 -0
- package/dist/core/index.cjs +2 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.mjs +2 -1
- package/dist/index.cjs +2 -0
- package/dist/index.mjs +2 -1
- package/package.json +1 -1
- package/src/apis/authHistory/AuthHistoryApi.test.ts +10 -2
- package/src/apis/authHistory/AuthHistoryApi.ts +13 -39
- package/src/apis/authHistory/types.ts +4 -3
- package/src/apis/customFields/types.ts +21 -25
- package/src/apis/dashboard/DashboardApi.test-d.ts +49 -0
- package/src/apis/dashboard/DashboardApi.test.ts +270 -0
- package/src/apis/dashboard/DashboardApi.ts +146 -0
- package/src/apis/dashboard/index.ts +2 -0
- package/src/apis/dashboard/types.ts +131 -0
- package/src/apis/holiday/types.ts +36 -8
- package/src/apis/index.ts +2 -0
- package/src/apis/now/NowApi.test.ts +125 -0
- package/src/apis/now/NowApi.ts +59 -0
- package/src/apis/now/index.ts +2 -0
- package/src/apis/now/types.ts +47 -0
- package/src/apis/payloads.test-d.ts +39 -0
- package/src/apis/payloads.test.ts +81 -0
- package/src/apis/people/types.ts +7 -2
- package/src/apis/permissions/PermissionsApi.test.ts +93 -0
- package/src/apis/permissions/PermissionsApi.ts +7 -7
- package/src/apis/permissions/types.ts +4 -16
- package/src/apis/personalAccessTokens/PersonalAccessTokensApi.ts +5 -0
- package/src/apis/personalAccessTokens/types.ts +25 -4
- package/src/apis/queryFormat.test.ts +139 -0
- package/src/apis/roles/RolesApi.ts +7 -2
- package/src/apis/roles/types.ts +27 -4
- package/src/apis/terms/TermsApi.ts +4 -1
- package/src/apis/timetable/types.ts +13 -7
- package/src/core/BaseCrudApi.ts +4 -60
- package/src/core/BaseReadApi.ts +70 -0
- package/src/core/index.ts +1 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { BaseApi } from '../../core/BaseApi'
|
|
2
|
+
import type {
|
|
3
|
+
DashboardDepartmentItem,
|
|
4
|
+
DashboardGeneral,
|
|
5
|
+
DashboardGrouped,
|
|
6
|
+
DashboardPeriodItem,
|
|
7
|
+
DashboardQuery,
|
|
8
|
+
DashboardServiceItem,
|
|
9
|
+
DashboardTopicItem,
|
|
10
|
+
DashboardUserItem,
|
|
11
|
+
DashboardWithTotals,
|
|
12
|
+
} from './types'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Service statistics ("Estatísticas de atendimento"). The `/dashboard` routes only require an
|
|
16
|
+
* authenticated user (no permission check). Every route takes the same {@link DashboardQuery}
|
|
17
|
+
* filters; the grouped routes answer one item per day, user, department, topic or connection.
|
|
18
|
+
*/
|
|
19
|
+
export class DashboardApi extends BaseApi {
|
|
20
|
+
public readonly basePath = '/dashboard'
|
|
21
|
+
|
|
22
|
+
/** Message, ticket, contact and time counts of the period (`GET /dashboard/general`). */
|
|
23
|
+
getGeneral(query?: DashboardQuery, headers?: Record<string, string>): Promise<DashboardGeneral> {
|
|
24
|
+
return this.client.get<DashboardGeneral>(
|
|
25
|
+
`${this.basePath}/general${this.toQueryString(query)}`,
|
|
26
|
+
headers,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The counts of each bucket of the period (`GET /dashboard/by-period`): one item per day by default,
|
|
32
|
+
* or per `grouping` (hour, week, month, year). With `withTotals: true` the answer is
|
|
33
|
+
* `{ totals, items }`.
|
|
34
|
+
*/
|
|
35
|
+
getByPeriod(
|
|
36
|
+
query: DashboardQuery & { withTotals: true },
|
|
37
|
+
headers?: Record<string, string>,
|
|
38
|
+
): Promise<DashboardGrouped<DashboardPeriodItem>>
|
|
39
|
+
getByPeriod(
|
|
40
|
+
query?: DashboardQuery & { withTotals?: false },
|
|
41
|
+
headers?: Record<string, string>,
|
|
42
|
+
): Promise<DashboardPeriodItem[]>
|
|
43
|
+
/** When `withTotals` is only known at runtime (e.g. a `boolean` variable). */
|
|
44
|
+
getByPeriod(
|
|
45
|
+
query?: DashboardQuery & DashboardWithTotals,
|
|
46
|
+
headers?: Record<string, string>,
|
|
47
|
+
): Promise<DashboardPeriodItem[] | DashboardGrouped<DashboardPeriodItem>>
|
|
48
|
+
getByPeriod(
|
|
49
|
+
query?: DashboardQuery & DashboardWithTotals,
|
|
50
|
+
headers?: Record<string, string>,
|
|
51
|
+
): Promise<DashboardPeriodItem[] | DashboardGrouped<DashboardPeriodItem>> {
|
|
52
|
+
return this.getGrouped<DashboardPeriodItem>('by-period', query, headers)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The counts of each user of the period (`GET /dashboard/by-user`). With `withTotals: true` the
|
|
57
|
+
* answer is `{ totals, items }`.
|
|
58
|
+
*/
|
|
59
|
+
getByUser(
|
|
60
|
+
query: DashboardQuery & { withTotals: true },
|
|
61
|
+
headers?: Record<string, string>,
|
|
62
|
+
): Promise<DashboardGrouped<DashboardUserItem>>
|
|
63
|
+
getByUser(
|
|
64
|
+
query?: DashboardQuery & { withTotals?: false },
|
|
65
|
+
headers?: Record<string, string>,
|
|
66
|
+
): Promise<DashboardUserItem[]>
|
|
67
|
+
/** When `withTotals` is only known at runtime (e.g. a `boolean` variable). */
|
|
68
|
+
getByUser(
|
|
69
|
+
query?: DashboardQuery & DashboardWithTotals,
|
|
70
|
+
headers?: Record<string, string>,
|
|
71
|
+
): Promise<DashboardUserItem[] | DashboardGrouped<DashboardUserItem>>
|
|
72
|
+
getByUser(
|
|
73
|
+
query?: DashboardQuery & DashboardWithTotals,
|
|
74
|
+
headers?: Record<string, string>,
|
|
75
|
+
): Promise<DashboardUserItem[] | DashboardGrouped<DashboardUserItem>> {
|
|
76
|
+
return this.getGrouped<DashboardUserItem>('by-user', query, headers)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The counts of each department of the period (`GET /dashboard/by-department`). With
|
|
81
|
+
* `withTotals: true` the answer is `{ totals, items }`.
|
|
82
|
+
*/
|
|
83
|
+
getByDepartment(
|
|
84
|
+
query: DashboardQuery & { withTotals: true },
|
|
85
|
+
headers?: Record<string, string>,
|
|
86
|
+
): Promise<DashboardGrouped<DashboardDepartmentItem>>
|
|
87
|
+
getByDepartment(
|
|
88
|
+
query?: DashboardQuery & { withTotals?: false },
|
|
89
|
+
headers?: Record<string, string>,
|
|
90
|
+
): Promise<DashboardDepartmentItem[]>
|
|
91
|
+
/** When `withTotals` is only known at runtime (e.g. a `boolean` variable). */
|
|
92
|
+
getByDepartment(
|
|
93
|
+
query?: DashboardQuery & DashboardWithTotals,
|
|
94
|
+
headers?: Record<string, string>,
|
|
95
|
+
): Promise<DashboardDepartmentItem[] | DashboardGrouped<DashboardDepartmentItem>>
|
|
96
|
+
getByDepartment(
|
|
97
|
+
query?: DashboardQuery & DashboardWithTotals,
|
|
98
|
+
headers?: Record<string, string>,
|
|
99
|
+
): Promise<DashboardDepartmentItem[] | DashboardGrouped<DashboardDepartmentItem>> {
|
|
100
|
+
return this.getGrouped<DashboardDepartmentItem>('by-department', query, headers)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The counts of each ticket topic of the period (`GET /dashboard/by-topic`). Does not take
|
|
105
|
+
* `withTotals` (the backend ignores it).
|
|
106
|
+
*/
|
|
107
|
+
getByTopic(
|
|
108
|
+
query?: DashboardQuery,
|
|
109
|
+
headers?: Record<string, string>,
|
|
110
|
+
): Promise<DashboardTopicItem[]> {
|
|
111
|
+
return this.client.get<DashboardTopicItem[]>(
|
|
112
|
+
`${this.basePath}/by-topic${this.toQueryString(query)}`,
|
|
113
|
+
headers,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The counts of each connection of the period (`GET /dashboard/by-service`). Does not take
|
|
119
|
+
* `withTotals` (the backend ignores it).
|
|
120
|
+
*/
|
|
121
|
+
getByService(
|
|
122
|
+
query?: DashboardQuery,
|
|
123
|
+
headers?: Record<string, string>,
|
|
124
|
+
): Promise<DashboardServiceItem[]> {
|
|
125
|
+
return this.client.get<DashboardServiceItem[]>(
|
|
126
|
+
`${this.basePath}/by-service${this.toQueryString(query)}`,
|
|
127
|
+
headers,
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* `withTotals` is only sent when `true`: the backend reads it with a truthiness check, so in the
|
|
133
|
+
* `qs` format a serialized `withTotals=false` (the string `'false'`) would wrap the answer anyway.
|
|
134
|
+
*/
|
|
135
|
+
private getGrouped<TItem>(
|
|
136
|
+
route: string,
|
|
137
|
+
query?: DashboardQuery & DashboardWithTotals,
|
|
138
|
+
headers?: Record<string, string>,
|
|
139
|
+
): Promise<TItem[] | DashboardGrouped<TItem>> {
|
|
140
|
+
const { withTotals, ...filters } = query ?? {}
|
|
141
|
+
return this.client.get<TItem[] | DashboardGrouped<TItem>>(
|
|
142
|
+
`${this.basePath}/${route}${this.toQueryString(withTotals ? { ...filters, withTotals: true } : filters)}`,
|
|
143
|
+
headers,
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Granularity of the by-period items (`grouping`). Default: `daily`. Whatever the granularity, the
|
|
3
|
+
* `name` of each item is its day (`dd/MM/yyyy`), so with `hourly` the items of one day share the same
|
|
4
|
+
* `name` (they come in chronological order).
|
|
5
|
+
*/
|
|
6
|
+
export type DashboardGrouping = 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Filters of the service statistics (`/dashboard`). Every field is optional; the period defaults to
|
|
10
|
+
* the last 7 days.
|
|
11
|
+
*/
|
|
12
|
+
export type DashboardQuery = {
|
|
13
|
+
/** ISO date-time. Default: 7 days ago, 00:00. */
|
|
14
|
+
startPeriod?: string
|
|
15
|
+
/** ISO date-time. Default: the end of the `startPeriod` day (or of today). */
|
|
16
|
+
endPeriod?: string
|
|
17
|
+
/** Which ticket date the period filters: opening (default) or closing. */
|
|
18
|
+
periodType?: 'openDate' | 'closeDate'
|
|
19
|
+
/** Ticket status (`closed`, not `close`). */
|
|
20
|
+
status?: 'all' | 'open' | 'closed'
|
|
21
|
+
/**
|
|
22
|
+
* A department id, a list of ids, or `all`. Only applied together with `departmentParticipation`:
|
|
23
|
+
* without it the backend ignores `departmentId` and answers the totals of every department.
|
|
24
|
+
*/
|
|
25
|
+
departmentId?: string | string[]
|
|
26
|
+
/**
|
|
27
|
+
* Whether the department must be the ticket's last one or any one it went through. Required for
|
|
28
|
+
* `departmentId` to filter; sent WITHOUT `departmentId`, it filters the tickets with no department.
|
|
29
|
+
*/
|
|
30
|
+
departmentParticipation?: 'last' | 'middle'
|
|
31
|
+
/**
|
|
32
|
+
* A user id, a list of ids, or `all`. Only applied together with `userParticipation`: without it
|
|
33
|
+
* the backend ignores `userId` and answers the totals of every user.
|
|
34
|
+
*/
|
|
35
|
+
userId?: string | string[]
|
|
36
|
+
/**
|
|
37
|
+
* Whether the user must be the ticket's last one or any one it went through. Required for `userId`
|
|
38
|
+
* to filter; sent WITHOUT `userId`, it filters the tickets with no user.
|
|
39
|
+
*/
|
|
40
|
+
userParticipation?: 'last' | 'middle'
|
|
41
|
+
userStatus?: 'active' | 'archived' | 'all'
|
|
42
|
+
serviceId?: string | string[]
|
|
43
|
+
tagId?: string | string[]
|
|
44
|
+
ticketTopicId?: string | string[]
|
|
45
|
+
/**
|
|
46
|
+
* Granularity of the `getByPeriod` items (the other routes ignore it). Default: `daily`. See
|
|
47
|
+
* {@link DashboardGrouping} for the `name` of the items.
|
|
48
|
+
*/
|
|
49
|
+
grouping?: DashboardGrouping
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* `withTotals: true` makes `getByPeriod`, `getByUser` and `getByDepartment` answer `{ totals, items }`,
|
|
54
|
+
* where `totals` are the `getGeneral` counts of the same filters (`getByTopic` and `getByService` do
|
|
55
|
+
* not take it). Kept out of `DashboardQuery` so a query without it keeps the plain array overload.
|
|
56
|
+
* Only sent to the backend when `true`.
|
|
57
|
+
*/
|
|
58
|
+
export type DashboardWithTotals = { withTotals?: boolean }
|
|
59
|
+
|
|
60
|
+
/** `GET /dashboard/general` — counts of the period. Times are in seconds. */
|
|
61
|
+
export type DashboardGeneral = {
|
|
62
|
+
sentMessagesCount: number
|
|
63
|
+
receivedMessagesCount: number
|
|
64
|
+
totalMessagesCount: number
|
|
65
|
+
openedTicketsCount: number
|
|
66
|
+
closedTicketsCount: number
|
|
67
|
+
totalTicketsCount: number
|
|
68
|
+
waitingTime: number
|
|
69
|
+
waitingTimeAfterBot: number
|
|
70
|
+
waitingTimeAvg: number
|
|
71
|
+
ticketTime: number
|
|
72
|
+
contactsCount: number
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Counts shared by the grouped routes (`by-period`, `by-user`, `by-department`, `by-topic`,
|
|
77
|
+
* `by-service`). The backend builds each item from the rows it found, so a count is ABSENT (not 0)
|
|
78
|
+
* when the group has no row for it: read them with a default. Times are in seconds.
|
|
79
|
+
*/
|
|
80
|
+
export type DashboardGroupCounts = {
|
|
81
|
+
sentMessagesCount?: number
|
|
82
|
+
receivedMessagesCount?: number
|
|
83
|
+
openedTicketsCount?: number
|
|
84
|
+
closedTicketsCount?: number
|
|
85
|
+
waitingTime?: number
|
|
86
|
+
ticketTime?: number
|
|
87
|
+
contactsCount?: number
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** `GET /dashboard/by-period` — the counts of one day of the period (`name` as `dd/MM/yyyy`). */
|
|
91
|
+
export type DashboardPeriodItem = DashboardGroupCounts & {
|
|
92
|
+
name: string
|
|
93
|
+
totalMessagesCount?: number
|
|
94
|
+
totalTicketsCount?: number
|
|
95
|
+
waitingTimeAfterBot?: number
|
|
96
|
+
waitingTimeAvg?: number
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** `GET /dashboard/by-user` — the counts of one user (`name` is the user's name). */
|
|
100
|
+
export type DashboardUserItem = DashboardGroupCounts & {
|
|
101
|
+
name: string
|
|
102
|
+
ticketTopicsCount?: number
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** `GET /dashboard/by-department` — the counts of one department (`name` is its name). */
|
|
106
|
+
export type DashboardDepartmentItem = DashboardGroupCounts & {
|
|
107
|
+
name: string
|
|
108
|
+
ticketTopicsCount?: number
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** `GET /dashboard/by-topic` — the counts of one ticket topic (`name` is the topic's name). */
|
|
112
|
+
export type DashboardTopicItem = DashboardGroupCounts & {
|
|
113
|
+
name: string
|
|
114
|
+
waitingTimeAfterBot?: number
|
|
115
|
+
waitingTimeAvg?: number
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** `GET /dashboard/by-service` — the counts of one connection (`name` is the connection's name). */
|
|
119
|
+
export type DashboardServiceItem = DashboardGroupCounts & {
|
|
120
|
+
name: string
|
|
121
|
+
waitingTimeAfterBot?: number
|
|
122
|
+
waitingTimeAvg?: number
|
|
123
|
+
ticketTopicsCount?: number
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Answer of `getByPeriod`, `getByUser` and `getByDepartment` with `withTotals: true`. */
|
|
127
|
+
export type DashboardGrouped<TItem> = {
|
|
128
|
+
/** The `getGeneral` counts of the same filters. */
|
|
129
|
+
totals: DashboardGeneral
|
|
130
|
+
items: TItem[]
|
|
131
|
+
}
|
|
@@ -1,25 +1,53 @@
|
|
|
1
|
+
import type { Account } from '../accounts/types'
|
|
2
|
+
import type { File } from '../files/types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A holiday: the account's services answer `message` during the period `from`–`to`.
|
|
6
|
+
* Holidays are soft-deleted, but `deletedAt` is never returned.
|
|
7
|
+
*/
|
|
1
8
|
export type Holiday = {
|
|
2
9
|
id: string
|
|
3
10
|
name: string
|
|
4
11
|
from: string
|
|
5
12
|
to: string
|
|
6
13
|
message: string
|
|
14
|
+
/** Whether the period is over. */
|
|
15
|
+
expired: boolean
|
|
16
|
+
/** Number of attached files (computed on the listing, derived from `files` on the detail). */
|
|
17
|
+
filesCount: number
|
|
18
|
+
/**
|
|
19
|
+
* Attached files — always an array. Only loaded on `getById` (or when listed with
|
|
20
|
+
* `include: ['files']`): `getMany` answers `[]` even when the holiday has files, so use
|
|
21
|
+
* `filesCount` there.
|
|
22
|
+
*/
|
|
23
|
+
files: File[]
|
|
7
24
|
accountId: string
|
|
8
25
|
createdAt: string
|
|
9
|
-
|
|
10
|
-
|
|
26
|
+
/** Virtual attribute of the model — may be absent. */
|
|
27
|
+
updatedAt?: string | null
|
|
28
|
+
// relationships
|
|
29
|
+
account?: Account
|
|
11
30
|
}
|
|
12
31
|
|
|
32
|
+
export type HolidayRelationships = 'account' | 'files'
|
|
33
|
+
|
|
34
|
+
/** A file attached by id (up to 10 per holiday). */
|
|
35
|
+
export type HolidayFileRef = { id: string }
|
|
36
|
+
|
|
13
37
|
export type CreateHolidayPayload = {
|
|
38
|
+
/** Up to 255 characters. */
|
|
14
39
|
name: string
|
|
40
|
+
/** Date string. A period overlapping another holiday answers 402 "Holiday time already exists.". */
|
|
15
41
|
from: string
|
|
42
|
+
/** Date string. */
|
|
16
43
|
to: string
|
|
44
|
+
/** Up to 1024 characters. */
|
|
17
45
|
message: string
|
|
46
|
+
files?: HolidayFileRef[]
|
|
18
47
|
}
|
|
19
48
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
49
|
+
/**
|
|
50
|
+
* The backend requires name, from, to and message on every update (same validator as the create).
|
|
51
|
+
* Omitting `files` keeps the current attachments.
|
|
52
|
+
*/
|
|
53
|
+
export type UpdateHolidayPayload = CreateHolidayPayload
|
package/src/apis/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from './contactBlockListsControls'
|
|
|
15
15
|
export * from './contacts'
|
|
16
16
|
export * from './creditMovements'
|
|
17
17
|
export * from './customFields'
|
|
18
|
+
export * from './dashboard'
|
|
18
19
|
export * from './departments'
|
|
19
20
|
export * from './distribution'
|
|
20
21
|
export * from './files'
|
|
@@ -29,6 +30,7 @@ export * from './knowledgeBaseItem'
|
|
|
29
30
|
export * from './me'
|
|
30
31
|
export * from './messages'
|
|
31
32
|
export * from './notifications'
|
|
33
|
+
export * from './now'
|
|
32
34
|
export * from './organizations'
|
|
33
35
|
export * from './people'
|
|
34
36
|
export * from './permissions'
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { NowApi } from './NowApi'
|
|
3
|
+
import type { ApiClient } from '../../core/ApiClient'
|
|
4
|
+
import type {
|
|
5
|
+
NowAttendanceResume,
|
|
6
|
+
NowDepartmentResume,
|
|
7
|
+
NowResponse,
|
|
8
|
+
NowResumeTotals,
|
|
9
|
+
} from './types'
|
|
10
|
+
|
|
11
|
+
// ── Mock client factory ──────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function createMockClient(): ApiClient {
|
|
14
|
+
return {
|
|
15
|
+
setAccessToken: vi.fn().mockReturnThis(),
|
|
16
|
+
request: vi.fn(),
|
|
17
|
+
get: vi.fn(),
|
|
18
|
+
post: vi.fn(),
|
|
19
|
+
put: vi.fn(),
|
|
20
|
+
patch: vi.fn(),
|
|
21
|
+
delete: vi.fn(),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── Fixtures ─────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
const resume: NowResponse<NowResumeTotals> = {
|
|
28
|
+
totals: {
|
|
29
|
+
openTickets: 3,
|
|
30
|
+
queueTickets: 1,
|
|
31
|
+
usersWithAttendance: 2,
|
|
32
|
+
usersWithoutAttendance: 1,
|
|
33
|
+
averageOpenTicketsTime: '125.5',
|
|
34
|
+
averageOpenTicketsWaitTime: 0,
|
|
35
|
+
onlineUsersCount: 2,
|
|
36
|
+
offlineUsersCount: 4,
|
|
37
|
+
absentUsersCount: 0,
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
const attendance: NowResponse<NowAttendanceResume[]> = {
|
|
41
|
+
totals: [{ name: 'Ana', departments: ['Suporte'], status: 'online', ticketsOpenCount: 2 }],
|
|
42
|
+
}
|
|
43
|
+
const departments: NowResponse<NowDepartmentResume[]> = {
|
|
44
|
+
totals: [
|
|
45
|
+
{
|
|
46
|
+
name: 'Suporte',
|
|
47
|
+
onlineUserCount: 1,
|
|
48
|
+
offlineUserCount: 0,
|
|
49
|
+
absentUserCount: 0,
|
|
50
|
+
onQueueTicketCount: 1,
|
|
51
|
+
openTicketCount: 2,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const jsonQuery = (query: object) => `?query=${encodeURIComponent(JSON.stringify(query))}`
|
|
57
|
+
|
|
58
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
describe('NowApi', () => {
|
|
61
|
+
let client: ReturnType<typeof createMockClient>
|
|
62
|
+
let api: NowApi
|
|
63
|
+
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
client = createMockClient()
|
|
66
|
+
api = new NowApi(client)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('has no CRUD routes', () => {
|
|
70
|
+
expect(api.basePath).toBe('/now')
|
|
71
|
+
expect(api).not.toHaveProperty('getMany')
|
|
72
|
+
expect(api).not.toHaveProperty('create')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('getResume', () => {
|
|
76
|
+
it('calls client.get with /now/resume and no query string', async () => {
|
|
77
|
+
vi.mocked(client.get).mockResolvedValue(resume)
|
|
78
|
+
const result = await api.getResume()
|
|
79
|
+
expect(client.get).toHaveBeenCalledWith('/now/resume', undefined)
|
|
80
|
+
expect(result).toEqual(resume)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('sends the department/user filters in the query string', async () => {
|
|
84
|
+
vi.mocked(client.get).mockResolvedValue(resume)
|
|
85
|
+
await api.getResume({ departmentId: ['d1', 'd2'], userId: 'u1' })
|
|
86
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
87
|
+
`/now/resume${jsonQuery({ departmentId: ['d1', 'd2'], userId: 'u1' })}`,
|
|
88
|
+
undefined,
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('forwards custom headers', async () => {
|
|
93
|
+
vi.mocked(client.get).mockResolvedValue(resume)
|
|
94
|
+
const headers = { 'X-Custom': 'h' }
|
|
95
|
+
await api.getResume(undefined, headers)
|
|
96
|
+
expect(client.get).toHaveBeenCalledWith('/now/resume', headers)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe('getAttendanceResume', () => {
|
|
101
|
+
it('calls client.get with /now/attendance-resume', async () => {
|
|
102
|
+
vi.mocked(client.get).mockResolvedValue(attendance)
|
|
103
|
+
const result = await api.getAttendanceResume({ departmentId: 'd1' })
|
|
104
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
105
|
+
`/now/attendance-resume${jsonQuery({ departmentId: 'd1' })}`,
|
|
106
|
+
undefined,
|
|
107
|
+
)
|
|
108
|
+
expect(result).toEqual(attendance)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('getDepartmentsResume', () => {
|
|
113
|
+
it('calls client.get with /now/departments-resume', async () => {
|
|
114
|
+
vi.mocked(client.get).mockResolvedValue(departments)
|
|
115
|
+
const result = await api.getDepartmentsResume()
|
|
116
|
+
expect(client.get).toHaveBeenCalledWith('/now/departments-resume', undefined)
|
|
117
|
+
expect(result).toEqual(departments)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('propagates errors from client.get', async () => {
|
|
121
|
+
vi.mocked(client.get).mockRejectedValue(new Error('Forbidden'))
|
|
122
|
+
await expect(api.getDepartmentsResume()).rejects.toThrow('Forbidden')
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { BaseApi } from '../../core/BaseApi'
|
|
2
|
+
import type {
|
|
3
|
+
NowAttendanceResume,
|
|
4
|
+
NowDepartmentResume,
|
|
5
|
+
NowQuery,
|
|
6
|
+
NowResponse,
|
|
7
|
+
NowResumeTotals,
|
|
8
|
+
} from './types'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The "Agora" (now) overview: live totals of tickets and attendants. Every route requires
|
|
12
|
+
* `overview.now.view.all` or `overview.now.view.departments` (the latter is scoped to the user's
|
|
13
|
+
* departments).
|
|
14
|
+
*/
|
|
15
|
+
export class NowApi extends BaseApi {
|
|
16
|
+
public readonly basePath = '/now'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Account totals: open/queued tickets, attendants with/without attendance and online/offline/absent users.
|
|
20
|
+
* @permissions overview.now.view.all | overview.now.view.departments
|
|
21
|
+
*/
|
|
22
|
+
getResume(
|
|
23
|
+
query?: NowQuery,
|
|
24
|
+
headers?: Record<string, string>,
|
|
25
|
+
): Promise<NowResponse<NowResumeTotals>> {
|
|
26
|
+
return this.client.get<NowResponse<NowResumeTotals>>(
|
|
27
|
+
`${this.basePath}/resume${this.toQueryString(query)}`,
|
|
28
|
+
headers,
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Per-attendant totals (name, departments, status and open tickets).
|
|
34
|
+
* @permissions overview.now.view.all | overview.now.view.departments
|
|
35
|
+
*/
|
|
36
|
+
getAttendanceResume(
|
|
37
|
+
query?: NowQuery,
|
|
38
|
+
headers?: Record<string, string>,
|
|
39
|
+
): Promise<NowResponse<NowAttendanceResume[]>> {
|
|
40
|
+
return this.client.get<NowResponse<NowAttendanceResume[]>>(
|
|
41
|
+
`${this.basePath}/attendance-resume${this.toQueryString(query)}`,
|
|
42
|
+
headers,
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Per-department totals (online/offline/absent users, queued and open tickets).
|
|
48
|
+
* @permissions overview.now.view.all | overview.now.view.departments
|
|
49
|
+
*/
|
|
50
|
+
getDepartmentsResume(
|
|
51
|
+
query?: NowQuery,
|
|
52
|
+
headers?: Record<string, string>,
|
|
53
|
+
): Promise<NowResponse<NowDepartmentResume[]>> {
|
|
54
|
+
return this.client.get<NowResponse<NowDepartmentResume[]>>(
|
|
55
|
+
`${this.basePath}/departments-resume${this.toQueryString(query)}`,
|
|
56
|
+
headers,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { User } from '../users/types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Filters of the "Agora" (now) overview. Each accepts one id, an array or a comma-separated list.
|
|
5
|
+
* A user with only `overview.now.view.departments` gets 403 for departments/users outside their
|
|
6
|
+
* departments.
|
|
7
|
+
*/
|
|
8
|
+
export type NowQuery = {
|
|
9
|
+
departmentId?: string | string[]
|
|
10
|
+
userId?: string | string[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Every `/now` route answers its data under `totals`. */
|
|
14
|
+
export type NowResponse<T> = { totals: T }
|
|
15
|
+
|
|
16
|
+
export type NowResumeTotals = {
|
|
17
|
+
openTickets: number
|
|
18
|
+
queueTickets: number
|
|
19
|
+
usersWithAttendance: number
|
|
20
|
+
usersWithoutAttendance: number
|
|
21
|
+
/** Postgres `avg()` — a numeric string, or `0` when there is no data. */
|
|
22
|
+
averageOpenTicketsTime: number | string
|
|
23
|
+
/** Postgres `avg()` — a numeric string, or `0` when there is no data. */
|
|
24
|
+
averageOpenTicketsWaitTime: number | string
|
|
25
|
+
onlineUsersCount: number
|
|
26
|
+
offlineUsersCount: number
|
|
27
|
+
absentUsersCount: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** One entry per department. */
|
|
31
|
+
export type NowDepartmentResume = {
|
|
32
|
+
name: string
|
|
33
|
+
onlineUserCount: number
|
|
34
|
+
offlineUserCount: number
|
|
35
|
+
absentUserCount: number
|
|
36
|
+
onQueueTicketCount: number
|
|
37
|
+
openTicketCount: number
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** One entry per attendant (user). */
|
|
41
|
+
export type NowAttendanceResume = {
|
|
42
|
+
name?: string
|
|
43
|
+
/** Department names — `0` when the attendant has no department. */
|
|
44
|
+
departments: string[] | 0
|
|
45
|
+
status?: User['status']
|
|
46
|
+
ticketsOpenCount: number
|
|
47
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { expectError } from 'tsd'
|
|
2
|
+
import type { ApiClient } from '../core/ApiClient'
|
|
3
|
+
import { PersonalAccessTokensApi } from './personalAccessTokens/PersonalAccessTokensApi'
|
|
4
|
+
import { RolesApi } from './roles/RolesApi'
|
|
5
|
+
import { TimetableApi } from './timetable/TimetableApi'
|
|
6
|
+
|
|
7
|
+
declare const client: ApiClient
|
|
8
|
+
|
|
9
|
+
// ── roles: the backend drops `permissionIds` in silence ─────────────
|
|
10
|
+
|
|
11
|
+
const roles = new RolesApi(client)
|
|
12
|
+
roles.create({ displayName: 'Supervisor', permissions: ['p-1', { id: 'p-2' }] })
|
|
13
|
+
expectError(roles.create({ displayName: 'Supervisor', permissionIds: ['p-1'] }))
|
|
14
|
+
expectError(roles.updateById('r-1', { permissions: ['p-1'] }))
|
|
15
|
+
|
|
16
|
+
// ── timetable: the old workPlan shape answers 400 ───────────────────
|
|
17
|
+
|
|
18
|
+
const timetable = new TimetableApi(client)
|
|
19
|
+
timetable.create({
|
|
20
|
+
name: 'Business hours',
|
|
21
|
+
previousTimeToNotification: 15,
|
|
22
|
+
workPlan: [{ start: '08:00', end: '18:00', weekDays: ['mon'] }],
|
|
23
|
+
})
|
|
24
|
+
expectError(
|
|
25
|
+
timetable.create({
|
|
26
|
+
name: 'Business hours',
|
|
27
|
+
previousTimeToNotification: 15,
|
|
28
|
+
workPlan: [{ day: 1, start: '08:00', end: '18:00', active: true }],
|
|
29
|
+
}),
|
|
30
|
+
)
|
|
31
|
+
expectError(timetable.updateById('t-1', { name: 'Business hours' }))
|
|
32
|
+
|
|
33
|
+
// ── personal access tokens: `expiresIn` is the only lifetime accepted ─
|
|
34
|
+
|
|
35
|
+
const tokens = new PersonalAccessTokensApi(client)
|
|
36
|
+
tokens.create({ name: 'CI', expiresIn: '90d' })
|
|
37
|
+
expectError(tokens.create({ name: 'CI', accessTokenExpiresAt: '2030-01-01T00:00:00Z' }))
|
|
38
|
+
expectError(tokens.create({ name: 'CI', expiresIn: '7d' }))
|
|
39
|
+
expectError(tokens.updateById('t-1', { scope: '*' }))
|