Fix TypeScript compilation errors
- Add searchReference to ApplicationListItem type - Fix result variable in toApplicationDetails function - Add query helper functions for req.query parameter handling - Fix req.query.* type errors in routes (applications, cache, classifications, objects) - Fix CompletenessCategoryConfig missing id property - Fix number | null type errors in dataService - Add utils/queryHelpers.ts for reusable query parameter helpers
This commit is contained in:
@@ -6,6 +6,7 @@ import { logger } from '../services/logger.js';
|
||||
import { calculateRequiredEffortApplicationManagementWithBreakdown } from '../services/effortCalculation.js';
|
||||
import { findBIAMatch, loadBIAData, clearBIACache, calculateSimilarity } from '../services/biaMatchingService.js';
|
||||
import { calculateApplicationCompleteness } from '../services/dataCompletenessConfig.js';
|
||||
import { getQueryString } from '../utils/queryHelpers.js';
|
||||
import type { SearchFilters, ReferenceValue, ClassificationResult, ApplicationDetails, ApplicationStatus } from '../types/index.js';
|
||||
import type { Server, Flows, Certificate, Domain, AzureSubscription, CMDBObjectTypeName } from '../generated/jira-types.js';
|
||||
|
||||
@@ -31,7 +32,7 @@ router.post('/search', async (req: Request, res: Response) => {
|
||||
// Get team dashboard data
|
||||
router.get('/team-dashboard', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const excludedStatusesParam = req.query.excludedStatuses as string | undefined;
|
||||
const excludedStatusesParam = getQueryString(req, 'excludedStatuses');
|
||||
let excludedStatuses: ApplicationStatus[] = [];
|
||||
|
||||
if (excludedStatusesParam && excludedStatusesParam.trim().length > 0) {
|
||||
@@ -56,7 +57,7 @@ router.get('/team-dashboard', async (req: Request, res: Response) => {
|
||||
// Get team portfolio health metrics
|
||||
router.get('/team-portfolio-health', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const excludedStatusesParam = req.query.excludedStatuses as string | undefined;
|
||||
const excludedStatusesParam = getQueryString(req, 'excludedStatuses');
|
||||
let excludedStatuses: ApplicationStatus[] = [];
|
||||
|
||||
if (excludedStatusesParam && excludedStatusesParam.trim().length > 0) {
|
||||
@@ -95,7 +96,7 @@ router.get('/business-importance-comparison', async (req: Request, res: Response
|
||||
// Test BIA data loading (for debugging)
|
||||
router.get('/bia-test', async (req: Request, res: Response) => {
|
||||
try {
|
||||
if (req.query.clear === 'true') {
|
||||
if (getQueryString(req, 'clear') === 'true') {
|
||||
clearBIACache();
|
||||
}
|
||||
const biaData = loadBIAData();
|
||||
@@ -133,7 +134,7 @@ router.get('/bia-debug', async (req: Request, res: Response) => {
|
||||
|
||||
// Test each sample app
|
||||
for (const app of [...sampleApps, ...testApps]) {
|
||||
const matchResult = findBIAMatch(app.name, app.searchReference);
|
||||
const matchResult = findBIAMatch(app.name, app.searchReference ?? null);
|
||||
|
||||
// Find all potential matches in Excel data for detailed analysis
|
||||
const normalizedAppName = app.name.toLowerCase().trim();
|
||||
@@ -246,7 +247,7 @@ router.get('/bia-comparison', async (req: Request, res: Response) => {
|
||||
|
||||
for (const app of applications) {
|
||||
// Find BIA match in Excel
|
||||
const matchResult = findBIAMatch(app.name, app.searchReference);
|
||||
const matchResult = findBIAMatch(app.name, app.searchReference ?? null);
|
||||
|
||||
// Log first few matches for debugging
|
||||
if (comparisonItems.length < 5) {
|
||||
@@ -323,7 +324,7 @@ router.get('/bia-comparison', async (req: Request, res: Response) => {
|
||||
router.get('/:id', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const mode = req.query.mode as string | undefined;
|
||||
const mode = getQueryString(req, 'mode');
|
||||
|
||||
// Don't treat special routes as application IDs
|
||||
if (id === 'team-dashboard' || id === 'team-portfolio-health' || id === 'business-importance-comparison' || id === 'bia-comparison' || id === 'bia-test' || id === 'calculate-effort' || id === 'search') {
|
||||
@@ -617,8 +618,9 @@ router.get('/:id/related/:objectType', async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
// Get requested attributes from query string
|
||||
const requestedAttrs = req.query.attributes
|
||||
? String(req.query.attributes).split(',').map(a => a.trim())
|
||||
const attributesParam = getQueryString(req, 'attributes');
|
||||
const requestedAttrs = attributesParam
|
||||
? attributesParam.split(',').map(a => a.trim())
|
||||
: [];
|
||||
|
||||
// Format response - must match RelatedObjectsResponse type expected by frontend
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Router, Request, Response } from 'express';
|
||||
import { cacheStore } from '../services/cacheStore.js';
|
||||
import { syncEngine } from '../services/syncEngine.js';
|
||||
import { logger } from '../services/logger.js';
|
||||
import { getQueryString } from '../utils/queryHelpers.js';
|
||||
import { OBJECT_TYPES } from '../generated/jira-schema.js';
|
||||
import type { CMDBObjectTypeName } from '../generated/jira-types.js';
|
||||
|
||||
@@ -78,10 +79,11 @@ router.post('/sync/:objectType', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { objectType } = req.params;
|
||||
|
||||
// Validate object type
|
||||
if (!OBJECT_TYPES[objectType]) {
|
||||
// Validate object type - convert to string if array
|
||||
const objectTypeStr = Array.isArray(objectType) ? objectType[0] : objectType;
|
||||
if (!OBJECT_TYPES[objectTypeStr]) {
|
||||
res.status(400).json({
|
||||
error: `Unknown object type: ${objectType}`,
|
||||
error: `Unknown object type: ${objectTypeStr}`,
|
||||
supportedTypes: Object.keys(OBJECT_TYPES),
|
||||
});
|
||||
return;
|
||||
@@ -89,11 +91,11 @@ router.post('/sync/:objectType', async (req: Request, res: Response) => {
|
||||
|
||||
logger.info(`Manual sync triggered for ${objectType}`);
|
||||
|
||||
const result = await syncEngine.syncType(objectType as CMDBObjectTypeName);
|
||||
const result = await syncEngine.syncType(objectTypeStr as CMDBObjectTypeName);
|
||||
|
||||
res.json({
|
||||
status: 'completed',
|
||||
objectType,
|
||||
objectType: objectTypeStr,
|
||||
stats: result,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -113,10 +115,11 @@ router.post('/sync/:objectType', async (req: Request, res: Response) => {
|
||||
router.delete('/clear/:objectType', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { objectType } = req.params;
|
||||
const objectTypeStr = Array.isArray(objectType) ? objectType[0] : objectType;
|
||||
|
||||
if (!OBJECT_TYPES[objectType]) {
|
||||
if (!OBJECT_TYPES[objectTypeStr]) {
|
||||
res.status(400).json({
|
||||
error: `Unknown object type: ${objectType}`,
|
||||
error: `Unknown object type: ${objectTypeStr}`,
|
||||
supportedTypes: Object.keys(OBJECT_TYPES),
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { dataService } from '../services/dataService.js';
|
||||
import { databaseService } from '../services/database.js';
|
||||
import { logger } from '../services/logger.js';
|
||||
import { config } from '../config/env.js';
|
||||
import { getQueryString, getQueryNumber } from '../utils/queryHelpers.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -12,7 +13,7 @@ router.post('/suggest/:id', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
// Get provider from query parameter or request body, default to config
|
||||
const provider = (req.query.provider as AIProvider) || (req.body.provider as AIProvider) || config.defaultAIProvider;
|
||||
const provider = (getQueryString(req, 'provider') as AIProvider) || (req.body.provider as AIProvider) || config.defaultAIProvider;
|
||||
|
||||
if (!aiService.isConfigured(provider)) {
|
||||
res.status(503).json({
|
||||
@@ -70,7 +71,7 @@ router.get('/function/:code', (req: Request, res: Response) => {
|
||||
// Get classification history
|
||||
router.get('/history', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const limit = parseInt(req.query.limit as string) || 50;
|
||||
const limit = getQueryNumber(req, 'limit', 50);
|
||||
const history = await databaseService.getClassificationHistory(limit);
|
||||
res.json(history);
|
||||
} catch (error) {
|
||||
|
||||
@@ -142,37 +142,39 @@ router.get('/data-completeness', async (req: Request, res: Response) => {
|
||||
description: 'Configuration for Data Completeness Score fields',
|
||||
lastUpdated: new Date().toISOString(),
|
||||
},
|
||||
categories: {
|
||||
general: {
|
||||
categories: [
|
||||
{
|
||||
id: 'general',
|
||||
name: 'General',
|
||||
description: 'General application information fields',
|
||||
fields: [
|
||||
{ name: 'Organisation', fieldPath: 'organisation', enabled: true },
|
||||
{ name: 'ApplicationFunction', fieldPath: 'applicationFunctions', enabled: true },
|
||||
{ name: 'Status', fieldPath: 'status', enabled: true },
|
||||
{ name: 'Business Impact Analyse', fieldPath: 'businessImpactAnalyse', enabled: true },
|
||||
{ name: 'Application Component Hosting Type', fieldPath: 'hostingType', enabled: true },
|
||||
{ name: 'Supplier Product', fieldPath: 'supplierProduct', enabled: true },
|
||||
{ name: 'Business Owner', fieldPath: 'businessOwner', enabled: true },
|
||||
{ name: 'System Owner', fieldPath: 'systemOwner', enabled: true },
|
||||
{ name: 'Functional Application Management', fieldPath: 'functionalApplicationManagement', enabled: true },
|
||||
{ name: 'Technical Application Management', fieldPath: 'technicalApplicationManagement', enabled: true },
|
||||
{ id: 'organisation', name: 'Organisation', fieldPath: 'organisation', enabled: true },
|
||||
{ id: 'applicationFunctions', name: 'ApplicationFunction', fieldPath: 'applicationFunctions', enabled: true },
|
||||
{ id: 'status', name: 'Status', fieldPath: 'status', enabled: true },
|
||||
{ id: 'businessImpactAnalyse', name: 'Business Impact Analyse', fieldPath: 'businessImpactAnalyse', enabled: true },
|
||||
{ id: 'hostingType', name: 'Application Component Hosting Type', fieldPath: 'hostingType', enabled: true },
|
||||
{ id: 'supplierProduct', name: 'Supplier Product', fieldPath: 'supplierProduct', enabled: true },
|
||||
{ id: 'businessOwner', name: 'Business Owner', fieldPath: 'businessOwner', enabled: true },
|
||||
{ id: 'systemOwner', name: 'System Owner', fieldPath: 'systemOwner', enabled: true },
|
||||
{ id: 'functionalApplicationManagement', name: 'Functional Application Management', fieldPath: 'functionalApplicationManagement', enabled: true },
|
||||
{ id: 'technicalApplicationManagement', name: 'Technical Application Management', fieldPath: 'technicalApplicationManagement', enabled: true },
|
||||
],
|
||||
},
|
||||
applicationManagement: {
|
||||
{
|
||||
id: 'applicationManagement',
|
||||
name: 'Application Management',
|
||||
description: 'Application management classification fields',
|
||||
fields: [
|
||||
{ name: 'ICT Governance Model', fieldPath: 'governanceModel', enabled: true },
|
||||
{ name: 'Application Management - Application Type', fieldPath: 'applicationType', enabled: true },
|
||||
{ name: 'Application Management - Hosting', fieldPath: 'applicationManagementHosting', enabled: true },
|
||||
{ name: 'Application Management - TAM', fieldPath: 'applicationManagementTAM', enabled: true },
|
||||
{ name: 'Application Management - Dynamics Factor', fieldPath: 'dynamicsFactor', enabled: true },
|
||||
{ name: 'Application Management - Complexity Factor', fieldPath: 'complexityFactor', enabled: true },
|
||||
{ name: 'Application Management - Number of Users', fieldPath: 'numberOfUsers', enabled: true },
|
||||
{ id: 'governanceModel', name: 'ICT Governance Model', fieldPath: 'governanceModel', enabled: true },
|
||||
{ id: 'applicationType', name: 'Application Management - Application Type', fieldPath: 'applicationType', enabled: true },
|
||||
{ id: 'applicationManagementHosting', name: 'Application Management - Hosting', fieldPath: 'applicationManagementHosting', enabled: true },
|
||||
{ id: 'applicationManagementTAM', name: 'Application Management - TAM', fieldPath: 'applicationManagementTAM', enabled: true },
|
||||
{ id: 'dynamicsFactor', name: 'Application Management - Dynamics Factor', fieldPath: 'dynamicsFactor', enabled: true },
|
||||
{ id: 'complexityFactor', name: 'Application Management - Complexity Factor', fieldPath: 'complexityFactor', enabled: true },
|
||||
{ id: 'numberOfUsers', name: 'Application Management - Number of Users', fieldPath: 'numberOfUsers', enabled: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
res.json(defaultConfig);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { cmdbService } from '../services/cmdbService.js';
|
||||
import { logger } from '../services/logger.js';
|
||||
import { getQueryString, getQueryNumber } from '../utils/queryHelpers.js';
|
||||
import { OBJECT_TYPES } from '../generated/jira-schema.js';
|
||||
import type { CMDBObject, CMDBObjectTypeName } from '../generated/jira-types.js';
|
||||
|
||||
@@ -32,9 +33,9 @@ router.get('/', (req: Request, res: Response) => {
|
||||
router.get('/:type', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { type } = req.params;
|
||||
const limit = parseInt(req.query.limit as string) || 1000;
|
||||
const offset = parseInt(req.query.offset as string) || 0;
|
||||
const search = req.query.search as string | undefined;
|
||||
const limit = getQueryNumber(req, 'limit', 1000);
|
||||
const offset = getQueryNumber(req, 'offset', 0);
|
||||
const search = getQueryString(req, 'search');
|
||||
|
||||
// Validate type
|
||||
if (!OBJECT_TYPES[type]) {
|
||||
@@ -71,7 +72,7 @@ router.get('/:type', async (req: Request, res: Response) => {
|
||||
router.get('/:type/:id', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { type, id } = req.params;
|
||||
const forceRefresh = req.query.refresh === 'true';
|
||||
const forceRefresh = getQueryString(req, 'refresh') === 'true';
|
||||
|
||||
// Validate type
|
||||
if (!OBJECT_TYPES[type]) {
|
||||
@@ -102,7 +103,7 @@ router.get('/:type/:id', async (req: Request, res: Response) => {
|
||||
router.get('/:type/:id/related/:relationType', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { type, id, relationType } = req.params;
|
||||
const attribute = req.query.attribute as string | undefined;
|
||||
const attribute = getQueryString(req, 'attribute');
|
||||
|
||||
// Validate types
|
||||
if (!OBJECT_TYPES[type]) {
|
||||
@@ -139,7 +140,7 @@ router.get('/:type/:id/related/:relationType', async (req: Request, res: Respons
|
||||
router.get('/:type/:id/referenced-by/:sourceType', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { type, id, sourceType } = req.params;
|
||||
const attribute = req.query.attribute as string | undefined;
|
||||
const attribute = getQueryString(req, 'attribute');
|
||||
|
||||
// Validate types
|
||||
if (!OBJECT_TYPES[type]) {
|
||||
|
||||
@@ -289,19 +289,18 @@ async function toApplicationDetails(app: ApplicationComponent): Promise<Applicat
|
||||
};
|
||||
|
||||
// Calculate data completeness percentage
|
||||
// Convert ApplicationListItem-like structure to format expected by completeness calculator
|
||||
// Convert ApplicationDetails-like structure to format expected by completeness calculator
|
||||
const appForCompleteness = {
|
||||
...result,
|
||||
organisation: organisation?.name || null,
|
||||
applicationFunctions: result.applicationFunctions,
|
||||
status: result.status,
|
||||
applicationFunctions: applicationFunctions,
|
||||
status: (app.status || 'In Production') as ApplicationStatus,
|
||||
businessImpactAnalyse: businessImpactAnalyse,
|
||||
hostingType: hostingType,
|
||||
supplierProduct: result.supplierProduct,
|
||||
businessOwner: result.businessOwner,
|
||||
systemOwner: result.systemOwner,
|
||||
functionalApplicationManagement: result.functionalApplicationManagement,
|
||||
technicalApplicationManagement: result.technicalApplicationManagement,
|
||||
supplierProduct: extractLabel(app.supplierProduct),
|
||||
businessOwner: extractLabel(app.businessOwner),
|
||||
systemOwner: extractLabel(app.systemOwner),
|
||||
functionalApplicationManagement: app.functionalApplicationManagement || null,
|
||||
technicalApplicationManagement: extractLabel(app.technicalApplicationManagement),
|
||||
governanceModel: governanceModel,
|
||||
applicationType: applicationType,
|
||||
applicationManagementHosting: applicationManagementHosting,
|
||||
@@ -314,7 +313,48 @@ async function toApplicationDetails(app: ApplicationComponent): Promise<Applicat
|
||||
const completenessPercentage = calculateApplicationCompleteness(appForCompleteness);
|
||||
|
||||
return {
|
||||
...result,
|
||||
id: app.id,
|
||||
key: app.objectKey,
|
||||
name: app.label,
|
||||
description: app.description || null,
|
||||
status: (app.status || 'In Production') as ApplicationStatus,
|
||||
searchReference: app.searchReference || null,
|
||||
|
||||
// Organization info
|
||||
organisation: organisation?.name || null,
|
||||
businessOwner: extractLabel(app.businessOwner),
|
||||
systemOwner: extractLabel(app.systemOwner),
|
||||
functionalApplicationManagement: app.functionalApplicationManagement || null,
|
||||
technicalApplicationManagement: extractLabel(app.technicalApplicationManagement),
|
||||
technicalApplicationManagementPrimary: extractDisplayValue(app.technicalApplicationManagementPrimary),
|
||||
technicalApplicationManagementSecondary: extractDisplayValue(app.technicalApplicationManagementSecondary),
|
||||
|
||||
// Technical info
|
||||
medischeTechniek: app.medischeTechniek || false,
|
||||
technischeArchitectuur: app.technischeArchitectuurTA || null,
|
||||
supplierProduct: extractLabel(app.supplierProduct),
|
||||
|
||||
// Classification
|
||||
applicationFunctions,
|
||||
businessImportance: businessImportance?.name || null,
|
||||
businessImpactAnalyse,
|
||||
hostingType,
|
||||
|
||||
// Application Management
|
||||
governanceModel,
|
||||
applicationType,
|
||||
applicationSubteam,
|
||||
applicationTeam,
|
||||
dynamicsFactor,
|
||||
complexityFactor,
|
||||
numberOfUsers,
|
||||
applicationManagementHosting,
|
||||
applicationManagementTAM,
|
||||
platform,
|
||||
|
||||
// Override
|
||||
overrideFTE: app.applicationManagementOverrideFTE ?? null,
|
||||
requiredEffortApplicationManagement: null,
|
||||
dataCompletenessPercentage: Math.round(completenessPercentage * 10) / 10, // Round to 1 decimal
|
||||
};
|
||||
}
|
||||
@@ -457,6 +497,7 @@ function toApplicationListItem(app: ApplicationComponent): ApplicationListItem {
|
||||
id: app.id,
|
||||
key: app.objectKey,
|
||||
name: app.label,
|
||||
searchReference: app.searchReference || null,
|
||||
status: app.status as ApplicationStatus | null,
|
||||
applicationFunctions,
|
||||
governanceModel,
|
||||
@@ -1209,7 +1250,7 @@ export const dataService = {
|
||||
// Get complexity factor value
|
||||
if (app.applicationManagementComplexityFactor && typeof app.applicationManagementComplexityFactor === 'object') {
|
||||
const factorObj = complexityFactorCache?.get(app.applicationManagementComplexityFactor.objectId);
|
||||
if (factorObj?.factor !== undefined) {
|
||||
if (factorObj?.factor !== undefined && factorObj.factor !== null) {
|
||||
metrics.complexityValues.push(factorObj.factor);
|
||||
}
|
||||
}
|
||||
@@ -1217,7 +1258,7 @@ export const dataService = {
|
||||
// Get dynamics factor value
|
||||
if (app.applicationManagementDynamicsFactor && typeof app.applicationManagementDynamicsFactor === 'object') {
|
||||
const factorObj = dynamicsFactorCache?.get(app.applicationManagementDynamicsFactor.objectId);
|
||||
if (factorObj?.factor !== undefined) {
|
||||
if (factorObj?.factor !== undefined && factorObj.factor !== null) {
|
||||
metrics.dynamicsValues.push(factorObj.factor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface ApplicationListItem {
|
||||
id: string;
|
||||
key: string;
|
||||
name: string;
|
||||
searchReference?: string | null; // Search reference for matching
|
||||
status: ApplicationStatus | null;
|
||||
applicationFunctions: ReferenceValue[]; // Multiple functions supported
|
||||
governanceModel: ReferenceValue | null;
|
||||
|
||||
34
backend/src/utils/queryHelpers.ts
Normal file
34
backend/src/utils/queryHelpers.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Helper functions for Express request query parameters
|
||||
*/
|
||||
|
||||
import { Request } from 'express';
|
||||
|
||||
/**
|
||||
* Get a query parameter as a string, handling both string and string[] types
|
||||
*/
|
||||
export function getQueryString(req: Request, key: string): string | undefined {
|
||||
const value = req.query[key];
|
||||
if (value === undefined) return undefined;
|
||||
if (Array.isArray(value)) return value[0] as string;
|
||||
return value as string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a query parameter as a number, handling both string and string[] types
|
||||
*/
|
||||
export function getQueryNumber(req: Request, key: string, defaultValue?: number): number {
|
||||
const value = getQueryString(req, key);
|
||||
if (value === undefined) return defaultValue ?? 0;
|
||||
const parsed = parseInt(value, 10);
|
||||
return isNaN(parsed) ? (defaultValue ?? 0) : parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a query parameter as a boolean
|
||||
*/
|
||||
export function getQueryBoolean(req: Request, key: string, defaultValue = false): boolean {
|
||||
const value = getQueryString(req, key);
|
||||
if (value === undefined) return defaultValue;
|
||||
return value === 'true' || value === '1';
|
||||
}
|
||||
Reference in New Issue
Block a user