Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 21 additions & 2 deletions src/resources/tickets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,32 @@ export class TicketsResource {
// Build filter criteria from params
const filters: Array<{ field: string; operator: string; value: unknown }> = [];
if (params?.status) {
filters.push({ field: 'status', operator: 'is', value: params.status });
// NinjaOne's filter API expects the numeric parent statusId, not the enum string.
// Custom tenant statuses inherit one of these via parentId.
// 1000/2000/3000/6000 confirmed via live tenant; 4000/5000/7000 inferred from
// NinjaOne docs and the 1000-stride pattern — verify against your tenant if you
// depend on filtering by IN_PROGRESS / RESOLVED / ON_HOLD.
const STATUS_PARENT_ID: Record<string, number> = {
NEW: 1000,
OPEN: 2000,
WAITING: 3000,
IN_PROGRESS: 4000,
RESOLVED: 5000,
CLOSED: 6000,
ON_HOLD: 7000,
};
const id = STATUS_PARENT_ID[params.status];
if (id !== undefined) {
filters.push({ field: 'status', operator: 'is', value: id });
}
}
if (params?.priority) {
filters.push({ field: 'priority', operator: 'is', value: params.priority });
}
if (params?.organizationId) {
filters.push({ field: 'organizationId', operator: 'is', value: params.organizationId });
// Ticket payloads carry `clientId`, not `organizationId` — the filter field
// must match the ticket column name or the API returns 400.
filters.push({ field: 'clientId', operator: 'is', value: params.organizationId });
}
if (params?.deviceId) {
filters.push({ field: 'nodeId', operator: 'is', value: params.deviceId });
Expand Down
6 changes: 5 additions & 1 deletion src/types/tickets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ export interface Ticket extends TimestampFields {
* with filters and pagination in the request body.
*/
export interface TicketListParams {
/** Board ID to query (default: 1, typically the "All Tickets" board) */
/**
* Board ID to query. Defaults to 1, which on most tenants is the "All Tickets"
* board — but this is not guaranteed. Call `listBoards()` to discover the
* board IDs for the current tenant if you need a specific board.
*/
boardId?: number;
/** Number of results per page (default: 50) */
pageSize?: number;
Expand Down
58 changes: 58 additions & 0 deletions tests/integration/tickets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
*/

import { describe, it, expect } from 'vitest';
import { http, HttpResponse } from 'msw';
import { NinjaOneClient } from '../../src/client.js';
import { NinjaOneNotFoundError } from '../../src/errors.js';
import { server } from '../mocks/server.js';
import * as ticketFixtures from '../fixtures/tickets.js';

describe('TicketsResource', () => {
const client = new NinjaOneClient({
Expand All @@ -20,6 +23,61 @@ describe('TicketsResource', () => {
expect(result.tickets).toHaveLength(2);
expect(result.totalCount).toBe(2);
});

it('should map status enum to parent statusId in filter body', async () => {
let capturedBody: Record<string, unknown> | undefined;
server.use(
http.post(
'https://app.ninjarmm.com/api/v2/ticketing/trigger/board/:boardId/run',
async ({ request }) => {
capturedBody = (await request.json()) as Record<string, unknown>;
return HttpResponse.json(ticketFixtures.list);
}
)
);

await client.tickets.list({ boardId: 2, status: 'OPEN' });

expect(capturedBody?.filters).toEqual([
{ field: 'status', operator: 'is', value: 2000 },
]);
});

it('should map organizationId filter to clientId field', async () => {
let capturedBody: Record<string, unknown> | undefined;
server.use(
http.post(
'https://app.ninjarmm.com/api/v2/ticketing/trigger/board/:boardId/run',
async ({ request }) => {
capturedBody = (await request.json()) as Record<string, unknown>;
return HttpResponse.json(ticketFixtures.list);
}
)
);

await client.tickets.list({ boardId: 2, organizationId: 42 });

expect(capturedBody?.filters).toEqual([
{ field: 'clientId', operator: 'is', value: 42 },
]);
});

it('should forward lastCursorId for pagination', async () => {
let capturedBody: Record<string, unknown> | undefined;
server.use(
http.post(
'https://app.ninjarmm.com/api/v2/ticketing/trigger/board/:boardId/run',
async ({ request }) => {
capturedBody = (await request.json()) as Record<string, unknown>;
return HttpResponse.json(ticketFixtures.list);
}
)
);

await client.tickets.list({ boardId: 2, lastCursorId: 50 });

expect(capturedBody?.lastCursorId).toBe(50);
});
});

describe('get', () => {
Expand Down
Loading