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
30 changes: 26 additions & 4 deletions .github/workflows/docker-build-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ on:

permissions:
contents: read
id-token: write
packages: write

env:
REGISTRY: ghcr.io
Expand All @@ -19,15 +21,33 @@ env:
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: OIDC Auth
uses: aws-actions/configure-aws-credentials@v4
with:
audience: sts.amazonaws.com
role-to-assume: arn:aws:iam::788942260905:role/github-actions-deploy
aws-region: us-west-2

- name: Get LaunchDarkly client ID from AWS Secrets Manager
uses: aws-actions/aws-secretsmanager-get-secrets@v2
with:
secret-ids: |
LAUNCHDARKLY, /cloudops/managed-secrets/launchdarkly/lfx_one/ld_client_id

- name: Set LaunchDarkly client ID environment variable
run: |
LAUNCHDARKLY_CLIENT_ID=$(echo "$LAUNCHDARKLY" | jq -r '.ld_client_id // empty')
if [ -z "$LAUNCHDARKLY_CLIENT_ID" ]; then
echo "❌ LaunchDarkly client ID not found in AWS Secrets Manager"
exit 1
fi
echo "LAUNCHDARKLY_CLIENT_ID=$LAUNCHDARKLY_CLIENT_ID" >> $GITHUB_ENV

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

Expand Down Expand Up @@ -68,3 +88,5 @@ jobs:
cache-to: type=gha,mode=max
build-args: |
BUILD_ENV=development
secret-envs: |
LAUNCHDARKLY_CLIENT_ID=LAUNCHDARKLY_CLIENT_ID
26 changes: 26 additions & 0 deletions .github/workflows/docker-build-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ permissions:
packages: write
pull-requests: write
issues: write
id-token: write

env:
REGISTRY: ghcr.io
Expand All @@ -27,10 +28,33 @@ jobs:
github.event.pull_request.head.repo.fork == false &&
contains(github.event.pull_request.labels.*.name, 'deploy-preview')
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: OIDC Auth
uses: aws-actions/configure-aws-credentials@v4
with:
audience: sts.amazonaws.com
role-to-assume: arn:aws:iam::788942260905:role/github-actions-deploy
aws-region: us-west-2

- name: Get LaunchDarkly client ID from AWS Secrets Manager
uses: aws-actions/aws-secretsmanager-get-secrets@v2
with:
secret-ids: |
LAUNCHDARKLY, /cloudops/managed-secrets/launchdarkly/lfx_one/ld_client_id

- name: Set LaunchDarkly client ID environment variable
run: |
LAUNCHDARKLY_CLIENT_ID=$(echo "$LAUNCHDARKLY" | jq -r '.ld_client_id // empty')
if [ -z "$LAUNCHDARKLY_CLIENT_ID" ]; then
echo "❌ LaunchDarkly client ID not found in AWS Secrets Manager"
exit 1
fi
echo "LAUNCHDARKLY_CLIENT_ID=$LAUNCHDARKLY_CLIENT_ID" >> $GITHUB_ENV

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

Expand Down Expand Up @@ -72,6 +96,8 @@ jobs:
cache-to: type=gha,mode=max
build-args: |
BUILD_ENV=development
secret-envs: |
LAUNCHDARKLY_CLIENT_ID=LAUNCHDARKLY_CLIENT_ID

- name: Comment deployment URL on PR
uses: actions/github-script@v7
Expand Down
30 changes: 26 additions & 4 deletions .github/workflows/docker-build-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:

permissions:
contents: read
id-token: write
packages: write

env:
REGISTRY: ghcr.io
Expand All @@ -20,10 +22,6 @@ env:
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
outputs:
app_version: ${{ steps.prepare.outputs.app_version }}
chart_name: ${{ steps.prepare.outputs.chart_name }}
Expand All @@ -33,6 +31,28 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4

- name: OIDC Auth
uses: aws-actions/configure-aws-credentials@v4
with:
audience: sts.amazonaws.com
role-to-assume: arn:aws:iam::788942260905:role/github-actions-deploy
aws-region: us-west-2

- name: Get LaunchDarkly client ID from AWS Secrets Manager
uses: aws-actions/aws-secretsmanager-get-secrets@v2
with:
secret-ids: |
LAUNCHDARKLY, /cloudops/managed-secrets/launchdarkly/lfx_one/ld_client_id

