Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ public class LeaseController : ControllerBase
private readonly ILookupRepository _lookupRepository;
private readonly ILeaseService _leaseService;
private readonly ILeaseReportsService _leaseReportService;
private readonly ILeasePaymentService _leasePaymentService;
private readonly IMapper _mapper;
private readonly IWebHostEnvironment _webHostEnvironment;
#endregion
Expand All @@ -52,15 +51,13 @@ public class LeaseController : ControllerBase
/// <param name="lookupRepository"></param>
/// <param name="leaseService"></param>
/// <param name="leaseReportService"></param>
/// <param name="leasePaymentService"></param>
/// <param name="webHostEnvironment"></param>
/// <param name="mapper"></param>
public LeaseController(ILookupRepository lookupRepository, ILeaseService leaseService, ILeaseReportsService leaseReportService, ILeasePaymentService leasePaymentService, IWebHostEnvironment webHostEnvironment, IMapper mapper)
public LeaseController(ILookupRepository lookupRepository, ILeaseService leaseService, ILeaseReportsService leaseReportService, IWebHostEnvironment webHostEnvironment, IMapper mapper)
{
_lookupRepository = lookupRepository;
_leaseService = leaseService;
_leaseReportService = leaseReportService;
_leasePaymentService = leasePaymentService;
_mapper = mapper;
_webHostEnvironment = webHostEnvironment;
}
Expand Down
2 changes: 1 addition & 1 deletion source/frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<link rel="manifest" href="/manifest.json" />
<title>Property Inventory Management System</title>
<script src="/config.js"></script>
<script src="/runtime-vars.js"></script>
</head>

<body>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// This file is meant to be replaced with configuration values via config maps in OpenShift
window.config = {};
window.runtime = {};
4 changes: 3 additions & 1 deletion source/frontend/src/components/common/ErrorModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import ErrorModal from './ErrorModal';

describe('Error modal tests...', () => {
it('renders correctly', () => {
const { container } = render(<ErrorModal error={{ message: 'test' }} />);
const { container } = render(
<ErrorModal error={new Error('test')} resetErrorBoundary={null} />,
);
expect(container).toMatchSnapshot();
});
});
3 changes: 2 additions & 1 deletion source/frontend/src/components/common/ErrorModal.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FallbackProps } from 'react-error-boundary';
import { useHistory } from 'react-router-dom';

