Skip to content

Commit

Permalink
Merge pull request #20 from millenr/A2-2036
Browse files Browse the repository at this point in the history
A2-2036 : Runtime configurations
  • Loading branch information
tsheils authored Oct 31, 2023
2 parents f6ea147 + 8b42cae commit e04d40c
Show file tree
Hide file tree
Showing 28 changed files with 217 additions and 114 deletions.
15 changes: 8 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
# Stage 1: Build the Angular application
FROM node:18 as build
FROM node:20.4 as build

# Set the working directory
WORKDIR /app

# Copy the package.json and package-lock.json
COPY package*.json ./

# Set default build environment
ARG ENV=production

# Install the Angular CLI and application's npm dependencies
RUN npm install -g @angular/cli && npm install

# Copy the rest of the application's files
COPY . .

# Build the application
RUN ng build --configuration=$ENV
RUN ng build --configuration=production

# Stage 2: Setup NGINX
FROM nginx:alpine
Expand All @@ -28,8 +25,12 @@ COPY nginx-custom.conf /etc/nginx/conf.d/default.conf
# Copy the build output from Stage 1 to the NGINX html directory
COPY --from=build /app/dist /usr/share/nginx/html

# Copy the entrypoint script
COPY ./entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# Expose port 4200
EXPOSE 4200

# Start NGINX
CMD ["nginx", "-g", "daemon off;"]
# Use the entrypoint script
ENTRYPOINT ["/entrypoint.sh"]
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,25 @@ The code base can be run locally using the following commands:

`ng serve` - To run with default local configuration

`ng serve --configuration={production|local|local-deployedws}` - To run with a specified configuration

## Building the code

The code base can be built locally using the following commands:

`ng build` - To run with default local configuration

`ng build --configuration={production|local|local-deployedws}` - To run with a specified configuration

## Building and Running the container

The docker container for the UI runs the application out of a NGINX server on port 4200.

To build this container you can either build the docker compose file with `docker compose -f docker-compose.yml build` or `docker build -t smartgraph-ui .`

The container can than be run with either `docker compose -f docker-compose.yml up` or `docker run -p 4200:4200 smartgraph-ui`

## Configuring at Runtime

The application reads settings from `/assets/config.json`. While using the docker run option this file can be generated at runtime by setting the following environment variables:

- `WRITE_CONFIG` - Tells the entrypoint script to write a new config.json
- `ENVIRONMENT` - Environment label
- `DATA_URL` - Websocket URL for the app to utilize
- `API_SWAGGER_URL` - Link to API swagger page
64 changes: 0 additions & 64 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,70 +48,6 @@
"with": "src/environments/environment.prod.ts"
}
]
},
"local": {
"optimization": false,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": false,
"extractLicenses": false,
"vendorChunk": false,
"buildOptimizer": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.local.ts"
}
]
},
"local-deployedws": {
"optimization": false,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": false,
"extractLicenses": false,
"vendorChunk": false,
"buildOptimizer": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.local-deployedws.ts"
}
]
},
"ci": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.ci.ts"
}
]
},
"qa": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.qa.ts"
}
]
}
},
"defaultConfiguration": ""
Expand Down
9 changes: 6 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ services:
build:
context: .
dockerfile: Dockerfile
args:
- ENV=local # default build environment, change as needed. Use 'local' to build for local development
ports:
- "4200:4200"
- "4200:4200"
environment:
- WRITE_CONFIG=true
- ENVIRONMENT=local
- DATA_URL=ws://localhost:1338/socket.io
- API_SWAGGER_URL=http://localhost:1337/docs/
25 changes: 25 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/sh
set -e

# Check for WRITE_CONFIG env variable
if [ "${WRITE_CONFIG}" != "true" ]; then
echo "WRITE_CONFIG is not set to 'true'. Skipping JSON configuration."
else
echo "Creating JSON configuration from environment variables."
# Read environment variables
ENVIRONMENT="${ENVIRONMENT:?Error: ENVIRONMENT environment variable not set}"
DATA_URL="${DATA_URL:?Error: DATA_URL environment variable not set}"
API_SWAGGER_URL="${API_SWAGGER_URL:?Error: API_SWAGGER_URL environment variable not set}"

# Write to config.json file
cat > /usr/share/nginx/html/assets/config.json <<EOF
{
"ENVIRONMENT": "$ENVIRONMENT",
"DATA_URL": "$DATA_URL",
"API_SWAGGER_URL": "$API_SWAGGER_URL"
}
EOF
fi

# Start NGINX
exec nginx -g "daemon off;"
13 changes: 11 additions & 2 deletions src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@ import {MaterialModule} from '../assets/material/material.module';
import {LinkService} from './d3/models/link.service';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {MessageService} from './services/message.service';
import {ConfigService} from './services/config.service';
import { DataConnectionService } from './services/data-connection.service';

