Skip to content

Commit

Permalink
Merge pull request #26 from flamingchickens1540/zach/fix
Browse files Browse the repository at this point in the history
Manager Stuff
  • Loading branch information
rutmanz authored Sep 10, 2024
2 parents c43489a + ef8b6be commit a792d11
Show file tree
Hide file tree
Showing 16 changed files with 146 additions and 63 deletions.
4 changes: 3 additions & 1 deletion config/example.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
"celebration": "CXXXX",
"void": "CXXX",
"approval": "DXXX",
"certification_approval": "DXXX"
"certification_approval": "DXXX",
"checkin": "CXXX"
},
"groups": {
"students": "SXXX"
},
"users": {
"copres": ["UXXX"],
"admins": ["UXXX"],
"devs": ["UXXX"]
}
Expand Down
2 changes: 1 addition & 1 deletion dev/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
},
"oauth_config": {
"scopes": {
"bot": ["app_mentions:read", "channels:history", "channels:join", "channels:read", "chat:write", "chat:write.public", "commands", "files:write", "groups:read", "im:history", "im:read", "im:write", "mpim:read", "usergroups:read", "users:read", "users.profile:read", "users:read.email"]
"bot": ["app_mentions:read", "channels:history", "channels:join", "channels:read", "chat:write", "chat:write.public", "commands", "files:write", "groups:read", "im:history", "im:read", "im:write", "mpim:read", "mpim:write", "reactions:write", "usergroups:read", "users.profile:read", "users:read", "users:read.email", "mpim:write.topic"]
}
},
"settings": {
Expand Down
4 changes: 2 additions & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export default tseslint.config(
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
caughtErrorsIgnorePattern: '^_'
}
],
'prefer-const': 'warn',
'@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': 'allow-with-description' }]
Expand Down
2 changes: 1 addition & 1 deletion src/lib/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const transport = pino.transport({
]
})

const logger = pino({ level: 'trace' }, transport)
const logger = pino({ level: 'debug' }, transport)

const pinoToBoltLevel: Record<LevelWithSilentOrString, BoltLogLevel> = {
fatal: BoltLogLevel.ERROR,
Expand Down
7 changes: 0 additions & 7 deletions src/lib/sockets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,6 @@ export function startWS(server: HttpServer) {
path: '/ws'
})
logger.info('Websocket server started')

io.on('connection', (socket) => {
socket.emit('hello', 'world')
socket.on('hello', (data) => {
socket.broadcast.emit('hello', data)
})
})
}