- name: Set LaunchDarkly client ID environment variable
run: |
LAUNCHDARKLY_CLIENT_ID=$(echo "$LAUNCHDARKLY" | jq -r '.ld_client_id // empty')
if [ -z "$LAUNCHDARKLY_CLIENT_ID" ]; then
echo "❌ LaunchDarkly client ID not found in AWS Secrets Manager"
exit 1
fi
echo "LAUNCHDARKLY_CLIENT_ID=$LAUNCHDARKLY_CLIENT_ID" >> $GITHUB_ENV

- name: Prepare versions and chart name
id: prepare
run: |
Expand Down Expand Up @@ -86,6 +106,8 @@ jobs:
cache-to: type=gha,mode=max
build-args: |
BUILD_ENV=production
secret-envs: |
LAUNCHDARKLY_CLIENT_ID=LAUNCHDARKLY_CLIENT_ID

release-helm-chart:
needs: build-and-push
Expand Down
9 changes: 7 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@ RUN yarn install --immutable
# NOW copy source code (changes here won't invalidate the dependency layer)
COPY . .

# Build the application with specified environment
RUN yarn build:${BUILD_ENV}
# Build shared package first (doesn't need --define flag)
RUN yarn workspace @lfx-one/shared build:${BUILD_ENV}

# Build the Angular application with LaunchDarkly client ID from secret
RUN --mount=type=secret,id=LAUNCHDARKLY_CLIENT_ID \
LAUNCHDARKLY_CLIENT_ID=$(cat /run/secrets/LAUNCHDARKLY_CLIENT_ID) && \
yarn workspace lfx-one-ui build:${BUILD_ENV} --define LAUNCHDARKLY_CLIENT_ID="'${LAUNCHDARKLY_CLIENT_ID}'"

# Expose port 4000
EXPOSE 4000
Expand Down
6 changes: 6 additions & 0 deletions apps/lfx-one/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ SNOWFLAKE_ROLE=your-read-role
AI_PROXY_URL=https://litellm.tools.lfx.dev/chat/completions
AI_API_KEY=your-litellm-ai-api-key

# LaunchDarkly Feature Flags Configuration
# Client ID for LaunchDarkly feature flag service
# This is injected at Docker build time via --build-arg
# For local development, set this to your LaunchDarkly client-side ID
LD_CLIENT_ID=your-launchdarkly-client-id

# E2E Test Configuration (Optional)
# Test user credentials for automated testing
TEST_USERNAME=your-test-username
Expand Down
25 changes: 20 additions & 5 deletions apps/lfx-one/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@
"entry": "src/server/server.ts"
},
"allowedCommonJsDependencies": ["@linuxfoundation/lfx-ui-core"],
"externalDependencies": ["snowflake-sdk"]
"externalDependencies": ["snowflake-sdk"],
"define": {
"LAUNCHDARKLY_CLIENT_ID": "''"
}
},
"configurations": {
"production": {
Expand All @@ -75,7 +78,10 @@
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
],
"define": {
"LAUNCHDARKLY_CLIENT_ID": "''"
}
},
"staging": {
"budgets": [
Expand All @@ -96,7 +102,10 @@
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.staging.ts"
}
]
],
"define": {
"LAUNCHDARKLY_CLIENT_ID": "''"
}
},
"development": {
"optimization": false,
Expand All @@ -107,12 +116,18 @@
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
]
],
"define": {
"LAUNCHDARKLY_CLIENT_ID": "''"
}
},
"local": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
"sourceMap": true,
"define": {
"LAUNCHDARKLY_CLIENT_ID": "''"
}
}
},
"defaultConfiguration": "production"
Expand Down
4 changes: 4 additions & 0 deletions apps/lfx-one/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,15 @@
"@fullcalendar/timegrid": "^6.1.19",
"@lfx-one/shared": "workspace:*",
"@linuxfoundation/lfx-ui-core": "^0.0.20",
"@openfeature/core": "^1.9.1",
"@openfeature/launchdarkly-client-provider": "^0.3.3",
"@openfeature/web-sdk": "^1.7.1",
"@primeng/themes": "^19.1.4",
"compression": "^1.8.1",
"dotenv": "^17.2.1",
"express": "^4.18.2",
"express-openid-connect": "^2.19.2",
"launchdarkly-js-client-sdk": "^3.9.0",
"nats": "^2.29.3",
"ngx-cookie-service-ssr": "^19.1.2",
"pino-http": "^10.5.0",
Expand Down
7 changes: 7 additions & 0 deletions apps/lfx-one/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { RouterOutlet } from '@angular/router';
import { AuthContext } from '@lfx-one/shared/interfaces';
import { ToastModule } from 'primeng/toast';

