1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-31 01:16:01 +02:00
Nuno Góis 2025-03-12 10:06:29 +00:00 committed by GitHub
parent d1d1a740f3
commit 242b0de592
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 39 additions and 3 deletions

View File

@ -89,6 +89,12 @@ const EventCard = ({ entry }: IEventCardProps) => {
<dd>{entry.type}</dd> <dd>{entry.type}</dd>
<StyledDefinitionTerm>Changed by:</StyledDefinitionTerm> <StyledDefinitionTerm>Changed by:</StyledDefinitionTerm>
<dd title={entry.createdBy}>{entry.createdBy}</dd> <dd title={entry.createdBy}>{entry.createdBy}</dd>
{entry.ip && (
<>
<StyledDefinitionTerm>IP:</StyledDefinitionTerm>
<dd title={entry.ip}>{entry.ip}</dd>
</>
)}
<ConditionallyRender <ConditionallyRender
condition={Boolean(entry.project)} condition={Boolean(entry.project)}
show={ show={

View File

@ -41,6 +41,11 @@ export interface EventSchema {
* @minimum 1 * @minimum 1
*/ */
id: number; id: number;
/**
* The IP address of the user that created the event. Only available in Enterprise.
* @nullable
*/
ip?: string | null;
/** /**
* The concise, human-readable name of the event. * The concise, human-readable name of the event.
* @nullable * @nullable

View File

@ -32,12 +32,18 @@ export default class EventService {
private eventBus: EventEmitter; private eventBus: EventEmitter;
private isEnterprise: boolean;
constructor( constructor(
{ {
eventStore, eventStore,
featureTagStore, featureTagStore,
}: Pick<IUnleashStores, 'eventStore' | 'featureTagStore'>, }: Pick<IUnleashStores, 'eventStore' | 'featureTagStore'>,
{ getLogger, eventBus }: Pick<IUnleashConfig, 'getLogger' | 'eventBus'>, {
getLogger,
eventBus,
isEnterprise,
}: Pick<IUnleashConfig, 'getLogger' | 'eventBus' | 'isEnterprise'>,
privateProjectChecker: IPrivateProjectChecker, privateProjectChecker: IPrivateProjectChecker,
accessReadModel: IAccessReadModel, accessReadModel: IAccessReadModel,
) { ) {
@ -46,6 +52,7 @@ export default class EventService {
this.privateProjectChecker = privateProjectChecker; this.privateProjectChecker = privateProjectChecker;
this.featureTagStore = featureTagStore; this.featureTagStore = featureTagStore;
this.eventBus = eventBus; this.eventBus = eventBus;
this.isEnterprise = isEnterprise;
this.accessReadModel = accessReadModel; this.accessReadModel = accessReadModel;
} }
@ -101,6 +108,9 @@ export default class EventService {
query: search.query, query: search.query,
}, },
queryParams, queryParams,
{
withIp: this.isEnterprise,
},
); );
return { return {
events, events,

View File

@ -93,6 +93,7 @@ export interface IEventTable {
project?: string; project?: string;
environment?: string; environment?: string;
tags: ITag[]; tags: ITag[];
ip?: string;
} }
const TABLE = 'events'; const TABLE = 'events';
@ -374,15 +375,20 @@ class EventStore implements IEventStore {
async searchEvents( async searchEvents(
params: IEventSearchParams, params: IEventSearchParams,
queryParams: IQueryParam[], queryParams: IQueryParam[],
options?: { withIp?: boolean },
): Promise<IEvent[]> { ): Promise<IEvent[]> {
const query = this.buildSearchQuery(params, queryParams) const query = this.buildSearchQuery(params, queryParams)
.select(EVENT_COLUMNS) .select(options?.withIp ? [...EVENT_COLUMNS, 'ip'] : EVENT_COLUMNS)
.orderBy('created_at', 'desc') .orderBy('created_at', 'desc')
.limit(Number(params.limit) ?? 100) .limit(Number(params.limit) ?? 100)
.offset(Number(params.offset) ?? 0); .offset(Number(params.offset) ?? 0);
try { try {
return (await query).map(this.rowToEvent); return (await query).map((row) =>
options?.withIp
? { ...this.rowToEvent(row), ip: row.ip }
: this.rowToEvent(row),
);
} catch (err) { } catch (err) {
return []; return [];
} }

View File

@ -102,6 +102,13 @@ export const eventSchema = {
nullable: true, nullable: true,
description: 'A markdown-formatted summary of the event.', description: 'A markdown-formatted summary of the event.',
}, },
ip: {
type: 'string',
nullable: true,
description:
'The IP address of the user that created the event. Only available in Enterprise.',
example: '192.168.1.1',
},
}, },
components: { components: {
schemas: { schemas: {

View File

@ -400,6 +400,7 @@ export interface IBaseEvent {
export interface IEvent extends Omit<IBaseEvent, 'ip'> { export interface IEvent extends Omit<IBaseEvent, 'ip'> {
id: number; id: number;
createdAt: Date; createdAt: Date;
ip?: string;
} }
export interface IEnrichedEvent extends IEvent { export interface IEnrichedEvent extends IEvent {

View File

@ -41,6 +41,7 @@ export interface IEventStore
searchEvents( searchEvents(
params: IEventSearchParams, params: IEventSearchParams,
queryParams: IQueryParam[], queryParams: IQueryParam[],
options?: { withIp?: boolean },
): Promise<IEvent[]>; ): Promise<IEvent[]>;
getMaxRevisionId(currentMax?: number): Promise<number>; getMaxRevisionId(currentMax?: number): Promise<number>;
getRevisionRange(start: number, end: number): Promise<IEvent[]>; getRevisionRange(start: number, end: number): Promise<IEvent[]>;