@oneuptime/common 8.0.5387 → 8.0.5409

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 (90) hide show
  1. package/Models/AnalyticsModels/Index.ts +0 -2
  2. package/Models/DatabaseModels/TelemetryException.ts +0 -7
  3. package/Server/API/MicrosoftTeamsAPI.ts +3 -4
  4. package/Server/API/UserOnCallLogTimelineAPI.ts +21 -17
  5. package/Server/DatabaseConfig.ts +7 -2
  6. package/Server/EnvironmentConfig.ts +20 -7
  7. package/Server/Infrastructure/GlobalCache.ts +12 -5
  8. package/Server/Services/AlertService.ts +0 -10
  9. package/Server/Services/AnalyticsDatabaseService.ts +5 -0
  10. package/Server/Services/IncidentService.ts +0 -10
  11. package/Server/Services/Index.ts +0 -2
  12. package/Server/Services/TeamMemberService.ts +11 -2
  13. package/Server/Services/TelemetryAttributeService.ts +261 -48
  14. package/Server/Services/WorkspaceProjectAuthTokenService.ts +33 -0
  15. package/Server/Utils/Monitor/MonitorResource.ts +0 -16
  16. package/Server/Utils/Telemetry/Telemetry.ts +0 -61
  17. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +149 -14
  18. package/Types/Permission.ts +0 -3
  19. package/UI/Components/Filters/FilterViewer.tsx +5 -1
  20. package/UI/Components/Filters/FiltersForm.tsx +8 -1
  21. package/UI/Components/List/List.tsx +4 -0
  22. package/UI/Components/LogsViewer/LogsViewer.tsx +102 -55
  23. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +1 -1
  24. package/UI/Components/ModelTable/BaseModelTable.tsx +6 -0
  25. package/UI/Components/Table/Table.tsx +4 -0
  26. package/UI/Config.ts +28 -19
  27. package/UI/Utils/API/ApiDocsAPI.ts +6 -1
  28. package/UI/Utils/API/DashboardAPI.ts +2 -1
  29. package/UI/Utils/API/IdentityAPI.ts +6 -1
  30. package/build/dist/Models/AnalyticsModels/Index.js +0 -2
  31. package/build/dist/Models/AnalyticsModels/Index.js.map +1 -1
  32. package/build/dist/Models/DatabaseModels/TelemetryException.js +0 -7
  33. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  34. package/build/dist/Server/API/MicrosoftTeamsAPI.js +4 -4
  35. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  36. package/build/dist/Server/API/UserOnCallLogTimelineAPI.js +6 -3
  37. package/build/dist/Server/API/UserOnCallLogTimelineAPI.js.map +1 -1
  38. package/build/dist/Server/DatabaseConfig.js +3 -2
  39. package/build/dist/Server/DatabaseConfig.js.map +1 -1
  40. package/build/dist/Server/EnvironmentConfig.js +8 -7
  41. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  42. package/build/dist/Server/Infrastructure/GlobalCache.js +11 -9
  43. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  44. package/build/dist/Server/Services/AlertService.js +0 -9
  45. package/build/dist/Server/Services/AlertService.js.map +1 -1
  46. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  47. package/build/dist/Server/Services/IncidentService.js +0 -9
  48. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  49. package/build/dist/Server/Services/Index.js +0 -2
  50. package/build/dist/Server/Services/Index.js.map +1 -1
  51. package/build/dist/Server/Services/TeamMemberService.js +3 -2
  52. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  53. package/build/dist/Server/Services/TelemetryAttributeService.js +165 -46
  54. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  55. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +25 -0
  56. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  57. package/build/dist/Server/Utils/Monitor/MonitorResource.js +0 -15
  58. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  59. package/build/dist/Server/Utils/Telemetry/Telemetry.js +0 -41
  60. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  61. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +84 -15
  62. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  63. package/build/dist/Types/Permission.js +0 -2
  64. package/build/dist/Types/Permission.js.map +1 -1
  65. package/build/dist/UI/Components/Filters/FilterViewer.js +2 -2
  66. package/build/dist/UI/Components/Filters/FilterViewer.js.map +1 -1
  67. package/build/dist/UI/Components/Filters/FiltersForm.js +6 -1
  68. package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
  69. package/build/dist/UI/Components/List/List.js +1 -1
  70. package/build/dist/UI/Components/List/List.js.map +1 -1
  71. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +43 -16
  72. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  73. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +1 -1
  74. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  75. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -2
  76. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  77. package/build/dist/UI/Components/Table/Table.js +1 -1
  78. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  79. package/build/dist/UI/Config.js +20 -19
  80. package/build/dist/UI/Config.js.map +1 -1
  81. package/build/dist/UI/Utils/API/ApiDocsAPI.js +2 -1
  82. package/build/dist/UI/Utils/API/ApiDocsAPI.js.map +1 -1
  83. package/build/dist/UI/Utils/API/DashboardAPI.js +2 -1
  84. package/build/dist/UI/Utils/API/DashboardAPI.js.map +1 -1
  85. package/build/dist/UI/Utils/API/IdentityAPI.js +2 -1
  86. package/build/dist/UI/Utils/API/IdentityAPI.js.map +1 -1
  87. package/package.json +1 -1
  88. package/Models/AnalyticsModels/TelemetryAttribute.ts +0 -164
  89. package/build/dist/Models/AnalyticsModels/TelemetryAttribute.js +0 -154
  90. package/build/dist/Models/AnalyticsModels/TelemetryAttribute.js.map +0 -1