import { FeatureFlagService } from './shared/services/feature-flag.service';
import { SegmentService } from './shared/services/segment.service';
import { UserService } from './shared/services/user.service';

Expand All @@ -19,6 +20,7 @@ import { UserService } from './shared/services/user.service';
export class AppComponent {
private readonly userService = inject(UserService);
private readonly segmentService = inject(SegmentService);
private readonly featureFlagService = inject(FeatureFlagService);

public auth: AuthContext | undefined;
public transferState = inject(TransferState);
Expand Down Expand Up @@ -52,6 +54,11 @@ export class AppComponent {

// Identify user with Segment tracking (pass entire Auth0 user object)
this.segmentService.identifyUser(this.auth.user);

// Initialize feature flags with user context
this.featureFlagService.initialize(this.auth.user).catch((error) => {
console.error('Failed to initialize feature flags:', error);
});
}
}
}
2 changes: 2 additions & 0 deletions apps/lfx-one/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { providePrimeNG } from 'primeng/config';
import { DialogService } from 'primeng/dynamicdialog';

import { routes } from './app.routes';
import { provideFeatureFlags } from './shared/providers/feature-flag.provider';
import { CustomPreloadingStrategy } from './shared/strategies/custom-preloading.strategy';

const customPreset = definePreset(Aura, {
Expand Down Expand Up @@ -45,6 +46,7 @@ export const appConfig: ApplicationConfig = {
},
},
}),
provideFeatureFlags(),
ConfirmationService,
DialogService,
MessageService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="flex min-h-screen">
<!-- Sidebar - Desktop -->
<div class="hidden lg:block w-64 flex-shrink-0 fixed top-0 left-0">
<lfx-sidebar [items]="sidebarItems" [footerItems]="sidebarFooterItems" [showProjectSelector]="true"></lfx-sidebar>
<lfx-sidebar [items]="sidebarItems()" [footerItems]="sidebarFooterItems" [showProjectSelector]="true"></lfx-sidebar>
</div>

<!-- Sidebar - Mobile Overlay -->
Expand All @@ -23,7 +23,7 @@ <h2 class="text-lg font-semibold text-gray-900">Menu</h2>
</button>
</div>
<div class="overflow-y-auto h-[calc(100vh-4rem)]">
<lfx-sidebar [items]="sidebarItems" [footerItems]="sidebarFooterItems" [showProjectSelector]="true"></lfx-sidebar>
<lfx-sidebar [items]="sidebarItems()" [footerItems]="sidebarFooterItems" [showProjectSelector]="true"></lfx-sidebar>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
// SPDX-License-Identifier: MIT

import { CommonModule } from '@angular/common';
import { Component, CUSTOM_ELEMENTS_SCHEMA, inject } from '@angular/core';
import { Component, computed, CUSTOM_ELEMENTS_SCHEMA, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { NavigationEnd, Router, RouterModule } from '@angular/router';
import { AppService } from '@app/shared/services/app.service';
import { FeatureFlagService } from '@app/shared/services/feature-flag.service';
import { SidebarComponent } from '@components/sidebar/sidebar.component';
import { SidebarMenuItem } from '@lfx-one/shared/interfaces';
import { filter } from 'rxjs';
Expand All @@ -21,12 +22,16 @@ import { filter } from 'rxjs';
export class MainLayoutComponent {
private readonly router = inject(Router);
private readonly appService = inject(AppService);
private readonly featureFlagService = inject(FeatureFlagService);

// Expose mobile sidebar state from service
protected readonly showMobileSidebar = this.appService.showMobileSidebar;

// Sidebar navigation items - matching React NavigationSidebar design
protected readonly sidebarItems: SidebarMenuItem[] = [
// Feature flags
private readonly showProjectsInSidebar = this.featureFlagService.getBooleanFlag('sidebar-projects', true);

// Base sidebar navigation items - matching React NavigationSidebar design
private readonly baseSidebarItems: SidebarMenuItem[] = [
{
label: 'Overview',
icon: 'fa-light fa-grid-2',
Expand All @@ -44,6 +49,18 @@ export class MainLayoutComponent {
},
];

// Computed sidebar items based on feature flags
protected readonly sidebarItems = computed(() => {
const items = [...this.baseSidebarItems];

// Filter out Projects if feature flag is disabled
if (!this.showProjectsInSidebar()) {
return items.filter((item) => item.label !== 'Projects');
}

return items;
});

// Sidebar footer items - matching React NavigationSidebar design
protected readonly sidebarFooterItems: SidebarMenuItem[] = [
{
Expand Down
Loading