@rh-support/troubleshoot 2.2.119 → 2.2.121

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.
Files changed (21) hide show
  1. package/lib/esm/components/CaseEditView/CaseDetailsTabs.d.ts.map +1 -1
  2. package/lib/esm/components/CaseEditView/CaseDetailsTabs.js +10 -0
  3. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/CaseContactPhoneNumber.d.ts.map +1 -1
  4. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/CaseContactPhoneNumber.js +2 -1
  5. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CaseExternalTrackerUpdate.js +1 -1
  6. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/CaseHistory.css +0 -0
  7. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/CaseHistory.d.ts +4 -2
  8. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/CaseHistory.d.ts.map +1 -1
  9. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/CaseHistory.js +7 -3
  10. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.css +257 -0
  11. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.d.ts +7 -0
  12. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.d.ts.map +1 -0
  13. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.js +236 -0
  14. package/lib/esm/components/ProductSelector/NewProductDropdownSelector.js +1 -1
  15. package/lib/esm/components/SessionRestore/SessionRestore.d.ts.map +1 -1
  16. package/lib/esm/components/SessionRestore/SessionRestore.js +0 -23
  17. package/lib/esm/constants/caseDetailsConstants.d.ts +2 -1
  18. package/lib/esm/constants/caseDetailsConstants.d.ts.map +1 -1
  19. package/lib/esm/constants/caseDetailsConstants.js +1 -0
  20. package/lib/esm/scss/_pf-overrides.scss +4 -0
  21. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"CaseDetailsTabs.d.ts","sourceRoot":"","sources":["../../../../src/components/CaseEditView/CaseDetailsTabs.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,EAAE,mBAAmB,EAAa,MAAM,kBAAkB,CAAC;AAwBlE,UAAU,MAAM;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,mBAAmB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,KAAK,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;CACnD;AACD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,qBAqN5C"}
