Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
3403131
Refactor some endpoints out of the rest.ts file
d-gubert Mar 6, 2025
ec3c6c0
Make logs endpoint return paginated data
d-gubert Mar 7, 2025
a2aeb6d
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Mar 10, 2025
03bc0d9
Add pagination to logs screen
d-gubert Mar 11, 2025
29d4f18
Remove deprecated fields from endpoint
d-gubert Mar 11, 2025
5caf4fe
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Mar 11, 2025
f51cddd
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Mar 11, 2025
bc07107
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Mar 13, 2025
ca74396
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Mar 13, 2025
2a513b4
wip
d-gubert Mar 13, 2025
7ba49e0
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Apr 2, 2025
541a4d3
Fix lint
d-gubert Apr 2, 2025
0373e94
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Apr 3, 2025
6b0d9f6
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Apr 16, 2025
042690a
Remove debugs
d-gubert Apr 16, 2025
763a9ad
Fix app languages endpoint type
d-gubert Apr 16, 2025
2e9e9cf
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Apr 16, 2025
71ba4c2
Add simple filters to logs endpoint
d-gubert Apr 16, 2025
149d966
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Apr 16, 2025
9f78c99
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Apr 17, 2025
dff7b09
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Apr 23, 2025
4ca76c9
Make the endpoint expect date-time instead of just date for the appro…
d-gubert Apr 23, 2025
b191a4e
Extract query building to function for future reuse
d-gubert Apr 23, 2025
f0848e9
Fix tests
d-gubert Apr 23, 2025
ba8a26f
makeAppLogsQuery comment/doc
d-gubert Apr 24, 2025
77e3ada
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Apr 24, 2025
148527b
Revert UI changes extracted to another branch
d-gubert Apr 24, 2025
f11094d
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Apr 24, 2025
604770a
Add tests to the logs endpoint
d-gubert Apr 24, 2025
0b84d82
Refactor test
d-gubert Apr 24, 2025
2d9d0bd
Improve app logs prop schema
d-gubert Apr 24, 2025
06f91b2
Fix typecheck
d-gubert Apr 25, 2025
2fdf136
Refactor makeAppLogsQuery signature
d-gubert Apr 25, 2025
d91794c
Remove dangling console
d-gubert Apr 25, 2025
b0edb7d
Add changeset
d-gubert Apr 25, 2025
b4704af
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert Apr 28, 2025
dfcc9e6
Refactor rest types and adapt makeAppLogsQuery function
d-gubert Apr 28, 2025
c785883
Add new endpoint to fetch logs from all apps
d-gubert Apr 28, 2025
84783aa
Improve endpoint tests
d-gubert Apr 28, 2025
5a08819
Add changeset
d-gubert Apr 28, 2025
16ee104
Merge branch 'develop' into feat/apps-logs-improvement
d-gubert May 12, 2025
1b889bc
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert May 13, 2025
7ea674b
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert May 15, 2025
63eee7c
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert May 15, 2025
c2e3e61
Add indexes to logs model
d-gubert May 18, 2025
a222878
Merge remote-tracking branch 'origin' into feat/apps-logs-improvement
d-gubert May 18, 2025
b4a13fa
Merge branch 'develop' into feat/apps-logs-improvement
d-gubert May 19, 2025
8bf2ae0
Merge branch 'develop' into feat/apps-logs-improvement
d-gubert May 19, 2025
69a8209
Merge branch 'develop' into feat/apps-logs-improvement
d-gubert May 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/rich-peas-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/rest-typings': minor
'@rocket.chat/apps-engine': minor
'@rocket.chat/meteor': minor
---

Improve the `/api/apps/:id/logs` endpoint to accept filters
7 changes: 7 additions & 0 deletions .changeset/short-mayflies-sing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/rest-typings': minor
'@rocket.chat/apps-engine': minor
'@rocket.chat/meteor': minor
---

Add a new endpoint `/api/apps/logs` that allows for fetching logs without a filter for app id
2 changes: 1 addition & 1 deletion apps/meteor/client/views/marketplace/hooks/useLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ export const useLogs = (appId: string): UseQueryResult<OperationResult<'GET', '/

return useQuery({
queryKey: ['marketplace', 'apps', appId, 'logs'],
queryFn: () => logs(),
queryFn: () => logs({}),
});
};
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import type { AppManager } from '@rocket.chat/apps-engine/server/AppManager';

import { API } from '../../../../../app/api/server';
import type { AppsRestApi } from '../rest';

