Merged PR 1874: Remi Add Item Dialog FLow

Related work items: #5135
This commit is contained in:
Lorenz Hilpert
2025-06-25 13:45:25 +00:00
committed by Nino Righi
parent f34f2164fc
commit 26fd5cb389
13 changed files with 837 additions and 461 deletions

View File

@@ -1,2 +1,2 @@
export * from './lib/catalouge-search.service';
export * from './lib/services';
export * from './lib/models';

View File

@@ -1,21 +0,0 @@
import { inject, Injectable } from '@angular/core';
import { SearchService } from '@generated/swagger/cat-search-api';
import { map, Observable } from 'rxjs';
import { Item } from './models';
@Injectable({ providedIn: 'root' })
export class CatalougeSearchService {
#searchService = inject(SearchService);
searchByEans(...ean: string[]): Observable<Item[]> {
return this.#searchService.SearchByEAN(ean).pipe(
map((res) => {
if (res.error) {
throw new Error(res.message);
}
return res.result as Item[];
}),
);
}
}

View File

@@ -0,0 +1,9 @@
import { z } from 'zod';
export const SearchByTermParamsSchema = z.object({
term: z.string().min(1, 'Search term must not be empty'),
skip: z.number().int().min(0).default(0),
take: z.number().int().min(1).max(100).default(20),
});
export type SearchByTermParams = z.infer<typeof SearchByTermParamsSchema>;

View File

@@ -0,0 +1 @@
export * from './catalouge-search.schemas';

View File

@@ -0,0 +1,51 @@
import { inject, Injectable } from '@angular/core';
import { SearchService } from '@generated/swagger/cat-search-api';
import { firstValueFrom, map, Observable } from 'rxjs';
import { takeUntilAborted } from '@isa/common/data-access';
import { Item } from '../models';
import {
SearchByTermParams,
SearchByTermParamsSchema,
} from '../schemas/catalouge-search.schemas';
@Injectable({ providedIn: 'root' })
export class CatalougeSearchService {
#searchService = inject(SearchService);
searchByEans(...ean: string[]): Observable<Item[]> {
return this.#searchService.SearchByEAN(ean).pipe(
map((res) => {
if (res.error) {
throw new Error(res.message);
}
return res.result as Item[];
}),
);
}
async searchByTerm(
params: SearchByTermParams,
abortSignal: AbortSignal,
): Promise<Item[]> {
const { term, skip, take } = SearchByTermParamsSchema.parse(params);
const req$ = this.#searchService
.SearchSearch({
filter: {
qs: term,
},
skip,
take,
})
.pipe(takeUntilAborted(abortSignal));
const res = await firstValueFrom(req$);
if (res.error) {
throw new Error(res.message);
}
return res.result as Item[];
}
}

View File

@@ -1,2 +1 @@
export * from './catalouge-search.service';
export * from './models';

View File

@@ -84,7 +84,7 @@ export class PrintButtonComponent {
directPrint: this.directPrint(),
});
} catch (error) {
this.#logger.error('Print operation failed', { error });
this.#logger.error('Print operation failed', error as Error);
}
this.printing.set(false);
}

View File