1
+ {"version":3,"file":"CaseDetailsTabs.d.ts","sourceRoot":"","sources":["../../../../src/components/CaseEditView/CaseDetailsTabs.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,EAAE,mBAAmB,EAAa,MAAM,kBAAkB,CAAC;AAyBlE,UAAU,MAAM;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,mBAAmB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,KAAK,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;CACnD;AACD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,qBAgO5C"}
@@ -11,6 +11,7 @@ import { useCaseSelector } from '../../context/CaseContext';
11
11
  import { useCaseDetailsPageStateContext } from '../../context/CaseDetailsPageContext';
12
12
  import { PDFContext } from './PDFContainer';
13
13
  import CaseDetailsManagement from './Tabs/CaseDetails/CaseDetailsManagement';
14
+ import CaseHistory from './Tabs/CaseHistory/CaseHistory';
14
15
  const RelatedTasks = React.lazy(() => import(/* webpackChunkName: 'CaseBugzilla' */ './Tabs/RelatedTasks/RelatedTasks'));
15
16
  const CaseActionPlan = React.lazy(() => import(/* webpackChunkName: 'CaseActionPlan' */ './Tabs/CaseActionPlan/CaseActionPlan'));
16
17
  const CasePrivateNotes = React.lazy(() => import(/* webpackChunkName: 'CasePrivateNotes' */ './Tabs/CasePrivateNotes/CasePrivateNotes'));
@@ -45,6 +46,7 @@ export function CaseDetailsTabs(props) {
45
46
  const actionPlanTabRef = useRef(null);
46
47
  const escalationTabRef = useRef(null);
47
48
  const relatedTasksRef = useRef(null);
49
+ const caseHistoryTabRef = useRef(null);
48
50
  const tabsToRender = [];
49
51
  const handleTabClick = (event, tabIndex) => {
50
52
  setActiveTabKey(tabIndex);
@@ -102,6 +104,14 @@ export function CaseDetailsTabs(props) {
102
104
  routePath: 'escalation',
103
105
  component: (React.createElement(RMEEscalationList, { escalations: caseEscalations.data, caseNumber: caseNumber, caseStatus: status, accountNumber: loggedInUserRights.data.getAccountNumber(), isInternal: loggedInUserRights.data.isInternal() })),
104
106
  });
107
+ tabsToRender.push({
108
+ 'data-tracking-id': 'case-history-tab',
109
+ title: CaseDetailsTabsEnum.HISTORY,
110
+ key: 'history',
111
+ routePath: 'history',
112
+ ref: caseHistoryTabRef,
113
+ component: React.createElement(CaseHistory, { caseNumber: caseNumber }),
114
+ });
105
115
  const getActiveTabKey = () => {
106
116
  if (!activeTab)
107
117
  return activeTabKey;
@@ -1 +1 @@
1
- {"version":3,"file":"CaseContactPhoneNumber.d.ts","sourceRoot":"","sources":["../../../../../../src/components/CaseEditView/Tabs/CaseDetails/CaseContactPhoneNumber.tsx"],"names":[],"mappings":"AASA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAgB/D,wBAAgB,sBAAsB,sBAiTrC"}
1
+ {"version":3,"file":"CaseContactPhoneNumber.d.ts","sourceRoot":"","sources":["../../../../../../src/components/CaseEditView/Tabs/CaseDetails/CaseContactPhoneNumber.tsx"],"names":[],"mappings":"AASA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAgB/D,wBAAgB,sBAAsB,sBAkTrC"}
@@ -111,7 +111,8 @@ export function CaseContactPhoneNumber() {
111
111
  contactSSOName === loggedInUser.data.ssoUsername &&
112
112
  suppliedPhoneNumberVerified === 'Deferred' &&
113
113
  !isCaseOwnerUpdating &&
114
- !isCasePhoneUpdating;
114
+ !isCasePhoneUpdating &&
115
+ !isEmpty(localFullPhoneState);
115
116
  const isPhoneNumberValid = (localFullPhoneState === null || localFullPhoneState === void 0 ? void 0 : localFullPhoneState.length) > PHONE_LIMIT
116
117
  ? ValidatedOptions.error
117
118
  : isPhoneNeedsReview
@@ -17,7 +17,7 @@ const CaseExternalTrackerUpdate = React.forwardRef((props, ref) => {
17
17
  const canSeePrivateComments = ability.can(resourceActions.PATCH, resources.CASE_COMMENTS, CaseDiscussionFields.VIEW_PRIVATE_COMMENT);
18
18
  const sanitize = (markdown) => {
19
19
  const htmlString = markdownToHTML(markdown, { openLinksInNewTab: true });
20
- return { __html: DOMPurify.sanitize(htmlString) };
20
+ return { __html: DOMPurify.sanitize(htmlString, { FORBID_TAGS: ['style'] }) };
21
21
  };
22
22
  const onJumpToComment = (externalTrackerId) => () => props.showJumpToComment && props.onJumpToComment && props.onJumpToComment(externalTrackerId);
23
23
  // To check and set if a comment is private or not
@@ -1,6 +1,8 @@
1
+ import './CaseHistory.css';
1
2
  import React from 'react';
2
3
  interface IProps {
4
+ caseNumber: string;
3
5
  }
4
- export default function CaseHistory(props: IProps): React.JSX.Element;
5
- export {};
6
+ declare const CaseHistory: (props: IProps) => React.JSX.Element;
7
+ export default CaseHistory;
6
8
  //# sourceMappingURL=CaseHistory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CaseHistory.d.ts","sourceRoot":"","sources":["../../../../../../src/components/CaseEditView/Tabs/CaseHistory/CaseHistory.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,UAAU,MAAM;CAAG;AAEnB,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,KAAK,EAAE,MAAM,qBAEhD"}
1
+ {"version":3,"file":"CaseHistory.d.ts","sourceRoot":"","sources":["../../../../../../src/components/CaseEditView/Tabs/CaseHistory/CaseHistory.tsx"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAC;AAE3B,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,UAAU,MAAM;IACZ,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,QAAA,MAAM,WAAW,UAAW,MAAM,sBAMjC,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -1,4 +1,8 @@
1
+ import './CaseHistory.css';
1
2
  import React from 'react';
2
- export default function CaseHistory(props) {
3
- return React.createElement(React.Fragment, null, "CaseHistory");
4
- }
3
+ import Timeline from './Timeline';
4
+ const CaseHistory = (props) => {
5
+ return (React.createElement("div", null,
6
+ React.createElement(Timeline, { caseNumber: props.caseNumber })));
7
+ };
8
+ export default CaseHistory;
@@ -0,0 +1,257 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ .timeline {
6
+ position: relative;
7
+ max-width: 100%;
8
+ margin: 0 auto;
9
+ padding: 0 20px;
10
+ }
11
+
12
+ .timeline::after {
13
+ content: '';
14
+ position: absolute;
15
+ width: 1px;
16
+ background-color: #6a6e73;
17
+ top: 30px;
18
+
19
+ /* This offset is needed to keep it responsive */
20
+ --timeline-bottom-offset: -30px;
21
+
22
+ /* This value is calculated in timeline.tsx */
23
+ bottom: calc(var(--timeline-bottom) + var(--timeline-bottom-offset, 18.35%));
24
+ left: calc(13% + 13px);
25
+ transform: translateX(-50%);
26
+ }
27
+
28
+ .timeline-loading-spinner {
29
+ flex: 1;
30
+ display: flex;
31
+ justify-content: center;
32
+ align-items: center;
33
+ }
34
+
35
+ .timeline-container::after {
36
+ content: '';
37
+ position: absolute;
38
+ width: 25px;
39
+ height: 25px;
40
+ right: -17px;
41
+ background-color: white;
42
+ border: 4px solid #ff9f55;
43
+ top: 15px;
44
+ border-radius: 50%;
45
+ z-index: 1;
46
+ }
47
+
48
+ .timeline-node {
49
+ display: flex;
50
+ justify-content: space-between;
51
+ align-items: flex-start;
52
+ position: relative;
53
+ margin-bottom: 20px;
54
+ min-height: 80px;
55
+ }
56
+
57
+ .timeline-avatar {
58
+ position: absolute;
59
+ left: calc(13% + 13px);
60
+ transform: translateX(-91%);
61
+ top: 12px;
62
+ z-index: 5;
63
+ }
64
+
65
+ .timeline-marker {
66
+ position: absolute;
67
+ background-color: #ff9f55;
68
+ border-radius: 50%;
69
+ width: 10px;
70
+ height: 10px;
71
+ z-index: 2;
72
+ left: calc(13% + 13px);
73
+ transform: translateX(-195%);
74
+ top: 25px;
75
+ }
76
+
77
+ .left-node {
78
+ flex: 0 0 13%;
79
+ max-width: 13%;
80
+ padding-right: 24px;
81
+ }
82
+
83
+ .content-date-left-history {
84
+ font-family: 'Red Hat Display';
85
+ font-size: 16px;
86
+ font-style: normal;
87
+ font-weight: 500;
88
+ text-align: right;
89
+ }
90
+
91
+ .content-time-left-history {
92
+ font-feature-settings: 'clig' off, 'liga' off;
93
+ font-family: 'Red Hat Text';
94
+ font-size: 14px;
95
+ font-style: normal;
96
+ font-weight: 400;
97
+ text-align: right;
98
+ line-height: 30px;
99
+ }
100
+
101
+ .content-date-right-history {
102
+ color: #151515;
103
+ font-feature-settings: 'clig' off, 'liga' off;
104
+ font-family: 'Red Hat Display';
105
+ font-size: 16px;
106
+ font-style: normal;
107
+ font-weight: 500;
108
+ line-height: 24px;
109
+ }
110
+
111
+ .content-time-right-history {
112
+ color: var(--Text-and-links-pf-global--Color--100, #151515);
113
+ font-feature-settings: 'clig' off, 'liga' off;
114
+ font-family: 'Red Hat Text';
115
+ font-size: 14px;
116
+ font-style: normal;
117
+ font-weight: 400;
118
+ line-height: 21px;
119
+ }
120
+
121
+ .right-node {
122
+ flex-grow: 1;
123
+ padding-left: 2.8%;
124
+ }
125
+
126
+ .right-node::before {
127
+ content: ' ';
128
+ height: 0;
129
+ position: relative;
130
+ display: list-item;
131
+ top: 22.5px;
132
+ width: 0;
133
+ z-index: 1;
134
+ left: -0.5%;
135
+ border: medium solid white;
136
+ border-width: 7px 0 7px 7px;
137
+ border-color: transparent transparent transparent white;
138
+ transform: rotate(180deg);
139
+ }
140
+
141
+ .internal.right-node::before {
142
+ border-color: transparent transparent transparent #ee0000;
143
+ }
144
+
145
+ .customer.right-node::before {
146
+ border-color: transparent transparent transparent #316dc1;
147
+ }
148
+
149
+ .timeline-internal {
150
+ background-color: #ee0000;
151
+ }
152
+
153
+ .timeline-customer {
154
+ background-color: #316dc1;
155
+ }
156
+
157
+ .timeline-avatar-internal {
158
+ border-color: #ee0000;
159
+ border-width: 2px;
160
+ border-style: solid;
161
+ }
162
+
163
+ .timeline-avatar-customer {
164
+ border-color: #316dc1;
165
+ border-width: 2px;
166
+ border-style: solid;
167
+ }
168
+
169
+ .contentTimeline {
170
+ padding: 16px;
171
+ position: absolute;
172
+ border-radius: 3px;
173
+ margin-bottom: 32px;
174
+ }
175
+
176
+ .content-right-history {
177
+ background-color: white;
178
+ border-left-width: 3px !important;
179
+ border-left-style: solid !important;
180
+ border-radius: 3px;
181
+ border: 1px solid #d2d2d2;
182
+ width: 80%;
183
+ top: 0rem;
184
+ }
185
+
186
+ .content-right-customer {
187
+ border-left-color: #316dc1 !important;
188
+ }
189
+
190
+ .content-right-internal {
191
+ border-left-color: #ee0000 !important;
192
+ }
193
+
194
+ ul#case-history-paginated-timeline {
195
+ list-style-type: none !important;
196
+ /* this is to deal with firefox they add in a ::marker and it is visible */
197
+ }
198
+
199
+ .case-history-timeline-search {
200
+ margin-bottom: 1%;
201
+ width: 30%;
202
+ }
203
+
204
+ .timelineMenu {
205
+ display: flex;
206
+ }
207
+
208
+ .case-history-timeline-datepicker {
209
+ display: flex;
210
+ margin-bottom: 1%;
211
+ /* position: absolute; */
212
+ justify-content: flex-end;
213
+ width: 64.2%;
214
+ }
215
+
216
+ .empty-state-date-picker {
217
+ margin-left: 82%;
218
+ }
219
+
220
+ .timeline-sort-order-select {
221
+ display: flex;
222
+ justify-content: flex-end;
223
+ margin: 1rem;
224
+ width: 93.2%;
225
+ }
226
+
227
+ @media screen and (max-width: 768px) {
228
+ .timeline::after {
229
+ left: 20px;
230
+ }
231
+
232
+ .timeline-node {
233
+ flex-direction: column;
234
+ }
235
+
236
+ .left-node,
237
+ .right-node {
238
+ max-width: 100%;
239
+ padding: 0;
240
+ }
241
+
242
+ .timeline-avatar,
243
+ .timeline-marker {
244
+ left: 0%;
245
+ transform: translateX(-50%);
246
+ }
247
+
248
+ .right-node::before {
249
+ left: 20px;
250
+ border-width: 10px 10px 10px 0;
251
+ border-color: transparent white transparent transparent;
252
+ }
253
+
254
+ .contentTimeline {
255
+ margin-left: 40px;
256
+ }
257
+ }
@@ -0,0 +1,7 @@
1
+ import './Timeline.css';
2
+ import React from 'react';
3
+ declare const Timeline: ({ caseNumber }: {
4
+ caseNumber: any;
5
+ }) => React.JSX.Element;
6
+ export default Timeline;
7
+ //# sourceMappingURL=Timeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Timeline.d.ts","sourceRoot":"","sources":["../../../../../../src/components/CaseEditView/Tabs/CaseHistory/Timeline.tsx"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,CAAC;AAwBxB,OAAO,KAAuD,MAAM,OAAO,CAAC;AA6I5E,QAAA,MAAM,QAAQ;;uBA8Qb,CAAC;AACF,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,236 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import './Timeline.css';
11
+ import { caseHistory } from '@cee-eng/hydrajs';
12
+ import { Avatar, Button, DatePicker, EmptyState, EmptyStateBody, EmptyStateHeader, EmptyStateIcon, EmptyStateVariant, MenuToggle, SearchInput, Select, SelectList, SelectOption, Spinner, } from '@patternfly/react-core';
13
+ import SearchIcon from '@patternfly/react-icons/dist/js/icons/search-icon';
14
+ import { PaginatedList } from '@rh-support/components';
15
+ import debounce from 'lodash/debounce';
16
+ import isEmpty from 'lodash/isEmpty';
17
+ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
18
+ import { Trans, useTranslation } from 'react-i18next';
19
+ const TimelineEvent = ({ date, text, side, user, useAvatar }) => {
20
+ if (side === 'left') {
21
+ if (useAvatar) {
22
+ // we only want to render the main date if the user changes, otherwise stay with just the time
23
+ return (React.createElement("div", { className: `${side}-node ${user}`, role: "complementary", "aria-label": `${user}'s timeline Event Time` },
24
+ React.createElement("div", { className: `contentTimeline content-${side}-history content-${side}-${user}` },
25
+ React.createElement("h2", { className: `content-date-${side}-history` }, date),
26
+ React.createElement("p", { className: `content-time-${side}-history` }, text))));
27
+ }
28
+ return (React.createElement("div", { className: `${side}-node ${user}`, role: "complementary", "aria-label": `${user}'s timeline Event Time` },
29
+ React.createElement("div", { className: `contentTimeline content-${side}-history content-${side}-${user}` },
30
+ React.createElement("p", { className: `content-time-${side}-history` }, text))));
31
+ }
32
+ return (React.createElement("div", { className: `${side}-node ${user}`, role: "complementary", "aria-label": `${user}'s timeline event content ` },
33
+ React.createElement("div", { className: `contentTimeline content-${side}-history content-${side}-${user}` },
34
+ React.createElement("h2", { className: `content-date-${side}-history` }, date),
35
+ text && React.createElement("p", { className: `content-time-${side}-history` }, text))));
36
+ };
37
+ const TimelineNode = React.forwardRef(({ leftEvent, rightEvent, user, useAvatar }, ref) => {
38
+ return (React.createElement("div", { className: "timeline-node", role: "region", "aria-label": "Timeline node" },
39
+ leftEvent && React.createElement(TimelineEvent, Object.assign({}, leftEvent, { side: "left", useAvatar: useAvatar })),
40
+ React.createElement("div", { ref: ref, "aria-label": `${useAvatar ? 'Open user profile' : 'Marker'}`, tabIndex: 0 }, useAvatar ? (React.createElement(Avatar, { src: 'https://www.patternfly.org/images/668560cd.svg', className: `timeline-avatar timeline-avatar-${user}`, alt: `${user}'s avatar` })) : (React.createElement("div", { className: `timeline-marker timeline-${user}` }))),
41
+ rightEvent && React.createElement(TimelineEvent, Object.assign({}, rightEvent, { side: "right" }))));
42
+ });
43
+ const transformApiResponseToTimelineData = (apiResponse) => {
44
+ let previousUser = null;
45
+ const timelineEvents = apiResponse.historyItems.map((item, index) => {
46
+ const currentUser = item.createdByUserName;
47
+ // If the current user is the same as the previous one, and it's not the first item, don't use an avatar. use !! cause previousUser start null.
48
+ const useAvatar = index === 0 || (!!previousUser && currentUser !== previousUser);
49
+ // Update the previousUser for the next iteration
50
+ previousUser = currentUser;
51
+ const eventDate = new Date(item.createdDate);
52
+ const date = eventDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
53
+ const time = eventDate.toLocaleTimeString('en-US', {
54
+ hour: 'numeric',
55
+ minute: '2-digit',
56
+ hour12: true,
57
+ timeZoneName: 'short',
58
+ });
59
+ const user = item.createdByCustomer !== undefined
60
+ ? item.createdByCustomer //using new case history field to determine this
61
+ ? 'customer'
62
+ : 'internal'
63
+ : currentUser === 'Case Diagnostics' || currentUser === 'GSS Tools' //for now since it is not on prod yet we have this same fallback
64
+ ? 'internal'
65
+ : 'customer';
66
+ const leftEvent = {
67
+ date: date,
68
+ text: time,
69
+ };
70
+ const rightEvent = {
71
+ date: `${item.createdByUserName} ${stripHTML(item.outputText)}`, //will change name later from date, for now date is title and text stays text due to initial set up mimicking same ITimelineEvent
72
+ text: '', //future iterations of API make this the 'show more' field
73
+ user: user,
74
+ useAvatar: useAvatar,
75
+ };
76
+ return {
77
+ left: leftEvent,
78
+ right: rightEvent,
79
+ };
80
+ });
81
+ return timelineEvents;
82
+ };
83
+ function stripHTML(htmlString) {
84
+ const anchorTagRegex = /<a[^>]*>(.*?)<\/a>/g;
85
+ // We are trying to return the text without having the info in the anchor.
86
+ return htmlString.replace(anchorTagRegex, '');
87
+ }
88
+ const Timeline = ({ caseNumber }) => {
89
+ const { t } = useTranslation();
90
+ const [timelineEvents, setTimelineEvents] = useState([]);
91
+ const [isFetchingData, setIsFetchingData] = useState();
92
+ const [paginatedListLength, setPaginatedListLength] = useState();
93
+ const lastNodeRef = useRef(null);
94
+ const [dateValue, setDateValue] = React.useState('');
95
+ const [searchValue, setSearchValue] = useState('');
96
+ const [filteredEvents, setFilteredEvents] = useState([]);
97
+ const [isSelectOpen, setIsSelectOpen] = useState(false);
98
+ const [selectedOrder, setSelectedOrder] = useState('Newest to Oldest');
99
+ const handleSelectToggle = () => {
100
+ setIsSelectOpen(!isSelectOpen);
101
+ };
102
+ const handleSelect = (_event, value) => {
103
+ setSelectedOrder(value);
104
+ setIsSelectOpen(false);
105
+ reorderTimelineEvents(value);
106
+ };
107
+ const reorderTimelineEvents = (order) => {
108
+ setTimelineEvents((prevEvents) => {
109
+ const reversedEvents = [...prevEvents].reverse();
110
+ return applyAvatarLogic(reversedEvents);
111
+ });
112
+ };
113
+ const applyAvatarLogic = (events) => {
114
+ let previousUser = null;
115
+ return events.map((event, index) => {
116
+ const currentUser = event.right.date.split(' ')[0];
117
+ const useAvatar = index === 0 || (!!previousUser && currentUser !== previousUser);
118
+ previousUser = currentUser;
119
+ return Object.assign(Object.assign({}, event), { right: Object.assign(Object.assign({}, event.right), { useAvatar: useAvatar }) });
120
+ });
121
+ };
122
+ useEffect(() => {
123
+ function fetchHistory() {
124
+ return __awaiter(this, void 0, void 0, function* () {
125
+ setIsFetchingData(true);
126
+ const options = {
127
+ sortField: 'createdDate',
128
+ sortOrder: 'DESC',
129
+ showVerbose: true,
130
+ };
131
+ try {
132
+ const response = yield caseHistory.getHistoryv1(caseNumber, options);
133
+ const transformedData = transformApiResponseToTimelineData(response);
134
+ setTimelineEvents(transformedData);
135
+ }
136
+ catch (error) {
137
+ console.error('Failed to fetch history:', error);
138
+ }
139
+ finally {
140
+ setIsFetchingData(false);
141
+ }
142
+ });
143
+ }
144
+ fetchHistory();
145
+ }, [caseNumber]);
146
+ //Using useLayoutEffect because we are measuring styles based on DOM, useLayoutEffect runs after DOM Mutations https://react.dev/reference/react/useLayoutEffect
147
+ useLayoutEffect(() => {
148
+ const updateLineStyle = debounce(() => {
149
+ var _a;
150
+ if (lastNodeRef.current) {
151
+ const timelineBottom = lastNodeRef.current.getBoundingClientRect().bottom + window.scrollY;
152
+ const timelineElement = document.querySelector('.timeline');
153
+ const avatarHeight = ((_a = lastNodeRef.current.querySelector('.timeline-avatar')) === null || _a === void 0 ? void 0 : _a.clientHeight) || 0;
154
+ if (timelineElement) {
155
+ // Get the timeline element's position relative to the document
156
+ const timelineTop = timelineElement.getBoundingClientRect().top + window.scrollY;
157
+ // Calculate value relative to the timeline's top must be adjusted for page scroll or we get random bottom value based on where user scrolled before execution
158
+ const calculatedBottomValue = `${timelineTop + timelineElement.offsetHeight - timelineBottom + avatarHeight}px`;
159
+ timelineElement.style.setProperty('--timeline-bottom', calculatedBottomValue);
160
+ }
161
+ }
162
+ }, 100);
163
+ updateLineStyle();
164
+ }, [timelineEvents, paginatedListLength, dateValue]);
165
+ const handleSearchChange = (value) => {
166
+ setSearchValue(value.target.value);
167
+ };
168
+ const handleClear = () => {
169
+ setSearchValue('');
170
+ };
171
+ const handleDateChange = (value) => {
172
+ setDateValue(value);
173
+ };
174
+ useEffect(() => {
175
+ let filteredBySearch = [...timelineEvents];
176
+ if (searchValue.trim()) {
177
+ const lowercasedFilter = searchValue.toLowerCase();
178
+ filteredBySearch = filteredBySearch.filter(({ left, right }) => {
179
+ const dateString = left.date.toLowerCase();
180
+ return (dateString.includes(lowercasedFilter) ||
181
+ left.text.toLocaleLowerCase().includes(lowercasedFilter) ||
182
+ right.date.toLowerCase().includes(lowercasedFilter));
183
+ });
184
+ }
185
+ let finalFilteredEvents = filteredBySearch;
186
+ if (dateValue) {
187
+ finalFilteredEvents = filteredBySearch.filter(({ left }) => {
188
+ // Format the event date to match the DatePicker format (YYYY-MM-DD) We need to do it this way so we dont have discrepancy (off by one month when selecting) due to the way that JS handles timezones
189
+ const eventDateFormatted = new Date(left.date).toISOString().split('T')[0];
190
+ // Compare the formatted event date with the selected date from DatePicker
191
+ return eventDateFormatted === dateValue;
192
+ });
193
+ }
194
+ setFilteredEvents(finalFilteredEvents);
195
+ }, [searchValue, dateValue, timelineEvents]);
196
+ if (filteredEvents.length < 1 && !isFetchingData) {
197
+ return (React.createElement("div", { key: "no-results" },
198
+ React.createElement("div", { className: "timelineMenu" },
199
+ React.createElement(SearchInput, { placeholder: t('Search for a user, action, or keyword'), value: searchValue, onChange: handleSearchChange, onClear: handleClear, className: "case-history-timeline-search" }),
200
+ React.createElement("div", { className: "case-history-timeline-datepicker" },
201
+ React.createElement(DatePicker, { value: dateValue, onChange: (_event, value) => handleDateChange(value) }),
202
+ React.createElement(Button, { onClick: () => setDateValue(''), isDisabled: isEmpty(dateValue) }, "Reset"))),
203
+ React.createElement("div", { className: "timeline-sort-order-select" },
204
+ React.createElement(Select, { id: "order-select", isOpen: isSelectOpen, selected: selectedOrder, onSelect: handleSelect, onOpenChange: (isOpen) => setIsSelectOpen(isOpen), toggle: (toggleRef) => (React.createElement(MenuToggle, { ref: toggleRef, onClick: handleSelectToggle, isExpanded: isSelectOpen, style: { width: '200px' } }, selectedOrder)), shouldFocusToggleOnSelect: true },
205
+ React.createElement(SelectList, null,
206
+ React.createElement(SelectOption, { value: "Newest to Oldest" }, "Newest to Oldest"),
207
+ React.createElement(SelectOption, { value: "Oldest to Newest" }, "Oldest to Newest")))),
208
+ React.createElement(EmptyState, { variant: EmptyStateVariant.full },
209
+ React.createElement(EmptyStateHeader, { titleText: React.createElement(Trans, null, "No results found"), icon: React.createElement(EmptyStateIcon, { icon: SearchIcon }), headingLevel: "h2" }),
210
+ React.createElement(EmptyStateBody, null,
211
+ React.createElement(Trans, null, "Try modifying your search query or changing the date range and try again.")))));
212
+ }
213
+ return (React.createElement(React.Fragment, null,
214
+ React.createElement("div", { className: "timelineMenu" },
215
+ React.createElement(SearchInput, { placeholder: "Search for a user, action, or keyword", value: searchValue, onChange: handleSearchChange, onClear: handleClear, className: "case-history-timeline-search" }),
216
+ React.createElement("div", { className: "case-history-timeline-datepicker" },
217
+ React.createElement(DatePicker, { value: dateValue, onChange: (_event, value) => handleDateChange(value) }),
218
+ React.createElement(Button, { onClick: () => setDateValue(''), isDisabled: isEmpty(dateValue) }, "Reset"))),
219
+ React.createElement("div", { className: "timeline-sort-order-select" },
220
+ React.createElement(Select, { id: "order-select", isOpen: isSelectOpen, selected: selectedOrder, onSelect: handleSelect, onOpenChange: (isOpen) => setIsSelectOpen(isOpen), toggle: (toggleRef) => (React.createElement(MenuToggle, { ref: toggleRef, onClick: handleSelectToggle, isExpanded: isSelectOpen, style: { width: '200px' } }, selectedOrder)), shouldFocusToggleOnSelect: true },
221
+ React.createElement(SelectList, null,
222
+ React.createElement(SelectOption, { value: "Newest to Oldest" }, "Newest to Oldest"),
223
+ React.createElement(SelectOption, { value: "Oldest to Newest" }, "Oldest to Newest")))),
224
+ React.createElement("div", { className: "timeline" },
225
+ isFetchingData && (React.createElement("div", { className: "timeline-loading-spinner" },
226
+ React.createElement(Spinner, { size: "xl" }))),
227
+ !isFetchingData && (React.createElement(PaginatedList, { id: "case-history-paginated-timeline", listItems: filteredEvents &&
228
+ filteredEvents.map((node, index) => (React.createElement(TimelineNode, { key: index, leftEvent: node.left, rightEvent: node.right, user: node.right.user, useAvatar: node.right.useAvatar, ref: index === (paginatedListLength && paginatedListLength - 1) ? lastNodeRef : null //this ref is needed to perform calculations on the final node height
229
+ }))), perPage: 20, perPageOptions: [
230
+ { title: '5', value: 5 },
231
+ { title: '10', value: 10 },
232
+ { title: '15', value: 15 },
233
+ { title: '20', value: 20 },
234
+ ], setPaginatedListLength: setPaginatedListLength })))));
235
+ };
236
+ export default Timeline;
@@ -77,5 +77,5 @@ export const NewProductDropdownSelector = (props) => {
77
77
  !isSelectedProductSupportedForCustomer && (React.createElement("div", { className: "pf-v5-u-ml-xs pf-v5-u-mt-sm form-instructions" },
78
78
  React.createElement(Trans, null, "Red Hat must confirm your subscription status before providing support."),
79
79
  React.createElement("br", null),
80
- React.createElement(Trans, null, "We'll contact you if we have question.")))));
80
+ React.createElement(Trans, null, "We'll contact you if we have any questions.")))));
81
81
  };
@@ -1 +1 @@
1
- {"version":3,"file":"SessionRestore.d.ts","sourceRoot":"","sources":["../../../../src/components/SessionRestore/SessionRestore.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAkD,MAAM,OAAO,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAQvD,OAAO,EAGH,eAAe,EAElB,MAAM,iCAAiC,CAAC;AAiBzC,UAAU,MAAM;IACZ,UAAU,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;CACpD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,qBA8U3C"}
1
+ {"version":3,"file":"SessionRestore.d.ts","sourceRoot":"","sources":["../../../../src/components/SessionRestore/SessionRestore.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAkD,MAAM,OAAO,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAQvD,OAAO,EAGH,eAAe,EAElB,MAAM,iCAAiC,CAAC;AAiBzC,UAAU,MAAM;IACZ,UAAU,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;CACpD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,qBAwT3C"}
@@ -234,29 +234,6 @@ export function SessionRestore(props) {
234
234
  const userAgent = getUserAgentForCaseMode(isCaseCreate);
235
235
  markAllSessionsUnresolved(sessionRestoreDispatch, sessionRestore.previousSessions.data, userAgent);
236
236
  };
237
- useEffect(() => {
238
- var _a, _b, _c, _d, _e, _f;
239
- if (sessionRestore.activeSessionId === previousActiveSessionId) {
240
- return;
241
- }
242
- const sessionItem = sessionRestore.previousSessions.data[sessionRestore.activeSessionId];
243
- const newParams = {
244
- product: (_c = (_b = (_a = sessionItem === null || sessionItem === void 0 ? void 0 : sessionItem.sessionDetails) === null || _a === void 0 ? void 0 : _a.product) !== null && _b !== void 0 ? _b : caseDetails.product) !== null && _c !== void 0 ? _c : queryParamsFromUrl.product,
245
- version: (_f = (_e = (_d = sessionItem === null || sessionItem === void 0 ? void 0 : sessionItem.sessionDetails) === null || _d === void 0 ? void 0 : _d.version) !== null && _e !== void 0 ? _e : caseDetails.version) !== null && _f !== void 0 ? _f : queryParamsFromUrl.version,
246
- seSessionId: sessionRestore.activeSessionId,
247
- };
248
- RouteUtils.updateQueryParams(props.routeProps, newParams);
249
- }, [
250
- caseDetails.product,
251
- caseDetails.version,
252
- currentUrlSeSessionId,
253
- previousActiveSessionId,
254
- props.routeProps,
255
- queryParamsFromUrl.product,
256
- queryParamsFromUrl.version,
257
- sessionRestore.activeSessionId,
258
- sessionRestore.previousSessions.data,
259
- ]);
260
237
  useDebounce(() => {
261
238
  let previousSessions = [];
262
239
  for (let s in sessionRestore.previousSessions.data) {
@@ -5,7 +5,8 @@ export declare enum CaseDetailsTabsEnum {
5
5
  ACTION_PLAN = "Action plan",
6
6
  PRIVATE_NOTES = "Private Notes",
7
7
  ESCALATION = "Escalations",
8
- RELATED_TASKS = "Related tasks"
8
+ RELATED_TASKS = "Related tasks",
9
+ HISTORY = "History"
9
10
  }
10
11
  export declare enum CaseStatusEnum {
11
12
  CLOSED = "Closed",
@@ -1 +1 @@
1
- {"version":3,"file":"caseDetailsConstants.d.ts","sourceRoot":"","sources":["../../../src/constants/caseDetailsConstants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oCAAoC,yBAAyB,CAAC;AAE3E,oBAAY,mBAAmB;IAC3B,UAAU,eAAe;IACzB,UAAU,eAAe;IACzB,WAAW,gBAAgB;IAC3B,aAAa,kBAAkB;IAC/B,UAAU,gBAAgB;IAC1B,aAAa,kBAAkB;CAClC;AAED,oBAAY,cAAc;IACtB,MAAM,WAAW;IACjB,mBAAmB,wBAAwB;IAC3C,iBAAiB,uBAAuB;CAC3C;AAED,oBAAY,0BAA0B;IAClC,OAAO,MAAM;IACb,WAAW,OAAO;IAClB,OAAO,MAAM;CAChB;AAED,eAAO,MAAM,0BAA0B,6DAA6D,CAAC;AAErG,eAAO,MAAM,mBAAmB,uCAAuC,CAAC;AAExE,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAE7D,eAAO,MAAM,iBAAiB,8EAA8E,CAAC"}
1
+ {"version":3,"file":"caseDetailsConstants.d.ts","sourceRoot":"","sources":["../../../src/constants/caseDetailsConstants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oCAAoC,yBAAyB,CAAC;AAE3E,oBAAY,mBAAmB;IAC3B,UAAU,eAAe;IACzB,UAAU,eAAe;IACzB,WAAW,gBAAgB;IAC3B,aAAa,kBAAkB;IAC/B,UAAU,gBAAgB;IAC1B,aAAa,kBAAkB;IAC/B,OAAO,YAAY;CACtB;AAED,oBAAY,cAAc;IACtB,MAAM,WAAW;IACjB,mBAAmB,wBAAwB;IAC3C,iBAAiB,uBAAuB;CAC3C;AAED,oBAAY,0BAA0B;IAClC,OAAO,MAAM;IACb,WAAW,OAAO;IAClB,OAAO,MAAM;CAChB;AAED,eAAO,MAAM,0BAA0B,6DAA6D,CAAC;AAErG,eAAO,MAAM,mBAAmB,uCAAuC,CAAC;AAExE,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAE7D,eAAO,MAAM,iBAAiB,8EAA8E,CAAC"}
@@ -7,6 +7,7 @@ export var CaseDetailsTabsEnum;
7
7
  CaseDetailsTabsEnum["PRIVATE_NOTES"] = "Private Notes";
8
8
  CaseDetailsTabsEnum["ESCALATION"] = "Escalations";
9
9
  CaseDetailsTabsEnum["RELATED_TASKS"] = "Related tasks";
10
+ CaseDetailsTabsEnum["HISTORY"] = "History";
10
11
  })(CaseDetailsTabsEnum || (CaseDetailsTabsEnum = {}));
11
12
  export var CaseStatusEnum;
12
13
  (function (CaseStatusEnum) {
@@ -702,3 +702,7 @@ label.react-typeahead-label-wrapper {
702
702
  color: #ffffff;
703
703
  }
704
704
  }
705
+ #case-history-paginated-timeline-pagination-bottom-pagination {
706
+ justify-content: flex-start;
707
+ background-color: var(--pf-v5-global--palette--black-200);
708
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rh-support/troubleshoot",
3
- "version": "2.2.119",
3
+ "version": "2.2.121",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org"
@@ -60,10 +60,10 @@
60
60
  "@progress/kendo-licensing": "1.3.5",
61
61
  "@progress/kendo-react-pdf": "^5.16.0",
62
62
  "@redux-devtools/extension": "^3.3.0",
63
- "@rh-support/components": "2.1.59",
64
- "@rh-support/react-context": "2.1.66",
63
+ "@rh-support/components": "2.1.61",
64
+ "@rh-support/react-context": "2.1.68",
65
65
  "@rh-support/types": "2.0.3",
66
- "@rh-support/user-permissions": "2.1.43",
66
+ "@rh-support/user-permissions": "2.1.44",
67
67
  "@rh-support/utils": "2.1.33",
68
68
  "@types/react-redux": "^7.1.33",
69
69
  "@types/redux": "^3.6.0",
@@ -131,5 +131,5 @@
131
131
  "defaults and supports es6-module",
132
132
  "maintained node versions"
133
133
  ],
134
- "gitHead": "184dcedae43592a133b6122d642a38b64ed4006c"
134
+ "gitHead": "0fa0968e831c199572669184c466e9fecd781d12"
135
135
  }