Skip to content

Commit

Permalink
feat: add reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
rutmanz committed Oct 29, 2024
1 parent d1d0c7e commit 7cb2d3d
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 3 deletions.
2 changes: 2 additions & 0 deletions config/example.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"void": "CXXX",
"approval": "DXXX",
"certification_approval": "DXXX",
"violation": "CXXXX",
"checkin": "CXXX"
},
"groups": {
Expand All @@ -25,6 +26,7 @@
"users": {
"copres": ["UXXX"],
"admins": ["UXXX"],
"reports": ["UXXX"],
"devs": ["UXXX"]
}
},
Expand Down
5 changes: 5 additions & 0 deletions dev/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@
"command": "/departments",
"description": "Allows you to manage your department associations",
"should_escape": false
},
{
"command": "/report",
"description": "[Manager Only]",
"should_escape": false
}
]
},
Expand Down
14 changes: 14 additions & 0 deletions prisma/migrations/20241029014205_add_violations/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- CreateTable
CREATE TABLE "Violations" (
"id" SERIAL NOT NULL,
"member" TEXT NOT NULL,
"reporter" TEXT,

CONSTRAINT "Violations_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "Violations" ADD CONSTRAINT "Violations_member_fkey" FOREIGN KEY ("member") REFERENCES "Members"("email") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Violations" ADD CONSTRAINT "Violations_reporter_fkey" FOREIGN KEY ("reporter") REFERENCES "Members"("email") ON DELETE SET NULL ON UPDATE CASCADE;
15 changes: 15 additions & 0 deletions prisma/migrations/20241029015815_reportings/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
Warnings:
- You are about to drop the column `reporter` on the `Violations` table. All the data in the column will be lost.
- Added the required column `description` to the `Violations` table without a default value. This is not possible if the table is not empty.
- Added the required column `reporter_slack_id` to the `Violations` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "Violations" DROP CONSTRAINT "Violations_reporter_fkey";

-- AlterTable
ALTER TABLE "Violations" DROP COLUMN "reporter",
ADD COLUMN "description" TEXT NOT NULL,
ADD COLUMN "reporter_slack_id" TEXT NOT NULL;
12 changes: 12 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ model Member {
MemberCertRequests MemberCertRequest[] @relation("MemberCertRecipient")
MemberCertsRequested MemberCertRequest[] @relation("MemberCertRequester")
Departments DepartmentAssociation[]
Violations Violation[]
@@index([slack_id], map: "members_slack_id")
@@index([full_name], map: "members_full_name")
Expand Down Expand Up @@ -181,6 +182,17 @@ model FallbackPhoto {
@@map("FallbackPhotos")
}

model Violation {
id Int @id @default(autoincrement())
member String
reporter_slack_id String
description String
Member Member @relation(fields: [member], references: [email], onDelete: Cascade)
@@map("Violations")
}

enum enum_HourLogs_state {
complete
pending
Expand Down
118 changes: 118 additions & 0 deletions src/slack/handlers/actions/report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { getCertifyModal, getCertRequestMessage } from '~slack/blocks/certify'

Check warning on line 1 in src/slack/handlers/actions/report.ts

View workflow job for this annotation

GitHub Actions / Lint

'getCertifyModal' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 1 in src/slack/handlers/actions/report.ts

View workflow job for this annotation

GitHub Actions / Lint

'getCertRequestMessage' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 1 in src/slack/handlers/actions/report.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

src/slack/handlers/actions/report.ts#L1

[@typescript-eslint/no-unused-vars] 'getCertifyModal' is defined but never used. Allowed unused vars must match /^_/u.

Check warning on line 1 in src/slack/handlers/actions/report.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

src/slack/handlers/actions/report.ts#L1

[@typescript-eslint/no-unused-vars] 'getCertRequestMessage' is defined but never used. Allowed unused vars must match /^_/u.
import { createCertRequest } from '~lib/cert_operations'

Check warning on line 2 in src/slack/handlers/actions/report.ts

View workflow job for this annotation

GitHub Actions / Lint

'createCertRequest' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 2 in src/slack/handlers/actions/report.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

src/slack/handlers/actions/report.ts#L2

[@typescript-eslint/no-unused-vars] 'createCertRequest' is defined but never used. Allowed unused vars must match /^_/u.
import { ActionMiddleware, CommandMiddleware, ViewMiddleware } from '~slack/lib/types'
import prisma from '~lib/prisma'
import { ordinal, safeParseInt } from '~lib/util'
import { Blocks, ConfirmationDialog, Elements, Message, Modal, ModalBuilder } from 'slack-block-builder'
import { ActionIDs, ViewIDs } from '..'
import config from '~config'
import { slack_client } from '~slack'
import logger from '~lib/logger'

async function createReportModal(user_id: string): Promise<ModalBuilder> {
const manager = await prisma.member.findUnique({
where: { slack_id: user_id },
select: { MemberCerts: { where: { Cert: { isManager: true } } } }
})
const slack_user = await slack_client.users.info({ user: user_id })
if (slack_user.user?.is_admin) {
logger.info('Accepting report from admin: ' + slack_user.user?.name)
} else if (!manager) {
return Modal().title('Unauthorized').blocks(Blocks.Header().text("I don't know you 🤨"))
} else if (manager.MemberCerts.length == 0) {
return Modal().title('Unauthorized').blocks(Blocks.Header().text("You're not a manager. Please reach out to a manager or Kevin directly if you have concerns"))
}

return Modal()
.title('Report a Violation')
.callbackId(ViewIDs.MODAL_REPORT)
.blocks(
Blocks.Input().label('Member').blockId('user').element(Elements.UserSelect().actionId('user').placeholder('Who are you reporting?')),
Blocks.Input().label('Description').blockId('description').element(Elements.TextInput().multiline().actionId('description').placeholder('What happened?'))
)
.submit('Report')
.close('Cancel')
}

export const handleReportCommand: CommandMiddleware = async ({ command, ack, client }) => {
await ack()
const modal = await createReportModal(command.user_id)
await client.views.open({
view: modal.buildToObject(),
trigger_id: command.trigger_id
})
}

export const handleSubmitReportModal: ViewMiddleware = async ({ ack, body, view, client }) => {
// Get the hours and task from the modal
const slack_id = view.state.values.user.user.selected_user
const description = view.state.values.description.description.value!
const member = await prisma.member.findUnique({ where: { slack_id: slack_id ?? '' }, select: { email: true, slack_id: true, Violations: true } })
if (slack_id == null || member == null) {
await ack({
response_action: 'errors',
errors: { user: 'Unknown user' }
})
return
} else {
await ack()
const violation = await prisma.violation.create({
data: {
description: description,
member: member.email,
reporter_slack_id: body.user.id
}
})
const log_message = Message()
.text('New Violation Reported')
.blocks(
Blocks.Header().text(`Violation Report #${violation.id}`),
Blocks.Section()
.text(`Report by <@${violation.reporter_slack_id}> for <@${member.slack_id}>'s conduct`)
.accessory(
Elements.Button()
.actionId(ActionIDs.DELETE_REPORT)
.value(violation.id.toString())
.text('🗑️')
.confirm(ConfirmationDialog().danger().confirm('Delete').title('Delete Report').text('Are you sure you want to delete this report?').deny('Cancel'))
.danger()
),
Blocks.Context().elements('This is the ' + ordinal(member.Violations.length + 1) + ' violation for this member'),
Blocks.Section().text('>>> ' + description),
Blocks.Divider()
)
.buildToObject()
await client.chat.postMessage({ channel: config.slack.channels.violation, text: log_message.text, blocks: log_message.blocks })

const discuss_message = Message()
.text('New Violation Reported')
.blocks(
Blocks.Header().text(`Violation Report #${violation.id}`),
Blocks.Context().elements(`For <@${member.slack_id}>`),
Blocks.Section().text('>>> ' + description),
Blocks.Divider()
)
.buildToObject()
console.log([...config.slack.users.admins, body.user.id])
const dm = await client.conversations.open({ users: [...config.slack.users.reports, body.user.id].join(',') })
await client.chat.postMessage({ channel: dm.channel!.id!, text: discuss_message.text, blocks: discuss_message.blocks })
}
}

export const handleReportDelete: ActionMiddleware = async ({ ack, respond, action, client, body }) => {

Check warning on line 102 in src/slack/handlers/actions/report.ts

View workflow job for this annotation

GitHub Actions / Lint

'client' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 102 in src/slack/handlers/actions/report.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

src/slack/handlers/actions/report.ts#L102

[@typescript-eslint/no-unused-vars] 'client' is defined but never used. Allowed unused args must match /^_/u.
await ack()
const report_id = safeParseInt(action.value)
if (report_id == null) {
return
}

await prisma.violation.delete({
where: { id: report_id }
})
const log_message = Message()
.text('New Violation Reported')
.blocks(Blocks.Header().text(`Violation Report #${report_id}`), Blocks.Section().text(`Deleted by <@${body.user.id}> at ${new Date().toLocaleString()}`), Blocks.Divider())
.buildToObject()

await respond({ response_type: 'ephemeral', blocks: log_message.blocks! })
}
8 changes: 7 additions & 1 deletion src/slack/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
import { handleRunTask } from '~slack/handlers/actions/run_task'
import { handleAppMentioned } from './actions/checkin'
import { handleOpenEventlogModal, handleSubmitEventlogModal } from './views/eventlog'
import { handleReportCommand, handleReportDelete, handleSubmitReportModal } from './actions/report'

export enum ActionIDs {
ACCEPT = 'accept',
Expand All @@ -39,14 +40,16 @@ export enum ActionIDs {
CERT_APPROVE = 'cert_approve',
CERT_REJECT = 'cert_reject',
RUN_TASK = 'run_task',
SETUP_EVENT_LOG = 'setup_event_log'
SETUP_EVENT_LOG = 'setup_event_log',
DELETE_REPORT = 'delete_report'
}

export enum ViewIDs {
MODAL_REJECT = 'reject_modal',
MODAL_ACCEPT = 'accept_modal',
MODAL_LOG = 'time_submission',
MODAL_CERTIFY = 'certify_modal',
MODAL_REPORT = 'report_modal',
MODAL_DEPARTMENTS = 'departments_modal',
MODAL_ONBOARDING = 'onboarding_modal',
MODAL_EVENTLOG = 'eventlog_modal'
Expand All @@ -66,6 +69,7 @@ export function registerSlackHandlers(app: App) {
app.command(cmd_prefix + 'hours', handleShowHoursCommand)
app.command(cmd_prefix + 'certify', handleCertifyCommand)
app.command(cmd_prefix + 'departments', handleDepartmentsCommand)
app.command(cmd_prefix + 'report', handleReportCommand)
app.shortcut('log_hours', handleLogShortcut)

// Buttons
Expand All @@ -85,6 +89,7 @@ export function registerSlackHandlers(app: App) {
app.action(ActionIDs.OPEN_ONBOARDING_MODAL, handleOpenOnboardingModal)
app.action(ActionIDs.RUN_TASK, handleRunTask)
app.action(ActionIDs.SETUP_EVENT_LOG, handleOpenEventlogModal)
app.action(ActionIDs.DELETE_REPORT, handleReportDelete)
app.action('jump_url', async ({ ack }) => {
await ack()
})
Expand All @@ -97,6 +102,7 @@ export function registerSlackHandlers(app: App) {
app.view(ViewIDs.MODAL_DEPARTMENTS, handleSubmitDepartmentsModal)
app.view(ViewIDs.MODAL_ONBOARDING, handleSubmitOnboardingModal)
app.view(ViewIDs.MODAL_EVENTLOG, handleSubmitEventlogModal)
app.view(ViewIDs.MODAL_REPORT, handleSubmitReportModal)
// Events
app.event('app_home_opened', handleAppHomeOpened)
app.event('app_mention', handleAppMentioned)
Expand Down
5 changes: 3 additions & 2 deletions src/slack/lib/hours_submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export async function handleHoursRequest(slack_id: string, hours: number, activi
const entry = await prisma.hourLog.create({ data: request })

// Send request message to approvers
const message = getHourSubmissionMessage({ slack_id, activity, hours, request_id: entry.id.toString(), state: 'pending' })
const message = getHourSubmissionMessage({ slack_id, activity, hours, request_id: entry.id.toString(), state: 'pending', createdAt: new Date() })
const msg = await slack_client.chat.postMessage({ channel: config.slack.channels.approval, text: message.text, blocks: message.blocks })

await slack_client.chat.postMessage({
Expand Down Expand Up @@ -71,7 +71,8 @@ export async function handleHoursResponse({
request_id: request_id.toString(),
state: action == 'approve' ? 'approved' : 'rejected',
response,
type
type,
createdAt: log.createdAt
})
await slack_client.chat.update({
channel: config.slack.channels.approval,
Expand Down

0 comments on commit 7cb2d3d

Please sign in to comment.