user_groups: Add get_recursive_group_members.

This function goes through all subgroups recursively and returns the
resultant set of the members of those subgroups in addition to the
members of our target_group.

This function is required in order to add the `everyone` pill to create
channel flow.

For the tests written in this commit, it uses the same pattern as the
`get_recursive_subgroups` test.

`is_user_in_group` could have been technically refactored to use
`get_recursive_group_members`, but since the former returns early for
direct members, I've let it be for now.
This commit is contained in:
Shubham Padia
2024-07-08 15:34:16 +00:00
committed by Tim Abbott
parent c7e59ed761
commit 9f7dc596f8
2 changed files with 79 additions and 0 deletions

View File

@@ -1,3 +1,4 @@
import assert from "minimalistic-assert";
import type {z} from "zod";
import * as blueslip from "./blueslip";
@@ -204,6 +205,24 @@ export function get_recursive_subgroups(target_user_group: UserGroup): Set<numbe
return subgroup_ids;
}
export function get_recursive_group_members(target_user_group: UserGroup): Set<number> {
const members = new Set(target_user_group.members);
const subgroup_ids = get_recursive_subgroups(target_user_group);
if (subgroup_ids === undefined) {
return members;
}
for (const subgroup_id of subgroup_ids) {
const subgroup = user_group_by_id_dict.get(subgroup_id);
assert(subgroup !== undefined);
for (const member of subgroup.members) {
members.add(member);
}
}
return members;
}
export function is_user_in_group(user_group_id: number, user_id: number): boolean {
const user_group = user_group_by_id_dict.get(user_group_id);
if (user_group === undefined) {

View File

@@ -190,6 +190,66 @@ run_test("get_recursive_subgroups", () => {
assert.deepEqual(user_groups.get_recursive_subgroups(test), undefined);
});
run_test("get_recursive_group_members", () => {
const admins = {
name: "Admins",
description: "foo",
id: 1,
members: new Set([1]),
is_system_group: false,
direct_subgroup_ids: new Set([4]),
};
const all = {
name: "Everyone",
id: 2,
members: new Set([2, 3]),
is_system_group: false,
direct_subgroup_ids: new Set([1, 3]),
};
const test = {
name: "Test",
id: 3,
members: new Set([3, 4, 5]),
is_system_group: false,
direct_subgroup_ids: new Set([2]),
};
const foo = {
name: "Foo",
id: 4,
members: new Set([6, 7]),
is_system_group: false,
direct_subgroup_ids: new Set([]),
};
user_groups.add(admins);
user_groups.add(all);
user_groups.add(test);
user_groups.add(foo);
// This test setup has a state that won't appear in real data: Groups 2 and 3
// each contain the other. We test this corner case because it is a simple way
// to verify whether our algorithm correctly avoids visiting groups multiple times
// when determining recursive subgroups.
// A test case that can occur in practice and would be problematic without this
// optimization is a tree where each layer connects to every node in the next layer.
assert.deepEqual([...user_groups.get_recursive_group_members(admins)].sort(), [1, 6, 7]);
assert.deepEqual(
[...user_groups.get_recursive_group_members(all)].sort(),
[1, 2, 3, 4, 5, 6, 7],
);
assert.deepEqual(
[...user_groups.get_recursive_group_members(test)].sort(),
[1, 2, 3, 4, 5, 6, 7],
);
assert.deepEqual([...user_groups.get_recursive_group_members(foo)].sort(), [6, 7]);
user_groups.add_subgroups(foo.id, [9999]);
const foo_group = user_groups.get_user_group_from_id(foo.id);
blueslip.expect("error", "Could not find subgroup", 2);
assert.deepEqual([...user_groups.get_recursive_group_members(foo_group)].sort(), [6, 7]);
assert.deepEqual([...user_groups.get_recursive_group_members(test)].sort(), [3, 4, 5]);
});
run_test("is_user_in_group", () => {
const admins = {
name: "Admins",