From 780514b2990da39113aa1d3e20da93f705a998b1 Mon Sep 17 00:00:00 2001 From: Zach R Date: Wed, 18 Sep 2024 20:14:21 -0700 Subject: [PATCH] feat: add manager usergroups --- prisma/schema.prisma | 31 ++++----- src/lib/cert_operations.ts | 29 ++++++++ src/slack/handlers/actions/checkin.ts | 46 +++---------- src/tasks/index.ts | 4 +- src/tasks/slack_groups.ts | 97 ++++++++++++++++++--------- 5 files changed, 124 insertions(+), 83 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f53e1a4..f9755ef 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -22,12 +22,13 @@ model Account { } model Department { - id String @id @db.VarChar(50) - name String @db.VarChar(100) - slack_group String? @db.VarChar(50) - createdAt DateTime @default(now()) @db.Timestamptz(6) - updatedAt DateTime @updatedAt @db.Timestamptz(6) - Certs Cert[] + id String @id @db.VarChar(50) + name String @db.VarChar(100) + slack_group String? @db.VarChar(50) + manager_slack_group String? @db.VarChar(50) + createdAt DateTime @default(now()) @db.Timestamptz(6) + updatedAt DateTime @updatedAt @db.Timestamptz(6) + Certs Cert[] Members DepartmentAssociation[] @@ -112,16 +113,16 @@ model Meetings { } model Member { - email String @id @db.VarChar(50) - first_name String @db.VarChar(50) - full_name String @db.VarChar(100) + email String @id @db.VarChar(50) + first_name String @db.VarChar(50) + full_name String @db.VarChar(100) use_slack_photo Boolean - slack_id String? @unique @db.VarChar(15) - slack_photo String? @db.VarChar(255) - slack_photo_small String? @db.VarChar(255) - fallback_photo String? @db.VarChar(255) - is_primary_team Boolean @default(true) - active Boolean @default(true) + slack_id String? @unique @db.VarChar(15) + slack_photo String? @db.VarChar(255) + slack_photo_small String? @db.VarChar(255) + fallback_photo String? @db.VarChar(255) + is_primary_team Boolean @default(true) + active Boolean @default(true) createdAt DateTime @default(now()) @db.Timestamptz(6) updatedAt DateTime @updatedAt @db.Timestamptz(6) diff --git a/src/lib/cert_operations.ts b/src/lib/cert_operations.ts index a4a1f74..8f9eadc 100644 --- a/src/lib/cert_operations.ts +++ b/src/lib/cert_operations.ts @@ -78,3 +78,32 @@ export async function createCertRequest(giver: Prisma.MemberWhereUniqueInput, re }) return { success: true } } + +export async function getManagers() { + const departments = await prisma.department.findMany({ + include: { + Certs: { + where: { + isManager: true + }, + select: { + Instances: { + select: { + Member: { + select: { + email: true, + slack_id: true + } + } + } + } + } + } + } + }) + + return departments.map((dept) => ({ + dept, + managers: dept.Certs.flatMap((cert) => cert.Instances.map((instance) => instance.Member.slack_id).filter((v) => v != null)) + })) +} diff --git a/src/slack/handlers/actions/checkin.ts b/src/slack/handlers/actions/checkin.ts index 46e809e..158d001 100644 --- a/src/slack/handlers/actions/checkin.ts +++ b/src/slack/handlers/actions/checkin.ts @@ -1,7 +1,7 @@ -import { Blocks, Elements, Message } from 'slack-block-builder' +import { Blocks, Message } from 'slack-block-builder' +import { getManagers } from '~lib/cert_operations' import config from '~lib/config' import logger from '~lib/logger' -import prisma from '~lib/prisma' import { formatList } from '~slack/lib/messages' import { EventMiddleware } from '~slack/lib/types' @@ -14,52 +14,26 @@ export const handleAppMentioned: EventMiddleware<'app_mention'> = async ({ event }) const user = await client.auth.test() - const departments = await prisma.department.findMany({ - select: { - name: true, - Certs: { - where: { - isManager: true - }, - select: { - Instances: { - select: { - Member: { - select: { - email: true, - slack_id: true - } - } - } - } - } - } - } - }) - - const dept_managers = departments.map((dept) => ({ - name: dept.name, - managers: dept.Certs.flatMap((cert) => cert.Instances.map((instance) => instance.Member.slack_id).filter((v) => v != null)) - })) + const dept_managers = await getManagers() - for (const dept of dept_managers) { - if (dept.managers.length == 0) { - logger.warn('No manager slack ids for dept ' + dept.name) + for (const manager_dept of dept_managers) { + if (manager_dept.managers.length == 0) { + logger.warn('No manager slack ids for dept ' + manager_dept.dept.name) continue } const dm = await client.conversations.open({ - users: [...config.slack.users.copres, ...dept.managers].join(',') + users: [...config.slack.users.copres, ...manager_dept.managers].join(',') }) if (dm.channel?.id == null) { - logger.warn('No group dm for dept ' + dept.name) + logger.warn('No group dm for dept ' + manager_dept.dept.name) continue } - const text = event.text.replace('<@' + user.user_id! + '>', formatList(dept.managers.map((id) => '<@' + id + '>'))) + const text = event.text.replace('<@' + user.user_id! + '>', formatList(manager_dept.managers.map((id) => '<@' + id + '>'))) await client.chat.postMessage({ channel: dm.channel!.id!, text, blocks: Message() - .blocks(Blocks.Section().text(text), Blocks.Context().elements('Copresident Checkin for ' + dept.name)) + .blocks(Blocks.Section().text(text), Blocks.Context().elements('Copresident Checkin for ' + manager_dept.dept.name)) .buildToObject().blocks }) } diff --git a/src/tasks/index.ts b/src/tasks/index.ts index 27f553a..eddaa81 100644 --- a/src/tasks/index.ts +++ b/src/tasks/index.ts @@ -53,7 +53,9 @@ export function scheduleTasks() { tasks['Sync Sheet'] = scheduleTask(updateSheet, 60 * 5, isProd, 0) tasks['Announce Certs'] = scheduleTask(announceNewCerts, 60 * 60, isProd, 60) // Just in case the cert announcement isn't automatically run on changes - tasks['Sync Usergroups'] = scheduleTask(updateSlackUsergroups, 60 * 60, isProd, 2 * 60) + if (isProd) { // This task affects workspace-wide groups, should not be run while testing if in the same workspace + tasks['Sync Usergroups'] = scheduleTask(updateSlackUsergroups, 60 * 60, isProd, 2 * 60) + } tasks['Link Fallback Photos'] = createTaskFunc(syncFallbackPhotos) tasks['Logout All'] = scheduleCronTask(createTaskFunc(logoutAll), '0 0 * * *') diff --git a/src/tasks/slack_groups.ts b/src/tasks/slack_groups.ts index 5b033c9..b9fef24 100644 --- a/src/tasks/slack_groups.ts +++ b/src/tasks/slack_groups.ts @@ -2,6 +2,7 @@ import prisma from '~lib/prisma' import logger from '~lib/logger' import { profile_client } from '~slack/lib/profile' import { slack_client } from '~slack' +import { getManagers } from '~lib/cert_operations' let timeout: NodeJS.Timeout @@ -12,6 +13,7 @@ export function scheduleUpdateSlackUsergroups() { timeout = setTimeout(updateSlackUsergroups, 1000 * 30) } } + export async function updateSlackUsergroups() { if (profile_client == null) { return @@ -19,58 +21,91 @@ export async function updateSlackUsergroups() { const usergroups_list = await slack_client.usergroups.list({ include_disabled: true }) const usergroups = new Map(usergroups_list.usergroups!.map((g) => [g.id!, g])) const departments = await prisma.department.findMany({ include: { Members: { select: { Member: { select: { slack_id: true } } } } } }) - for (const department of departments) { - const handle = department.name.toLowerCase().replace(' ', '-') + '-dept' - const existing = usergroups.get(department.slack_group ?? '') - if (department.slack_group == null || existing == null) { - const resp = await profile_client.usergroups + const syncUsergroup = async (data: { department_id: string; group_id: string | null; title: string; handle: string; member_ids: string[] }) => { + const existing = usergroups.get(data.group_id ?? '') + let returnValue: { group_id: string; isNew: boolean } + if (data.group_id == null || existing == null) { + const resp = await profile_client!.usergroups .create({ - name: department.name, - handle + name: data.title, + handle: data.handle }) .catch((e) => { return { usergroup: null, error: e } }) if (resp.error != null) { - logger.error({ error: resp.error, department: department.id, handle: department.name.toLowerCase().replace(' ', '-') }, 'Could not create usergroup') - continue + logger.error({ error: resp.error, department: data.department_id, handle: data.handle }, 'Could not create usergroup') + return } else { - await prisma.department.update({ - where: { id: department.id }, - data: { - slack_group: resp.usergroup!.id - } - }) - department.slack_group = resp.usergroup!.id! + data.group_id = resp.usergroup!.id! + returnValue = { group_id: resp.usergroup!.id!, isNew: true } } } else { - if (existing.name != department.name || existing.handle != handle) { - await profile_client.usergroups.update({ - usergroup: department.slack_group, - name: department.name, - handle + if (existing.name != data.title || existing.handle != data.handle) { + await profile_client!.usergroups.update({ + usergroup: data.group_id, + name: data.title, + handle: data.handle }) } + returnValue = { group_id: data.group_id, isNew: false } } - const members = department.Members.filter((m) => m.Member.slack_id != null).map((m) => m.Member.slack_id!) - if (members.length == 0 && existing != null) { + if (data.member_ids.length == 0 && existing != null) { // If it's already disabled, don't disable it again if (!existing.date_delete) { - await profile_client.usergroups.disable({ - usergroup: department.slack_group + await profile_client!.usergroups.disable({ + usergroup: data.group_id }) } } - if (members.length > 0) { + + if (data.member_ids.length > 0) { if (existing?.date_delete) { - await profile_client.usergroups.enable({ - usergroup: department.slack_group + await profile_client!.usergroups.enable({ + usergroup: data.group_id }) } - await profile_client.usergroups.users.update({ - usergroup: department.slack_group, - users: members.join(',') + await profile_client!.usergroups.users.update({ + usergroup: data.group_id, + users: data.member_ids.join(',') + }) + } + + return returnValue + } + for (const department of departments) { + const res = await syncUsergroup({ + department_id: department.id, + group_id: department.slack_group, + title: department.name, + handle: department.name.toLowerCase().replace(' ', '-') + '-dept', + member_ids: department.Members.filter((m) => m.Member.slack_id != null).map((m) => m.Member.slack_id!) + }) + if (res?.isNew) { + await prisma.department.update({ + where: { id: department.id }, + data: { + slack_group: res.group_id + } + }) + } + } + for (const manager_department of await getManagers()) { + const department = manager_department.dept + const res = await syncUsergroup({ + department_id: department.id, + group_id: department.manager_slack_group, + title: department.name + ' Managers', + handle: department.name.toLowerCase().replace(' ', '-') + '-managers', + member_ids: manager_department.managers + }) + if (res?.isNew) { + await prisma.department.update({ + where: { id: department.id }, + data: { + manager_slack_group: res.group_id + } }) } }