version update
test-org/trust-module-backend/pipeline/pr-rc This commit looks good Details

pull/39/head
DmitriyA 2025-12-02 04:36:15 -05:00
parent 6a47ef4601
commit d97a0b95f6
5 changed files with 413 additions and 3 deletions

View File

@ -0,0 +1,38 @@
// controllers/enriched-formula.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import { FormulaEnrichmentService } from './formula-enrichment.service';
import { EnrichedFormulaMetric } from './formula.interface';
@ApiTags('Enriched Formulas')
@Controller('enriched-formulas')
export class EnrichedFormulaController {
constructor(
private readonly formulaEnrichmentService: FormulaEnrichmentService
) { }
@Get()
@ApiOperation({ summary: 'Получить все формулы с обогащенными данными' })
@ApiResponse({
status: 200,
description: 'Список формул с обогащенными метриками'
})
async getAllEnrichedFormulas(): Promise<EnrichedFormulaMetric[]> {
return this.formulaEnrichmentService.getAllEnrichedFormulas();
}
@Get(':id')
@ApiOperation({ summary: 'Получить конкретную формулу с обогащенными данными' })
@ApiParam({ name: 'id', description: 'ID формулы' })
@ApiResponse({
status: 200,
description: 'Формула с обогащенными метриками'
})
@ApiResponse({
status: 404,
description: 'Формула не найдена'
})
async getEnrichedFormula(@Param('id') id: string): Promise<EnrichedFormulaMetric> {
return this.formulaEnrichmentService.getEnrichedFormula(id);
}
}

View File

