import { Action } from '../action'; import { Icon } from '../core'; import { ProblemView } from './view'; import { ProblemMetadataEntry } from './metadata'; import { Problem } from './problem'; import { ProblemQueryOptions, ProblemStatus, ProblemType } from './query'; /** * Interface that packages implement to provide problems to the platform. * * Providers are responsible for: * - Storing and managing their own problems * - Defining problem types with display information * - Supplying actions users can take on problems * - Responding to queries from the platform * * The platform aggregates problems from all registered providers. * * @example Provider implementation * ```ts * import { * Action, * Command, * Problem, * ProblemProvider, * ProblemQueryOptions, * ProblemStatus, * ProblemType, * } from '@limetech/lime-web-components'; * * @Command({ id: 'retry-email-delivery' }) * class RetryEmailDeliveryCommand { * public problemId: string; * } * * class EmailProblemProvider implements ProblemProvider { * public id = 'email-integration'; * public title = 'Email Integration'; * public description = 'Problems related to email sending and delivery'; * public icon = 'envelope'; * * private types: ProblemType[] = [ * { id: 'delivery-failed', title: 'Delivery Failed' }, * ]; * * private statuses: ProblemStatus[] = [ * { id: 'open', title: 'Open' }, * { id: 'resolved', title: 'Resolved' }, * ]; * * private problems: Omit[] = [ * { * id: 'email-123', * type: 'delivery-failed', * description: 'Email bounced, the mailbox is full', * createdAt: '2024-01-15T10:30:00Z', * status: 'open', * }, * ]; * * public async getProblems( * options?: ProblemQueryOptions * ): Promise[]> { * return this.filter(options); * } * * public async getCount(options?: ProblemQueryOptions): Promise { * return this.filter(options).length; * } * * public async get( * id: string * ): Promise | undefined> { * return this.problems.find((problem) => problem.id === id); * } * * public getActions(problem: Problem): Action[] { * if (problem.status === 'resolved') { * return []; * } * * const command = new RetryEmailDeliveryCommand(); * command.problemId = problem.id; * * return [{ label: 'Retry delivery', icon: 'refresh', command: command }]; * } * * public getTypes(): ProblemType[] { * return this.types; * } * * public getType(type: string): ProblemType | undefined { * return this.types.find((candidate) => candidate.id === type); * } * * public getStatuses(): ProblemStatus[] { * return this.statuses; * } * * public getStatus(status: string): ProblemStatus | undefined { * return this.statuses.find((candidate) => candidate.id === status); * } * * // Unsupported filters are ignored, so a provider only reads what it knows. * private filter( * options?: ProblemQueryOptions * ): Omit[] { * const status = options?.status; * if (!status) { * return this.problems; * } * * const wanted = Array.isArray(status) ? status : [status]; * * return this.problems.filter( * (problem) => problem.status && wanted.includes(problem.status) * ); * } * } * ``` * * @example Registering a provider * ```ts * import { * Action, * PlatformServiceName, * Problem, * ProblemProvider, * ProblemStatus, * ProblemType, * } from '@limetech/lime-web-components'; * * class EmailProblemProvider implements ProblemProvider { * public id = 'email-integration'; * public title = 'Email Integration'; * public description = 'Problems related to email sending and delivery'; * public icon = 'envelope'; * * public async getProblems(): Promise[]> { * return []; * } * * public async getCount(): Promise { * return 0; * } * * public async get(): Promise | undefined> { * return undefined; * } * * public getActions(): Action[] { * return []; * } * * public getTypes(): ProblemType[] { * return [{ id: 'delivery-failed', title: 'Delivery Failed' }]; * } * * public getType(type: string): ProblemType | undefined { * return this.getTypes().find((candidate) => candidate.id === type); * } * * public getStatuses(): ProblemStatus[] { * return [{ id: 'open', title: 'Open' }]; * } * * public getStatus(status: string): ProblemStatus | undefined { * return this.getStatuses().find((candidate) => candidate.id === status); * } * } * * const repository = platform.get(PlatformServiceName.ProblemRepository); * * repository.registerProvider(new EmailProblemProvider()); * ``` * * @see {@link ProblemRepository} for registering providers and querying problems * @see {@link Problem} for the problem structure * @see {@link ProblemQueryOptions} for query filtering * @see {@link ProblemType} for problem type display information * @see {@link Action} for problem actions * * @alpha * @group Problem */ export interface ProblemProvider { /** * Unique identifier for this provider. * * Used to identify the provider in the repository and to link problems back * to their source. Should be a stable, lowercase identifier using hyphens * for word separation (e.g., 'email-integration', 'external-crm-sync'). * * The platform uses this id to set {@link Problem.providerId} when * returning problems to consumers. */ id: string; /** * Human-readable title for the provider. * * Should clearly identify the source of problems * (e.g., 'Email Integration', 'External CRM Sync'). */ title: string; /** * Description of what problems this provider handles. */ description: string; /** * Icon representing this provider. * * Can be either a string referencing an icon name from the platform's icon * library, or an {@link Icon} object with additional properties like color. * * @see {@link Icon} */ icon: string | Icon; /** * Retrieves problems from this provider. * * Implement filtering based on the provided options where supported. * Unsupported filters should be silently ignored. * * @param options - Query options for filtering and pagination. All properties * are optional. Providers should handle what they support and ignore the rest. * @returns A promise resolving to an array of problems matching the query. * * @see {@link ProblemQueryOptions} */ getProblems(options?: ProblemQueryOptions): Promise[]>; /** * Returns the count of problems matching the query options. * * Should return the total count of matching problems, not affected by * pagination (limit/offset). * * @param options - Query options for filtering. Pagination options (limit/offset) * should be ignored when counting. * @returns A promise resolving to the number of matching problems. */ getCount(options?: ProblemQueryOptions): Promise; /** * Retrieves a specific problem by its id. * * @param id - The unique identifier of the problem within this provider. * @returns A promise resolving to the problem if found, or undefined if * no problem with that id exists. */ get(id: string): Promise | undefined>; /** * Returns available actions for a specific problem. * * Actions allow users to take action on problems (e.g., retry, dismiss, * navigate to configuration). Each action wraps a command that executes * when triggered. * * @param problem - The problem to get actions for. * @returns An array of actions available for this problem. Return an empty * array `[]` if no actions are available. * * @see {@link Action} */ getActions(problem: Problem): Action[]; /** * Returns available actions for a selection of problems. * * Bulk actions let users act on multiple problems at once (e.g., retry * all failed imports in a single operation). The provider decides which * actions make sense across a set of problems and constructs the command * payload accordingly. * * When this method is not implemented, the platform does not offer bulk * actions for this provider. * * @param problems - The selected problems to get bulk actions for. * @returns An array of actions available for the selection. Return an * empty array `[]` if no bulk actions apply. * * @example Retrying every selected import in one command * ```ts * import { * Action, * Command, * Problem, * ProblemProvider, * } from '@limetech/lime-web-components'; * * @Command({ id: 'bulk-retry-import' }) * class BulkRetryImportCommand { * public messageIds: string[]; * } * * class ImportProblemProvider implements Pick { * public getBulkActions(problems: Problem[]): Action[] { * const messageIds = problems.map((problem) => problem.id); * * if (messageIds.length === 0) { * return []; * } * * const command = new BulkRetryImportCommand(); * command.messageIds = messageIds; * * return [ * { * label: 'Retry import', * icon: 'refresh', * command: command, * }, * ]; * } * } * ``` * * @see {@link Action} * @see {@link ProblemProvider.getActions} */ getBulkActions?(problems: Problem[]): Action[]; /** * Returns all problem types supported by this provider. * * Used by the platform to render type filters and understand * the provider's problem categories. * * @returns An array of all problem types this provider can produce. * * @see {@link ProblemType} * @see {@link Problem."type"} */ getTypes(): ProblemType[]; /** * Returns display information for a problem type. * * Problem types are provider-specific identifiers (e.g., 'delivery-failed', * 'connection-error'). This method provides the human-readable title, * description, and icon for each type. * * @param type - The problem type identifier (from {@link Problem."type"}). * @returns Display information for the type, or undefined if the type * is not recognized by this provider. * * @see {@link ProblemType} * @see {@link Problem."type"} */ getType(type: string): ProblemType | undefined; /** * Returns all statuses supported by this provider. * * Used by the platform to render status filters and understand * the provider's workflow. * * @returns An array of all statuses this provider uses. Return an empty * array `[]` if this provider doesn't track status. * * @see {@link ProblemStatus} * @see {@link Problem.status} */ getStatuses(): ProblemStatus[]; /** * Returns display information for a problem status. * * @param status - The status identifier (from {@link Problem.status}). * @returns Display information for the status, or undefined if the status * is not recognized by this provider. * * @see {@link ProblemStatus} * @see {@link Problem.status} */ getStatus(status: string): ProblemStatus | undefined; /** * Returns the metadata entries to render in the problem detail dialog's * metadata section, after the platform's standard properties (severity, * status, created). * * Each entry is either a built-in {@link ProblemMetadataProperty} (a * label/value pair the platform renders) or a {@link ComponentDescriptor} * for a custom component the provider owns. Entries render in array order, * so properties and components can be mixed and ordered freely. * * When this method is not implemented or returns an empty array, the * section renders only the standard properties. * * @param problem - The problem whose metadata should be rendered. * @returns An ordered array of metadata entries. Return an empty array * `[]` if there is nothing to add. * * @example Adding one built-in property from the problem metadata * ```ts * import { * Problem, * ProblemMetadataEntry, * ProblemProvider, * } from '@limetech/lime-web-components'; * * interface EmailProblemMetadata { * emailDate?: string; * } * * class EmailProblemProvider implements Pick { * public getMetadata( * problem: Problem * ): ProblemMetadataEntry[] { * const date = problem.metadata?.emailDate; * if (!date) { * return []; * } * * // A built-in property with a type, so the platform formats the value. * return [{ props: { label: 'Email date', value: date, type: 'time' } }]; * } * } * ``` * * @see {@link ProblemMetadataEntry} * @see {@link Problem.metadata} */ getMetadata?(problem: Problem): ProblemMetadataEntry[]; /** * Returns additional views to display in the problem detail dialog. * * Each view appears alongside the default detail panel. The provider * controls the label and rendered component for every view; the * platform decides how to present them (tabs, panels, etc.). * * When this method is not implemented or returns an empty array, the * detail dialog renders without additional views. * * @param problem - The problem being viewed in the detail dialog. * @returns An array of view descriptors. Return an empty array `[]` * if no additional views are needed for this problem. * * @example Adding a raw email view and an audit log view * ```ts * import { * Problem, * ProblemProvider, * ProblemView, * } from '@limetech/lime-web-components'; * * class EmailProblemProvider implements Pick { * public getViews(problem: Problem): ProblemView[] { * return [ * { * id: 'raw-email', * label: 'Raw Email', * icon: 'email', * component: { * name: 'email-raw-content', * props: { problem: problem }, * }, * }, * { * id: 'audit-log', * label: 'Audit Log', * icon: { name: 'history', color: 'gray' }, * component: { * name: 'email-audit-log', * props: { problemId: problem.id }, * }, * }, * ]; * } * } * ``` * * @see {@link ProblemView} * @see {@link ComponentDescriptor} */ getViews?(problem: Problem): ProblemView[]; }