export function emitCluckChange(data: WSCluckChange) {
Expand Down
15 changes: 13 additions & 2 deletions src/routes/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,17 @@ router.get('/members', requireReadAPI, async (c) => {
use_slack_photo: true,
slack_photo: true,
slack_photo_small: true,
fallback_photo: true
fallback_photo: true,
MemberCerts: {
where: {
Cert: {
isManager: true
}
},
select: {
cert_id: true
}
}
},
where: {
active: true
Expand All @@ -34,7 +44,8 @@ router.get('/members', requireReadAPI, async (c) => {
first_name: member.first_name,
full_name: member.full_name,
photo: getMemberPhotoOrDefault(member, false),
photo_small: getMemberPhotoOrDefault(member, true)
photo_small: getMemberPhotoOrDefault(member, true),
isManager: member.MemberCerts.length > 0
}))
return c.json(resp)
})
Expand Down
2 changes: 1 addition & 1 deletion src/slack/blocks/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export default {
autoSignoutDM(v: { slack_id: string; time_in: Date }) {
return Message()
.text(
`Hey <@${v.slack_id}>! You signed into the lab today at ${v.time_in.toLocaleTimeString()} but forgot to sign out, so we didn't log your hours for today :( Make sure you always sign out before you leave. Hope you had fun and excited to see you in the lab again!`
`Hey <@${v.slack_id}>! You signed into the lab today at ${v.time_in.toLocaleTimeString('en-us', { hour: 'numeric', hour12: true, minute: '2-digit' })} but forgot to sign out, so we didn't log your hours for today :( Make sure you always sign out before you leave. Hope you had fun and excited to see you in the lab again!`
)
.buildToObject()
}
Expand Down
44 changes: 44 additions & 0 deletions src/slack/handlers/actions/checkin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import config from '~lib/config'
import logger from '~lib/logger'
import prisma from '~lib/prisma'
import { EventMiddleware } from '~slack/lib/types'

export const handleAppMentioned: EventMiddleware<'app_mention'> = async ({ event, client }) => {
if (event.channel == config.slack.channels.checkin) {
await client.reactions.add({
channel: event.channel,
timestamp: event.ts,
name: 'stopwatch'
})
const user = await client.auth.test()
const managers = await prisma.member.findMany({ where: { MemberCerts: { some: { Cert: { isManager: true } } } } })
const copres_string = config.slack.users.copres.join(',')
for (const manager of managers) {
if (!manager.slack_id) {
logger.warn('No slack id for manager ' + manager.email)
continue
}
const dm = await client.conversations.open({
users: copres_string + ',' + manager.slack_id
})
if (dm.channel?.id == null) {
logger.warn('No group dm for manager ' + manager.email)
continue
}
await client.chat.postMessage({
channel: dm.channel!.id!,
text: event.text.replace(user.user_id!, manager.slack_id)
})
}
await client.reactions.remove({
channel: event.channel,
timestamp: event.ts,
name: 'stopwatch'
})
await client.reactions.add({
channel: event.channel,
timestamp: event.ts,
name: 'white_check_mark'
})
}
}
2 changes: 2 additions & 0 deletions src/slack/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
handleSubmitHoursRejectModal
} from './actions/hours_response'
import { handleRunTask } from '~slack/handlers/actions/run_task'
import { handleAppMentioned } from './actions/checkin'

