@backstage-community/plugin-servicenow 1.5.1 → 1.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @backstage-community/plugin-servicenow
2
2
 
3
+ ## 1.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8472540: Support custom ServiceNow incident table fields in entity annotations
8
+
3
9
  ## 1.5.1
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -25,7 +25,7 @@ Software Catalog integration:
25
25
 
26
26
  ```tsx
27
27
  import {
28
- EntityServicenowContent,
28
+ Router,
29
29
  isServicenowAvailable,
30
30
  isMyProfile,
31
31
  } from '@backstage-community/plugin-servicenow';
@@ -40,7 +40,7 @@ Software Catalog integration:
40
40
  title="ServiceNow"
41
41
  if={isServicenowAvailable}
42
42
  >
43
- <EntityServicenowContent />
43
+ <Router />
44
44
  </EntityLayout.Route>
45
45
  ```
46
46
 
@@ -49,7 +49,7 @@ Software Catalog integration:
49
49
 
50
50
  ```tsx
51
51
  <EntityLayout.Route path="/servicenow" title="ServiceNow" if={isMyProfile}>
52
- <EntityServicenowContent />
52
+ <Router />
53
53
  </EntityLayout.Route>
54
54
  ```
55
55
 
@@ -18,7 +18,16 @@ class ServiceNowBackendClient {
18
18
  };
19
19
  const response = await this.fetchApi.fetch(url, { headers });
20
20
  if (!response.ok) {
21
- throw new Error(`ServiceNow API request failed: ${response.status}`);
21
+ let errorBody;
22
+ try {
23
+ errorBody = await response.json();
24
+ } catch (e) {
25
+ throw new Error(
26
+ `ServiceNow API request failed: ${response.status} - Could not parse error response.`
27
+ );
28
+ }
29
+ const errorMessage = errorBody?.error?.message ?? `ServiceNow API request failed: ${response.status}`;
30
+ throw new Error(errorMessage);
22
31
  }
23
32
  return await response.json();
24
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ServiceNowBackendClient.esm.js","sources":["../../src/api/ServiceNowBackendClient.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { IncidentPick } from '@backstage-community/plugin-servicenow-common';\nimport {\n createApiRef,\n DiscoveryApi,\n FetchApi,\n IdentityApi,\n} from '@backstage/core-plugin-api';\nimport { IncidentsData, PaginatedIncidentsData } from '../types';\n\nexport interface ServiceNowBackendAPI {\n getIncidents(queryParams: URLSearchParams): Promise<PaginatedIncidentsData>;\n}\n\nexport const serviceNowApiRef = createApiRef<ServiceNowBackendAPI>({\n id: 'plugin.servicenow.service',\n});\n\nexport class ServiceNowBackendClient implements ServiceNowBackendAPI {\n constructor(\n private readonly discoveryApi: DiscoveryApi,\n private readonly fetchApi: FetchApi,\n private readonly identityApi: IdentityApi,\n ) {}\n\n private async fetchFromServiceNow<T>(\n path: string,\n queryParams?: URLSearchParams,\n ): Promise<{ items: T; totalCount: number }> {\n const proxyBase = await this.discoveryApi.getBaseUrl('servicenow');\n const url = `${proxyBase}${path}${queryParams ? `?${queryParams}` : ''}`;\n\n const { token } = await this.identityApi.getCredentials();\n const headers = {\n Authorization: `Bearer ${token}`,\n };\n\n const response = await this.fetchApi.fetch(url, { headers });\n if (!response.ok) {\n throw new Error(`ServiceNow API request failed: ${response.status}`);\n }\n\n return (await response.json()) as { items: T; totalCount: number };\n }\n\n async getIncidents(\n queryParams: URLSearchParams,\n ): Promise<PaginatedIncidentsData> {\n const { items, totalCount } = await this.fetchFromServiceNow<\n IncidentPick[]\n >('/incidents', queryParams);\n\n const incidents = incidentsPickToIncidentsData(items);\n return { incidents, totalCount };\n }\n}\n\nexport function incidentsPickToIncidentsData(\n data: IncidentPick[],\n): IncidentsData[] {\n return data.map(item => ({\n sysId: item.sys_id,\n number: item.number,\n shortDescription: item.short_description,\n description: item.description,\n sysCreatedOn: item.sys_created_on,\n priority: item.priority,\n incidentState: item.incident_state,\n url: item.url,\n }));\n}\n"],"names":[],"mappings":";;AA4BO,MAAM,mBAAmB,YAAmC,CAAA;AAAA,EACjE,EAAI,EAAA;AACN,CAAC;AAEM,MAAM,uBAAwD,CAAA;AAAA,EACnE,WAAA,CACmB,YACA,EAAA,QAAA,EACA,WACjB,EAAA;AAHiB,IAAA,IAAA,CAAA,YAAA,GAAA,YAAA;AACA,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AACA,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAAA;AAChB,EAEH,MAAc,mBACZ,CAAA,IAAA,EACA,WAC2C,EAAA;AAC3C,IAAA,MAAM,SAAY,GAAA,MAAM,IAAK,CAAA,YAAA,CAAa,WAAW,YAAY,CAAA;AACjE,IAAM,MAAA,GAAA,GAAM,CAAG,EAAA,SAAS,CAAG,EAAA,IAAI,GAAG,WAAc,GAAA,CAAA,CAAA,EAAI,WAAW,CAAA,CAAA,GAAK,EAAE,CAAA,CAAA;AAEtE,IAAA,MAAM,EAAE,KAAM,EAAA,GAAI,MAAM,IAAA,CAAK,YAAY,cAAe,EAAA;AACxD,IAAA,MAAM,OAAU,GAAA;AAAA,MACd,aAAA,EAAe,UAAU,KAAK,CAAA;AAAA,KAChC;AAEA,IAAM,MAAA,QAAA,GAAW,MAAM,IAAK,CAAA,QAAA,CAAS,MAAM,GAAK,EAAA,EAAE,SAAS,CAAA;AAC3D,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAkC,+BAAA,EAAA,QAAA,CAAS,MAAM,CAAE,CAAA,CAAA;AAAA;AAGrE,IAAQ,OAAA,MAAM,SAAS,IAAK,EAAA;AAAA;AAC9B,EAEA,MAAM,aACJ,WACiC,EAAA;AACjC,IAAM,MAAA,EAAE,OAAO,UAAW,EAAA,GAAI,MAAM,IAAK,CAAA,mBAAA,CAEvC,cAAc,WAAW,CAAA;AAE3B,IAAM,MAAA,SAAA,GAAY,6BAA6B,KAAK,CAAA;AACpD,IAAO,OAAA,EAAE,WAAW,UAAW,EAAA;AAAA;AAEnC;AAEO,SAAS,6BACd,IACiB,EAAA;AACjB,EAAO,OAAA,IAAA,CAAK,IAAI,CAAS,IAAA,MAAA;AAAA,IACvB,OAAO,IAAK,CAAA,MAAA;AAAA,IACZ,QAAQ,IAAK,CAAA,MAAA;AAAA,IACb,kBAAkB,IAAK,CAAA,iBAAA;AAAA,IACvB,aAAa,IAAK,CAAA,WAAA;AAAA,IAClB,cAAc,IAAK,CAAA,cAAA;AAAA,IACnB,UAAU,IAAK,CAAA,QAAA;AAAA,IACf,eAAe,IAAK,CAAA,cAAA;AAAA,IACpB,KAAK,IAAK,CAAA;AAAA,GACV,CAAA,CAAA;AACJ;;;;"}
1
+ {"version":3,"file":"ServiceNowBackendClient.esm.js","sources":["../../src/api/ServiceNowBackendClient.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { IncidentPick } from '@backstage-community/plugin-servicenow-common';\nimport {\n createApiRef,\n DiscoveryApi,\n FetchApi,\n IdentityApi,\n} from '@backstage/core-plugin-api';\nimport { IncidentsData, PaginatedIncidentsData } from '../types';\n\nexport interface ServiceNowBackendAPI {\n getIncidents(queryParams: URLSearchParams): Promise<PaginatedIncidentsData>;\n}\n\nexport const serviceNowApiRef = createApiRef<ServiceNowBackendAPI>({\n id: 'plugin.servicenow.service',\n});\n\nexport class ServiceNowBackendClient implements ServiceNowBackendAPI {\n constructor(\n private readonly discoveryApi: DiscoveryApi,\n private readonly fetchApi: FetchApi,\n private readonly identityApi: IdentityApi,\n ) {}\n\n private async fetchFromServiceNow<T>(\n path: string,\n queryParams?: URLSearchParams,\n ): Promise<{ items: T; totalCount: number }> {\n const proxyBase = await this.discoveryApi.getBaseUrl('servicenow');\n const url = `${proxyBase}${path}${queryParams ? `?${queryParams}` : ''}`;\n\n const { token } = await this.identityApi.getCredentials();\n const headers = {\n Authorization: `Bearer ${token}`,\n };\n\n const response = await this.fetchApi.fetch(url, { headers });\n if (!response.ok) {\n let errorBody;\n try {\n errorBody = await response.json();\n } catch (e) {\n throw new Error(\n `ServiceNow API request failed: ${response.status} - Could not parse error response.`,\n );\n }\n\n const errorMessage =\n errorBody?.error?.message ??\n `ServiceNow API request failed: ${response.status}`;\n throw new Error(errorMessage);\n }\n\n return (await response.json()) as { items: T; totalCount: number };\n }\n\n async getIncidents(\n queryParams: URLSearchParams,\n ): Promise<PaginatedIncidentsData> {\n const { items, totalCount } = await this.fetchFromServiceNow<\n IncidentPick[]\n >('/incidents', queryParams);\n\n const incidents = incidentsPickToIncidentsData(items);\n return { incidents, totalCount };\n }\n}\n\nexport function incidentsPickToIncidentsData(\n data: IncidentPick[],\n): IncidentsData[] {\n return data.map(item => ({\n sysId: item.sys_id,\n number: item.number,\n shortDescription: item.short_description,\n description: item.description,\n sysCreatedOn: item.sys_created_on,\n priority: item.priority,\n incidentState: item.incident_state,\n url: item.url,\n }));\n}\n"],"names":[],"mappings":";;AA4BO,MAAM,mBAAmB,YAAmC,CAAA;AAAA,EACjE,EAAI,EAAA;AACN,CAAC;AAEM,MAAM,uBAAwD,CAAA;AAAA,EACnE,WAAA,CACmB,YACA,EAAA,QAAA,EACA,WACjB,EAAA;AAHiB,IAAA,IAAA,CAAA,YAAA,GAAA,YAAA;AACA,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AACA,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAAA;AAChB,EAEH,MAAc,mBACZ,CAAA,IAAA,EACA,WAC2C,EAAA;AAC3C,IAAA,MAAM,SAAY,GAAA,MAAM,IAAK,CAAA,YAAA,CAAa,WAAW,YAAY,CAAA;AACjE,IAAM,MAAA,GAAA,GAAM,CAAG,EAAA,SAAS,CAAG,EAAA,IAAI,GAAG,WAAc,GAAA,CAAA,CAAA,EAAI,WAAW,CAAA,CAAA,GAAK,EAAE,CAAA,CAAA;AAEtE,IAAA,MAAM,EAAE,KAAM,EAAA,GAAI,MAAM,IAAA,CAAK,YAAY,cAAe,EAAA;AACxD,IAAA,MAAM,OAAU,GAAA;AAAA,MACd,aAAA,EAAe,UAAU,KAAK,CAAA;AAAA,KAChC;AAEA,IAAM,MAAA,QAAA,GAAW,MAAM,IAAK,CAAA,QAAA,CAAS,MAAM,GAAK,EAAA,EAAE,SAAS,CAAA;AAC3D,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAI,IAAA,SAAA;AACJ,MAAI,IAAA;AACF,QAAY,SAAA,GAAA,MAAM,SAAS,IAAK,EAAA;AAAA,eACzB,CAAG,EAAA;AACV,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,+BAAA,EAAkC,SAAS,MAAM,CAAA,kCAAA;AAAA,SACnD;AAAA;AAGF,MAAA,MAAM,eACJ,SAAW,EAAA,KAAA,EAAO,OAClB,IAAA,CAAA,+BAAA,EAAkC,SAAS,MAAM,CAAA,CAAA;AACnD,MAAM,MAAA,IAAI,MAAM,YAAY,CAAA;AAAA;AAG9B,IAAQ,OAAA,MAAM,SAAS,IAAK,EAAA;AAAA;AAC9B,EAEA,MAAM,aACJ,WACiC,EAAA;AACjC,IAAM,MAAA,EAAE,OAAO,UAAW,EAAA,GAAI,MAAM,IAAK,CAAA,mBAAA,CAEvC,cAAc,WAAW,CAAA;AAE3B,IAAM,MAAA,SAAA,GAAY,6BAA6B,KAAK,CAAA;AACpD,IAAO,OAAA,EAAE,WAAW,UAAW,EAAA;AAAA;AAEnC;AAEO,SAAS,6BACd,IACiB,EAAA;AACjB,EAAO,OAAA,IAAA,CAAK,IAAI,CAAS,IAAA,MAAA;AAAA,IACvB,OAAO,IAAK,CAAA,MAAA;AAAA,IACZ,QAAQ,IAAK,CAAA,MAAA;AAAA,IACb,kBAAkB,IAAK,CAAA,iBAAA;AAAA,IACvB,aAAa,IAAK,CAAA,WAAA;AAAA,IAClB,cAAc,IAAK,CAAA,cAAA;AAAA,IACnB,UAAU,IAAK,CAAA,QAAA;AAAA,IACf,eAAe,IAAK,CAAA,cAAA;AAAA,IACpB,KAAK,IAAK,CAAA;AAAA,GACV,CAAA,CAAA;AACJ;;;;"}
@@ -0,0 +1,10 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { MemoryRouter } from 'react-router-dom';
3
+ import { EntityServicenowContent } from './Servicenow/EntityServicenowContent.esm.js';
4
+
5
+ const Router = () => {
6
+ return /* @__PURE__ */ jsx(MemoryRouter, { children: /* @__PURE__ */ jsx(EntityServicenowContent, {}) });
7
+ };
8
+
9
+ export { Router };
10
+ //# sourceMappingURL=Router.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Router.esm.js","sources":["../../src/components/Router.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { MemoryRouter } from 'react-router-dom';\nimport { EntityServicenowContent } from './Servicenow';\n\n/**\n * separately export the router\n *\n * @public\n */\nexport const Router = () => {\n return (\n <MemoryRouter>\n <EntityServicenowContent />\n </MemoryRouter>\n );\n};\n"],"names":[],"mappings":";;;;AAuBO,MAAM,SAAS,MAAM;AAC1B,EAAA,uBACG,GAAA,CAAA,YAAA,EAAA,EACC,QAAC,kBAAA,GAAA,CAAA,uBAAA,EAAA,EAAwB,CAC3B,EAAA,CAAA;AAEJ;;;;"}
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { useState, useEffect } from 'react';
2
+ import { useState, useMemo, useEffect } from 'react';
3
3
  import { useApi, identityApiRef } from '@backstage/core-plugin-api';
