Skip to content

Commit 6101980

Browse files
Merge branch 'develop' into feat/openapi-e2e-fetchMyKeys
2 parents 2ad6961 + 8d73ce5 commit 6101980

File tree

14 files changed

+408
-41
lines changed

14 files changed

+408
-41
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@rocket.chat/message-parser': patch
3+
---
4+
5+
Replaces wasteful `filter().shift()` with `find(Boolean)` in `extractFirstResult` to avoid allocating an intermediate filtered array just to get the first truthy element.

.changeset/loud-weeks-protect.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@rocket.chat/message-parser': patch
3+
---
4+
5+
Fixes ordered list AST generation to preserve `number: 0` for list items that start at index `0`.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@rocket.chat/meteor': minor
3+
---
4+
5+
adds `instances.get` API endpoint to new chained pattern with response schemas

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ We're hiring developers, technical support, and product managers all the time. C
115115
- [Twitter](https://twitter.com/RocketChat)
116116
- [Facebook](https://www.facebook.com/RocketChatApp)
117117
- [LinkedIn](https://www.linkedin.com/company/rocket-chat)
118-
- [Youtube](https://www.youtube.com/channel/UCin9nv7mUjoqrRiwrzS5UVQ)
118+
- [YouTube](https://www.youtube.com/channel/UCin9nv7mUjoqrRiwrzS5UVQ)
119119

120120
# 🗒️ Credits
121121

apps/meteor/app/api/server/v1/groups.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ API.v1.addRoute(
509509
oldestDate = new Date(this.queryParams.oldest);
510510
}
511511

512-
const inclusive = this.queryParams.inclusive || false;
512+
const inclusive = this.queryParams.inclusive === 'true';
513513

514514
let count = 20;
515515
if (this.queryParams.count) {
@@ -521,7 +521,7 @@ API.v1.addRoute(
521521
offset = parseInt(String(this.queryParams.offset));
522522
}
523523

524-
const unreads = this.queryParams.unreads || false;
524+
const unreads = this.queryParams.unreads === 'true';
525525

526526
const showThreadMessages = this.queryParams.showThreadMessages !== 'false';
527527

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { InstanceStatus } from '@rocket.chat/models';
2+
import { ajv, validateUnauthorizedErrorResponse, validateForbiddenErrorResponse } from '@rocket.chat/rest-typings';
23

34
import { isRunningMs } from '../../../../server/lib/isRunningMs';
45
import { API } from '../api';
@@ -12,33 +13,82 @@ const getConnections = (() => {
1213
return () => getInstanceList();
1314
})();
1415

15-
API.v1.addRoute(
16+
API.v1.get(
1617
'instances.get',
17-
{ authRequired: true, permissionsRequired: ['view-statistics'] },
1818
{
19-
async get() {
20-
const instanceRecords = await InstanceStatus.find().toArray();
21-
22-
const connections = await getConnections();
23-
24-
const result = instanceRecords.map((instanceRecord) => {
25-
const connection = connections.find((c) => c.id === instanceRecord._id);
26-
27-
return {
28-
address: connection?.ipList[0],
19+
authRequired: true,
20+
permissionsRequired: ['view-statistics'],
21+
response: {
22+
200: ajv.compile<{
23+
instances: {
24+
address?: string;
2925
currentStatus: {
30-
connected: connection?.available || false,
31-
lastHeartbeatTime: connection?.lastHeartbeatTime,
32-
local: connection?.local,
26+
connected: boolean;
27+
lastHeartbeatTime?: number;
28+
local?: boolean;
29+
};
30+
instanceRecord: object;
31+
broadcastAuth: boolean;
32+
}[];
33+
success: true;
34+
}>({
35+
type: 'object',
36+
properties: {
37+
instances: {
38+
type: 'array',
39+
items: {
40+
type: 'object',
41+
properties: {
42+
address: { type: 'string' },
43+
currentStatus: {
44+
type: 'object',
45+
properties: {
46+
connected: { type: 'boolean' },
47+
lastHeartbeatTime: { type: 'number' },
48+
local: { type: 'boolean' },
49+
},
50+
required: ['connected'],
51+
},
52+
instanceRecord: { type: 'object' },
53+
broadcastAuth: { type: 'boolean' },
54+
},
55+
required: ['currentStatus', 'instanceRecord', 'broadcastAuth'],
56+
},
3357
},
34-
instanceRecord,
35-
broadcastAuth: true,
36-
};
37-
});
38-
39-
return API.v1.success({
40-
instances: result,
41-
});
58+
success: {
59+
type: 'boolean',
60+
enum: [true],
61+
},
62+
},
63+
required: ['instances', 'success'],
64+
additionalProperties: false,
65+
}),
66+
401: validateUnauthorizedErrorResponse,
67+
403: validateForbiddenErrorResponse,
4268
},
4369
},
70+
async function action() {
71+
const instanceRecords = await InstanceStatus.find().toArray();
72+
73+
const connections = await getConnections();
74+
75+
const result = instanceRecords.map((instanceRecord) => {
76+
const connection = connections.find((c) => c.id === instanceRecord._id);
77+
78+
return {
79+
address: connection?.ipList[0],
80+
currentStatus: {
81+
connected: connection?.available || false,
82+
lastHeartbeatTime: connection?.lastHeartbeatTime,
83+
local: connection?.local,
84+
},
85+
instanceRecord,
86+
broadcastAuth: true,
87+
};
88+
});
89+
90+
return API.v1.success({
91+
instances: result,
92+
});
93+
},
4494
);

apps/meteor/client/views/omnichannel/additionalForms/CurrentChatTags.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { useHasLicenseModule } from '../../../hooks/useHasLicenseModule';
44
import AutoCompleteTagsMultiple from '../tags/AutoCompleteTagsMultiple';
55

66
type CurrentChatTagsProps = Pick<ComponentProps<typeof AutoCompleteTagsMultiple>, 'id' | 'aria-labelledby'> & {
7-
value: Array<{ value: string; label: string }>;
8-
handler: (value: { label: string; value: string }[]) => void;
7+
value: NonNullable<ComponentProps<typeof AutoCompleteTagsMultiple>['value']>;
8+
handler: NonNullable<ComponentProps<typeof AutoCompleteTagsMultiple>['onChange']>;
99
department?: string;
1010
viewAll?: boolean;
1111
};
@@ -17,15 +17,7 @@ const CurrentChatTags = ({ value, handler, department, viewAll, ...props }: Curr
1717
return null;
1818
}
1919

20-
return (
21-
<AutoCompleteTagsMultiple
22-
{...props}
23-
onChange={handler as any} // FIXME: any
24-
value={value}
25-
department={department}
26-
viewAll={viewAll}
27-
/>
28-
);
20+
return <AutoCompleteTagsMultiple {...props} onChange={handler} value={value} department={department} viewAll={viewAll} />;
2921
};
3022

3123
export default CurrentChatTags;

apps/meteor/client/views/omnichannel/components/Tags.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ const Tags = ({ tags = [], handler, error, tagRequired, department }: TagsProps)
7676
<CurrentChatTags
7777
id={tagsFieldId}
7878
value={paginatedTagValue}
79-
handler={(tags: { label: string; value: string }[]): void => {
79+
handler={(tags): void => {
8080
handler(tags.map((tag) => tag.label));
8181
}}
8282
department={department}

apps/meteor/tests/end-to-end/api/channels.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1771,6 +1771,106 @@ describe('[Channels]', () => {
17711771
})
17721772
.end(done);
17731773
});
1774+
1775+
describe('inclusive parameter', () => {
1776+
let testChannel: IRoom;
1777+
let oldestMessage: IMessage;
1778+
let middleMessage: IMessage;
1779+
let latestMessage: IMessage;
1780+
1781+
before(async () => {
1782+
const channelRes = await request
1783+
.post(api('channels.create'))
1784+
.set(credentials)
1785+
.send({ name: `inclusive-test-channel-${Date.now()}` });
1786+
testChannel = channelRes.body.channel;
1787+
1788+
// Send messages with small delays to ensure distinct timestamps
1789+
const msg1 = await sendMessage({ message: { rid: testChannel._id, msg: 'oldest message' } });
1790+
oldestMessage = msg1.body.message;
1791+
1792+
// Small delay to ensure timestamps are different
1793+
await new Promise((resolve) => setTimeout(resolve, 50));
1794+
1795+
const msg2 = await sendMessage({ message: { rid: testChannel._id, msg: 'middle message' } });
1796+
middleMessage = msg2.body.message;
1797+
1798+
await new Promise((resolve) => setTimeout(resolve, 50));
1799+
1800+
const msg3 = await sendMessage({ message: { rid: testChannel._id, msg: 'latest message' } });
1801+
latestMessage = msg3.body.message;
1802+
});
1803+
1804+
after(async () => {
1805+
if (testChannel?._id) {
1806+
await deleteRoom({ type: 'c', roomId: testChannel._id });
1807+
}
1808+
});
1809+
1810+
it('should include boundary messages when inclusive=true', async () => {
1811+
const res = await request
1812+
.get(api('channels.history'))
1813+
.set(credentials)
1814+
.query({
1815+
roomId: testChannel._id,
1816+
oldest: oldestMessage.ts,
1817+
latest: latestMessage.ts,
1818+
inclusive: 'true',
1819+
})
1820+
.expect('Content-Type', 'application/json')
1821+
.expect(200);
1822+
1823+
expect(res.body).to.have.property('success', true);
1824+
expect(res.body).to.have.property('messages').that.is.an('array');
1825+
1826+
const messageIds = res.body.messages.map((m: IMessage) => m._id);
1827+
expect(messageIds).to.include(oldestMessage._id, 'oldest message should be included');
1828+
expect(messageIds).to.include(latestMessage._id, 'latest message should be included');
1829+
});
1830+
1831+
it('should exclude boundary messages when inclusive=false', async () => {
1832+
const res = await request
1833+
.get(api('channels.history'))
1834+
.set(credentials)
1835+
.query({
1836+
roomId: testChannel._id,
1837+
oldest: oldestMessage.ts,
1838+
latest: latestMessage.ts,
1839+
inclusive: 'false',
1840+
})
1841+
.expect('Content-Type', 'application/json')
1842+
.expect(200);
1843+
1844+
expect(res.body).to.have.property('success', true);
1845+
expect(res.body).to.have.property('messages').that.is.an('array');
1846+
1847+
const messageIds = res.body.messages.map((m: IMessage) => m._id);
1848+
expect(messageIds).to.not.include(oldestMessage._id, 'oldest message should be excluded');
1849+
expect(messageIds).to.not.include(latestMessage._id, 'latest message should be excluded');
1850+
// Middle message should still be included if it exists in the range
1851+
expect(messageIds).to.include(middleMessage._id, 'middle message should be included');
1852+
});
1853+
1854+
it('should exclude boundary messages by default (no inclusive param)', async () => {
1855+
const res = await request
1856+
.get(api('channels.history'))
1857+
.set(credentials)
1858+
.query({
1859+
roomId: testChannel._id,
1860+
oldest: oldestMessage.ts,
1861+
latest: latestMessage.ts,
1862+
})
1863+
.expect('Content-Type', 'application/json')
1864+
.expect(200);
1865+
1866+
expect(res.body).to.have.property('success', true);
1867+
expect(res.body).to.have.property('messages').that.is.an('array');
1868+
1869+
const messageIds = res.body.messages.map((m: IMessage) => m._id);
1870+
expect(messageIds).to.not.include(oldestMessage._id, 'oldest message should be excluded by default');
1871+
expect(messageIds).to.not.include(latestMessage._id, 'latest message should be excluded by default');
1872+
});
1873+
});
17741874
});
17751875

17761876
describe('/channels.members', () => {

0 commit comments

Comments
 (0)