describe('AppComponent', () => {
beforeEach(async(() => {
// Mock ConfigService
const mockConfigService = {
get: jasmine.createSpy('get').and.returnValue('ws://localhost:1234/socket')
};

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
Expand All @@ -22,6 +29,8 @@ describe('AppComponent', () => {
BrowserAnimationsModule
],
providers: [
{ provide: ConfigService, useValue: mockConfigService }, // Provide the mock
DataConnectionService,
MessageService,
NodeService,
LinkService,
Expand All @@ -32,7 +41,7 @@ describe('AppComponent', () => {
CUSTOM_ELEMENTS_SCHEMA
],
}).compileComponents();
}));
});

it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
Expand Down
14 changes: 12 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
Expand Down Expand Up @@ -44,6 +44,8 @@ import {NodeMenuComponent} from './visuals/shared/node-menu/node-menu.component'
import { DisclaimerModalComponent } from './smrtgraph-settings/disclaimer-modal/disclaimer-modal.component';
import {AboutModalComponent} from './smrtgraph-menu/about-modal/about-modal.component';
import {HelpPanelComponent} from './help-panel/help-panel.component';
import { ConfigService } from './services/config.service';
import { HttpClientModule } from '@angular/common/http';


@NgModule({
Expand Down Expand Up @@ -85,9 +87,17 @@ import {HelpPanelComponent} from './help-panel/help-panel.component';
ReactiveFormsModule,
BrowserAnimationsModule,
MaterialModule,
FlexLayoutModule
FlexLayoutModule,
HttpClientModule
],
providers: [
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: (config: ConfigService) => () => config.loadConfig(),
deps: [ConfigService],
multi: true
},
WebSocketService,
DataConnectionService,
D3Service,
Expand Down
9 changes: 9 additions & 0 deletions src/app/download-button/download-button.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@ import {MaterialModule} from '../../assets/material/material.module';
import {LinkService} from '../d3/models/link.service';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {MessageService} from '../services/message.service';
import { ConfigService } from '../services/config.service';
import { DataConnectionService } from '../services/data-connection.service';

describe('DownloadButtonComponent', () => {
let component: DownloadButtonComponent;
let fixture: ComponentFixture<DownloadButtonComponent>;

// Mock ConfigService
const mockConfigService = {
get: jasmine.createSpy('get').and.returnValue('ws://localhost:1234/socket')
};

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DownloadButtonComponent ],
Expand All @@ -23,6 +30,8 @@ describe('DownloadButtonComponent', () => {
BrowserAnimationsModule
],
providers: [
{ provide: ConfigService, useValue: mockConfigService }, // Provide the mock
DataConnectionService,
MessageService,
NodeService,
LinkService,
Expand Down
26 changes: 26 additions & 0 deletions src/app/services/config.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ConfigService } from './config.service';

describe('ConfigService', () => {
let service: ConfigService;
let httpMock: HttpTestingController;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [ConfigService]
});

service = TestBed.inject(ConfigService);
httpMock = TestBed.inject(HttpTestingController);
});

afterEach(() => {
httpMock.verify();
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
22 changes: 22 additions & 0 deletions src/app/services/config.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// src/app/config.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
providedIn: 'root'
})
export class ConfigService {
private config: any;

constructor(private http: HttpClient) {}

loadConfig(): Promise<any> {
return this.http.get('/assets/config.json').toPromise().then((config) => {
this.config = config;
});
}

get(key: string) {
return this.config[key];
}
}
13 changes: 11 additions & 2 deletions src/app/services/data-connection.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { TestBed, inject } from '@angular/core/testing';

import { DataConnectionService } from './data-connection.service';
import { ConfigService } from './config.service'; // Assuming the path is correct

describe('DataConnectionService', () => {

// Mock ConfigService
const mockConfigService = {
get: jasmine.createSpy('get').and.returnValue('ws://localhost:1234/socket')
};

beforeEach(() => {
TestBed.configureTestingModule({
providers: [DataConnectionService]
providers: [
DataConnectionService,
{ provide: ConfigService, useValue: mockConfigService } // Provide the mock
]
});
});

Expand Down
10 changes: 7 additions & 3 deletions src/app/services/data-connection.service.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import {Injectable} from '@angular/core';
import {Injectable, OnInit} from '@angular/core';
import {Subject} from 'rxjs';
import {webSocket, WebSocketSubject} from 'rxjs/webSocket';
import {environment} from '../../environments/environment';
import { ConfigService } from './config.service';

const DATA_URL = environment.DATA_URL;
// const DATA_URL = environment.DATA_URL;


@Injectable({
providedIn: 'root'
})
export class DataConnectionService {

subject;
responses: WebSocketSubject<any>;

public messages: Subject<any> = new Subject<any>();

constructor() {
constructor(private configService: ConfigService) {
const DATA_URL = this.configService.get('DATA_URL');

this.responses = webSocket(DATA_URL);

this.responses.subscribe(
Expand Down
Loading

0 comments on commit e04d40c

Please sign in to comment.