Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
42 changes: 26 additions & 16 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ jobs:
secret-ids: |
SUPABASE, /cloudops/managed-secrets/cloud/supabase/api_key
AUTH0, /cloudops/managed-secrets/auth0/LFX_V2_PCC

AI, /cloudops/managed-secrets/ai/ai_config
- name: Setup Turborepo cache
uses: actions/cache@v4
with:
Expand All @@ -119,7 +119,10 @@ jobs:
if [ -z "$SUPABASE" ]; then
missing_secrets="$missing_secrets SUPABASE (from AWS Secrets Manager)"
fi

if [ -z "$AI" ]; then
missing_secrets="$missing_secrets AI (from AWS Secrets Manager)"
fi

# Check GitHub secrets (fallback)
if [ -z "${{ secrets.TEST_USERNAME }}" ]; then
missing_secrets="$missing_secrets TEST_USERNAME"
Expand Down Expand Up @@ -164,22 +167,38 @@ jobs:
echo "PCC_AUTH0_CLIENT_SECRET=$AUTH0_CLIENT_SECRET" >> $GITHUB_ENV
echo "✅ AUTH0 secrets set as masked environment variables"
fi

# Parse and set SUPABASE secrets
if [ -n "$SUPABASE" ]; then
SUPABASE_URL=$(echo "$SUPABASE" | jq -r '.url // empty')
SUPABASE_API_KEY=$(echo "$SUPABASE" | jq -r '.api_key // empty')

# Explicitly mask the values
echo "::add-mask::$SUPABASE_URL"
echo "::add-mask::$SUPABASE_API_KEY"

# Set as environment variables
echo "SUPABASE_URL=$SUPABASE_URL" >> $GITHUB_ENV
echo "POSTGRES_API_KEY=$SUPABASE_API_KEY" >> $GITHUB_ENV
echo "✅ SUPABASE secrets set as masked environment variables"
fi



# Parse and set AI secrets
if [ -n "$AI" ]; then
AI_API_KEY=$(echo "$AI" | jq -r '.api_key // empty')
AI_PROXY_URL=$(echo "$AI" | jq -r '.proxy_url // empty')

# Explicitly mask the values
echo "::add-mask::$AI_API_KEY"
echo "::add-mask::$AI_PROXY_URL"

# Set as environment variables
echo "AI_API_KEY=$AI_API_KEY" >> $GITHUB_ENV
echo "AI_PROXY_URL=$AI_PROXY_URL" >> $GITHUB_ENV
echo "✅ AI secrets set as masked environment variables"
fi

# Set test credentials
echo "::add-mask::${{ secrets.TEST_USERNAME }}"
echo "::add-mask::${{ secrets.TEST_PASSWORD }}"
Expand Down Expand Up @@ -239,6 +258,7 @@ jobs:
echo "AWS Secrets Manager (required):"
echo " - /cloudops/managed-secrets/auth0/LFX_V2_PCC (AUTH0 configuration)"
echo " - /cloudops/managed-secrets/cloud/supabase/api_key (SUPABASE configuration)"
echo " - /cloudops/managed-secrets/ai/ai_config (AI configuration)"
echo ""
echo "GitHub Secrets (required for authenticated tests):"
echo " - TEST_USERNAME"
Expand All @@ -263,16 +283,6 @@ jobs:
echo "results=failure" >> $GITHUB_OUTPUT
fi

- name: Upload Playwright report
id: upload-report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-${{ inputs.browser }}-${{ github.run_id }}
path: |
apps/lfx-pcc/playwright-report/
retention-days: 7

- name: Comment test results on PR
if: github.event_name == 'pull_request' && always()
uses: actions/github-script@v7
Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"iconfield",
"inputicon",
"Linkify",
"litellm",
"networkidle",
"nonexistentproject",
"PostgreSQL",
Expand Down
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ LFX PCC is a Turborepo monorepo containing an Angular 19 SSR application with ex
## Monorepo Structure