4
4
  import { useEntity, CatalogFilterLayout } from '@backstage/plugin-catalog-react';
5
5
  import { Table } from '@backstage/core-components';
@@ -9,7 +9,7 @@ import TableBody from '@mui/material/TableBody';
9
9
  import TableRow from '@mui/material/TableRow';
10
10
  import TableCell from '@mui/material/TableCell';
11
11
  import TablePagination from '@mui/material/TablePagination';
12
- import { SortingOrderEnum, ServiceAnnotationFieldName } from '@backstage-community/plugin-servicenow-common';
12
+ import { SortingOrderEnum } from '@backstage-community/plugin-servicenow-common';
13
13
  import { IncidentsFilter } from './IncidentsFilter.esm.js';
14
14
  import { useIncidentsListColumns } from './IncidentsListColumns.esm.js';
15
15
  import { IncidentsTableBody } from './IncidentsTableBody.esm.js';
@@ -47,7 +47,23 @@ const EntityServicenowContent = () => {
47
47
  const [loading, setLoading] = useState(false);
48
48
  const [error, setError] = useState(null);
49
49
  const updateQueryParams = useUpdateQueryParams();
50
- const entityId = entity.metadata.annotations?.[ServiceAnnotationFieldName];
50
+ const servicenowAnnotations = useMemo(() => {
51
+ const annotations = entity.metadata.annotations;
52
+ const snAnnotations = {};
53
+ if (annotations) {
54
+ for (const [key, value] of Object.entries(annotations)) {
55
+ if (key.startsWith("servicenow.com/entity-id")) {
56
+ snAnnotations.entityId = value;
57
+ continue;
58
+ }
59
+ if (key.startsWith("servicenow.com/")) {
60
+ const field = key.substring("servicenow.com/".length);
61
+ snAnnotations[field] = value;
62
+ }
63
+ }
64
+ }
65
+ return snAnnotations;
66
+ }, [entity.metadata.annotations]);
51
67
  const kind = entity.kind.toLocaleLowerCase("en-US");
52
68
  const userEmail = useUserEmail(kind);
53
69
  const identityApi = useApi(identityApiRef);
@@ -68,7 +84,7 @@ const EntityServicenowContent = () => {
68
84
  async function fetchIncidents() {
69
85
  setLoading(true);
70
86
  setError(null);
71
- if (!userEmail && !entityId) {
87
+ if (!userEmail && Object.keys(servicenowAnnotations).length === 0) {
72
88
  setIncidentsData([]);
73
89
  setLoading(false);
74
90
  return;
@@ -83,7 +99,7 @@ const EntityServicenowContent = () => {
83
99
  priority: priority ?? void 0,
84
100
  state: state ?? void 0,
85
101
  userEmail,
86
- entityId
102
+ ...servicenowAnnotations
87
103
  });
88
104
  const { incidents, totalCount } = await serviceNowApi.getIncidents(
89
105
  queryParams
@@ -105,7 +121,7 @@ const EntityServicenowContent = () => {
105
121
  orderBy,
106
122
  search,
107
123
  serviceNowApi,
108
- entityId,
124
+ servicenowAnnotations,
109
125
  priority,
110
126
  state,
111
127
  userEmail
@@ -1 +1 @@
1
- {"version":3,"file":"EntityServicenowContent.esm.js","sources":["../../../src/components/Servicenow/EntityServicenowContent.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useEffect, useState, MouseEvent, ChangeEvent } from 'react';\nimport { identityApiRef, useApi } from '@backstage/core-plugin-api';\n\nimport {\n CatalogFilterLayout,\n useEntity,\n} from '@backstage/plugin-catalog-react';\nimport { Table } from '@backstage/core-components';\n\nimport Box from '@mui/material/Box';\nimport CircularProgress from '@mui/material/CircularProgress';\nimport TableBody from '@mui/material/TableBody';\nimport TableRow from '@mui/material/TableRow';\nimport TableCell from '@mui/material/TableCell';\nimport TablePagination from '@mui/material/TablePagination';\n\nimport {\n Order,\n SortingOrderEnum,\n ServiceAnnotationFieldName,\n} from '@backstage-community/plugin-servicenow-common';\n\nimport { IncidentsFilter } from './IncidentsFilter';\nimport { useIncidentsListColumns } from './IncidentsListColumns';\nimport { IncidentsTableBody } from './IncidentsTableBody';\nimport { IncidentsTableHeader } from './IncidentsTableHeader';\nimport {\n IncidentTableFieldEnum,\n type IncidentsData,\n type IncidentTableField,\n} from '../../types';\nimport { buildIncidentQueryParams } from '../../utils/queryParamsUtils';\nimport { useDebouncedValue } from '../../hooks/useDebouncedValue';\nimport { useQueryState } from '../../hooks/useQueryState';\nimport { useQueryArrayState } from '../../hooks/useQueryArrayState';\nimport { serviceNowApiRef } from '../../api/ServiceNowBackendClient';\nimport useUserEmail from '../../hooks/useUserEmail';\nimport { useUpdateQueryParams } from '../../hooks/useQueryHelpers';\nimport { useTranslation } from '../../hooks/useTranslation';\n\nexport const EntityServicenowContent = () => {\n const { t } = useTranslation();\n const { entity } = useEntity();\n const serviceNowApi = useApi(serviceNowApiRef);\n const incidentsListColumns = useIncidentsListColumns();\n\n const [search, setSearch] = useQueryState<string>('search', '');\n const [input, setInput] = useState(search);\n const debouncedInput = useDebouncedValue(input, 300);\n\n const [order, setOrder] = useQueryState<Order>('order', SortingOrderEnum.Asc);\n const [orderBy, setOrderBy] = useQueryState<IncidentTableField>(\n 'orderBy',\n IncidentTableFieldEnum.Number,\n );\n\n const [rowsPerPage] = useQueryState<number>('limit', 5);\n const [offset, setOffset] = useQueryState<number>('offset', 0);\n const [state] = useQueryArrayState('state', []);\n const [priority] = useQueryArrayState('priority', []);\n\n const pageNumber = Math.floor(offset / rowsPerPage);\n\n const [count, setCount] = useState<number>(0);\n const [incidentsData, setIncidentsData] = useState<IncidentsData[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const updateQueryParams = useUpdateQueryParams();\n\n const entityId = entity.metadata.annotations?.[ServiceAnnotationFieldName];\n\n const kind = entity.kind.toLocaleLowerCase('en-US');\n const userEmail = useUserEmail(kind);\n\n const identityApi = useApi(identityApiRef);\n\n useEffect(() => {\n identityApi.getBackstageIdentity().then(identity => {\n if (identity.userEntityRef) {\n localStorage.setItem('userEntityRef', identity.userEntityRef);\n }\n });\n }, [identityApi]);\n\n useEffect(() => {\n if (debouncedInput !== search) {\n setSearch(debouncedInput);\n setOffset(0);\n }\n }, [debouncedInput, search, setSearch, setOffset]);\n\n useEffect(() => {\n async function fetchIncidents() {\n setLoading(true);\n setError(null);\n\n if (!userEmail && !entityId) {\n setIncidentsData([]);\n setLoading(false);\n return;\n }\n\n try {\n const queryParams = buildIncidentQueryParams({\n limit: rowsPerPage,\n offset,\n order,\n orderBy,\n search: search,\n priority: priority ?? undefined,\n state: state ?? undefined,\n userEmail,\n entityId,\n });\n\n const { incidents, totalCount } = await serviceNowApi.getIncidents(\n queryParams,\n );\n setIncidentsData(incidents);\n setCount(totalCount);\n } catch (e) {\n setError((e as Error).message);\n setIncidentsData([]);\n } finally {\n setLoading(false);\n }\n }\n\n fetchIncidents();\n }, [\n rowsPerPage,\n offset,\n order,\n orderBy,\n search,\n serviceNowApi,\n entityId,\n priority,\n state,\n userEmail,\n ]);\n\n const handleRowsPerPageChange = (event: ChangeEvent<HTMLInputElement>) => {\n const newLimit = parseInt(event.target.value, 10);\n updateQueryParams({\n limit: String(newLimit),\n offset: '0',\n });\n };\n\n const handlePageChange = (_event: unknown, page: number) => {\n setOffset(page * rowsPerPage);\n };\n\n const handleRequestSort = (\n _event: MouseEvent<unknown>,\n property: IncidentTableField,\n ) => {\n const isAsc = orderBy === property && order === SortingOrderEnum.Asc;\n setOrder(isAsc ? SortingOrderEnum.Desc : SortingOrderEnum.Asc);\n setOrderBy(property);\n setOffset(0);\n };\n\n const handleSearch = (str: string) => {\n setInput(str);\n };\n\n const IncidentsTableHeaderComponent = () => (\n <IncidentsTableHeader\n order={order}\n orderBy={orderBy}\n onRequestSort={handleRequestSort}\n />\n );\n\n const IncidentsTableBodyComponent = () =>\n loading ? (\n <TableBody>\n <TableRow>\n <TableCell colSpan={incidentsListColumns.length} align=\"center\">\n <Box sx={{ py: 3 }}>\n <CircularProgress />\n </Box>\n </TableCell>\n </TableRow>\n </TableBody>\n ) : (\n <IncidentsTableBody rows={incidentsData} />\n );\n\n const TablePaginationComponent = () => {\n return (\n <TablePagination\n rowsPerPageOptions={[5, 10, 20, 50, 100].map(n => ({\n value: n,\n label: t('table.labelRowsSelect', { count: String(n) }),\n }))}\n component=\"div\"\n sx={{ mr: 1 }}\n count={count ?? 0}\n rowsPerPage={rowsPerPage}\n page={pageNumber}\n onPageChange={handlePageChange}\n onRowsPerPageChange={handleRowsPerPageChange}\n labelRowsPerPage={null}\n showFirstButton\n showLastButton\n />\n );\n };\n\n return (\n <Box>\n <CatalogFilterLayout>\n <CatalogFilterLayout.Filters>\n <IncidentsFilter />\n </CatalogFilterLayout.Filters>\n <CatalogFilterLayout.Content>\n {error ? (\n <Box sx={{ padding: 2, color: 'error.main' }}>\n {t('errors.loadingIncidents', { error })}\n </Box>\n ) : (\n <Table\n data={loading ? [] : incidentsData}\n columns={incidentsListColumns}\n onSearchChange={handleSearch}\n title={\n count === 0\n ? t('page.title')\n : t('page.titleWithCount', { count: String(count) })\n }\n localization={{\n toolbar: { searchPlaceholder: t('table.searchPlaceholder') },\n }}\n components={{\n Header: IncidentsTableHeaderComponent,\n Body: IncidentsTableBodyComponent,\n Pagination: TablePaginationComponent,\n }}\n emptyContent={\n loading ? null : (\n <Box\n data-testid=\"no-incidents-found\"\n sx={{\n padding: 2,\n display: 'flex',\n justifyContent: 'center',\n }}\n >\n {t('table.emptyContent')}\n </Box>\n )\n }\n isLoading={loading}\n />\n )}\n </CatalogFilterLayout.Content>\n </CatalogFilterLayout>\n </Box>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAwDO,MAAM,0BAA0B,MAAM;AAC3C,EAAM,MAAA,EAAE,CAAE,EAAA,GAAI,cAAe,EAAA;AAC7B,EAAM,MAAA,EAAE,MAAO,EAAA,GAAI,SAAU,EAAA;AAC7B,EAAM,MAAA,aAAA,GAAgB,OAAO,gBAAgB,CAAA;AAC7C,EAAA,MAAM,uBAAuB,uBAAwB,EAAA;AAErD,EAAA,MAAM,CAAC,MAAQ,EAAA,SAAS,CAAI,GAAA,aAAA,CAAsB,UAAU,EAAE,CAAA;AAC9D,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAS,MAAM,CAAA;AACzC,EAAM,MAAA,cAAA,GAAiB,iBAAkB,CAAA,KAAA,EAAO,GAAG,CAAA;AAEnD,EAAA,MAAM,CAAC,KAAO,EAAA,QAAQ,IAAI,aAAqB,CAAA,OAAA,EAAS,iBAAiB,GAAG,CAAA;AAC5E,EAAM,MAAA,CAAC,OAAS,EAAA,UAAU,CAAI,GAAA,aAAA;AAAA,IAC5B,SAAA;AAAA,IACA,sBAAuB,CAAA;AAAA,GACzB;AAEA,EAAA,MAAM,CAAC,WAAW,CAAI,GAAA,aAAA,CAAsB,SAAS,CAAC,CAAA;AACtD,EAAA,MAAM,CAAC,MAAQ,EAAA,SAAS,CAAI,GAAA,aAAA,CAAsB,UAAU,CAAC,CAAA;AAC7D,EAAA,MAAM,CAAC,KAAK,CAAA,GAAI,kBAAmB,CAAA,OAAA,EAAS,EAAE,CAAA;AAC9C,EAAA,MAAM,CAAC,QAAQ,CAAA,GAAI,kBAAmB,CAAA,UAAA,EAAY,EAAE,CAAA;AAEpD,EAAA,MAAM,UAAa,GAAA,IAAA,CAAK,KAAM,CAAA,MAAA,GAAS,WAAW,CAAA;AAElD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAiB,CAAC,CAAA;AAC5C,EAAA,MAAM,CAAC,aAAe,EAAA,gBAAgB,CAAI,GAAA,QAAA,CAA0B,EAAE,CAAA;AACtE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AACtD,EAAA,MAAM,oBAAoB,oBAAqB,EAAA;AAE/C,EAAA,MAAM,QAAW,GAAA,MAAA,CAAO,QAAS,CAAA,WAAA,GAAc,0BAA0B,CAAA;AAEzE,EAAA,MAAM,IAAO,GAAA,MAAA,CAAO,IAAK,CAAA,iBAAA,CAAkB,OAAO,CAAA;AAClD,EAAM,MAAA,SAAA,GAAY,aAAa,IAAI,CAAA;AAEnC,EAAM,MAAA,WAAA,GAAc,OAAO,cAAc,CAAA;AAEzC,EAAA,SAAA,CAAU,MAAM;AACd,IAAY,WAAA,CAAA,oBAAA,EAAuB,CAAA,IAAA,CAAK,CAAY,QAAA,KAAA;AAClD,MAAA,IAAI,SAAS,aAAe,EAAA;AAC1B,QAAa,YAAA,CAAA,OAAA,CAAQ,eAAiB,EAAA,QAAA,CAAS,aAAa,CAAA;AAAA;AAC9D,KACD,CAAA;AAAA,GACH,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,mBAAmB,MAAQ,EAAA;AAC7B,MAAA,SAAA,CAAU,cAAc,CAAA;AACxB,MAAA,SAAA,CAAU,CAAC,CAAA;AAAA;AACb,KACC,CAAC,cAAA,EAAgB,MAAQ,EAAA,SAAA,EAAW,SAAS,CAAC,CAAA;AAEjD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,eAAe,cAAiB,GAAA;AAC9B,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,QAAA,CAAS,IAAI,CAAA;AAEb,MAAI,IAAA,CAAC,SAAa,IAAA,CAAC,QAAU,EAAA;AAC3B,QAAA,gBAAA,CAAiB,EAAE,CAAA;AACnB,QAAA,UAAA,CAAW,KAAK,CAAA;AAChB,QAAA;AAAA;AAGF,MAAI,IAAA;AACF,QAAA,MAAM,cAAc,wBAAyB,CAAA;AAAA,UAC3C,KAAO,EAAA,WAAA;AAAA,UACP,MAAA;AAAA,UACA,KAAA;AAAA,UACA,OAAA;AAAA,UACA,MAAA;AAAA,UACA,UAAU,QAAY,IAAA,KAAA,CAAA;AAAA,UACtB,OAAO,KAAS,IAAA,KAAA,CAAA;AAAA,UAChB,SAAA;AAAA,UACA;AAAA,SACD,CAAA;AAED,QAAA,MAAM,EAAE,SAAA,EAAW,UAAW,EAAA,GAAI,MAAM,aAAc,CAAA,YAAA;AAAA,UACpD;AAAA,SACF;AACA,QAAA,gBAAA,CAAiB,SAAS,CAAA;AAC1B,QAAA,QAAA,CAAS,UAAU,CAAA;AAAA,eACZ,CAAG,EAAA;AACV,QAAA,QAAA,CAAU,EAAY,OAAO,CAAA;AAC7B,QAAA,gBAAA,CAAiB,EAAE,CAAA;AAAA,OACnB,SAAA;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA;AAClB;AAGF,IAAe,cAAA,EAAA;AAAA,GACd,EAAA;AAAA,IACD,WAAA;AAAA,IACA,MAAA;AAAA,IACA,KAAA;AAAA,IACA,OAAA;AAAA,IACA,MAAA;AAAA,IACA,aAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAM,MAAA,uBAAA,GAA0B,CAAC,KAAyC,KAAA;AACxE,IAAA,MAAM,QAAW,GAAA,QAAA,CAAS,KAAM,CAAA,MAAA,CAAO,OAAO,EAAE,CAAA;AAChD,IAAkB,iBAAA,CAAA;AAAA,MAChB,KAAA,EAAO,OAAO,QAAQ,CAAA;AAAA,MACtB,MAAQ,EAAA;AAAA,KACT,CAAA;AAAA,GACH;AAEA,EAAM,MAAA,gBAAA,GAAmB,CAAC,MAAA,EAAiB,IAAiB,KAAA;AAC1D,IAAA,SAAA,CAAU,OAAO,WAAW,CAAA;AAAA,GAC9B;AAEA,EAAM,MAAA,iBAAA,GAAoB,CACxB,MAAA,EACA,QACG,KAAA;AACH,IAAA,MAAM,KAAQ,GAAA,OAAA,KAAY,QAAY,IAAA,KAAA,KAAU,gBAAiB,CAAA,GAAA;AACjE,IAAA,QAAA,CAAS,KAAQ,GAAA,gBAAA,CAAiB,IAAO,GAAA,gBAAA,CAAiB,GAAG,CAAA;AAC7D,IAAA,UAAA,CAAW,QAAQ,CAAA;AACnB,IAAA,SAAA,CAAU,CAAC,CAAA;AAAA,GACb;AAEA,EAAM,MAAA,YAAA,GAAe,CAAC,GAAgB,KAAA;AACpC,IAAA,QAAA,CAAS,GAAG,CAAA;AAAA,GACd;AAEA,EAAA,MAAM,gCAAgC,sBACpC,GAAA;AAAA,IAAC,oBAAA;AAAA,IAAA;AAAA,MACC,KAAA;AAAA,MACA,OAAA;AAAA,MACA,aAAe,EAAA;AAAA;AAAA,GACjB;AAGF,EAAA,MAAM,2BAA8B,GAAA,MAClC,OACE,mBAAA,GAAA,CAAC,SACC,EAAA,EAAA,QAAA,kBAAA,GAAA,CAAC,QACC,EAAA,EAAA,QAAA,kBAAA,GAAA,CAAC,SAAU,EAAA,EAAA,OAAA,EAAS,oBAAqB,CAAA,MAAA,EAAQ,OAAM,QACrD,EAAA,QAAA,kBAAA,GAAA,CAAC,GAAI,EAAA,EAAA,EAAA,EAAI,EAAE,EAAA,EAAI,CAAE,EAAA,EACf,8BAAC,gBAAiB,EAAA,EAAA,CAAA,EACpB,CACF,EAAA,CAAA,EACF,CACF,EAAA,CAAA,mBAEC,GAAA,CAAA,kBAAA,EAAA,EAAmB,MAAM,aAAe,EAAA,CAAA;AAG7C,EAAA,MAAM,2BAA2B,MAAM;AACrC,IACE,uBAAA,GAAA;AAAA,MAAC,eAAA;AAAA,MAAA;AAAA,QACC,kBAAA,EAAoB,CAAC,CAAG,EAAA,EAAA,EAAI,IAAI,EAAI,EAAA,GAAG,CAAE,CAAA,GAAA,CAAI,CAAM,CAAA,MAAA;AAAA,UACjD,KAAO,EAAA,CAAA;AAAA,UACP,KAAA,EAAO,EAAE,uBAAyB,EAAA,EAAE,OAAO,MAAO,CAAA,CAAC,GAAG;AAAA,SACtD,CAAA,CAAA;AAAA,QACF,SAAU,EAAA,KAAA;AAAA,QACV,EAAA,EAAI,EAAE,EAAA,EAAI,CAAE,EAAA;AAAA,QACZ,OAAO,KAAS,IAAA,CAAA;AAAA,QAChB,WAAA;AAAA,QACA,IAAM,EAAA,UAAA;AAAA,QACN,YAAc,EAAA,gBAAA;AAAA,QACd,mBAAqB,EAAA,uBAAA;AAAA,QACrB,gBAAkB,EAAA,IAAA;AAAA,QAClB,eAAe,EAAA,IAAA;AAAA,QACf,cAAc,EAAA;AAAA;AAAA,KAChB;AAAA,GAEJ;AAEA,EACE,uBAAA,GAAA,CAAC,GACC,EAAA,EAAA,QAAA,kBAAA,IAAA,CAAC,mBACC,EAAA,EAAA,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,mBAAoB,CAAA,OAAA,EAApB,EACC,QAAA,kBAAA,GAAA,CAAC,mBAAgB,CACnB,EAAA,CAAA;AAAA,oBACA,GAAA,CAAC,oBAAoB,OAApB,EAAA,EACE,kCACE,GAAA,CAAA,GAAA,EAAA,EAAI,IAAI,EAAE,OAAA,EAAS,GAAG,KAAO,EAAA,YAAA,IAC3B,QAAE,EAAA,CAAA,CAAA,yBAAA,EAA2B,EAAE,KAAM,EAAC,GACzC,CAEA,mBAAA,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAM,OAAU,GAAA,EAAK,GAAA,aAAA;AAAA,QACrB,OAAS,EAAA,oBAAA;AAAA,QACT,cAAgB,EAAA,YAAA;AAAA,QAChB,KACE,EAAA,KAAA,KAAU,CACN,GAAA,CAAA,CAAE,YAAY,CAAA,GACd,CAAE,CAAA,qBAAA,EAAuB,EAAE,KAAA,EAAO,MAAO,CAAA,KAAK,GAAG,CAAA;AAAA,QAEvD,YAAc,EAAA;AAAA,UACZ,OAAS,EAAA,EAAE,iBAAmB,EAAA,CAAA,CAAE,yBAAyB,CAAE;AAAA,SAC7D;AAAA,QACA,UAAY,EAAA;AAAA,UACV,MAAQ,EAAA,6BAAA;AAAA,UACR,IAAM,EAAA,2BAAA;AAAA,UACN,UAAY,EAAA;AAAA,SACd;AAAA,QACA,YAAA,EACE,UAAU,IACR,mBAAA,GAAA;AAAA,UAAC,GAAA;AAAA,UAAA;AAAA,YACC,aAAY,EAAA,oBAAA;AAAA,YACZ,EAAI,EAAA;AAAA,cACF,OAAS,EAAA,CAAA;AAAA,cACT,OAAS,EAAA,MAAA;AAAA,cACT,cAAgB,EAAA;AAAA,aAClB;AAAA,YAEC,YAAE,oBAAoB;AAAA;AAAA,SACzB;AAAA,QAGJ,SAAW,EAAA;AAAA;AAAA,KAGjB,EAAA;AAAA,GAAA,EACF,CACF,EAAA,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"EntityServicenowContent.esm.js","sources":["../../../src/components/Servicenow/EntityServicenowContent.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useEffect, useState, MouseEvent, ChangeEvent, useMemo } from 'react';\nimport { identityApiRef, useApi } from '@backstage/core-plugin-api';\n\nimport {\n CatalogFilterLayout,\n useEntity,\n} from '@backstage/plugin-catalog-react';\nimport { Table } from '@backstage/core-components';\n\nimport Box from '@mui/material/Box';\nimport CircularProgress from '@mui/material/CircularProgress';\nimport TableBody from '@mui/material/TableBody';\nimport TableRow from '@mui/material/TableRow';\nimport TableCell from '@mui/material/TableCell';\nimport TablePagination from '@mui/material/TablePagination';\n\nimport {\n Order,\n SortingOrderEnum,\n} from '@backstage-community/plugin-servicenow-common';\n\nimport { IncidentsFilter } from './IncidentsFilter';\nimport { useIncidentsListColumns } from './IncidentsListColumns';\nimport { IncidentsTableBody } from './IncidentsTableBody';\nimport { IncidentsTableHeader } from './IncidentsTableHeader';\nimport {\n IncidentTableFieldEnum,\n type IncidentsData,\n type IncidentTableField,\n} from '../../types';\nimport { buildIncidentQueryParams } from '../../utils/queryParamsUtils';\nimport { useDebouncedValue } from '../../hooks/useDebouncedValue';\nimport { useQueryState } from '../../hooks/useQueryState';\nimport { useQueryArrayState } from '../../hooks/useQueryArrayState';\nimport { serviceNowApiRef } from '../../api/ServiceNowBackendClient';\nimport useUserEmail from '../../hooks/useUserEmail';\nimport { useUpdateQueryParams } from '../../hooks/useQueryHelpers';\nimport { useTranslation } from '../../hooks/useTranslation';\n\nexport const EntityServicenowContent = () => {\n const { t } = useTranslation();\n const { entity } = useEntity();\n const serviceNowApi = useApi(serviceNowApiRef);\n const incidentsListColumns = useIncidentsListColumns();\n\n const [search, setSearch] = useQueryState<string>('search', '');\n const [input, setInput] = useState(search);\n const debouncedInput = useDebouncedValue(input, 300);\n\n const [order, setOrder] = useQueryState<Order>('order', SortingOrderEnum.Asc);\n const [orderBy, setOrderBy] = useQueryState<IncidentTableField>(\n 'orderBy',\n IncidentTableFieldEnum.Number,\n );\n\n const [rowsPerPage] = useQueryState<number>('limit', 5);\n const [offset, setOffset] = useQueryState<number>('offset', 0);\n const [state] = useQueryArrayState('state', []);\n const [priority] = useQueryArrayState('priority', []);\n\n const pageNumber = Math.floor(offset / rowsPerPage);\n\n const [count, setCount] = useState<number>(0);\n const [incidentsData, setIncidentsData] = useState<IncidentsData[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const updateQueryParams = useUpdateQueryParams();\n\n const servicenowAnnotations = useMemo(() => {\n const annotations = entity.metadata.annotations;\n const snAnnotations: Record<string, string> = {};\n if (annotations) {\n for (const [key, value] of Object.entries(annotations)) {\n if (key.startsWith('servicenow.com/entity-id')) {\n snAnnotations.entityId = value;\n continue;\n }\n if (key.startsWith('servicenow.com/')) {\n const field = key.substring('servicenow.com/'.length);\n snAnnotations[field] = value;\n }\n }\n }\n return snAnnotations;\n }, [entity.metadata.annotations]);\n\n const kind = entity.kind.toLocaleLowerCase('en-US');\n const userEmail = useUserEmail(kind);\n\n const identityApi = useApi(identityApiRef);\n\n useEffect(() => {\n identityApi.getBackstageIdentity().then(identity => {\n if (identity.userEntityRef) {\n localStorage.setItem('userEntityRef', identity.userEntityRef);\n }\n });\n }, [identityApi]);\n\n useEffect(() => {\n if (debouncedInput !== search) {\n setSearch(debouncedInput);\n setOffset(0);\n }\n }, [debouncedInput, search, setSearch, setOffset]);\n\n useEffect(() => {\n async function fetchIncidents() {\n setLoading(true);\n setError(null);\n\n if (!userEmail && Object.keys(servicenowAnnotations).length === 0) {\n setIncidentsData([]);\n setLoading(false);\n return;\n }\n\n try {\n const queryParams = buildIncidentQueryParams({\n limit: rowsPerPage,\n offset,\n order,\n orderBy,\n search: search,\n priority: priority ?? undefined,\n state: state ?? undefined,\n userEmail,\n ...servicenowAnnotations,\n });\n\n const { incidents, totalCount } = await serviceNowApi.getIncidents(\n queryParams,\n );\n setIncidentsData(incidents);\n setCount(totalCount);\n } catch (e) {\n setError((e as Error).message);\n setIncidentsData([]);\n } finally {\n setLoading(false);\n }\n }\n\n fetchIncidents();\n }, [\n rowsPerPage,\n offset,\n order,\n orderBy,\n search,\n serviceNowApi,\n servicenowAnnotations,\n priority,\n state,\n userEmail,\n ]);\n\n const handleRowsPerPageChange = (event: ChangeEvent<HTMLInputElement>) => {\n const newLimit = parseInt(event.target.value, 10);\n updateQueryParams({\n limit: String(newLimit),\n offset: '0',\n });\n };\n\n const handlePageChange = (_event: unknown, page: number) => {\n setOffset(page * rowsPerPage);\n };\n\n const handleRequestSort = (\n _event: MouseEvent<unknown>,\n property: IncidentTableField,\n ) => {\n const isAsc = orderBy === property && order === SortingOrderEnum.Asc;\n setOrder(isAsc ? SortingOrderEnum.Desc : SortingOrderEnum.Asc);\n setOrderBy(property);\n setOffset(0);\n };\n\n const handleSearch = (str: string) => {\n setInput(str);\n };\n\n const IncidentsTableHeaderComponent = () => (\n <IncidentsTableHeader\n order={order}\n orderBy={orderBy}\n onRequestSort={handleRequestSort}\n />\n );\n\n const IncidentsTableBodyComponent = () =>\n loading ? (\n <TableBody>\n <TableRow>\n <TableCell colSpan={incidentsListColumns.length} align=\"center\">\n <Box sx={{ py: 3 }}>\n <CircularProgress />\n </Box>\n </TableCell>\n </TableRow>\n </TableBody>\n ) : (\n <IncidentsTableBody rows={incidentsData} />\n );\n\n const TablePaginationComponent = () => {\n return (\n <TablePagination\n rowsPerPageOptions={[5, 10, 20, 50, 100].map(n => ({\n value: n,\n label: t('table.labelRowsSelect', { count: String(n) }),\n }))}\n component=\"div\"\n sx={{ mr: 1 }}\n count={count ?? 0}\n rowsPerPage={rowsPerPage}\n page={pageNumber}\n onPageChange={handlePageChange}\n onRowsPerPageChange={handleRowsPerPageChange}\n labelRowsPerPage={null}\n showFirstButton\n showLastButton\n />\n );\n };\n\n return (\n <Box>\n <CatalogFilterLayout>\n <CatalogFilterLayout.Filters>\n <IncidentsFilter />\n </CatalogFilterLayout.Filters>\n <CatalogFilterLayout.Content>\n {error ? (\n <Box sx={{ padding: 2, color: 'error.main' }}>\n {t('errors.loadingIncidents', { error })}\n </Box>\n ) : (\n <Table\n data={loading ? [] : incidentsData}\n columns={incidentsListColumns}\n onSearchChange={handleSearch}\n title={\n count === 0\n ? t('page.title')\n : t('page.titleWithCount', { count: String(count) })\n }\n localization={{\n toolbar: { searchPlaceholder: t('table.searchPlaceholder') },\n }}\n components={{\n Header: IncidentsTableHeaderComponent,\n Body: IncidentsTableBodyComponent,\n Pagination: TablePaginationComponent,\n }}\n emptyContent={\n loading ? null : (\n <Box\n data-testid=\"no-incidents-found\"\n sx={{\n padding: 2,\n display: 'flex',\n justifyContent: 'center',\n }}\n >\n {t('table.emptyContent')}\n </Box>\n )\n }\n isLoading={loading}\n />\n )}\n </CatalogFilterLayout.Content>\n </CatalogFilterLayout>\n </Box>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAuDO,MAAM,0BAA0B,MAAM;AAC3C,EAAM,MAAA,EAAE,CAAE,EAAA,GAAI,cAAe,EAAA;AAC7B,EAAM,MAAA,EAAE,MAAO,EAAA,GAAI,SAAU,EAAA;AAC7B,EAAM,MAAA,aAAA,GAAgB,OAAO,gBAAgB,CAAA;AAC7C,EAAA,MAAM,uBAAuB,uBAAwB,EAAA;AAErD,EAAA,MAAM,CAAC,MAAQ,EAAA,SAAS,CAAI,GAAA,aAAA,CAAsB,UAAU,EAAE,CAAA;AAC9D,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAS,MAAM,CAAA;AACzC,EAAM,MAAA,cAAA,GAAiB,iBAAkB,CAAA,KAAA,EAAO,GAAG,CAAA;AAEnD,EAAA,MAAM,CAAC,KAAO,EAAA,QAAQ,IAAI,aAAqB,CAAA,OAAA,EAAS,iBAAiB,GAAG,CAAA;AAC5E,EAAM,MAAA,CAAC,OAAS,EAAA,UAAU,CAAI,GAAA,aAAA;AAAA,IAC5B,SAAA;AAAA,IACA,sBAAuB,CAAA;AAAA,GACzB;AAEA,EAAA,MAAM,CAAC,WAAW,CAAI,GAAA,aAAA,CAAsB,SAAS,CAAC,CAAA;AACtD,EAAA,MAAM,CAAC,MAAQ,EAAA,SAAS,CAAI,GAAA,aAAA,CAAsB,UAAU,CAAC,CAAA;AAC7D,EAAA,MAAM,CAAC,KAAK,CAAA,GAAI,kBAAmB,CAAA,OAAA,EAAS,EAAE,CAAA;AAC9C,EAAA,MAAM,CAAC,QAAQ,CAAA,GAAI,kBAAmB,CAAA,UAAA,EAAY,EAAE,CAAA;AAEpD,EAAA,MAAM,UAAa,GAAA,IAAA,CAAK,KAAM,CAAA,MAAA,GAAS,WAAW,CAAA;AAElD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAiB,CAAC,CAAA;AAC5C,EAAA,MAAM,CAAC,aAAe,EAAA,gBAAgB,CAAI,GAAA,QAAA,CAA0B,EAAE,CAAA;AACtE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AACtD,EAAA,MAAM,oBAAoB,oBAAqB,EAAA;AAE/C,EAAM,MAAA,qBAAA,GAAwB,QAAQ,MAAM;AAC1C,IAAM,MAAA,WAAA,GAAc,OAAO,QAAS,CAAA,WAAA;AACpC,IAAA,MAAM,gBAAwC,EAAC;AAC/C,IAAA,IAAI,WAAa,EAAA;AACf,MAAA,KAAA,MAAW,CAAC,GAAK,EAAA,KAAK,KAAK,MAAO,CAAA,OAAA,CAAQ,WAAW,CAAG,EAAA;AACtD,QAAI,IAAA,GAAA,CAAI,UAAW,CAAA,0BAA0B,CAAG,EAAA;AAC9C,UAAA,aAAA,CAAc,QAAW,GAAA,KAAA;AACzB,UAAA;AAAA;AAEF,QAAI,IAAA,GAAA,CAAI,UAAW,CAAA,iBAAiB,CAAG,EAAA;AACrC,UAAA,MAAM,KAAQ,GAAA,GAAA,CAAI,SAAU,CAAA,iBAAA,CAAkB,MAAM,CAAA;AACpD,UAAA,aAAA,CAAc,KAAK,CAAI,GAAA,KAAA;AAAA;AACzB;AACF;AAEF,IAAO,OAAA,aAAA;AAAA,GACN,EAAA,CAAC,MAAO,CAAA,QAAA,CAAS,WAAW,CAAC,CAAA;AAEhC,EAAA,MAAM,IAAO,GAAA,MAAA,CAAO,IAAK,CAAA,iBAAA,CAAkB,OAAO,CAAA;AAClD,EAAM,MAAA,SAAA,GAAY,aAAa,IAAI,CAAA;AAEnC,EAAM,MAAA,WAAA,GAAc,OAAO,cAAc,CAAA;AAEzC,EAAA,SAAA,CAAU,MAAM;AACd,IAAY,WAAA,CAAA,oBAAA,EAAuB,CAAA,IAAA,CAAK,CAAY,QAAA,KAAA;AAClD,MAAA,IAAI,SAAS,aAAe,EAAA;AAC1B,QAAa,YAAA,CAAA,OAAA,CAAQ,eAAiB,EAAA,QAAA,CAAS,aAAa,CAAA;AAAA;AAC9D,KACD,CAAA;AAAA,GACH,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,mBAAmB,MAAQ,EAAA;AAC7B,MAAA,SAAA,CAAU,cAAc,CAAA;AACxB,MAAA,SAAA,CAAU,CAAC,CAAA;AAAA;AACb,KACC,CAAC,cAAA,EAAgB,MAAQ,EAAA,SAAA,EAAW,SAAS,CAAC,CAAA;AAEjD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,eAAe,cAAiB,GAAA;AAC9B,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,QAAA,CAAS,IAAI,CAAA;AAEb,MAAA,IAAI,CAAC,SAAa,IAAA,MAAA,CAAO,KAAK,qBAAqB,CAAA,CAAE,WAAW,CAAG,EAAA;AACjE,QAAA,gBAAA,CAAiB,EAAE,CAAA;AACnB,QAAA,UAAA,CAAW,KAAK,CAAA;AAChB,QAAA;AAAA;AAGF,MAAI,IAAA;AACF,QAAA,MAAM,cAAc,wBAAyB,CAAA;AAAA,UAC3C,KAAO,EAAA,WAAA;AAAA,UACP,MAAA;AAAA,UACA,KAAA;AAAA,UACA,OAAA;AAAA,UACA,MAAA;AAAA,UACA,UAAU,QAAY,IAAA,KAAA,CAAA;AAAA,UACtB,OAAO,KAAS,IAAA,KAAA,CAAA;AAAA,UAChB,SAAA;AAAA,UACA,GAAG;AAAA,SACJ,CAAA;AAED,QAAA,MAAM,EAAE,SAAA,EAAW,UAAW,EAAA,GAAI,MAAM,aAAc,CAAA,YAAA;AAAA,UACpD;AAAA,SACF;AACA,QAAA,gBAAA,CAAiB,SAAS,CAAA;AAC1B,QAAA,QAAA,CAAS,UAAU,CAAA;AAAA,eACZ,CAAG,EAAA;AACV,QAAA,QAAA,CAAU,EAAY,OAAO,CAAA;AAC7B,QAAA,gBAAA,CAAiB,EAAE,CAAA;AAAA,OACnB,SAAA;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA;AAClB;AAGF,IAAe,cAAA,EAAA;AAAA,GACd,EAAA;AAAA,IACD,WAAA;AAAA,IACA,MAAA;AAAA,IACA,KAAA;AAAA,IACA,OAAA;AAAA,IACA,MAAA;AAAA,IACA,aAAA;AAAA,IACA,qBAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAM,MAAA,uBAAA,GAA0B,CAAC,KAAyC,KAAA;AACxE,IAAA,MAAM,QAAW,GAAA,QAAA,CAAS,KAAM,CAAA,MAAA,CAAO,OAAO,EAAE,CAAA;AAChD,IAAkB,iBAAA,CAAA;AAAA,MAChB,KAAA,EAAO,OAAO,QAAQ,CAAA;AAAA,MACtB,MAAQ,EAAA;AAAA,KACT,CAAA;AAAA,GACH;AAEA,EAAM,MAAA,gBAAA,GAAmB,CAAC,MAAA,EAAiB,IAAiB,KAAA;AAC1D,IAAA,SAAA,CAAU,OAAO,WAAW,CAAA;AAAA,GAC9B;AAEA,EAAM,MAAA,iBAAA,GAAoB,CACxB,MAAA,EACA,QACG,KAAA;AACH,IAAA,MAAM,KAAQ,GAAA,OAAA,KAAY,QAAY,IAAA,KAAA,KAAU,gBAAiB,CAAA,GAAA;AACjE,IAAA,QAAA,CAAS,KAAQ,GAAA,gBAAA,CAAiB,IAAO,GAAA,gBAAA,CAAiB,GAAG,CAAA;AAC7D,IAAA,UAAA,CAAW,QAAQ,CAAA;AACnB,IAAA,SAAA,CAAU,CAAC,CAAA;AAAA,GACb;AAEA,EAAM,MAAA,YAAA,GAAe,CAAC,GAAgB,KAAA;AACpC,IAAA,QAAA,CAAS,GAAG,CAAA;AAAA,GACd;AAEA,EAAA,MAAM,gCAAgC,sBACpC,GAAA;AAAA,IAAC,oBAAA;AAAA,IAAA;AAAA,MACC,KAAA;AAAA,MACA,OAAA;AAAA,MACA,aAAe,EAAA;AAAA;AAAA,GACjB;AAGF,EAAA,MAAM,2BAA8B,GAAA,MAClC,OACE,mBAAA,GAAA,CAAC,SACC,EAAA,EAAA,QAAA,kBAAA,GAAA,CAAC,QACC,EAAA,EAAA,QAAA,kBAAA,GAAA,CAAC,SAAU,EAAA,EAAA,OAAA,EAAS,oBAAqB,CAAA,MAAA,EAAQ,OAAM,QACrD,EAAA,QAAA,kBAAA,GAAA,CAAC,GAAI,EAAA,EAAA,EAAA,EAAI,EAAE,EAAA,EAAI,CAAE,EAAA,EACf,8BAAC,gBAAiB,EAAA,EAAA,CAAA,EACpB,CACF,EAAA,CAAA,EACF,CACF,EAAA,CAAA,mBAEC,GAAA,CAAA,kBAAA,EAAA,EAAmB,MAAM,aAAe,EAAA,CAAA;AAG7C,EAAA,MAAM,2BAA2B,MAAM;AACrC,IACE,uBAAA,GAAA;AAAA,MAAC,eAAA;AAAA,MAAA;AAAA,QACC,kBAAA,EAAoB,CAAC,CAAG,EAAA,EAAA,EAAI,IAAI,EAAI,EAAA,GAAG,CAAE,CAAA,GAAA,CAAI,CAAM,CAAA,MAAA;AAAA,UACjD,KAAO,EAAA,CAAA;AAAA,UACP,KAAA,EAAO,EAAE,uBAAyB,EAAA,EAAE,OAAO,MAAO,CAAA,CAAC,GAAG;AAAA,SACtD,CAAA,CAAA;AAAA,QACF,SAAU,EAAA,KAAA;AAAA,QACV,EAAA,EAAI,EAAE,EAAA,EAAI,CAAE,EAAA;AAAA,QACZ,OAAO,KAAS,IAAA,CAAA;AAAA,QAChB,WAAA;AAAA,QACA,IAAM,EAAA,UAAA;AAAA,QACN,YAAc,EAAA,gBAAA;AAAA,QACd,mBAAqB,EAAA,uBAAA;AAAA,QACrB,gBAAkB,EAAA,IAAA;AAAA,QAClB,eAAe,EAAA,IAAA;AAAA,QACf,cAAc,EAAA;AAAA;AAAA,KAChB;AAAA,GAEJ;AAEA,EACE,uBAAA,GAAA,CAAC,GACC,EAAA,EAAA,QAAA,kBAAA,IAAA,CAAC,mBACC,EAAA,EAAA,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,mBAAoB,CAAA,OAAA,EAApB,EACC,QAAA,kBAAA,GAAA,CAAC,mBAAgB,CACnB,EAAA,CAAA;AAAA,oBACA,GAAA,CAAC,oBAAoB,OAApB,EAAA,EACE,kCACE,GAAA,CAAA,GAAA,EAAA,EAAI,IAAI,EAAE,OAAA,EAAS,GAAG,KAAO,EAAA,YAAA,IAC3B,QAAE,EAAA,CAAA,CAAA,yBAAA,EAA2B,EAAE,KAAM,EAAC,GACzC,CAEA,mBAAA,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAM,OAAU,GAAA,EAAK,GAAA,aAAA;AAAA,QACrB,OAAS,EAAA,oBAAA;AAAA,QACT,cAAgB,EAAA,YAAA;AAAA,QAChB,KACE,EAAA,KAAA,KAAU,CACN,GAAA,CAAA,CAAE,YAAY,CAAA,GACd,CAAE,CAAA,qBAAA,EAAuB,EAAE,KAAA,EAAO,MAAO,CAAA,KAAK,GAAG,CAAA;AAAA,QAEvD,YAAc,EAAA;AAAA,UACZ,OAAS,EAAA,EAAE,iBAAmB,EAAA,CAAA,CAAE,yBAAyB,CAAE;AAAA,SAC7D;AAAA,QACA,UAAY,EAAA;AAAA,UACV,MAAQ,EAAA,6BAAA;AAAA,UACR,IAAM,EAAA,2BAAA;AAAA,UACN,UAAY,EAAA;AAAA,SACd;AAAA,QACA,YAAA,EACE,UAAU,IACR,mBAAA,GAAA;AAAA,UAAC,GAAA;AAAA,UAAA;AAAA,YACC,aAAY,EAAA,oBAAA;AAAA,YACZ,EAAI,EAAA;AAAA,cACF,OAAS,EAAA,CAAA;AAAA,cACT,OAAS,EAAA,MAAA;AAAA,cACT,cAAgB,EAAA;AAAA,aAClB;AAAA,YAEC,YAAE,oBAAoB;AAAA;AAAA,SACzB;AAAA,QAGJ,SAAW,EAAA;AAAA;AAAA,KAGjB,EAAA;AAAA,GAAA,EACF,CACF,EAAA,CAAA;AAEJ;;;;"}
package/dist/index.d.ts CHANGED
@@ -16,6 +16,11 @@ declare const servicenowPlugin: _backstage_core_plugin_api.BackstagePlugin<{
16
16
  * @public
17
17
  */
18
18
  declare const EntityServicenowContent: () => react_jsx_runtime.JSX.Element;
19
+ /**
20
+ * A component that wraps the service-now content in a memory router
21
+ * @public
22
+ */
23
+ declare const Router: () => react_jsx_runtime.JSX.Element;
19
24
  /**
20
25
  * @deprecated please use `EntityServicenowCard` instead, this page
21
26
  * might be removed in future releases or replaced with a page
@@ -29,4 +34,4 @@ declare const ServicenowPage: () => react_jsx_runtime.JSX.Element;
29
34
  */
30
35
  declare const isMyProfile: (entity: Entity) => boolean;
31
36
 
32
- export { EntityServicenowContent, ServicenowPage, isMyProfile, servicenowPlugin };
37
+ export { EntityServicenowContent, Router, ServicenowPage, isMyProfile, servicenowPlugin };
package/dist/index.esm.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export { isServicenowAvailable } from '@backstage-community/plugin-servicenow-common';
2
- export { EntityServicenowContent, ServicenowPage, isMyProfile, servicenowPlugin } from './plugin.esm.js';
2
+ export { EntityServicenowContent, Router, ServicenowPage, isMyProfile, servicenowPlugin } from './plugin.esm.js';
3
3
  //# sourceMappingURL=index.esm.js.map
@@ -28,6 +28,14 @@ const EntityServicenowContent = servicenowPlugin.provide(
28
28
  }
29
29
  })
30
30
  );
31
+ const Router = servicenowPlugin.provide(
32
+ createComponentExtension({
33
+ name: "Router",
34
+ component: {
35
+ lazy: () => import('./components/Router.esm.js').then((m) => m.Router)
36
+ }
37
+ })
38
+ );
31
39
  const ServicenowPage = EntityServicenowContent;
32
40
  const isMyProfile = (entity) => {
33
41
  const currentUserRef = localStorage.getItem("userEntityRef");
@@ -41,5 +49,5 @@ const isMyProfile = (entity) => {
41
49
  }
42
50
  };
43
51
 
44
- export { EntityServicenowContent, ServicenowPage, isMyProfile, servicenowPlugin };
52
+ export { EntityServicenowContent, Router, ServicenowPage, isMyProfile, servicenowPlugin };
45
53
  //# sourceMappingURL=plugin.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n createApiFactory,\n createComponentExtension,\n createPlugin,\n discoveryApiRef,\n fetchApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\nimport {\n Entity,\n parseEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\n\nimport { rootRouteRef } from './routes';\nimport {\n serviceNowApiRef,\n ServiceNowBackendClient,\n} from './api/ServiceNowBackendClient';\n\n/**\n * Servicenow Plugin\n * @public\n */\nexport const servicenowPlugin = createPlugin({\n id: 'servicenow',\n apis: [\n createApiFactory({\n api: serviceNowApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n fetchApi: fetchApiRef,\n identityApi: identityApiRef,\n },\n factory: ({ discoveryApi, fetchApi, identityApi }) =>\n new ServiceNowBackendClient(discoveryApi, fetchApi, identityApi),\n }),\n ],\n routes: {\n root: rootRouteRef,\n },\n});\n\n/**\n * Servicenow entity content that shows an incident table with a\n * filter similar to the catalog table\n * @public\n */\nexport const EntityServicenowContent = servicenowPlugin.provide(\n createComponentExtension({\n name: 'EntityServicenowContent',\n component: {\n lazy: () =>\n import('./components/Servicenow').then(m => m.EntityServicenowContent),\n },\n }),\n);\n\n/**\n * @deprecated please use `EntityServicenowCard` instead, this page\n * might be removed in future releases or replaced with a page\n * that lists the incidents assigned to the current user.\n * @public\n */\nexport const ServicenowPage = EntityServicenowContent;\n\n/**\n * Check if the current entity is the logged-in user\n * @public\n */\nexport const isMyProfile = (entity: Entity): boolean => {\n const currentUserRef = localStorage.getItem('userEntityRef');\n if (!currentUserRef) return false;\n\n try {\n const a = parseEntityRef(currentUserRef);\n const b = parseEntityRef(stringifyEntityRef(entity));\n return (\n a.kind.toLowerCase() === b.kind.toLowerCase() &&\n a.namespace.toLowerCase() === b.namespace.toLowerCase() &&\n a.name.toLowerCase() === b.name.toLowerCase()\n );\n } catch {\n return false;\n }\n};\n"],"names":[],"mappings":";;;;;AAuCO,MAAM,mBAAmB,YAAa,CAAA;AAAA,EAC3C,EAAI,EAAA,YAAA;AAAA,EACJ,IAAM,EAAA;AAAA,IACJ,gBAAiB,CAAA;AAAA,MACf,GAAK,EAAA,gBAAA;AAAA,MACL,IAAM,EAAA;AAAA,QACJ,YAAc,EAAA,eAAA;AAAA,QACd,QAAU,EAAA,WAAA;AAAA,QACV,WAAa,EAAA;AAAA,OACf;AAAA,MACA,OAAA,EAAS,CAAC,EAAE,YAAc,EAAA,QAAA,EAAU,WAAY,EAAA,KAC9C,IAAI,uBAAA,CAAwB,YAAc,EAAA,QAAA,EAAU,WAAW;AAAA,KAClE;AAAA,GACH;AAAA,EACA,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA;AAAA;AAEV,CAAC;AAOM,MAAM,0BAA0B,gBAAiB,CAAA,OAAA;AAAA,EACtD,wBAAyB,CAAA;AAAA,IACvB,IAAM,EAAA,yBAAA;AAAA,IACN,SAAW,EAAA;AAAA,MACT,IAAA,EAAM,MACJ,OAAO,sCAAyB,EAAE,IAAK,CAAA,CAAA,CAAA,KAAK,EAAE,uBAAuB;AAAA;AACzE,GACD;AACH;AAQO,MAAM,cAAiB,GAAA;AAMjB,MAAA,WAAA,GAAc,CAAC,MAA4B,KAAA;AACtD,EAAM,MAAA,cAAA,GAAiB,YAAa,CAAA,OAAA,CAAQ,eAAe,CAAA;AAC3D,EAAI,IAAA,CAAC,gBAAuB,OAAA,KAAA;AAE5B,EAAI,IAAA;AACF,IAAM,MAAA,CAAA,GAAI,eAAe,cAAc,CAAA;AACvC,IAAA,MAAM,CAAI,GAAA,cAAA,CAAe,kBAAmB,CAAA,MAAM,CAAC,CAAA;AACnD,IACE,OAAA,CAAA,CAAE,KAAK,WAAY,EAAA,KAAM,EAAE,IAAK,CAAA,WAAA,EAChC,IAAA,CAAA,CAAE,SAAU,CAAA,WAAA,OAAkB,CAAE,CAAA,SAAA,CAAU,aAC1C,IAAA,CAAA,CAAE,KAAK,WAAY,EAAA,KAAM,CAAE,CAAA,IAAA,CAAK,WAAY,EAAA;AAAA,GAExC,CAAA,MAAA;AACN,IAAO,OAAA,KAAA;AAAA;AAEX;;;;"}
1
+ {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n createApiFactory,\n createComponentExtension,\n createPlugin,\n discoveryApiRef,\n fetchApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\nimport {\n Entity,\n parseEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\n\nimport { rootRouteRef } from './routes';\nimport {\n serviceNowApiRef,\n ServiceNowBackendClient,\n} from './api/ServiceNowBackendClient';\n\n/**\n * Servicenow Plugin\n * @public\n */\nexport const servicenowPlugin = createPlugin({\n id: 'servicenow',\n apis: [\n createApiFactory({\n api: serviceNowApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n fetchApi: fetchApiRef,\n identityApi: identityApiRef,\n },\n factory: ({ discoveryApi, fetchApi, identityApi }) =>\n new ServiceNowBackendClient(discoveryApi, fetchApi, identityApi),\n }),\n ],\n routes: {\n root: rootRouteRef,\n },\n});\n\n/**\n * Servicenow entity content that shows an incident table with a\n * filter similar to the catalog table\n * @public\n */\nexport const EntityServicenowContent = servicenowPlugin.provide(\n createComponentExtension({\n name: 'EntityServicenowContent',\n component: {\n lazy: () =>\n import('./components/Servicenow').then(m => m.EntityServicenowContent),\n },\n }),\n);\n\n/**\n * A component that wraps the service-now content in a memory router\n * @public\n */\nexport const Router = servicenowPlugin.provide(\n createComponentExtension({\n name: 'Router',\n component: {\n lazy: () => import('./components/Router').then(m => m.Router),\n },\n }),\n);\n\n/**\n * @deprecated please use `EntityServicenowCard` instead, this page\n * might be removed in future releases or replaced with a page\n * that lists the incidents assigned to the current user.\n * @public\n */\nexport const ServicenowPage = EntityServicenowContent;\n\n/**\n * Check if the current entity is the logged-in user\n * @public\n */\nexport const isMyProfile = (entity: Entity): boolean => {\n const currentUserRef = localStorage.getItem('userEntityRef');\n if (!currentUserRef) return false;\n\n try {\n const a = parseEntityRef(currentUserRef);\n const b = parseEntityRef(stringifyEntityRef(entity));\n return (\n a.kind.toLowerCase() === b.kind.toLowerCase() &&\n a.namespace.toLowerCase() === b.namespace.toLowerCase() &&\n a.name.toLowerCase() === b.name.toLowerCase()\n );\n } catch {\n return false;\n }\n};\n"],"names":[],"mappings":";;;;;AAuCO,MAAM,mBAAmB,YAAa,CAAA;AAAA,EAC3C,EAAI,EAAA,YAAA;AAAA,EACJ,IAAM,EAAA;AAAA,IACJ,gBAAiB,CAAA;AAAA,MACf,GAAK,EAAA,gBAAA;AAAA,MACL,IAAM,EAAA;AAAA,QACJ,YAAc,EAAA,eAAA;AAAA,QACd,QAAU,EAAA,WAAA;AAAA,QACV,WAAa,EAAA;AAAA,OACf;AAAA,MACA,OAAA,EAAS,CAAC,EAAE,YAAc,EAAA,QAAA,EAAU,WAAY,EAAA,KAC9C,IAAI,uBAAA,CAAwB,YAAc,EAAA,QAAA,EAAU,WAAW;AAAA,KAClE;AAAA,GACH;AAAA,EACA,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA;AAAA;AAEV,CAAC;AAOM,MAAM,0BAA0B,gBAAiB,CAAA,OAAA;AAAA,EACtD,wBAAyB,CAAA;AAAA,IACvB,IAAM,EAAA,yBAAA;AAAA,IACN,SAAW,EAAA;AAAA,MACT,IAAA,EAAM,MACJ,OAAO,sCAAyB,EAAE,IAAK,CAAA,CAAA,CAAA,KAAK,EAAE,uBAAuB;AAAA;AACzE,GACD;AACH;AAMO,MAAM,SAAS,gBAAiB,CAAA,OAAA;AAAA,EACrC,wBAAyB,CAAA;AAAA,IACvB,IAAM,EAAA,QAAA;AAAA,IACN,SAAW,EAAA;AAAA,MACT,IAAA,EAAM,MAAM,OAAO,4BAAqB,EAAE,IAAK,CAAA,CAAA,CAAA,KAAK,EAAE,MAAM;AAAA;AAC9D,GACD;AACH;AAQO,MAAM,cAAiB,GAAA;AAMjB,MAAA,WAAA,GAAc,CAAC,MAA4B,KAAA;AACtD,EAAM,MAAA,cAAA,GAAiB,YAAa,CAAA,OAAA,CAAQ,eAAe,CAAA;AAC3D,EAAI,IAAA,CAAC,gBAAuB,OAAA,KAAA;AAE5B,EAAI,IAAA;AACF,IAAM,MAAA,CAAA,GAAI,eAAe,cAAc,CAAA;AACvC,IAAA,MAAM,CAAI,GAAA,cAAA,CAAe,kBAAmB,CAAA,MAAM,CAAC,CAAA;AACnD,IACE,OAAA,CAAA,CAAE,KAAK,WAAY,EAAA,KAAM,EAAE,IAAK,CAAA,WAAA,EAChC,IAAA,CAAA,CAAE,SAAU,CAAA,WAAA,OAAkB,CAAE,CAAA,SAAA,CAAU,aAC1C,IAAA,CAAA,CAAE,KAAK,WAAY,EAAA,KAAM,CAAE,CAAA,IAAA,CAAK,WAAY,EAAA;AAAA,GAExC,CAAA,MAAA;AACN,IAAO,OAAA,KAAA;AAAA;AAEX;;;;"}
@@ -1,13 +1,13 @@
1
1
  function buildIncidentQueryParams({
2
2
  userEmail,
3
- entityId,
4
3
  limit,
5
4
  offset,
6
5
  order,
7
6
  orderBy,
8
7
  search,
9
8
  priority,
10
- state
9
+ state,
10
+ ...annotations
11
11
  }) {
12
12
  const params = {
13
13
  order,
@@ -24,12 +24,14 @@ function buildIncidentQueryParams({
24
24
  if (search) {
25
25
  params.search = search;
26
26
  }
27
- if (entityId) {
28
- params.entityId = entityId;
29
- }
30
27
  if (userEmail) {
31
28
  params.userEmail = userEmail;
32
29
  }
30
+ for (const key in annotations) {
31
+ if (key in annotations) {
32
+ params[key] = annotations[key];
33
+ }
34
+ }
33
35
  return new URLSearchParams(params);
34
36
  }
35
37
 
@@ -1 +1 @@
1
- {"version":3,"file":"queryParamsUtils.esm.js","sources":["../../src/utils/queryParamsUtils.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { IncidentTableField } from '../types';\n\nexport function buildIncidentQueryParams({\n userEmail,\n entityId,\n limit,\n offset,\n order,\n orderBy,\n search,\n priority,\n state,\n}: {\n limit: number;\n offset: number;\n order: 'asc' | 'desc';\n orderBy: IncidentTableField;\n search?: string;\n priority?: string[];\n state?: string[];\n userEmail?: string;\n entityId?: string;\n}) {\n const params: Record<string, string> = {\n order,\n orderBy,\n limit: String(limit),\n offset: String(offset),\n };\n\n if (priority?.length) {\n params.priority = `IN${priority.join(',')}`;\n }\n\n if (state?.length) {\n params.state = `IN${state.join(',')}`;\n }\n\n if (search) {\n params.search = search;\n }\n\n if (entityId) {\n params.entityId = entityId;\n }\n\n if (userEmail) {\n params.userEmail = userEmail;\n }\n\n return new URLSearchParams(params);\n}\n"],"names":[],"mappings":"AAkBO,SAAS,wBAAyB,CAAA;AAAA,EACvC,SAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAUG,EAAA;AACD,EAAA,MAAM,MAAiC,GAAA;AAAA,IACrC,KAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA,EAAO,OAAO,KAAK,CAAA;AAAA,IACnB,MAAA,EAAQ,OAAO,MAAM;AAAA,GACvB;AAEA,EAAA,IAAI,UAAU,MAAQ,EAAA;AACpB,IAAA,MAAA,CAAO,QAAW,GAAA,CAAA,EAAA,EAAK,QAAS,CAAA,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA;AAAA;AAG3C,EAAA,IAAI,OAAO,MAAQ,EAAA;AACjB,IAAA,MAAA,CAAO,KAAQ,GAAA,CAAA,EAAA,EAAK,KAAM,CAAA,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA;AAAA;AAGrC,EAAA,IAAI,MAAQ,EAAA;AACV,IAAA,MAAA,CAAO,MAAS,GAAA,MAAA;AAAA;AAGlB,EAAA,IAAI,QAAU,EAAA;AACZ,IAAA,MAAA,CAAO,QAAW,GAAA,QAAA;AAAA;AAGpB,EAAA,IAAI,SAAW,EAAA;AACb,IAAA,MAAA,CAAO,SAAY,GAAA,SAAA;AAAA;AAGrB,EAAO,OAAA,IAAI,gBAAgB,MAAM,CAAA;AACnC;;;;"}
1
+ {"version":3,"file":"queryParamsUtils.esm.js","sources":["../../src/utils/queryParamsUtils.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { IncidentTableField } from '../types';\n\nexport function buildIncidentQueryParams({\n userEmail,\n limit,\n offset,\n order,\n orderBy,\n search,\n priority,\n state,\n ...annotations\n}: {\n limit: number;\n offset: number;\n order: 'asc' | 'desc';\n orderBy: IncidentTableField;\n search?: string;\n priority?: string[];\n state?: string[];\n userEmail?: string;\n [key: string]: any;\n}) {\n const params: Record<string, string> = {\n order,\n orderBy,\n limit: String(limit),\n offset: String(offset),\n };\n\n if (priority?.length) {\n params.priority = `IN${priority.join(',')}`;\n }\n\n if (state?.length) {\n params.state = `IN${state.join(',')}`;\n }\n\n if (search) {\n params.search = search;\n }\n\n if (userEmail) {\n params.userEmail = userEmail;\n }\n\n for (const key in annotations) {\n if (key in annotations) {\n params[key] = annotations[key];\n }\n }\n\n return new URLSearchParams(params);\n}\n"],"names":[],"mappings":"AAkBO,SAAS,wBAAyB,CAAA;AAAA,EACvC,SAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,GAAG;AACL,CAUG,EAAA;AACD,EAAA,MAAM,MAAiC,GAAA;AAAA,IACrC,KAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA,EAAO,OAAO,KAAK,CAAA;AAAA,IACnB,MAAA,EAAQ,OAAO,MAAM;AAAA,GACvB;AAEA,EAAA,IAAI,UAAU,MAAQ,EAAA;AACpB,IAAA,MAAA,CAAO,QAAW,GAAA,CAAA,EAAA,EAAK,QAAS,CAAA,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA;AAAA;AAG3C,EAAA,IAAI,OAAO,MAAQ,EAAA;AACjB,IAAA,MAAA,CAAO,KAAQ,GAAA,CAAA,EAAA,EAAK,KAAM,CAAA,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA;AAAA;AAGrC,EAAA,IAAI,MAAQ,EAAA;AACV,IAAA,MAAA,CAAO,MAAS,GAAA,MAAA;AAAA;AAGlB,EAAA,IAAI,SAAW,EAAA;AACb,IAAA,MAAA,CAAO,SAAY,GAAA,SAAA;AAAA;AAGrB,EAAA,KAAA,MAAW,OAAO,WAAa,EAAA;AAC7B,IAAA,IAAI,OAAO,WAAa,EAAA;AACtB,MAAO,MAAA,CAAA,GAAG,CAAI,GAAA,WAAA,CAAY,GAAG,CAAA;AAAA;AAC/B;AAGF,EAAO,OAAA,IAAI,gBAAgB,MAAM,CAAA;AACnC;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage-community/plugin-servicenow",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/index.esm.js",
6
6
  "types": "./dist/index.d.ts",