@@ -98,6 +98,10 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
98
98
  );
99
99
  }
100
100
 
101
+ if (!microsoftAppTenantId) {
102
+ throw new BadDataException("Microsoft Teams tenant ID is required");
103
+ }
104
+
101
105
  logger.debug(
102
106
  "Creating Bot Framework adapter with authentication configuration",
103
107
  );
@@ -125,6 +129,12 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
125
129
  projectId: ObjectID;
126
130
  }): Promise<string> {
127
131
  logger.debug("=== getValidAccessToken called ===");
132
+
133
+ if (!data.projectId) {
134
+ throw new BadDataException(
135
+ "projectId is required to get Microsoft Teams access token",
136
+ );
137
+ }
128
138
  logger.debug(`Project ID: ${data.projectId.toString()}`);
129
139
  logger.debug(
130
140
  `Auth token (first 20 chars): ${data.authToken?.substring(0, 20)}...`,
@@ -155,6 +165,19 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
155
165
 
156
166
  const miscData: MicrosoftTeamsMiscData =
157
167
  projectAuth.miscData as MicrosoftTeamsMiscData;
168
+ const tenantId: string | undefined = projectAuth.workspaceProjectId;
169
+
170
+ logger.debug(`Resolved tenant ID: ${tenantId}`);
171
+
172
+ if (!tenantId) {
173
+ logger.error(
174
+ "Microsoft Teams tenant ID missing from project auth configuration",
175
+ );
176
+ throw new BadDataException(
177
+ "Microsoft Teams tenant ID not found for this project",
178
+ );
179
+ }
180
+
158
181
  logger.debug(
159
182
  `MiscData appAccessToken exists: ${Boolean(miscData.appAccessToken)}`,
160
183
  );
@@ -184,6 +207,7 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
184
207
  const newToken: string | null = await this.refreshAccessToken({
185
208
  projectId: data.projectId,
186
209
  miscData,
210
+ tenantId,
187
211
  });
188
212
  if (newToken) {
189
213
  logger.debug("Successfully refreshed token");
@@ -210,6 +234,7 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
210
234
  const newToken: string | null = await this.refreshAccessToken({
211
235
  projectId: data.projectId,
212
236
  miscData,
237
+ tenantId,
213
238
  });
214
239
  if (newToken) {
215
240
  logger.debug("Successfully refreshed token");
@@ -227,10 +252,24 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
227
252
  private static async refreshAccessToken(data: {
228
253
  projectId: ObjectID;
229
254
  miscData: MicrosoftTeamsMiscData;
255
+ tenantId: string;
230
256
  }): Promise<string | null> {
231
257
  logger.debug("=== refreshAccessToken called ===");
258
+
259
+ if (!data.projectId) {
260
+ throw new BadDataException(
261
+ "projectId is required to refresh Microsoft Teams access token",
262
+ );
263
+ }
264
+
265
+ if (!data.miscData) {
266
+ throw new BadDataException(
267
+ "miscData is required to refresh Microsoft Teams access token",
268
+ );
269
+ }
270
+
232
271
  logger.debug(`Project ID: ${data.projectId.toString()}`);
233
- logger.debug(`Tenant ID: ${data.miscData.tenantId}`);
272
+ logger.debug(`Tenant ID: ${data.tenantId}`);
234
273
 
235
274
  try {
236
275
  // Check if we have the necessary client credentials
@@ -246,18 +285,18 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
246
285
 
247
286
  logger.debug("Client credentials are configured");
248
287
 
249
- if (!data.miscData.tenantId) {
250
- logger.error("Tenant ID not found in miscData, cannot refresh token");
288
+ if (!data.tenantId) {
289
+ logger.error("Tenant ID not provided, cannot refresh token");
251
290
  return null;
252
291
  }
253
292
 
254
293
  logger.debug(
255
294
  `Attempting to refresh Microsoft Teams access token for project ${data.projectId.toString()}`,
256
295
  );
257
- logger.debug(`Using tenant ID: ${data.miscData.tenantId}`);
296
+ logger.debug(`Using tenant ID: ${data.tenantId}`);
258
297
 
259
298
  // Use OAuth 2.0 client credentials flow to get a new app access token
260
- const tokenUrl: string = `https://login.microsoftonline.com/${data.miscData.tenantId}/oauth2/v2.0/token`;
299
+ const tokenUrl: string = `https://login.microsoftonline.com/${data.tenantId}/oauth2/v2.0/token`;
261
300
  logger.debug(`Token URL: ${tokenUrl}`);
262
301
 
263
302
  const tokenRequestBody: JSONObject = {
@@ -314,6 +353,7 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
314
353
  appAccessToken: newAccessToken,
315
354
  appAccessTokenExpiresAt: OneUptimeDate.toString(expiryDate),
316
355
  lastAppTokenIssuedAt: OneUptimeDate.toString(now),
356
+ tenantId: data.tenantId,
317
357
  };
318
358
 
319
359
  logger.debug("Saving updated token to database");
@@ -322,7 +362,7 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
322
362
  projectId: data.projectId,
323
363
  workspaceType: WorkspaceType.MicrosoftTeams,
324
364
  authToken: newAccessToken,
325
- workspaceProjectId: data.miscData.tenantId,
365
+ workspaceProjectId: data.tenantId,
326
366
  miscData: updatedMiscData as any,
327
367
  });
328
368
 
@@ -780,6 +820,24 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
780
820
  projectId: ObjectID;
781
821
  teamId: string;
782
822
  }): Promise<WorkspaceChannel | null> {
823
+ if (!data.projectId) {
824
+ throw new BadDataException(
825
+ "projectId is required to get Microsoft Teams channel by name",
826
+ );
827
+ }
828
+
829
+ if (!data.teamId) {
830
+ throw new BadDataException(
831
+ "teamId is required to get Microsoft Teams channel by name",
832
+ );
833
+ }
834
+
835
+ if (!data.channelName) {
836
+ throw new BadDataException(
837
+ "channelName is required to get Microsoft Teams channel by name",
838
+ );
839
+ }
840
+
783
841
  logger.debug(`Getting workspace channel by name: ${data.channelName}`);
784
842
 
785
843
  // Get project auth to get available teams
@@ -1004,6 +1062,36 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
1004
1062
  adaptiveCard: JSONObject;
1005
1063
  projectId: ObjectID;
1006
1064
  }): Promise<WorkspaceThread> {
1065
+ if (!data.projectId) {
1066
+ throw new BadDataException(
1067
+ "projectId is required to send Microsoft Teams adaptive card",
1068
+ );
1069
+ }
1070
+
1071
+ if (!data.teamId) {
1072
+ throw new BadDataException(
1073
+ "teamId is required to send Microsoft Teams adaptive card",
1074
+ );
1075
+ }
1076
+
1077
+ if (!data.workspaceChannel) {
1078
+ throw new BadDataException(
1079
+ "workspaceChannel is required to send Microsoft Teams adaptive card",
1080
+ );
1081
+ }
1082
+
1083
+ if (!data.workspaceChannel.id) {
1084
+ throw new BadDataException(
1085
+ "workspaceChannel.id is required to send Microsoft Teams adaptive card",
1086
+ );
1087
+ }
1088
+
1089
+ if (!data.adaptiveCard) {
1090
+ throw new BadDataException(
1091
+ "adaptiveCard is required to send Microsoft Teams adaptive card",
1092
+ );
1093
+ }
1094
+
1007
1095
  logger.debug(
1008
1096
  `Sending adaptive card to channel via Bot Framework: ${data.workspaceChannel.name} (${data.workspaceChannel.id})`,
1009
1097
  );
@@ -1032,6 +1120,14 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
1032
1120
  );
1033
1121
  }
1034
1122
 
1123
+ const tenantId: string | undefined = projectAuth.workspaceProjectId;
1124
+
1125
+ if (!tenantId) {
1126
+ throw new BadDataException(
1127
+ "Tenant ID not found in Microsoft Teams integration",
1128
+ );
1129
+ }
1130
+
1035
1131
  // Check if app client ID is configured
1036
1132
  if (!MicrosoftTeamsAppClientId) {
1037
1133
  throw new BadDataException(
@@ -1042,7 +1138,7 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
1042
1138
  logger.debug(`Using bot ID: ${miscData.botId}`);
1043
1139
 
1044
1140
  // Get Bot Framework adapter
1045
- const adapter: CloudAdapter = this.getBotAdapter(miscData.tenantId);
1141
+ const adapter: CloudAdapter = this.getBotAdapter(tenantId);
1046
1142
 
1047
1143
  // Create conversation reference for the channel
1048
1144
  const conversationReference: ConversationReference = {
@@ -1055,7 +1151,7 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
1055
1151
  name: data.workspaceChannel.name,
1056
1152
  isGroup: true,
1057
1153
  conversationType: "channel",
1058
- tenantId: miscData.tenantId,
1154
+ tenantId: tenantId,
1059
1155
  },
1060
1156
  channelId: "msteams",
1061
1157
  serviceUrl: "https://smba.trafficmanager.net/teams/",
@@ -1113,6 +1209,25 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
1113
1209
  projectId: ObjectID;
1114
1210
  }): Promise<WorkspaceChannel> {
1115
1211
  logger.debug("=== getWorkspaceChannelFromChannelId called ===");
1212
+
1213
+ if (!data.projectId) {
1214
+ throw new BadDataException(
1215
+ "projectId is required to get Microsoft Teams channel by ID",
1216
+ );
1217
+ }
1218
+
1219
+ if (!data.teamId) {
1220
+ throw new BadDataException(
1221
+ "teamId is required to get Microsoft Teams channel by ID",
1222
+ );
1223
+ }
1224
+
1225
+ if (!data.channelId) {
1226
+ throw new BadDataException(
1227
+ "channelId is required to get Microsoft Teams channel by ID",
1228
+ );
1229
+ }
1230
+
1116
1231
  logger.debug(`Channel ID: ${data.channelId}`);
1117
1232
  logger.debug(`Team ID: ${data.teamId}`);
1118
1233
  logger.debug(`Project ID: ${data.projectId.toString()}`);
@@ -1638,13 +1753,11 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
1638
1753
  await WorkspaceProjectAuthTokenService.findOneBy({
1639
1754
  query: {
1640
1755
  workspaceType: WorkspaceType.MicrosoftTeams,
1641
- miscData: {
1642
- tenantId: tenantId,
1643
- } as any,
1756
+ workspaceProjectId: tenantId,
1644
1757
  },
1645
1758
  select: {
1646
1759
  projectId: true,
1647
- miscData: true,
1760
+ workspaceProjectId: true,
1648
1761
  },
1649
1762
  props: {
1650
1763
  isRoot: true,
@@ -2231,9 +2344,13 @@ All monitoring checks are passing normally.`;
2231
2344
  await WorkspaceProjectAuthTokenService.findOneBy({
2232
2345
  query: {
2233
2346
  workspaceType: WorkspaceType.MicrosoftTeams,
2234
- miscData: { tenantId: tenantId } as any,
2347
+ workspaceProjectId: tenantId,
2348
+ },
2349
+ select: {
2350
+ projectId: true,
2351
+ authToken: true,
2352
+ workspaceProjectId: true,
2235
2353
  },
2236
- select: { projectId: true, authToken: true },
2237
2354
  props: { isRoot: true },
2238
2355
  });
2239
2356
 
@@ -2683,6 +2800,13 @@ All monitoring checks are passing normally.`;
2683
2800
  projectId: ObjectID;
2684
2801
  }): Promise<Record<string, { id: string; name: string }>> {
2685
2802
  logger.debug("=== refreshTeams called ===");
2803
+
2804
+ if (!data.projectId) {
2805
+ throw new BadDataException(
2806
+ "projectId is required to refresh Microsoft Teams teams",
2807
+ );
2808
+ }
2809
+
2686
2810
  logger.debug(`Project ID: ${data.projectId.toString()}`);
2687
2811
 
2688
2812
  try {
@@ -2699,10 +2823,19 @@ All monitoring checks are passing normally.`;
2699
2823
  );
2700
2824
  }
2701
2825
 
2826
+ const tenantId: string | undefined = projectAuth.workspaceProjectId;
2827
+
2828
+ if (!tenantId) {
2829
+ throw new BadDataException(
2830
+ "Microsoft Teams tenant ID not found for this project",
2831
+ );
2832
+ }
2833
+
2702
2834
  // Get a valid app access token
2703
2835
  const accessToken: string | null = await this.refreshAccessToken({
2704
2836
  projectId: data.projectId,
2705
2837
  miscData: projectAuth.miscData as MicrosoftTeamsMiscData,
2838
+ tenantId,
2706
2839
  });
2707
2840
 
2708
2841
  if (!accessToken) {
@@ -2759,11 +2892,13 @@ All monitoring checks are passing normally.`;
2759
2892
  const miscData: MicrosoftTeamsMiscData =
2760
2893
  (projectAuth.miscData as MicrosoftTeamsMiscData) || {};
2761
2894
  miscData.availableTeams = availableTeams;
2895
+ miscData.tenantId = tenantId;
2762
2896
 
2763
2897
  await WorkspaceProjectAuthTokenService.updateOneById({
2764
2898
  id: projectAuth.id!,
2765
2899
  data: {
2766
2900
  miscData: miscData,
2901
+ workspaceProjectId: tenantId,
2767
2902
  },
2768
2903
  props: {
2769
2904
  isRoot: true,
@@ -80,9 +80,6 @@ enum Permission {
80
80
  EditTelemetryServiceMetrics = "EditTelemetryServiceMetrics",
81
81
  ReadTelemetryServiceMetrics = "ReadTelemetryServiceMetrics",
82
82
 
83
- // Telemetry Attributes
84
- DeleteTelemetryAttributes = "DeleteTelemetryAttributes",
85
-
86
83
  // Billing Permissions (Owner Permission)
87
84
  ManageProjectBilling = "ManageProjectBilling",
88
85
 
@@ -30,6 +30,9 @@ export interface ComponentProps<T extends GenericObject> {
30
30
  isModalLoading?: boolean;
31
31
  onFilterRefreshClick?: undefined | (() => void);
32
32
  filterData?: FilterData<T> | undefined;
33
+ onAdvancedFiltersToggle?:
34
+ | undefined
35
+ | ((showAdvancedFilters: boolean) => void);
33
36
  }
34
37
 
35
38
  type FilterComponentFunction = <T extends GenericObject>(
@@ -355,7 +358,7 @@ const FilterComponent: FilterComponentFunction = <T extends GenericObject>(
355
358
  <div>
356
359
  {showViewer && (
357
360
  <div>
358
- <div className="mt-5 mb-5 bg-gray-50 rounded rounded-xl p-5 border border-2 border-gray-100">
361
+ <div className="mt-5 mb-5 bg-gray-50 rounded-xl p-5 border-2 border-gray-100">
359
362
  <div className="flex mt-1 mb-2">
360
363
  <div className="flex-auto py-0.5 text-sm leading-5">
361
364
  <span className="font-semibold">
@@ -442,6 +445,7 @@ const FilterComponent: FilterComponentFunction = <T extends GenericObject>(
442
445
  onFilterChanged={(filterData: FilterData<T>) => {
443
446
  setTempFilterDataForModal(filterData);
444
447
  }}
448
+ onAdvancedFiltersToggle={props.onAdvancedFiltersToggle}
445
449
  />
446
450
  </Modal>
447
451
  )}
@@ -24,6 +24,9 @@ export interface ComponentProps<T extends GenericObject> {
24
24
  isFilterLoading?: undefined | boolean;
25
25
  filterError?: string | undefined;
26
26
  onFilterRefreshClick?: undefined | (() => void);
27
+ onAdvancedFiltersToggle?:
28
+ | undefined
29
+ | ((showAdvancedFilters: boolean) => void);
27
30
  }
28
31
 
29
32
  type FiltersFormFunction = <T extends GenericObject>(
@@ -131,7 +134,11 @@ const FiltersForm: FiltersFormFunction = <T extends GenericObject>(
131
134
  : "Show Advanced Filters"
132
135
  }
133
136
  onClick={() => {
134
- setShowMoreFilters(!showMoreFilters);
137
+ setShowMoreFilters((currentValue: boolean) => {
138
+ const newValue: boolean = !currentValue;
139
+ props.onAdvancedFiltersToggle?.(newValue);
140
+ return newValue;
141
+ });
135
142
  }}
136
143
  />
137
144
  )}
@@ -43,6 +43,9 @@ export interface ComponentProps<T extends GenericObject> {
43
43
  onFilterRefreshClick?: undefined | (() => void);
44
44
  onFilterModalClose?: (() => void) | undefined;
45
45
  onFilterModalOpen?: (() => void) | undefined;
46
+ onAdvancedFiltersToggle?:
47
+ | undefined
48
+ | ((showAdvancedFilters: boolean) => void);
46
49
  }
47
50
 
48
51
  type ListFunction = <T extends GenericObject>(
@@ -118,6 +121,7 @@ const List: ListFunction = <T extends GenericObject>(
118
121
  }}
119
122
  singularLabel={props.singularLabel}
120
123
  pluralLabel={props.pluralLabel}
124
+ onAdvancedFiltersToggle={props.onAdvancedFiltersToggle}
121
125
  />
122
126
  </div>
123
127
  <div className="">
@@ -10,7 +10,12 @@ import {
10
10
  } from "../../../Types/FunctionTypes";
11
11
  import Log from "../../../Models/AnalyticsModels/Log";
12
12
  import LogSeverity from "../../../Types/Log/LogSeverity";
13
- import React, { FunctionComponent, ReactElement, Ref } from "react";
13
+ import React, {
14
+ FunctionComponent,
15
+ ReactElement,
16
+ Ref,
17
+ useCallback,
18
+ } from "react";
14
19
  import Toggle from "../Toggle/Toggle";
15
20
  import Card from "../Card/Card";
16
21
  import Button, { ButtonSize, ButtonStyleType } from "../Button/Button";
@@ -58,6 +63,13 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
58
63
  React.useRef<HTMLDivElement>(null);
59
64
 
60
65
  const [logAttributes, setLogAttributes] = React.useState<Array<string>>([]);
66
+ const [attributesLoaded, setAttributesLoaded] =
67
+ React.useState<boolean>(false);
68
+ const [attributesLoading, setAttributesLoading] =
69
+ React.useState<boolean>(false);
70
+ const [attributesError, setAttributesError] = React.useState<string>("");
71
+ const [areAdvancedFiltersVisible, setAreAdvancedFiltersVisible] =
72
+ React.useState<boolean>(false);
61
73
 
62
74
  const [isPageLoading, setIsPageLoading] = React.useState<boolean>(true);
63
75
  const [pageError, setPageError] = React.useState<string>("");
@@ -66,66 +78,83 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
66
78
  Dictionary<TelemetryService>
67
79
  >({});
68
80
 
69
- const loadAttributes: PromiseVoidFunction = async (): Promise<void> => {
70
- try {
71
- setIsPageLoading(true);
72
-
73
- const telemetryServices: ListResult<TelemetryService> =
74
- await ModelAPI.getList({
75
- modelType: TelemetryService,
76
- query: {},
77
- select: {
78
- name: true,
79
- serviceColor: true,
80
- },
81
- limit: LIMIT_PER_PROJECT,
82
- skip: 0,
83
- sort: {
84
- name: SortOrder.Ascending,
85
- },
86
- });
87
- const services: Dictionary<TelemetryService> = {};
88
-
89
- telemetryServices.data.forEach((service: TelemetryService) => {
90
- services[service.id!.toString()!] = service;
91
- });
92
-
93
- setServiceMap(services);
94
-
95
- const attributeRepsonse: HTTPResponse<JSONObject> | HTTPErrorResponse =
96
- await API.post({
97
- url: URL.fromString(APP_API_URL.toString()).addRoute(
98
- "/telemetry/logs/get-attributes",
99
- ),
100
- data: {},
101
- headers: {
102
- ...ModelAPI.getCommonHeaders(),
103
- },
81
+ const loadTelemetryServices: PromiseVoidFunction =
82
+ useCallback(async (): Promise<void> => {
83
+ try {
84
+ setIsPageLoading(true);
85
+ setPageError("");
86
+
87
+ const telemetryServices: ListResult<TelemetryService> =
88
+ await ModelAPI.getList({
89
+ modelType: TelemetryService,
90
+ query: {},
91
+ select: {
92
+ name: true,
93
+ serviceColor: true,
94
+ },
95
+ limit: LIMIT_PER_PROJECT,
96
+ skip: 0,
97
+ sort: {
98
+ name: SortOrder.Ascending,
99
+ },
100
+ });
101
+ const services: Dictionary<TelemetryService> = {};
102
+
103
+ telemetryServices.data.forEach((service: TelemetryService) => {
104
+ services[service.id!.toString()!] = service;
104
105
  });
105
106
 
106
- if (attributeRepsonse instanceof HTTPErrorResponse) {
107
- throw attributeRepsonse;
108
- } else {
109
- const attributes: Array<string> = attributeRepsonse.data[
107
+ setServiceMap(services);
108
+ } catch (err) {
109
+ setPageError(
110
+ `We couldn't load telemetry service metadata. ${API.getFriendlyErrorMessage(err as Error)}`,
111
+ );
112
+ } finally {
113
+ setIsPageLoading(false);
114
+ }
115
+ }, []);
116
+
117
+ const loadAttributes: PromiseVoidFunction =
118
+ useCallback(async (): Promise<void> => {
119
+ try {
120
+ setAttributesLoading(true);
121
+ setAttributesError("");
122
+
123
+ const attributeRepsonse: HTTPResponse<JSONObject> | HTTPErrorResponse =
124
+ await API.post({
125
+ url: URL.fromString(APP_API_URL.toString()).addRoute(
126
+ "/telemetry/logs/get-attributes",
127
+ ),
128
+ data: {},
129
+ headers: {
130
+ ...ModelAPI.getCommonHeaders(),
131
+ },
132
+ });
133
+
134
+ if (attributeRepsonse instanceof HTTPErrorResponse) {
135
+ throw attributeRepsonse;
136
+ }
137
+
138
+ const attributes: Array<string> = (attributeRepsonse.data[
110
139
  "attributes"
111
- ] as Array<string>;
140
+ ] || []) as Array<string>;
112
141
  setLogAttributes(attributes);
142
+ setAttributesLoaded(true);
143
+ } catch (err) {
144
+ setLogAttributes([]);
145
+ setAttributesLoaded(false);
146
+ setAttributesError(
147
+ `We couldn't load log attributes. Filters may be limited. ${API.getFriendlyErrorMessage(err as Error)}`,
148
+ );
149
+ } finally {
150
+ setAttributesLoading(false);
113
151
  }
114
-
115
- setIsPageLoading(false);
116
- setPageError("");
117
- } catch (err) {
118
- setIsPageLoading(false);
119
- setPageError(API.getFriendlyErrorMessage(err as Error));
120
- }
121
- };
152
+ }, []);
122
153
 
123
154
  // Update the screen height when the window is resized
124
155
 
125
156
  React.useEffect(() => {
126
- loadAttributes().catch((err: unknown) => {
127
- setPageError(API.getFriendlyErrorMessage(err as Error));
128
- });
157
+ void loadTelemetryServices();
129
158
 
130
159
  const handleResize: any = (): void => {
131
160
  setScreenHeight(window.innerHeight);
@@ -136,7 +165,7 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
136
165
  return () => {
137
166
  window.removeEventListener("resize", handleResize);
138
167
  };
139
- }, []);
168
+ }, [loadTelemetryServices]);
140
169
 
141
170
  // Keep scroll to the bottom of the log
142
171
 
@@ -180,11 +209,9 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
180
209
  if (isPageLoading) {
181
210
  return <PageLoader isVisible={true} />;
182
211
  }
183
-
184
212
  if (pageError) {
185
213
  return <ErrorMessage message={pageError} />;
186
214
  }
187
-
188
215
  return (
189
216
  <div>
190
217
  {props.showFilters && (
@@ -198,6 +225,26 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
198
225
  onFilterChanged={(filterData: Query<Log>) => {
199
226
  setFilterData(filterData);
200
227
  }}
228
+ onAdvancedFiltersToggle={(show: boolean) => {
229
+ setAreAdvancedFiltersVisible(show);
230
+
231
+ if (show && !attributesLoaded && !attributesLoading) {
232
+ void loadAttributes();
233
+ }
234
+ }}
235
+ isFilterLoading={areAdvancedFiltersVisible && attributesLoading}
236
+ filterError={
237
+ areAdvancedFiltersVisible && attributesError
238
+ ? attributesError
239
+ : undefined
240
+ }
241
+ onFilterRefreshClick={
242
+ areAdvancedFiltersVisible && attributesError
243
+ ? () => {
244
+ void loadAttributes();
245
+ }
246
+ : undefined
247
+ }
201
248
  filters={[
202
249
  {
203
250
  key: "body",
@@ -14,7 +14,7 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
14
14
  props: ComponentProps,
15
15
  ): ReactElement => {
16
16
  return (
17
- <div className="max-w-none p-3">
17
+ <div className="max-w-none">
18
18
  <ReactMarkdown
19
19
  components={{
20
20
  // because tailwind does not supply <h1 ... /> styles https://tailwindcss.com/docs/preflight#headings-are-unstyled
@@ -224,6 +224,10 @@ export interface BaseTableProps<
224
224
 
225
225
  formSummary?: FormSummaryConfig | undefined;
226
226
 
227
+ onAdvancedFiltersToggle?:
228
+ | undefined
229
+ | ((showAdvancedFilters: boolean) => void);
230
+
227
231
  /*
228
232
  * this key is used to save table user preferences in local storage.
229
233
  * If you provide this key, the table will save the user preferences in local storage.
@@ -1516,6 +1520,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
1516
1520
  onFilterModalOpen={() => {
1517
1521
  setShowFilterModal(true);
1518
1522
  }}
1523
+ onAdvancedFiltersToggle={props.onAdvancedFiltersToggle}
1519
1524
  onSortChanged={(
1520
1525
  sortBy: keyof TBaseModel | null,
1521
1526
  sortOrder: SortOrder,
@@ -1662,6 +1667,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
1662
1667
  onFilterModalOpen={() => {
1663
1668
  setShowFilterModal(true);
1664
1669
  }}
1670
+ onAdvancedFiltersToggle={props.onAdvancedFiltersToggle}
1665
1671
  singularLabel={props.singularName || model.singularName || "Item"}
1666
1672
  pluralLabel={props.pluralName || model.pluralName || "Items"}
1667
1673
  error={error}
@@ -54,6 +54,9 @@ export interface ComponentProps<T extends GenericObject> {
54
54
  onFilterModalClose?: (() => void) | undefined;
55
55
  onFilterModalOpen?: (() => void) | undefined;
56
56
  filterData?: undefined | FilterData<T>;
57
+ onAdvancedFiltersToggle?:
58
+ | undefined
59
+ | ((showAdvancedFilters: boolean) => void);
57
60
 
58
61
  enableDragAndDrop?: boolean | undefined;
59
62
  dragDropIndexField?: keyof T | undefined;
@@ -242,6 +245,7 @@ const Table: TableFunction = <T extends GenericObject>(
242
245
  singularLabel={props.singularLabel}
243
246
  pluralLabel={props.pluralLabel}
244
247
  filterData={props.filterData}
248
+ onAdvancedFiltersToggle={props.onAdvancedFiltersToggle}
245
249
  />
246
250
  {props.bulkActions?.buttons && (
247
251
  <BulkUpdateForm