export enum ActionIDs {
ACCEPT = 'accept',
Expand Down Expand Up @@ -91,6 +92,7 @@ export function registerSlackHandlers(app: App) {
app.view(ViewIDs.MODAL_ONBOARDING, handleSubmitOnboardingModal)
// Events
app.event('app_home_opened', handleAppHomeOpened)
app.event('app_mention', handleAppMentioned)
app.action(/./, async ({ body, logger, action }) => {
const details: Record<string, string> = {
type: body?.type,
Expand Down
49 changes: 49 additions & 0 deletions src/tasks/calendar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Prisma } from '@prisma/client'
import config from '~lib/config'
import logger from '~lib/logger'
import prisma from '~lib/prisma'
import { emitCluckChange } from '~lib/sockets'
import { slack_client } from '~slack'
import responses from '~slack/blocks/responses'

export async function promptCheckinMessage() {
await slack_client.chat.postMessage({
channel: config.slack.channels.checkin,
text: "<!channel> it's that time again! Make a checkin post"
})
}

export async function logoutAll() {
const loggedIn = await prisma.hourLog.findMany({
where: {
state: 'pending',
type: 'lab'
},
select: { time_in: true, Member: { select: { slack_id: true, email: true } } }
})
for (const log of loggedIn) {
try {
const slack_id = log.Member.slack_id
emitCluckChange({ email: log.Member.email, logging_in: false })
if (slack_id) {
await slack_client.chat.postMessage({
...responses.autoSignoutDM({ slack_id, time_in: log.time_in }),
channel: slack_id
})
}
} catch (e) {
logger.warn(e)
}
}
await prisma.hourLog.updateMany({
where: {
state: 'pending',
type: 'lab'
},
data: {
state: 'cancelled',
time_out: new Date(),
duration: new Prisma.Decimal(0)
}
})
}
20 changes: 15 additions & 5 deletions src/tasks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import { syncSlackMembers } from '~tasks/slack'
import { announceNewCerts, updateProfileCerts } from '~tasks/certs'
import { updateSheet } from '~spreadsheet'
import { syncFallbackPhotos } from './photos'
import { setupAutoLogout } from './midnight'
import schedule from 'node-schedule'
import { logoutAll, promptCheckinMessage } from './calendar'

type TaskFunc = (reason: string) => Promise<void>
type TaskFunc = ((reason: string) => Promise<void>) & { label: string }
type Func = (() => void) | (() => Promise<void>)

const tasks: Record<string, TaskFunc> = {}

function createTaskFunc(task: Func): TaskFunc {
const label = 'task/' + task.name
return async (reason: string) => {
const func = async (reason: string) => {
try {
await task()
} catch (e) {
Expand All @@ -23,6 +24,8 @@ function createTaskFunc(task: Func): TaskFunc {
logger.info({ name: label }, 'Task ran successfully')
return
}
func.label = label
return func
}
function scheduleTask(task: Func, interval_seconds: number, runOnInit: boolean, offset_seconds: number): TaskFunc {
const cb = createTaskFunc(task)
Expand All @@ -39,18 +42,25 @@ function scheduleTask(task: Func, interval_seconds: number, runOnInit: boolean,
return cb
}

function scheduleCronTask(task: TaskFunc, cron_exp: string) {
schedule.scheduleJob(task.label, cron_exp, (_date) => task('scheduled run'))
return task
}

export function scheduleTasks() {
// Offset is to combat Slack's rate limits
const isProd = process.env.NODE_ENV === 'prod'

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)
tasks['Update Profile Certs'] = scheduleTask(updateProfileCerts, 60 * 60 * 24, isProd, 5 * 60)
tasks['Link Fallback Photos'] = createTaskFunc(syncFallbackPhotos)
setupAutoLogout()
tasks['Logout All'] = scheduleCronTask(createTaskFunc(logoutAll), '0 0 * * *')

// Slack is silly and can only handle 5 items in the overflow menu
scheduleCronTask(createTaskFunc(promptCheckinMessage), '0 9 * * SAT')
scheduleTask(syncSlackMembers, 60 * 60, isProd, 0) // can be run from the admin members page
scheduleTask(updateProfileCerts, 60 * 60 * 24, isProd, 5 * 60)
}

export async function runTask(key: string) {
Expand Down
42 changes: 0 additions & 42 deletions src/tasks/midnight.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type APIMember = {
full_name: string
photo: string
photo_small: string
isManager: boolean
}

export type APIClockLabRequest = {
Expand Down
3 changes: 3 additions & 0 deletions src/views/grid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export async function buildGrid() {
// Init button
const memberButton = document.createElement('div')
memberButton.classList.add('memberButton')
if (member.isManager) {
memberButton.classList.add('manager')
}
memberButton.id = member.email

// Set click toggle
Expand Down
10 changes: 10 additions & 0 deletions src/views/grid/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ box-shadow: inset 0 0 0 1000px rgba(0, 255, 85, 0.2); */
}
}

.memberButton.manager[data-loggedin='true'] {
box-shadow:
inset 0 0 0 1000px rgba(255, 255, 255, 0),
0 0 15px 7px rgb(251, 0, 255);
}

body[data-gridstyle='void'] {
.memberButton {
.buttonText {
Expand All @@ -124,6 +130,10 @@ body[data-gridstyle='void'] {
}
}

.manager .buttonText {
box-shadow: inset 0 0 0 1000px rgba(255, 70, 237, 0.6);
}

.buttonText {
user-select: none;
box-shadow: inset 0 0 0 1000px rgba(70, 255, 169, 0.6);
Expand Down
2 changes: 1 addition & 1 deletion src/views/grid/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ if (gridstyle == 'void') {

export function applyRandomStyles(e: HTMLDivElement) {
e.classList.add(randomChoice('labelLeft', 'labelRight', 'labelCenter'))
e.classList.add(randomChoice('labelTop', 'labelBottom'))
// e.classList.add(randomChoice('labelTop', 'labelBottom'))
e.style.fontFamily = randomChoice('gilroy', 'cocogoose', 'tcm')
}

Expand Down

0 comments on commit a792d11

Please sign in to comment.