@ -0,0 +1,307 @@
// services/formula-enrichment.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { PrometheusService } from '../prometheus/prometheus.service';
import { FormulaService } from './formula.service';
import {
FormulaMetric,
EnrichedFormulaMetric,
EnrichedMetric
} from './formula.interface';
@Injectable()
export class FormulaEnrichmentService {
private readonly logger = new Logger(FormulaEnrichmentService.name);
// Конфигурируемые префиксы
private readonly metricPrefixes = ['zvks_', 'server_', 'application_'];
private readonly defaultPrefix = 'zvks_';
constructor(
private readonly prometheusService: PrometheusService,
private readonly formulaService: FormulaService
) { }
/**
* Автоматически определяет имя метрики в Prometheus
*/
private resolveMetricName(originalName: string): string {
// Если имя уже содержит префикс, используем как есть
if (this.metricPrefixes.some(prefix => originalName.startsWith(prefix))) {
return originalName;
}
// Иначе добавляем дефолтный префикс
return `${this.defaultPrefix}${originalName}`;
}
/**
* Ищет метрику в Prometheus с учетом различных вариантов имен
*/
private async findMetricInPrometheus(originalName: string): Promise<{
prometheusName: string;
metrics: any[];
found: boolean;
}> {
const possibleNames = [
originalName, // как есть
this.resolveMetricName(originalName), // с дефолтным префиксом
// Можно добавить другие варианты если нужно
];
for (const metricName of possibleNames) {
try {
this.logger.debug(`Trying to find metric: ${metricName}`);
const metrics = await this.prometheusService.fetchMetrics(metricName);
if (metrics && metrics.length > 0) {
this.logger.debug(`Found metric: ${metricName} with ${metrics.length} entries`);
return {
prometheusName: metricName,
metrics,
found: true
};
}
} catch (error) {
this.logger.debug(`Metric ${metricName} not found: ${error.message}`);
// Продолжаем поиск с следующим вариантом
continue;
}
}
return {
prometheusName: this.resolveMetricName(originalName),
metrics: [],
found: false
};
}
/**
* Получить обогащенные данные формулы по ID
*/
async getEnrichedFormula(id: string): Promise<EnrichedFormulaMetric> {
const formulaData = await this.formulaService.getFormulaData(id);
if (!formulaData || !formulaData.values) {
throw new Error(`Formula data not found for id: ${id}`);
}
const enrichedMetrics = await this.enrichMetrics(formulaData.values.statusarr);
const { parsedFormula, humanReadableFormula } = this.parseFormula(
formulaData.formula,
enrichedMetrics
);
return {
...formulaData,
enrichedMetrics,
parsedFormula,
humanReadableFormula,
metadata: {
totalMetrics: enrichedMetrics.length,
foundMetrics: enrichedMetrics.filter(m => m.found).length,
missingMetrics: enrichedMetrics.filter(m => !m.found).length
}
};
}
/**
* Обогащает массив метрик данными из Prometheus
*/
private async enrichMetrics(metricNames: string[]): Promise<EnrichedMetric[]> {
const enrichmentPromises = metricNames.map(async (originalName) => {
try {
const { prometheusName, metrics, found } = await this.findMetricInPrometheus(originalName);
if (found && metrics.length > 0) {
const metric = metrics[0]; // Берем первую метрику как пример
const description = metric.description || await this.getMetricDescription(prometheusName);
return {
originalName,
prometheusName,
description,
currentValue: metric.value,
device: metric.device,
source_id: metric.source_id,
timestamp: metric.timestamp,
type: metric.type,
found: true,
valuesCount: metrics.length
};
} else {
// Метрика не найдена
const description = await this.getMetricDescription(prometheusName);
return {
originalName,
prometheusName,
description: description || 'Метрика не найдена в Prometheus',
currentValue: undefined,
found: false,
valuesCount: 0,
error: `Метрика не найдена. Проверенные имена: ${prometheusName}`
};
}
} catch (error) {
this.logger.error(`Error enriching metric ${originalName}:`, error);
return {
originalName,
prometheusName: this.resolveMetricName(originalName),
description: `Ошибка при получении данных: ${error.message}`,
currentValue: undefined,
found: false,
valuesCount: 0,
error: error.message
};
}
});
return Promise.all(enrichmentPromises);
}
/**
* Получает описание метрики
*/
private async getMetricDescription(metricName: string): Promise<string> {
try {
const description = await this.prometheusService.fetchMetricDescription(metricName);
return description || 'Описание недоступно';
} catch (error) {
return 'Описание недоступно';
}
}
/**
* Парсит формулу для лучшего отображения
*/
private parseFormula(
formula: string,
enrichedMetrics: EnrichedMetric[]
): { parsedFormula: string; humanReadableFormula: string } {
let humanReadableFormula = formula;
// Заменяем statusarr[index] на описания метрик
enrichedMetrics.forEach((metric, index) => {
const arrayIndex = index + 1; // В формулах индексы с 1
const statusarrPattern = new RegExp(`statusarr\\[${arrayIndex}\\]`, 'g');
// Для humanReadableFormula используем описания метрик
if (metric.found) {
humanReadableFormula = humanReadableFormula.replace(
statusarrPattern,
metric.description
);
} else {
humanReadableFormula = humanReadableFormula.replace(
statusarrPattern,
`${metric.originalName} (НЕ НАЙДЕНА)`
);
}
});
// Форматируем для лучшей читаемости
humanReadableFormula = humanReadableFormula
.replace(/\*/g, ' × ')
.replace(/\//g, ' ÷ ')
.replace(/\+/g, ' + ')
.replace(/-/g, ' - ')
.replace(/\s+/g, ' ')
.trim();
return {
parsedFormula: formula,
humanReadableFormula
};
}
/**
* Получить все доступные формулы с обогащенными данными
*/
async getAllEnrichedFormulas(): Promise<EnrichedFormulaMetric[]> {
try {
const formulaOptions = await this.formulaService.getFormulaOptions('');
if (!Array.isArray(formulaOptions)) {
throw new Error('Invalid formula options response');
}
const enrichmentPromises = formulaOptions.map(async (formulaOption) => {
try {
return await this.getEnrichedFormula(formulaOption.id);
} catch (error) {
this.logger.error(`Error enriching formula ${formulaOption.id}:`, error);
return {
...formulaOption,
enrichedMetrics: [],
parsedFormula: formulaOption.formula || '',
humanReadableFormula: formulaOption.formula || '',
metadata: {
totalMetrics: 0,
foundMetrics: 0,
missingMetrics: 0
}
};
}
});
return Promise.all(enrichmentPromises);
} catch (error) {
this.logger.error('Error getting all enriched formulas:', error);
return [];
}
}
/**
* Диагностика - проверка доступности метрик
*/
async diagnoseMetrics(metricNames: string[]): Promise<any> {
const results = await Promise.all(
metricNames.map(async (originalName) => {
const { prometheusName, metrics, found } = await this.findMetricInPrometheus(originalName);
return {
originalName,
prometheusName,
found,
availableNames: await this.findAvailableMetricNames(originalName),
metricsCount: metrics.length,
sampleValue: found && metrics[0] ? metrics[0].value : null
};
})
);
return {
diagnosis: results,
summary: {
total: results.length,
found: results.filter(r => r.found).length,
notFound: results.filter(r => !r.found).length
}
};
}
/**
* Поиск доступных вариантов имен метрик
*/
private async findAvailableMetricNames(baseName: string): Promise<string[]> {
const possibleNames = [
baseName,
`${this.defaultPrefix}${baseName}`,
...this.metricPrefixes.map(prefix => `${prefix}${baseName}`)
];
const availableNames: string[] = [];
for (const name of possibleNames) {
try {
const metrics = await this.prometheusService.fetchMetrics(name);
if (metrics && metrics.length > 0) {
availableNames.push(name);
}
} catch (error) {
// Игнорируем ошибки - метрика не найдена
}
}
return availableNames;
}
}

View File

@ -1,12 +1,14 @@
import { Controller, Get, Post, Body, HttpException, HttpStatus, Param } from '@nestjs/common'; import { Controller, Get, Post, Body, HttpException, HttpStatus, Param } from '@nestjs/common';
import { FormulaService } from './formula.service'; import { FormulaService } from './formula.service';
import { MenuService } from './menu.service'; import { MenuService } from './menu.service';
import { FormulaEnrichmentService } from './formula-enrichment.service';
@Controller('formula') @Controller('formula')
export class FormulaController { export class FormulaController {
constructor( constructor(
private readonly FormulaService: FormulaService, private readonly FormulaService: FormulaService,
private readonly menuService: MenuService private readonly menuService: MenuService,
private readonly formulaEnrichmentService: FormulaEnrichmentService
) { } ) { }
@Get(':id') @Get(':id')
@ -45,4 +47,28 @@ export class FormulaController {
throw new HttpException('Failed to fetch Formula options', HttpStatus.INTERNAL_SERVER_ERROR); throw new HttpException('Failed to fetch Formula options', HttpStatus.INTERNAL_SERVER_ERROR);
} }
} }
@Get(':id/enriched')
async getEnrichedFormulaData(@Param('id') id: string) {
try {
return await this.formulaEnrichmentService.getEnrichedFormula(id);
} catch (error) {
throw new HttpException(
'Failed to fetch enriched formula data',
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
@Get('all/enriched')
async getAllEnrichedFormulas() {
try {
return await this.formulaEnrichmentService.getAllEnrichedFormulas();
} catch (error) {
throw new HttpException(
'Failed to fetch enriched formulas',
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
} }

View File

@ -0,0 +1,36 @@
// interfaces/formula.interface.ts
export interface FormulaMetric {
id: string;
name: string;
description: string;
values: {
statusarr: string[];
warr: string[];
};
formula: string;
}
export interface EnrichedFormulaMetric extends FormulaMetric {
enrichedMetrics: EnrichedMetric[];
parsedFormula: string;
humanReadableFormula: string;
metadata: {
totalMetrics: number;
foundMetrics: number;
missingMetrics: number;
};
}
export interface EnrichedMetric {
originalName: string;
prometheusName: string;
description: string;
currentValue?: number;
device?: string;
source_id?: string;
timestamp?: number;
type?: string;
found: boolean;
valuesCount?: number;
error?: string;
}

View File

@ -7,10 +7,13 @@ import { RangeService } from './range.service';
import { RangeController } from './range.controller'; import { RangeController } from './range.controller';
import { FormulaController } from './formula.controller'; import { FormulaController } from './formula.controller';
import { FormulaService } from './formula.service'; import { FormulaService } from './formula.service';
import { FormulaEnrichmentService } from './formula-enrichment.service';
import { EnrichedFormulaController } from './enriched-formula.controller';
@Module({ @Module({
imports: [PrometheusModule, HttpModule], imports: [PrometheusModule, HttpModule],
controllers: [MenuController, RangeController, FormulaController], controllers: [MenuController, RangeController, FormulaController, EnrichedFormulaController],
providers: [MenuService, RangeService, FormulaService] providers: [MenuService, RangeService, FormulaService, FormulaEnrichmentService],
exports: [FormulaEnrichmentService]
}) })
export class MenuModule { } export class MenuModule { }