export const actionButtonsHandler = (apiManager: AppsRestApi) =>
[
{
authRequired: false,
},
export const registerActionButtonsHandler = ({ api, _manager }: AppsRestApi) =>
void api.addRoute(
'actionButtons',
{ authRequired: false },
{
get(): any {
const manager = apiManager._manager as AppManager;

const buttons = manager.getUIActionButtonManager().getAllActionButtons();
get() {
const buttons = _manager.getUIActionButtonManager().getAllActionButtons();

return API.v1.success(buttons);
},
},
] as const;
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { isAppLogsProps } from '@rocket.chat/rest-typings';

import { getPaginationItems } from '../../../../../app/api/server/helpers/getPaginationItems';
import type { AppsRestApi } from '../rest';
import { makeAppLogsQuery } from './lib/makeAppLogsQuery';

export const registerAppGeneralLogsHandler = ({ api, _orch }: AppsRestApi) =>
void api.addRoute(
'logs',
{ authRequired: true, permissionRequired: ['manage-apps'], validateParams: isAppLogsProps },
{
async get() {
const { offset, count } = await getPaginationItems(this.queryParams);
const { sort } = await this.parseJsonQuery();

const options = {
sort: sort || { _updatedAt: -1 },
skip: offset,
limit: count,
};

let query: Record<string, any>;

try {
query = makeAppLogsQuery(this.queryParams);
} catch (error) {
return api.failure({ error: error instanceof Error ? error.message : 'Unknown error' });
}

const result = await _orch.getLogStorage().find(query, options);

return api.success({ offset, logs: result.logs, count: result.logs.length, total: result.total });
},
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { isAppLogsProps } from '@rocket.chat/rest-typings';

import { getPaginationItems } from '../../../../../app/api/server/helpers/getPaginationItems';
import type { AppsRestApi } from '../rest';
import { makeAppLogsQuery } from './lib/makeAppLogsQuery';

export const registerAppLogsHandler = ({ api, _manager, _orch }: AppsRestApi) =>
void api.addRoute(
':id/logs',
{ authRequired: true, permissionRequired: ['manage-apps'], validateParams: isAppLogsProps },
{
async get() {
const proxiedApp = _manager.getOneById(this.urlParams.id);

if (!proxiedApp) {
return api.notFound(`No App found by the id of: ${this.urlParams.id}`);
}

if (this.queryParams.appId && this.queryParams.appId !== this.urlParams.id) {
return api.notFound(`Invalid query parameter "appId": ${this.queryParams.appId}`);
}

const { offset, count } = await getPaginationItems(this.queryParams);
const { sort } = await this.parseJsonQuery();

const options = {
sort: sort || { _updatedAt: -1 },
skip: offset,
limit: count,
};

let query: Record<string, any>;

try {
query = makeAppLogsQuery(this.queryParams);
} catch (error) {
return api.failure({ error: error instanceof Error ? error.message : 'Unknown error' });
}

const result = await _orch.getLogStorage().find(query, options);

return api.success({ offset, logs: result.logs, count: result.logs.length, total: result.total });
},
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,16 @@ import type { AppManager } from '@rocket.chat/apps-engine/server/AppManager';
import { License } from '@rocket.chat/license';

import { API } from '../../../../../app/api/server';
import type { SuccessResult } from '../../../../../app/api/server/definition';
import { getInstallationSourceFromAppStorageItem } from '../../../../../lib/apps/getInstallationSourceFromAppStorageItem';
import type { AppsRestApi } from '../rest';

type AppsCountResult = {
totalMarketplaceEnabled: number;
totalPrivateEnabled: number;
maxMarketplaceApps: number;
maxPrivateApps: number;
};

export const appsCountHandler = (apiManager: AppsRestApi) =>
[
{
authRequired: false,
},
export const registerAppsCountHandler = ({ api, _manager }: AppsRestApi) =>
void api.addRoute(
'count',
{ authRequired: false },
{
async get(): Promise<SuccessResult<AppsCountResult>> {
const manager = apiManager._manager as AppManager;
async get() {
const manager = _manager as AppManager;

const apps = await manager.get({ enabled: true });
const { maxMarketplaceApps, maxPrivateApps } = License.getAppsConfig();
Expand All @@ -34,4 +25,4 @@ export const appsCountHandler = (apiManager: AppsRestApi) =>
});
},
},
] as const;
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { AppLogsProps } from '@rocket.chat/rest-typings';

/**
* Creates a query object for fetching app logs based on provided parameters.
*
* NOTE: This function expects that all values are in the correct format, as it is
* used by an endpoint handler which has query parameter validation.
*
* @param queryParams - The query parameters.
* @returns A query object for fetching app logs.
* @throws {Error} If the date range is invalid.
*/
export function makeAppLogsQuery(queryParams: AppLogsProps) {
const query: Record<string, any> = {};

if (queryParams.appId) {
query.appId = queryParams.appId;
}

if (queryParams.logLevel) {
const queryLogLevel = Number(queryParams.logLevel);
const logLevel = ['error'];

if (queryLogLevel >= 1) {
logLevel.push('warn', 'info', 'log');
}

if (queryLogLevel >= 2) {
logLevel.push('debug', 'success');
}

query['entries.severity'] = { $in: logLevel };
}

if (queryParams.method) {
query.method = queryParams.method;
}

if (queryParams.startDate) {
query._updatedAt = {
$gte: new Date(queryParams.startDate),
};
}

if (queryParams.endDate) {
const endDate = new Date(queryParams.endDate);
endDate.setDate(endDate.getDate() + 1);

if (query._updatedAt?.$gte && query._updatedAt.$gte >= endDate) {
throw new Error('Invalid date range');
}

query._updatedAt = {
...(query._updatedAt || {}),
$lte: endDate,
};
}

return query;
}
42 changes: 9 additions & 33 deletions apps/meteor/ee/server/apps/communication/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { Meteor } from 'meteor/meteor';
import { ZodError } from 'zod';

import { actionButtonsHandler } from './endpoints/actionButtonsHandler';
import { appsCountHandler } from './endpoints/appsCountHandler';
import { registerActionButtonsHandler } from './endpoints/actionButtonsHandler';
import { registerAppGeneralLogsHandler } from './endpoints/appGeneralLogsHandler';
import { registerAppLogsHandler } from './endpoints/appLogsHandler';
import { registerAppsCountHandler } from './endpoints/appsCountHandler';
import type { APIClass } from '../../../../app/api/server';
import { API } from '../../../../app/api/server';
import { getPaginationItems } from '../../../../app/api/server/helpers/getPaginationItems';
import { getUploadFormData } from '../../../../app/api/server/lib/getUploadFormData';
import { getWorkspaceAccessToken, getWorkspaceAccessTokenWithScope } from '../../../../app/cloud/server';
import { apiDeprecationLogger } from '../../../../app/lib/server/lib/deprecationWarningLogger';
Expand Down Expand Up @@ -93,8 +94,11 @@ export class AppsRestApi {
return API.v1.failure();
};

this.api.addRoute('actionButtons', ...actionButtonsHandler(this));
this.api.addRoute('count', ...appsCountHandler(this));
registerActionButtonsHandler(this);
registerAppsCountHandler(this);

registerAppLogsHandler(this);
registerAppGeneralLogsHandler(this);

this.api.addRoute(
'incompatibleModal',
Expand Down Expand Up @@ -1138,34 +1142,6 @@ export class AppsRestApi {
},
);

this.api.addRoute(
':id/logs',
{ authRequired: true, permissionsRequired: ['manage-apps'] },
{
async get() {
const prl = manager.getOneById(this.urlParams.id);

if (prl) {
const { offset, count } = await getPaginationItems(this.queryParams);
const { sort, fields, query } = await this.parseJsonQuery();

const ourQuery = Object.assign({}, query, { appId: prl.getID() });
const options = {
sort: sort || { _updatedAt: -1 },
skip: offset,
limit: count,
fields,
};

const logs = await orchestrator?.getLogStorage()?.find(ourQuery, options);

return API.v1.success({ logs });
}
return API.v1.notFound(`No App found by the id of: ${this.urlParams.id}`);
},
},
);

this.api.addRoute(
':id/settings',
{ authRequired: true, permissionsRequired: ['manage-apps'] },
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/ee/server/apps/orchestrator.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ export class AppServerOrchestrator {
}

getLogStorage() {
if (!this._logStorage) {
throw new Error('Apps-Engine not yet fully initialized');
}

return this._logStorage;
}

Expand Down
13 changes: 10 additions & 3 deletions apps/meteor/ee/server/apps/storage/AppRealLogStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@ export class AppRealLogStorage extends AppLogStorage {
query: {
[field: string]: any;
},
{ fields, ...options }: IAppLogStorageFindOptions,
): Promise<ILoggerStorageEntry[]> {
return this.db.findPaginated(query, { projection: fields, ...options }).cursor.toArray();
options: IAppLogStorageFindOptions,
) {
const { cursor, totalCount } = this.db.findPaginated<ILoggerStorageEntry>(query, options);

const [logs, total] = await Promise.all([cursor.toArray(), totalCount]);

return {
logs,
total,
};
}

async storeEntries(logEntry: ILoggerStorageEntry): Promise<ILoggerStorageEntry> {
Expand Down
Loading
Loading