import GenericModal from './GenericModal';
Expand All @@ -10,7 +11,7 @@ import GenericModal from './GenericModal';
* see https://reactjs.org/docs/error-boundaries.html for more details.
* @param props
*/
const ErrorModal = (props: any) => {
const ErrorModal = (props: FallbackProps) => {
const history = useHistory();
return (
<GenericModal
Expand Down
21 changes: 0 additions & 21 deletions source/frontend/src/config.ts

This file was deleted.

18 changes: 7 additions & 11 deletions source/frontend/src/customAxios.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Attributes, Span, SpanStatusCode } from '@opentelemetry/api';
import { Attributes, Span } from '@opentelemetry/api';
import { ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from '@opentelemetry/semantic-conventions';
import { Dispatch } from '@reduxjs/toolkit';
import axios, { AxiosError, AxiosRequestHeaders } from 'axios';
Expand All @@ -9,11 +9,10 @@ import { toast } from 'react-toastify';
import { IGenericNetworkAction } from '@/store/slices/network/interfaces';
import { logError } from '@/store/slices/network/networkSlice';
import { RootState, store } from '@/store/store';
import { Telemetry } from '@/telemetry';
import { SpanEnrichment } from '@/telemetry/traces/SpanEnrichment';

import { startTrace } from './telemetry/traces';
import { SpanEnrichment } from './telemetry/traces/SpanEnrichment';
import { buildUrl } from './telemetry/utils';
import { exists } from './utils';
import { buildUrl, exists } from './utils';

export const defaultEnvelope = (x: any) => ({ data: { records: x } });

Expand Down Expand Up @@ -67,7 +66,7 @@ export const CustomAxios = ({
// clear query parameters - we don't want to include them in the span name
url.search = '';
const spanName = `HTTP ${method} ${url.href}`;
span = startTrace(spanName, spanAttributes);
span = Telemetry.startSpan(spanName, spanAttributes);

if (config.headers === undefined) {
config.headers = {} as AxiosRequestHeaders;
Expand All @@ -90,8 +89,7 @@ export const CustomAxios = ({
instance.interceptors.response.use(
response => {
SpanEnrichment.enrichWithXhrResponse(span, response);
span.setStatus({ code: SpanStatusCode.OK });
span.end();
Telemetry.endSpan(span);

if (lifecycleToasts?.successToast && response.status < 300) {
loadingToastId && toast.dismiss(loadingToastId);
Expand All @@ -102,9 +100,7 @@ export const CustomAxios = ({
return response;
},
error => {
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR });
span.end();
Telemetry.endSpan(span, error);

if (axios.isCancel(error)) {
return Promise.resolve(error.message);
Expand Down
9 changes: 4 additions & 5 deletions source/frontend/src/features/leases/hooks/useLeaseExport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { hideLoading, showLoading } from 'react-redux-loading-bar';

import * as actionTypes from '@/constants/actionTypes';
import { catchAxiosError } from '@/customAxios';
import { IPaginateLeases, useApiLeases } from '@/hooks/pims-api/useApiLeases';
import { logRequest, logSuccess } from '@/store/slices/network/networkSlice';
Expand All @@ -26,7 +25,7 @@ export const useLeaseExport = () => {
filter: IPaginateLeases,
outputFormat: 'csv' | 'excel' = 'excel',
fileName = `pims-leases.${outputFormat === 'csv' ? 'csv' : 'xlsx'}`,
requestId = 'properties-report',
requestId = 'leases-report',
) => {
dispatch(logRequest(requestId));
dispatch(showLoading());
Expand All @@ -38,7 +37,7 @@ export const useLeaseExport = () => {
fileDownload(data, fileName);
} catch (axiosError) {
if (axios.isAxiosError(axiosError)) {
catchAxiosError(axiosError, dispatch, actionTypes.DELETE_PARCEL);
catchAxiosError(axiosError, dispatch, requestId);
}
}
},
Expand All @@ -57,7 +56,7 @@ export const useLeaseExport = () => {
fileDownload(data, `pims-aggregated-leases-${fiscalYearStart}-${fiscalYearStart + 1}.xlsx`);
} catch (axiosError) {
if (axios.isAxiosError(axiosError)) {
catchAxiosError(axiosError, dispatch, actionTypes.DELETE_PARCEL);
catchAxiosError(axiosError, dispatch, requestId);
}
}
},
Expand All @@ -81,7 +80,7 @@ export const useLeaseExport = () => {
);
} catch (axiosError) {
if (axios.isAxiosError(axiosError)) {
catchAxiosError(axiosError, dispatch, actionTypes.DELETE_PARCEL);
catchAxiosError(axiosError, dispatch, requestId);
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion source/frontend/src/hooks/repositories/useProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const useProperties = () => {
fileDownload(data, fileName);
} catch (axiosError) {
if (axios.isAxiosError(axiosError)) {
catchAxiosError(axiosError, dispatch, actionTypes.DELETE_PARCEL);
catchAxiosError(axiosError, dispatch, requestId);
}
}
},
Expand Down
53 changes: 16 additions & 37 deletions source/frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,18 @@ import LoginLoading from '@/features/account/LoginLoading';
import EmptyLayout from '@/layouts/EmptyLayout';
import { store } from '@/store/store';
import { TenantConsumer, TenantProvider } from '@/tenants';
import { TelemetryConfig } from '@/utils/config';
import getKeycloakEventHandler from '@/utils/getKeycloakEventHandler';

import App from './App';
import { config } from './config';
import { NavigationIntentProvider } from './contexts/NavigationIntentContext';
import { DocumentViewerContextProvider } from './features/documents/context/DocumentViewerContext';
import { WorklistContextProvider } from './features/properties/worklist/context/WorklistContext';
import { ITenantConfig2 } from './hooks/pims-api/interfaces/ITenantConfig';
import { useRefreshSiteminder } from './hooks/useRefreshSiteminder';
import { initializeTelemetry } from './telemetry';
import { defaultHistogramBuckets, TelemetryConfig } from './telemetry/config';
import { Telemetry } from './telemetry';
import { ReactRouterSpanProcessor } from './telemetry/traces/ReactRouterSpanProcessor';
import { exists } from './utils';
import { stringToNull, stringToNullableBoolean, stringToNumberOrNull } from './utils/formUtils';
import { stringToNullableBoolean, stringToNumberOrNull } from './utils/formUtils';

async function prepare() {
if (process.env.NODE_ENV === 'development') {
Expand Down Expand Up @@ -93,39 +91,20 @@ const InnerComponent = ({ tenant }: { tenant: ITenantConfig2 }) => {
);
};

// get telemetry options from global configuration.
// window.config is set in index.html, populated by env variables.
const setupTelemetry = () => {
const isTelemetryEnabled = stringToNullableBoolean(config.VITE_TELEMERY_ENABLED) ?? false;
const isDebugEnabled = stringToNullableBoolean(config.VITE_TELEMERY_DEBUG) ?? false;

if (isTelemetryEnabled) {
const jsonValues = stringToNull(config.VITE_TELEMERY_HISTOGRAM_BUCKETS);
const buckets: number[] = exists(jsonValues) ? JSON.parse(jsonValues) : defaultHistogramBuckets;
const options: TelemetryConfig = {
name: config.VITE_TELEMERY_SERVICE_NAME ?? 'frontend',
appVersion: import.meta.env.VITE_PACKAGE_VERSION ?? '',
environment: config.VITE_TELEMERY_ENVIRONMENT || 'local',
otlpEndpoint: config.VITE_TELEMERY_URL || '',
debug: isDebugEnabled,
exportInterval: stringToNumberOrNull(config.VITE_TELEMERY_EXPORT_INTERVAL) ?? 30000,
histogramBuckets: buckets,
};

// configure browser telemetry (if enabled via dynamic config-map)
initializeTelemetry(options);

console.log('[INFO] Telemetry enabled');
if (isDebugEnabled) {
console.log(options);
}
} else {
console.log('[INFO] Telemetry disabled');
}
};

prepare().then(() => {
setupTelemetry();
// Bootstrap once at app entry point to ensure telemetry is setup before any other code runs, so that we can capture telemetry for the entire app lifecycle.
Telemetry.init({
appName: TelemetryConfig.appName || 'pims-frontend',
appVersion: TelemetryConfig.appVersion || 'unknown',
environment: TelemetryConfig.environment || 'local',
collectorUrl: TelemetryConfig.telemetryUrl || '',
debug: stringToNullableBoolean(TelemetryConfig.debug) ?? false,
metricExportIntervalMs: stringToNumberOrNull(TelemetryConfig.metricExportIntervalMs) ?? 30000,
traceExportIntervalMs: stringToNumberOrNull(TelemetryConfig.traceExportIntervalMs) ?? 5000,
histogramBuckets: TelemetryConfig.histogramBuckets,
});

// Now that telemetry is initialized, we can render the app.
const root = createRoot(document.getElementById('root') as Element);
root.render(<Index />);
});
6 changes: 5 additions & 1 deletion source/frontend/src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import { loadingBarMiddleware } from 'react-redux-loading-bar';
import logger from 'redux-logger';

import { reducer } from './rootReducer';
import { telemetryMiddleware } from './telemetryMiddleware';

export const store = configureStore({
reducer: reducer,
middleware: (getDefaultMiddleware: () => any[]) =>
getDefaultMiddleware().concat(logger).concat(loadingBarMiddleware()),
getDefaultMiddleware()
.concat(telemetryMiddleware)
.concat(logger)
.concat(loadingBarMiddleware()),
devTools: import.meta.env.PROD === false,
});
export type AppDispatch = typeof store.dispatch;
Expand Down
29 changes: 29 additions & 0 deletions source/frontend/src/store/telemetryMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Middleware } from '@reduxjs/toolkit';

import { Telemetry } from '@/telemetry';

import { logError } from './slices/network/networkSlice';

// Log "bomb" errors to telemetry with relevant attributes for easier debugging and monitoring of network errors
export const telemetryMiddleware: Middleware = _storeAPI => next => async action => {
const result = await next(action);

if (logError.match(action)) {
const { name, status, error } = action.payload;

Telemetry.recordException(
error,
{
'network.request.name': name,
'network.response.status': status,
'network.error.message': error?.message ?? 'Unknown error',
'network.error.code': error?.code,
'network.error.config.url': error?.config?.url,
'network.error.config.method': error?.config?.method,
},
'bomb.network.error',
);
}

return result;
};
25 changes: 15 additions & 10 deletions source/frontend/src/telemetry/config.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { Attributes } from '@opentelemetry/api';

// The configuration for browser telemetry (metrics and logs)
export interface TelemetryConfig {
// by default the service name is set to 'frontend' - helps finding traces in the trace UI dashboard
name?: string;
export interface TelemetrySettings {
appName: string;
appVersion?: string;
// set this to match the deployed environment (dev, test, uat, prod) or set to local for local development
// Set this to match the deployed environment (dev, test, uat, prod) or set to local for local development
environment?: string;
// the URL to the open-telemetry collector
otlpEndpoint?: string;
// The URL to the open-telemetry collector service that will receive the telemetry data
collectorUrl?: string;
// a list of URLs to ignore for traces and metrics
denyUrls?: string[];
// if true, it will output extra information to the console
// If true, it will output extra information to the console
debug?: boolean;
// how often to send traces and metrics back to the collector - defaults to 30 seconds
exportInterval?: number;
// the default buckets to apply to histogram metrics
// Metric export interval in ms (default: 30,000)
metricExportIntervalMs?: number;
// Trace export interval in ms (default: 5,000)
traceExportIntervalMs?: number;
// Default buckets to apply to histogram metrics
histogramBuckets?: number[];
// Additional resource atttributes to add to all telemetry data (traces and metrics)
resourceAttributes?: Attributes;
}

export const defaultHistogramBuckets = [
Expand Down
Loading
Loading