feat: Morning NOC Summary adaptive card for Teams
- Add MorningSummaryService with Zabbix aggregation and adaptive card builder - Add webhook delivery system with Teams incoming webhooks - Add admin UI at /admin/morning-summary for webhook/config management - Add API routes: /send, /test, /webhooks, /webhooks/[id], /config, /history - Register morning-summary cron job in SyncScheduler (Mon-Fri 6:30 AM) - Add outages_only filter (Unavailable triggers only) - Fix host resolution: use getTriggerEnabledHosts to exclude disabled hosts - Fix resolved events: event.get value:1 scoped to window with r_eventid filter - Remove emojis from fact rows and section headers in card - Remove Open Zabbix button (duplicate of View Problems) - Add migrations: morning_summary_config + morning_summaries tables - Add outages_only column to morning_summary_config
This commit is contained in:
parent
19605f82aa
commit
c518eefdb2
61 changed files with 11236 additions and 237 deletions
36
migrations/040_create_contract_services_table.sql
Normal file
36
migrations/040_create_contract_services_table.sql
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
CREATE TABLE IF NOT EXISTS contract_services (
|
||||
id BIGINT PRIMARY KEY,
|
||||
contract_id BIGINT NOT NULL,
|
||||
company_id BIGINT,
|
||||
service_id BIGINT,
|
||||
service_name TEXT,
|
||||
description TEXT,
|
||||
period_type INTEGER,
|
||||
unit_price NUMERIC(15,2),
|
||||
unit_cost NUMERIC(15,2),
|
||||
discount_percent NUMERIC(5,2),
|
||||
adjusted_price NUMERIC(15,2),
|
||||
quantity NUMERIC(10,2),
|
||||
invoice_description TEXT,
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
internal_currency_price NUMERIC(15,2),
|
||||
create_date TIMESTAMPTZ,
|
||||
last_modified_date TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_contract_services_contract_id ON contract_services(contract_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_contract_services_company_id ON contract_services(company_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_contract_services_service_name ON contract_services(service_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_contract_services_is_deleted ON contract_services(is_deleted);
|
||||
|
||||
ALTER TABLE contract_services
|
||||
DROP CONSTRAINT IF EXISTS fk_contract_services_contract;
|
||||
ALTER TABLE contract_services
|
||||
ADD CONSTRAINT fk_contract_services_contract
|
||||
FOREIGN KEY (contract_id) REFERENCES contracts(id) ON DELETE CASCADE;
|
||||
37
migrations/041_create_engagement_tables.sql
Normal file
37
migrations/041_create_engagement_tables.sql
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
-- Migration 041: Create engagement tables for Microsoft Graph activity data
|
||||
|
||||
CREATE TABLE IF NOT EXISTS graph_users (
|
||||
id VARCHAR(255) PRIMARY KEY, -- Azure AD object ID
|
||||
display_name VARCHAR(255),
|
||||
email VARCHAR(255) UNIQUE, -- matches resources.email
|
||||
job_title VARCHAR(255),
|
||||
department VARCHAR(255),
|
||||
account_enabled BOOLEAN DEFAULT true,
|
||||
last_activity_date DATE,
|
||||
synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS engagement_snapshots (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_email VARCHAR(255) NOT NULL,
|
||||
period_type VARCHAR(10) NOT NULL, -- 'D7', 'D30', 'D90'
|
||||
period_end DATE NOT NULL,
|
||||
-- Teams
|
||||
teams_chat_messages INT DEFAULT 0,
|
||||
teams_private_messages INT DEFAULT 0,
|
||||
teams_calls INT DEFAULT 0,
|
||||
teams_meetings_attended INT DEFAULT 0,
|
||||
teams_meetings_organized INT DEFAULT 0,
|
||||
-- Email
|
||||
emails_sent INT DEFAULT 0,
|
||||
emails_received INT DEFAULT 0,
|
||||
emails_read INT DEFAULT 0,
|
||||
-- Last activity
|
||||
last_activity_date DATE,
|
||||
synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_email, period_type, period_end)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_engagement_snapshots_email ON engagement_snapshots(user_email);
|
||||
CREATE INDEX IF NOT EXISTS idx_engagement_snapshots_period ON engagement_snapshots(period_type, period_end);
|
||||
CREATE INDEX IF NOT EXISTS idx_graph_users_email ON graph_users(email);
|
||||
6
migrations/042_add_engagement_calendar_columns.sql
Normal file
6
migrations/042_add_engagement_calendar_columns.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
-- Migration 042: Add meeting time and external meeting columns to engagement_snapshots
|
||||
|
||||
ALTER TABLE engagement_snapshots
|
||||
ADD COLUMN IF NOT EXISTS audio_duration_seconds INT DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS meeting_duration_seconds INT DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS meetings_with_external INT DEFAULT 0;
|
||||
5
migrations/043_add_hours_to_bill_to_time_entries.sql
Normal file
5
migrations/043_add_hours_to_bill_to_time_entries.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
-- Add hours_to_bill column to time_entries
|
||||
-- Autotask's read-only hoursToBill field: actual hours that will be billed,
|
||||
-- which may differ from hours_worked due to contract caps or billing rules.
|
||||
ALTER TABLE time_entries
|
||||
ADD COLUMN IF NOT EXISTS hours_to_bill DECIMAL(10, 2);
|
||||
4
migrations/044_add_contract_id_to_time_entries.sql
Normal file
4
migrations/044_add_contract_id_to_time_entries.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
-- Add contract_id column to time_entries
|
||||
-- Autotask contractID field on time entries
|
||||
ALTER TABLE time_entries
|
||||
ADD COLUMN IF NOT EXISTS contract_id BIGINT;
|
||||
58
migrations/045_create_zoom_tables.sql
Normal file
58
migrations/045_create_zoom_tables.sql
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
-- Zoom Phone and Meetings Integration Tables
|
||||
-- Supports cross-referencing with Autotask contacts/companies
|
||||
|
||||
CREATE TABLE IF NOT EXISTS zoom_users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
zoom_id VARCHAR(255) UNIQUE NOT NULL,
|
||||
email VARCHAR(255) UNIQUE,
|
||||
display_name VARCHAR(255),
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
synced_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS zoom_calls (
|
||||
id SERIAL PRIMARY KEY,
|
||||
zoom_call_id VARCHAR(255) UNIQUE NOT NULL,
|
||||
resource_email VARCHAR(255),
|
||||
direction VARCHAR(20),
|
||||
call_status VARCHAR(20),
|
||||
other_party_number VARCHAR(50),
|
||||
other_party_name VARCHAR(255),
|
||||
start_time TIMESTAMPTZ,
|
||||
duration_seconds INTEGER,
|
||||
matched_contact_id BIGINT REFERENCES contacts(id),
|
||||
matched_company_id BIGINT REFERENCES companies(id),
|
||||
synced_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS zoom_meetings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
zoom_meeting_id VARCHAR(255) UNIQUE NOT NULL,
|
||||
zoom_meeting_uuid VARCHAR(500),
|
||||
host_email VARCHAR(255),
|
||||
topic VARCHAR(500),
|
||||
start_time TIMESTAMPTZ,
|
||||
end_time TIMESTAMPTZ,
|
||||
duration_minutes INTEGER,
|
||||
participant_count INTEGER DEFAULT 0,
|
||||
client_participant_count INTEGER DEFAULT 0,
|
||||
has_client_attendees BOOLEAN DEFAULT false,
|
||||
synced_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS zoom_meeting_participants (
|
||||
id SERIAL PRIMARY KEY,
|
||||
meeting_id INTEGER NOT NULL REFERENCES zoom_meetings(id) ON DELETE CASCADE,
|
||||
participant_email VARCHAR(255),
|
||||
participant_name VARCHAR(255),
|
||||
duration_seconds INTEGER,
|
||||
matched_contact_id BIGINT REFERENCES contacts(id),
|
||||
matched_company_id BIGINT REFERENCES companies(id),
|
||||
is_internal BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_zoom_calls_resource_email_time ON zoom_calls(resource_email, start_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_zoom_calls_matched_company ON zoom_calls(matched_company_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_zoom_meetings_host_email_time ON zoom_meetings(host_email, start_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_zoom_meeting_participants_email ON zoom_meeting_participants(participant_email);
|
||||
28
migrations/046_create_teams_meetings_table.sql
Normal file
28
migrations/046_create_teams_meetings_table.sql
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
CREATE TABLE IF NOT EXISTS teams_meetings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
graph_event_id VARCHAR(500) NOT NULL,
|
||||
user_email VARCHAR(255) NOT NULL,
|
||||
subject VARCHAR(500),
|
||||
start_time TIMESTAMPTZ,
|
||||
end_time TIMESTAMPTZ,
|
||||
duration_minutes INTEGER,
|
||||
is_online_meeting BOOLEAN DEFAULT false,
|
||||
attendee_count INTEGER DEFAULT 0,
|
||||
client_attendee_count INTEGER DEFAULT 0,
|
||||
has_client_attendees BOOLEAN DEFAULT false,
|
||||
synced_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(user_email, graph_event_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS teams_meeting_attendees (
|
||||
id SERIAL PRIMARY KEY,
|
||||
meeting_id INTEGER NOT NULL REFERENCES teams_meetings(id) ON DELETE CASCADE,
|
||||
attendee_email VARCHAR(255),
|
||||
attendee_name VARCHAR(255),
|
||||
matched_contact_id BIGINT REFERENCES contacts(id),
|
||||
matched_company_id BIGINT REFERENCES companies(id),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_teams_meetings_user_email_time ON teams_meetings(user_email, start_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_teams_meeting_attendees_meeting_id ON teams_meeting_attendees(meeting_id);
|
||||
6
migrations/047_add_after_hours_messages.sql
Normal file
6
migrations/047_add_after_hours_messages.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
-- Migration 047: Add after-hours message tracking to engagement snapshots
|
||||
-- Counts Teams messages sent by the employee between 5:30 PM and 7:00 AM UTC.
|
||||
-- Populated during sync if the Azure AD app has the Chat.Read.All permission.
|
||||
|
||||
ALTER TABLE engagement_snapshots
|
||||
ADD COLUMN IF NOT EXISTS after_hours_messages INT DEFAULT 0;
|
||||
45
migrations/048_create_morning_summary_tables.sql
Normal file
45
migrations/048_create_morning_summary_tables.sql
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
-- Morning Summary: webhooks, config, and run history
|
||||
|
||||
CREATE TABLE IF NOT EXISTS morning_summary_webhooks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
label TEXT NOT NULL,
|
||||
webhook_url TEXT NOT NULL,
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
last_delivered_at TIMESTAMPTZ,
|
||||
last_status TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS morning_summary_config (
|
||||
id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1),
|
||||
weekend_suppression BOOLEAN NOT NULL DEFAULT true,
|
||||
monday_extended_window BOOLEAN NOT NULL DEFAULT true,
|
||||
severity_filter INTEGER NOT NULL DEFAULT 2,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
INSERT INTO morning_summary_config (id, weekend_suppression, monday_extended_window, severity_filter)
|
||||
VALUES (1, true, true, 2)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS morning_summaries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
generated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
window_from TIMESTAMPTZ NOT NULL,
|
||||
window_to TIMESTAMPTZ NOT NULL,
|
||||
open_count INTEGER NOT NULL DEFAULT 0,
|
||||
resolved_count INTEGER NOT NULL DEFAULT 0,
|
||||
mttr_minutes INTEGER,
|
||||
clients_affected TEXT[] NOT NULL DEFAULT '{}',
|
||||
is_weekend_window BOOLEAN NOT NULL DEFAULT false,
|
||||
card_payload JSONB,
|
||||
delivery_status JSONB NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_morning_summaries_generated_at ON morning_summaries(generated_at DESC);
|
||||
|
||||
INSERT INTO morning_summary_webhooks (label, webhook_url, enabled) VALUES
|
||||
('Technical / On-Call', 'https://appriver3651004158.webhook.office.com/webhookb2/43394fff-d546-440f-ae34-eed04c5bcd5d@1c260a52-b84d-4b7a-a179-44971dbc3949/IncomingWebhook/fe9e1350791f4923bdbd56c076eae6ed/ee54b2ef-4f27-4188-b865-47fda0eae4d1/V2y29hRVISyBYOdDq6dPs-ekvlhqCF6XGE3yUUFxt1dKQ1', true),
|
||||
('Technical / Outages-Issues','https://appriver3651004158.webhook.office.com/webhookb2/43394fff-d546-440f-ae34-eed04c5bcd5d@1c260a52-b84d-4b7a-a179-44971dbc3949/IncomingWebhook/b7cf42691520467986db5a993dcd07b6/ee54b2ef-4f27-4188-b865-47fda0eae4d1/V2kdADKBhv3h3CfOfeIbe3O6HI5hIh4BRVX5UMqIxhLE81', false),
|
||||
('Finance (Testing)', 'https://appriver3651004158.webhook.office.com/webhookb2/033405b3-b02e-4ce5-a200-4a3b9f387c92@1c260a52-b84d-4b7a-a179-44971dbc3949/IncomingWebhook/5fe8882c1c0342a7bbe91d763abbca57/ee54b2ef-4f27-4188-b865-47fda0eae4d1/V2rSVLy80XTdPJlBJVzUBYrMyrTi_Tmts46dA8BZjlPaw1', false)
|
||||
ON CONFLICT DO NOTHING;
|
||||
Loading…
Add table
Add a link
Reference in a new issue