```text
lfx-pcc-v3/
lfx-v2-pcc-ui/
├── apps/
│ └── lfx-pcc/ # Angular 19 SSR application with zoneless change detection
│ ├── eslint.config.mjs # Angular-specific ESLint rules
Expand Down Expand Up @@ -96,6 +96,8 @@ lfx-pcc-v3/
- Logging uses Pino for structured JSON logs with sensitive data redaction
- Health checks are available at /health and are not logged or authenticated
- All shared types, interfaces, and constants are centralized in @lfx-pcc/shared package
- **AI Service Integration**: Claude Sonnet 4 model via LiteLLM proxy for meeting agenda generation
- **AI Environment Variables**: AI_PROXY_URL and AI_API_KEY required for AI functionality
- Use TypeScript interfaces instead of union types for better maintainability
- Shared package uses direct source imports during development for hot reloading
- **Interfaces go into the shared packages**
Expand Down
5 changes: 5 additions & 0 deletions apps/lfx-pcc/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ SUPABASE_STORAGE_BUCKET=your-supabase-bucket-name
# Internal k8s service DNS for NATS cluster
NATS_URL=nats://lfx-platform-nats.lfx.svc.cluster.local:4222

# AI Service Configuration
# OpenAI-compatible proxy for meeting agenda generation
AI_PROXY_URL=https://litellm.tools.lfx.dev/chat/completions
AI_API_KEY=your-litellm-ai-api-key

# E2E Test Configuration (Optional)
# Test user credentials for automated testing
TEST_USERNAME=your-test-username
Expand Down
10 changes: 5 additions & 5 deletions apps/lfx-pcc/e2e/project-dashboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,16 +191,16 @@ test.describe('Project Dashboard', () => {
});

test('should display all quick action items', async ({ page }) => {
await expect(page.getByRole('menuitem', { name: 'Schedule Meeting' })).toBeVisible();
await expect(page.getByRole('menuitem', { name: 'Create Meeting' })).toBeVisible();
await expect(page.getByRole('menuitem', { name: 'Create Committee' })).toBeVisible();
await expect(page.getByRole('menuitem', { name: 'View All Committees' })).toBeVisible();
await expect(page.getByRole('menuitem', { name: 'View Calendar' })).toBeVisible();
});

test('should have working links in quick actions', async ({ page }) => {
// Schedule Meeting should link to meetings page
const scheduleMeetingLink = page.getByRole('link', { name: 'Schedule Meeting' });
await expect(scheduleMeetingLink).toHaveAttribute('href', /\/meetings$/);
// Create Meeting should link to meetings page
const createMeetingLink = page.getByRole('link', { name: 'Create Meeting' });
await expect(createMeetingLink).toHaveAttribute('href', /\/meetings$/);

// View All Committees should link to committees page
const viewCommitteesLink = page.getByRole('link', { name: 'View All Committees' });
Expand All @@ -216,7 +216,7 @@ test.describe('Project Dashboard', () => {
const quickActionsSection = page.getByText('Quick Actions').locator('..');

// Check that menu items are present and interactive
await expect(quickActionsSection.getByRole('menuitem', { name: 'Schedule Meeting' })).toBeVisible();
await expect(quickActionsSection.getByRole('menuitem', { name: 'Create Meeting' })).toBeVisible();
await expect(quickActionsSection.getByRole('menuitem', { name: 'Create Committee' })).toBeVisible();
await expect(quickActionsSection.getByRole('menuitem', { name: 'View All Committees' })).toBeVisible();
await expect(quickActionsSection.getByRole('menuitem', { name: 'View Calendar' })).toBeVisible();
Expand Down
4 changes: 4 additions & 0 deletions apps/lfx-pcc/src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
}
}

.p-textarea {
field-sizing: content;
}

.pill {
@apply inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-full transition-colors text-gray-600 border border-gray-200 hover:bg-gray-50;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,11 @@ <h1 class="text-2xl font-display font-semibold text-gray-900 mb-3">

<!-- Project Description -->
@if (projectDescription(); as description) {
<p class="text-gray-600 text-sm mb-6 max-w-2xl leading-relaxed">
<p class="text-gray-600 text-sm mb-6 max-w-3xl leading-relaxed">
{{ description }}
</p>
}
</div>
<!-- Metrics Section -->
@if (metrics().length > 0) {
<div class="flex items-center gap-8 mb-8 flex-wrap">
@for (metric of metrics(); track metric.label) {
<div class="flex flex-col">
<div class="flex items-center gap-2 mb-1" data-testid="metric-label">
<i [class]="metric.icon" class="text-sm"></i>
<span class="text-sm text-gray-600">{{ metric.label }}</span>
</div>
<div class="text-2xl font-normal text-gray-900 font-display" data-testid="metric-value">{{ metric.value }}</div>
</div>
}
</div>
}
</div>

<!-- Menu Items Section -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,11 @@ <h3 class="text-sm font-display text-gray-500">Upcoming Meetings</h3>
<h3 class="text-lg font-medium text-gray-900 mb-2">No Upcoming Meetings</h3>
<p class="text-gray-600 mb-4 text-sm">There are no upcoming meetings scheduled.</p>
<lfx-button
label="Schedule Meeting"
label="Create Meeting"
icon="fa-light fa-calendar-plus"
severity="secondary"
size="small"
[routerLink]="['meetings']"
[routerLink]="['meetings/create']"
[relativeTo]="activatedRoute">
</lfx-button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -541,9 +541,9 @@ export class ProjectComponent {
private initializeQuickActionMenuItems(): MenuItem[] {
return [
{
label: 'Schedule Meeting',
label: 'Create Meeting',
icon: 'fa-light fa-calendar-plus text-sm',
routerLink: ['meetings'],
routerLink: ['meetings/create'],
},
{
label: 'Create Committee',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,7 @@ <h4 class="text-sm font-semibold text-gray-900 mb-1">AI Agenda Generator</h4>
control="agenda"
id="meeting-agenda"
placeholder="Enter meeting agenda and key discussion points"
[rows]="6"
[autoResize]="true"
styleClass="w-full"
styleClass="w-full min-h-32"
data-testid="meeting-details-agenda-textarea"></lfx-textarea>
<p class="mt-1 text-xs text-gray-500">A clear agenda helps participants prepare and keeps discussions focused</p>
@if (form().get('agenda')?.errors?.['required'] && form().get('agenda')?.touched) {
Expand Down
Loading
Loading