@@ -1,21 +1,103 @@
# Core Logging
# @isa/core/logging
A structured, high-performance logging library for Angular applications.
A structured, high-performance logging library for Angular applications with hierarchical context support and flexible sink architecture.
## Overview
The Core Logging library provides a centralized logging service for the ISA Frontend application. It offers a structured way to log messages, errors, and other information across the application, with support for different log levels and output destinations.
The Core Logging library provides a centralized logging service for the ISA Frontend application. It offers a structured way to log messages, errors, and other information across the application, with support for different log levels, multiple output destinations, and rich contextual metadata.
## Table of Contents
- [Features](#features)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [API Reference](#api-reference)
- [Configuration](#configuration)
- [Usage Examples](#usage-examples)
- [Creating Custom Sinks](#creating-custom-sinks)
- [Performance Considerations](#performance-considerations)
- [Testing](#testing)
- [Migration Guide](#migration-guide)
## Features
- Multiple log levels (trace, debug, info, warn, error)
- Configurable logging targets (console, remote server, etc.)
- Context-aware logging with metadata support
- Production/development mode detection
- Filtering capabilities based on log level or context
- Highly optimized for performance
- Type-safe APIs
- Error resilience - logging failures don't affect application behavior
- **Multiple log levels** (trace, debug, info, warn, error, off)
- **Flexible sink architecture** - Multiple output destinations (console, remote server, etc.)
- **Hierarchical context system** - Component, instance, and message-level contexts
- **Performance optimized** - Early filtering and lazy evaluation
- **Type-safe APIs** - Full TypeScript support with comprehensive interfaces
- **Error resilience** - Logging failures don't affect application behavior
- **Angular integration** - Native dependency injection support
- **Factory pattern** - Easy logger creation with `logger()` function
- **Configurable providers** - Simple configuration through Angular providers
## Quick Start
### 1. Install and Configure
Add the logging provider to your application configuration:
```typescript
// app.config.ts
import { ApplicationConfig, isDevMode } from '@angular/core';
import {
provideLogging,
withLogLevel,
withSink,
LogLevel,
ConsoleLogSink
} from '@isa/core/logging';
export const appConfig: ApplicationConfig = {
providers: [
// Other providers...
provideLogging(
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn),
withSink(ConsoleLogSink)
)
]
};
```
### 2. Use in Components
```typescript
import { Component } from '@angular/core';
import { logger } from '@isa/core/logging';
@Component({
selector: 'app-example',
template: '...'
})
export class ExampleComponent {
#logger = logger();
ngOnInit(): void {
this.#logger.info('Component initialized');
}
handleUserAction(): void {
this.#logger.debug('User action triggered', () => ({
action: 'button-click',
timestamp: Date.now()
}));
}
}
```
### 3. Error Handling
```typescript
try {
await this.processData();
} catch (error) {
this.#logger.error(
'Data processing failed',
error as Error,
() => ({ operationId: '12345' })
);
}
```
## Core Concepts
@@ -40,29 +122,191 @@ Log sinks are destinations where log messages are sent. The library supports mul
- Send critical errors to a monitoring service
- Store logs for later analysis
## API
### Hierarchical Context System
### LoggingService
The logging system supports a powerful hierarchical context system that merges context from multiple levels:
The main service for logging functionality.
1. **Global Context** - Set during application configuration
2. **Component Context** - Provided at component/service level
3. **Instance Context** - Provided when creating logger instances
4. **Message Context** - Provided with individual log calls
#### Methods
| Method | Description |
| ---------------------------------------------------------- | --------------------------- |
| `trace(message: string, context?: unknown)` | Logs trace information |
| `debug(message: string, context?: unknown)` | Logs debug information |
| `info(message: string, context?: unknown)` | Logs informational messages |
| `warn(message: string, context?: unknown)` | Logs warning messages |
| `error(message: string, error?: Error, context?: unknown)` | Logs error messages |
### Logger Factory
The recommended way to use the logging system is through the `logger()` factory function:
Contexts are merged in order, with later contexts taking precedence:
```typescript
export class MyComponent {
#logger = logger();
// 1. Global context (app.config.ts)
provideLogging(
withContext({ app: 'ISA', version: '1.0.0' })
)
// 2. Component context
@Component({
providers: [
provideLoggerContext({ component: 'UserProfile', module: 'CRM' })
]
})
export class UserProfileComponent {
// 3. Instance context
#logger = logger(() => ({
userId: this.currentUser?.id,
sessionId: this.sessionService.id
}));
saveProfile(): void {
// 4. Message context
this.#logger.info('Saving profile', () => ({
profileId: this.profile.id,
changedFields: this.getChangedFields()
}));
// Final merged context will include all levels:
// {
// app: 'ISA',
// version: '1.0.0',
// component: 'UserProfile',
// module: 'CRM',
// userId: '12345',
// sessionId: 'sess-abc',
// profileId: 'prof-456',
// changedFields: ['name', 'email']
// }
}
}
```
## API Reference
### Core Interfaces
#### `LoggerApi`
The main interface for logging operations returned by the `logger()` factory:
```typescript
interface LoggerApi {
trace(message: string, context?: () => LoggerContext): void;
debug(message: string, context?: () => LoggerContext): void;
info(message: string, context?: () => LoggerContext): void;
warn(message: string, context?: () => LoggerContext): void;
error(message: string, error?: Error, context?: () => LoggerContext): void;
}
```
#### `LoggerContext`
Type definition for context data passed to logging methods:
```typescript
interface LoggerContext {
[key: string]: unknown;
}
```
#### `Sink`
Interface for creating custom logging destinations:
```typescript
interface Sink {
log(
level: LogLevel,
message: string,
context?: LoggerContext,
error?: Error
): void;
}
```
### Factory Functions
#### `logger(ctxFn?: () => LoggerContext): LoggerApi`
Creates a logger instance with optional dynamic context:
```typescript
// Basic usage
const basicLogger = logger();
// With dynamic context
const contextLogger = logger(() => ({
userId: getCurrentUserId(),
sessionId: getSessionId()
}));
```
**Parameters:**
- `ctxFn` (optional): Function that returns context data to be included with each log message
**Returns:** A `LoggerApi` instance configured with the specified context
### Configuration Functions
#### `provideLogging(...configs: LoggingConfigData[]): EnvironmentProviders`
Main configuration function for setting up the logging system:
```typescript
provideLogging(
withLogLevel(LogLevel.Debug),
withSink(ConsoleLogSink),
withContext({ app: 'MyApp' })
)
```
#### `withLogLevel(level: LogLevel): LoggingConfigData`
Sets the minimum log level for processing:
```typescript
withLogLevel(LogLevel.Debug) // Process debug and above
withLogLevel(LogLevel.Error) // Process only errors
```
#### `withSink(sink: Sink | Type<Sink>): LoggingConfigData`
Adds a logging destination:
```typescript
withSink(ConsoleLogSink) // Class reference
withSink(new CustomSink()) // Instance
```
#### `withSinkFn(sinkFn: SinkFn): LoggingConfigData`
Adds a sink factory function:
```typescript
withSinkFn(() => {
const http = inject(HttpClient);
return (level, message, context) => {
// Custom sink implementation
};
})
```
#### `withContext(context: LoggerContext): LoggingConfigData`
Adds global context to all log messages:
```typescript
withContext({
app: 'ISA',
version: '1.0.0',
environment: 'production'
})
```
#### `provideLoggerContext(context: LoggerContext): Provider[]`
Provides component-level context:
```typescript
@Component({
providers: [
provideLoggerContext({ component: 'UserProfile' })
]
})
export class UserProfileComponent {
ngOnInit(): void {
this.#logger.info('Component initialized');
@@ -355,36 +599,63 @@ describe('MyService', () => {
});
```
## Recent Changes
## Migration Guide
### Logger Factory Enhancements
### Migrating from Previous Versions
- **Context Functionality**: The `logger` factory function now accepts an optional `ctxFn` parameter. This allows additional context to be dynamically merged into log messages at runtime.
- **Enhanced Context Merging**: The `mergeContexts` function has been updated to include injector-level context, component-level context, and message-specific context. This ensures a more comprehensive and flexible logging context.
#### Example Usage
#### Context Parameter Changes
**Old API:**
```typescript
import { logger } from '@isa/core/logging';
@Component({
selector: 'app-example',
template: '...'
})
export class ExampleComponent {
#logger = logger(() => ({ userId: '12345' }));
ngOnInit(): void {
this.#logger.info('Component initialized');
}
performAction(): void {
this.#logger.debug('Action performed', { action: 'example' });
}
}
// Previous version - context as direct parameter
logger.info('Message', { userId: '123' });
logger.error('Error occurred', error, { operationId: '456' });
```
### Benefits
**New API:**
```typescript
// Current version - context as function
logger.info('Message', () => ({ userId: '123' }));
logger.error('Error occurred', error, () => ({ operationId: '456' }));
```
- **Dynamic Context**: Add runtime-specific context to log messages without modifying the logger instance.
- **Improved Debugging**: Enhanced context merging provides richer information for troubleshooting.
#### Factory Function Updates
**Old API:**
```typescript
// Previous version - no context support in factory
const logger = logger();
```
**New API:**
```typescript
// Current version - dynamic context support
const logger = logger(() => ({ sessionId: getSessionId() }));
```
#### Configuration Changes
**Old API:**
```typescript
// Previous version - direct service injection
constructor(private loggingService: LoggingService) {}
```
**New API:**
```typescript
// Current version - factory function
#logger = logger();
```
### Breaking Changes
1. **Context parameters** now require function wrappers for lazy evaluation
2. **Error parameter** in LoggerApi is now optional instead of unknown type
3. **Factory function** signature changed to support dynamic context
### Migration Steps
1. **Update context calls** - Wrap context objects in functions
2. **Replace service injection** - Use `logger()` factory instead of injecting `LoggingService`
3. **Update error handling** - Error parameter is now optional in error() method
4. **Add dynamic context** - Leverage new context functionality for better debugging

View File

@@ -1,23 +1,53 @@
import { Injectable } from '@angular/core';
import { LogLevel } from './log-level.enum';
import { Sink } from './logging.types';
import { Sink, LoggerContext } from './logging.types';
/**
* A sink implementation that outputs logs to the browser console.
* This is the primary logging destination for development environments.
*
* The sink maps each log level to the appropriate console method:
* - Trace → console.trace (with stack trace)
* - Debug → console.debug
* - Info → console.info
* - Warn → console.warn
* - Error → console.error
*
* Context data and error objects are passed as additional arguments to provide
* rich debugging information in the browser developer tools.
*
* @example
* ```typescript
* // Configure in app initialization
* provideLogging(
* withLogLevel(LogLevel.Debug),
* withSink(ConsoleLogSink)
* );
* ```
*/
@Injectable()
export class ConsoleLogSink implements Sink {
/**
* Logs a message to the browser console with the appropriate log level.
* @param level The log level.
* @param message The log message.
* @param context Optional context data.
* @param error Optional error object.
* The console method used depends on the log level, and context/error data
* is passed as additional arguments for improved debugging experience.
*
* @param level - The severity level of the log message
* @param message - The main log message content
* @param context - Optional structured data or metadata about the log event
* @param error - Optional error object when logging errors
*
* @example
* ```typescript
* const sink = new ConsoleLogSink();
* sink.log(LogLevel.Info, 'User logged in', { userId: '123' });
* sink.log(LogLevel.Error, 'API failed', { endpoint: '/users' }, new Error('Network error'));
* ```
*/
log(
level: LogLevel,
message: string,
context?: unknown,
context?: LoggerContext,
error?: Error,
): void {
switch (level) {

View File

@@ -133,10 +133,10 @@ export interface LoggerApi {
* Use for error conditions that affect functionality.
*
* @param message - The error message to log
* @param error - Any error object or value that caused this error condition
* @param error - Optional error object that caused this error condition
* @param context - Optional context data associated with the error
*/
error(message: string, error: unknown, context?: () => LoggerContext): void;
error(message: string, error?: Error, context?: () => LoggerContext): void;
}
/**

View File

@@ -90,7 +90,7 @@ export const ReturnTaskListStore = signalStore(
* @param error - The error that occurred during the fetch operation
*/
fetchError(id: number, error: unknown) {
logService.error('Error fetching task list items', error);
logService.error('Error fetching task list items', error as Error);
patchState(
store,
updateEntity({

769
package-lock.json generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -58,7 +58,7 @@
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "20.0.2",
"@angular-devkit/build-angular": "20.0.3",
"@angular-devkit/core": "20.0.2",
"@angular-devkit/schematics": "20.0.2",
"@angular/cli": "20.0.2",
@@ -101,7 +101,7 @@
"jest-preset-angular": "14.6.0",
"jsonc-eslint-parser": "^2.1.0",
"ng-mocks": "14.13.5",
"ng-packagr": "^20.0.0",
"ng-packagr": "20.0.1",
"ng-swagger-gen": "^2.3.1",
"nx": "21.2.0",
"postcss": "^8.5.3",
@@ -119,12 +119,7 @@
"@esbuild/linux-x64": "^0.25.5"
},
"engines": {
"node": ">=22.13.0",
"npm": ">=11.0.0"
},
"overrides": {
"jest-environment-jsdom": {
"jsdom": "26.0.0"
}
"node": ">=22.0.0",
"npm": ">=10.0.0"
}
}