Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/app/components/dashboard/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@
font-weight: 600;
}

.integration-banner.slack-banner {
background: rgba(245, 232, 255, 0.9);
color: #6b21a8;
border-color: rgba(124, 58, 237, 0.22);
}

.filter-row {
display: flex;
flex-wrap: wrap;
Expand Down
8 changes: 5 additions & 3 deletions src/app/components/dashboard/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@

<div class="hero-copy">
<h1>Decision Workspace Dashboard</h1>
<p>Track active workspaces and GitHub work in one place with clean structure and minimal noise.</p>
<p>Track active workspaces, Slack conversations, and GitHub work in one place with clean structure and minimal noise.</p>
</div>
</section>

<p class="integration-banner slack-banner" *ngIf="slackBanner">{{ slackBanner }}</p>
<p class="integration-banner" *ngIf="githubBanner">{{ githubBanner }}</p>

<section class="content">
Expand Down Expand Up @@ -54,10 +55,11 @@ <h3>No workspaces yet</h3>
<div class="section-header">
<div>
<h2>Signals</h2>
<p class="section-copy">Assigned GitHub issues and pull requests appear here as action-ready signals.</p>
<p class="section-copy">Slack messages and GitHub updates appear here as action-ready signals.</p>
</div>
<div class="filter-row">
<button type="button" class="filter-pill" [class.active]="filters.source === 'all'" (click)="setSourceFilter('all')">All</button>
<button type="button" class="filter-pill" [class.active]="filters.source === 'slack'" (click)="setSourceFilter('slack')">Slack</button>
<button type="button" class="filter-pill" [class.active]="filters.source === 'github'" (click)="setSourceFilter('github')">GitHub</button>
<button type="button" class="filter-pill" [class.active]="filters.status === 'all'" (click)="setStatusFilter('all')">All statuses</button>
<button type="button" class="filter-pill" [class.active]="filters.status === 'unread'" (click)="setStatusFilter('unread')">Unread</button>
Expand All @@ -74,7 +76,7 @@ <h2>Signals</h2>
<ng-template #noSignals>
<div class="empty-state">
<h3>No matching signals</h3>
<p>Connect GitHub and run a sync to start pulling assigned issues and pull requests into Sentinent.</p>
<p>Connect Slack or GitHub in workspace integrations to start pulling messages and assigned work into Sentinent.</p>
</div>
</ng-template>
</section>
Expand Down
21 changes: 21 additions & 0 deletions src/app/components/dashboard/dashboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ describe('Dashboard', () => {

mockSignalService = {
getSignals: jasmine.createSpy('getSignals').and.returnValue(of([
{
id: 'slack-1',
sourceType: 'slack',
sourceId: 'C123456',
externalId: '1',
title: 'Test Slack Signal',
content: 'Slack signal content',
author: '@ops',
status: 'unread',
receivedAt: new Date(),
metadata: {
channel: 'general',
channelId: 'C123456'
}
},
{
id: 'github-1',
sourceType: 'github',
Expand Down Expand Up @@ -86,4 +101,10 @@ describe('Dashboard', () => {
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.textContent).toContain('Test GitHub Signal');
});

it('should render slack filter and signals', () => {
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.textContent).toContain('Slack');
expect(compiled.textContent).toContain('Test Slack Signal');
});
});
7 changes: 7 additions & 0 deletions src/app/components/dashboard/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class Dashboard implements OnInit {
signals: Signal[] = [];
filters: SignalFilters = { source: 'all', status: 'all' };
githubBanner = '';
slackBanner = '';

ngOnInit() {
this.workspaceService.getWorkspaces().subscribe(ws => {
Expand All @@ -34,11 +35,17 @@ export class Dashboard implements OnInit {

this.route.queryParamMap.subscribe(params => {
const githubStatus = params.get('github');
const slackStatus = params.get('slack');
this.githubBanner = githubStatus === 'connected'
? 'GitHub connected successfully. Review repository access in your workspace integrations.'
: githubStatus === 'error'
? 'GitHub connection failed. Please try the OAuth flow again.'
: '';
this.slackBanner = slackStatus === 'connected'
? 'Slack connected successfully. Choose the channels you want Sentinent to monitor.'
: slackStatus === 'error'
? 'Slack connection failed. Please try the OAuth flow again.'
: '';
});

this.loadSignals();
Expand Down
8 changes: 8 additions & 0 deletions src/app/components/signal-board/signal-board.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
font-weight: 700;
}

.signal-source.slack {
background: #7c3aed;
}

.signal-source.github {
background: #111827;
}

.signal-card h3 {
margin: 0;
font-size: 1.05rem;
Expand Down
11 changes: 5 additions & 6 deletions src/app/components/signal-board/signal-board.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@
<article class="signal-card" *ngFor="let signal of signals; trackBy: trackBySignal">
<div class="signal-top">
<div>
<span class="signal-source">GitHub</span>
<span class="signal-source" [ngClass]="getSourceClass(signal)">{{ getSourceLabel(signal) }}</span>
<h3>{{ signal.title }}</h3>
</div>
<span class="signal-status" [class.unread]="signal.status === 'unread'">
{{ signal.status }}
</span>
</div>

<p class="signal-meta">
{{ getTypeLabel(signal) }} #{{ signal.metadata.number }} in {{ signal.metadata.repository }}
</p>
<p class="signal-meta">{{ getPrimaryContext(signal) }}</p>
<p class="signal-body">{{ signal.content }}</p>

<div class="signal-badges">
<span class="badge neutral">{{ signal.metadata.state }}</span>
<span class="badge neutral" *ngIf="signal.sourceType === 'github'">{{ signal.metadata.state }}</span>
<span class="badge neutral" *ngIf="signal.sourceType === 'slack'">#{{ getSlackChannel(signal) }}</span>
<span class="badge" *ngFor="let label of signal.metadata.labels">{{ label }}</span>
</div>

Expand All @@ -28,7 +27,7 @@ <h3>{{ signal.title }}</h3>
</div>

<div class="signal-actions">
<a class="link-btn" *ngIf="signal.url" [href]="signal.url" target="_blank" rel="noreferrer">Open in GitHub</a>
<a class="link-btn" *ngIf="signal.url" [href]="signal.url" target="_blank" rel="noreferrer">{{ getOpenLabel(signal) }}</a>
<button type="button" class="ghost-btn" (click)="markAsRead.emit(signal.id)">Mark as read</button>
<button type="button" class="ghost-btn" (click)="archive.emit(signal.id)">Archive</button>
</div>
Expand Down
28 changes: 28 additions & 0 deletions src/app/components/signal-board/signal-board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,34 @@ export class SignalBoardComponent {
}

getTypeLabel(signal: Signal): string {
if (signal.sourceType === 'slack') {
return 'Slack Message';
}

return signal.metadata.type === 'pull_request' ? 'Pull Request' : 'Issue';
}

getSourceLabel(signal: Signal): string {
return signal.sourceType === 'slack' ? 'Slack' : 'GitHub';
}

getSourceClass(signal: Signal): string {
return signal.sourceType === 'slack' ? 'slack' : 'github';
}

getPrimaryContext(signal: Signal): string {
if (signal.sourceType === 'slack') {
return `#${this.getSlackChannel(signal)} in Slack`;
}

return `${this.getTypeLabel(signal)} #${signal.metadata.number} in ${signal.metadata.repository}`;
}

getOpenLabel(signal: Signal): string {
return signal.sourceType === 'slack' ? 'Open in Slack' : 'Open in GitHub';
}

getSlackChannel(signal: Signal): string {
return String(signal.metadata['channel'] ?? 'channel');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@
font-weight: 700;
}

.provider-badge.slack-badge {
background: #fce7f3;
color: #9d174d;
}

.card-actions,
.section-head {
display: flex;
Expand All @@ -100,6 +105,11 @@
color: #fff;
}

.slack-btn {
border-color: #7c3aed;
background: #7c3aed;
}

.secondary-btn {
border: 1px solid #cbd5e1;
background: #fff;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,73 @@
<header class="integrations-header">
<div>
<p class="eyebrow">Workspace Integration</p>
<h2>GitHub Activity Feed</h2>
<h2>Workspace Integrations</h2>
<p class="intro">
Connect GitHub to pull assigned issues and pull requests into Sentinent without leaving your workspace flow.
Connect Slack and GitHub so Sentinent can pull team messages and assigned development work into one dashboard.
</p>
</div>
<a class="back-link" [routerLink]="['/workspaces', workspaceId, 'decisions']">Back to workspace</a>
</header>

<article class="integration-card">
<div class="card-copy">
<span class="provider-badge slack-badge">Slack</span>
<h3>Slack connection</h3>
<p *ngIf="isSlackConnected; else slackDisconnectedState">
Connected to <strong>{{ slackWorkspaceName }}</strong>
<span *ngIf="slackWorkspaceUrl">({{ slackWorkspaceUrl }})</span>.
</p>
<ng-template #slackDisconnectedState>
<p>Not connected yet. Start the OAuth flow to import Slack messages into Sentinent.</p>
</ng-template>
</div>

<div class="card-actions">
<button *ngIf="!isSlackConnected" type="button" class="primary-btn slack-btn" (click)="connectSlack()">Connect Slack</button>
<button *ngIf="isSlackConnected" type="button" class="secondary-btn" (click)="disconnectSlack()">Disconnect</button>
</div>
</article>

<p class="feedback success" *ngIf="slackFeedbackMessage">{{ slackFeedbackMessage }}</p>
<p class="feedback error" *ngIf="slackErrorMessage">{{ slackErrorMessage }}</p>

<section class="selection-card" *ngIf="isSlackConnected">
<div class="section-head">
<div>
<h3>Channel selection</h3>
<p>Choose which Slack channels should appear as signals in the dashboard.</p>
</div>
<button type="button" class="primary-btn slack-btn" [disabled]="isSlackSaving" (click)="saveSlackChannelSelection()">
{{ isSlackSaving ? 'Saving...' : 'Save Channels' }}
</button>
</div>

<div class="repo-list">
<label class="repo-item" *ngFor="let channel of slackChannels">
<input
type="checkbox"
[checked]="isSlackChannelSelected(channel.id)"
(change)="onSlackChannelToggle(channel.id, $event)"
/>
<span>
<strong>#{{ channel.name }}</strong>
<small>{{ channel.id }}</small>
</span>
</label>
</div>
</section>

<section class="sync-card" *ngIf="isSlackConnected">
<h3>Slack sync status</h3>
<p *ngIf="slackLastSyncAt">Last channel update: {{ slackLastSyncAt | date: 'medium' }}</p>
<p *ngIf="!slackLastSyncAt">No Slack sync has run yet.</p>
</section>

<article class="integration-card">
<div class="card-copy">
<span class="provider-badge">GitHub</span>
<h3>Connection status</h3>
<p *ngIf="isConnected; else disconnectedState">
<h3>GitHub connection</h3>
<p *ngIf="isGitHubConnected; else disconnectedState">
Connected as <strong>{{ accountName }}</strong>
<span *ngIf="accountHandle">({{ accountHandle }})</span>.
</p>
Expand All @@ -24,25 +78,25 @@ <h3>Connection status</h3>
</div>

<div class="card-actions">
<button *ngIf="!isConnected" type="button" class="primary-btn" (click)="connectGitHub()">Connect GitHub</button>
<button *ngIf="isConnected" type="button" class="secondary-btn" (click)="disconnectGitHub()">Disconnect</button>
<button *ngIf="isConnected" type="button" class="primary-btn" [disabled]="isSyncing" (click)="syncNow()">
<button *ngIf="!isGitHubConnected" type="button" class="primary-btn" (click)="connectGitHub()">Connect GitHub</button>
<button *ngIf="isGitHubConnected" type="button" class="secondary-btn" (click)="disconnectGitHub()">Disconnect</button>
<button *ngIf="isGitHubConnected" type="button" class="primary-btn" [disabled]="isSyncing" (click)="syncNow()">
{{ isSyncing ? 'Syncing...' : 'Sync Now' }}
</button>
</div>
</article>

<p class="feedback success" *ngIf="feedbackMessage">{{ feedbackMessage }}</p>
<p class="feedback error" *ngIf="errorMessage">{{ errorMessage }}</p>
<p class="feedback success" *ngIf="githubFeedbackMessage">{{ githubFeedbackMessage }}</p>
<p class="feedback error" *ngIf="githubErrorMessage">{{ githubErrorMessage }}</p>

<section class="selection-card" *ngIf="isConnected">
<section class="selection-card" *ngIf="isGitHubConnected">
<div class="section-head">
<div>
<h3>Repository selection</h3>
<p>Choose which repositories should contribute GitHub signals to the dashboard.</p>
</div>
<button type="button" class="primary-btn" [disabled]="isSaving" (click)="saveRepoSelection()">
{{ isSaving ? 'Saving...' : 'Save Selection' }}
<button type="button" class="primary-btn" [disabled]="isGitHubSaving" (click)="saveRepoSelection()">
{{ isGitHubSaving ? 'Saving...' : 'Save Selection' }}
</button>
</div>

Expand All @@ -61,14 +115,14 @@ <h3>Repository selection</h3>
</div>
</section>

<section class="sync-card" *ngIf="isConnected">
<h3>Sync status</h3>
<p *ngIf="lastSyncAt">Last sync: {{ lastSyncAt | date: 'medium' }}</p>
<p *ngIf="!lastSyncAt">No sync has run yet.</p>
<p *ngIf="syncStatus">
<section class="sync-card" *ngIf="isGitHubConnected">
<h3>GitHub sync status</h3>
<p *ngIf="githubLastSyncAt">Last sync: {{ githubLastSyncAt | date: 'medium' }}</p>
<p *ngIf="!githubLastSyncAt">No sync has run yet.</p>
<p *ngIf="githubSyncStatus">
Current status:
<strong>{{ syncStatus.status }}</strong>
<span *ngIf="syncStatus.itemsSynced">({{ syncStatus.itemsSynced }} items)</span>
<strong>{{ githubSyncStatus.status }}</strong>
<span *ngIf="githubSyncStatus.itemsSynced">({{ githubSyncStatus.itemsSynced }} items)</span>
</p>
</section>
</section>
Loading
Loading