Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: P002271-6007 distributed state in config service #24

Merged
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
pull_request:
branches:
- main
- v4
jobs:
main:
runs-on: ubuntu-latest
Expand Down
3 changes: 2 additions & 1 deletion libs/accelerator/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './lib/topic/topic'
export * from './lib/topic/topic'
export * from './lib/topic/syncable-topic'
54 changes: 54 additions & 0 deletions libs/accelerator/src/lib/topic/syncable-topic.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* The test environment that will be used for testing.
* The default environment in Jest is a Node.js environment.
* If you are building a web app, you can use a browser-like environment through jsdom instead.
*
* @jest-environment jsdom
*/

import { SyncableTopic } from './syncable-topic'

describe('Syncable Topic', () => {
const origAddEventListener = window.addEventListener
const origPostMessage = window.postMessage

let listeners: any[] = []
window.addEventListener = (_type: any, listener: any) => {
listeners.push(listener)
}

window.removeEventListener = (_type: any, listener: any) => {
listeners = listeners.filter((l) => l !== listener)
}

window.postMessage = (m: any) => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
listeners.forEach((l) => l({ data: m, stopImmediatePropagation: () => {}, stopPropagation: () => {} }))
}

afterAll(() => {
window.addEventListener = origAddEventListener
window.postMessage = origPostMessage
})

let testSyncableTopic1: SyncableTopic<string>
let testSyncableTopic2: SyncableTopic<string>

beforeEach(() => {
listeners = []

testSyncableTopic1 = new SyncableTopic<string>('test', 1)
testSyncableTopic2 = new SyncableTopic<string>('test', 1)

})

it('should get correct value', async () => {
expect(testSyncableTopic1.getValue()).toEqual(undefined)

testSyncableTopic1.publish('value1')
await testSyncableTopic1.isInitialized

expect(testSyncableTopic1.getValue()).toEqual('value1')
expect(testSyncableTopic2.getValue()).toEqual('value1')
})
})
33 changes: 33 additions & 0 deletions libs/accelerator/src/lib/topic/syncable-topic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Topic } from './topic'
import { TopicDataMessage } from './topic-data-message'

/**
* This class allows sync access to the value of a Topic.
* If you use this as a super class, you have to make sure that
* in all cases when the value is accessed it is initialized.
* This means that you have to make sure that in all possible
* code paths reaching your sync access you made sure that it
* is initialized (in standalone and shell mode).
* Keep in mind that there can be multiple instances of the Topic
* so you cannot rely on the fact that the shell has already checked
* that it is initialized.
*/
export class SyncableTopic<T> extends Topic<T> {
constructor(name: string, version: number) {
super(name, version)
}

/**
* This function does not offer read after write consistency!
* This means you cannot call it directly after publish, because the new value will not be there yet!
* This function will return undefined until the isInitialized Promise is resolved.
* @returns the current value of the Topic in a synchronous way
*/
getValue(): T | undefined {
return this.isInit ? (<TopicDataMessage<T>>this.data.value).data : undefined
}

get isInitialized(): Promise<void> {
return this.isInitializedPromise
}
}
22 changes: 6 additions & 16 deletions libs/accelerator/src/lib/topic/topic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* The test environment that will be used for testing.
* The default environment in Jest is a Node.js environment.
* If you are building a web app, you can use a browser-like environment through jsdom instead.
*
*
* @jest-environment jsdom
*/

Expand All @@ -18,7 +18,7 @@ describe('Topic', () => {
listeners.push(listener)
}

window.removeEventListener = (_type: any, listener: any, ) => {
window.removeEventListener = (_type: any, listener: any) => {
listeners = listeners.filter((l) => l !== listener)
}

Expand Down Expand Up @@ -49,7 +49,6 @@ describe('Topic', () => {

testTopic1.subscribe((v) => values1.push(v))
testTopic2.subscribe((v) => values2.push(v))

})

it('should have correct value for 2 topics after first topic publishes', () => {
Expand Down Expand Up @@ -118,7 +117,7 @@ describe('Topic', () => {

expect(values1).toEqual(['value1'])
expect(values2).toEqual(['value1'])

const values3: any[] = []
const testTopic3 = new Topic<undefined>('', 0)
testTopic3.subscribe((v) => values3.push(v))
Expand All @@ -127,28 +126,19 @@ describe('Topic', () => {
expect(values3).toEqual([])
})

it('should get correct value', () => {
expect(testTopic1.getValue()).toEqual(undefined)

testTopic1.publish('value1')

expect(testTopic1.getValue()).toEqual('value1')
expect(testTopic2.getValue()).toEqual('value1')
})

it('should remove event listener', () => {
testTopic1.destroy()
testTopic2.publish('value1')

expect(values1).toEqual([])
expect(values2).toEqual(['value1'])
})

it('should pipe to get the length of the value', () => {
let v = 0
testTopic1.pipe(map((v) => v.length)).subscribe((s) => v = s)
testTopic1.pipe(map((v) => v.length)).subscribe((s) => (v = s))
testTopic1.publish('value1')

expect(v).toEqual(6)
expect(values1).toEqual(['value1'])
})
Expand Down
36 changes: 17 additions & 19 deletions libs/accelerator/src/lib/topic/topic.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
import { filter, map } from 'rxjs/operators'
import { BehaviorSubject, Observable, Observer, OperatorFunction, Subscribable, Subscription, UnaryFunction } from 'rxjs'
import {
BehaviorSubject,
Observable,
Observer,
OperatorFunction,
Subscribable,
Subscription,
UnaryFunction,
} from 'rxjs'
import { TopicDataMessage } from './topic-data-message'
import { TopicMessage } from './topic-message'
import { TopicMessageType } from './topic-message-type'

export class Topic<T> implements Subscribable<T>{
public isInitialized: Promise<void>
private data = new BehaviorSubject<TopicDataMessage<T> | undefined>(undefined)
export class Topic<T> implements Subscribable<T> {
protected isInitializedPromise: Promise<void>
protected data = new BehaviorSubject<TopicDataMessage<T> | undefined>(undefined)

private isInit = false
protected isInit = false
private resolveInitPromise!: (value: void | PromiseLike<void>) => void
private eventListener = (m: MessageEvent<TopicMessage>) => this.onMessage(m)

constructor(private name: string, private version: number) {
this.isInitialized = new Promise<void>((resolve) => {
this.isInitializedPromise = new Promise<void>((resolve) => {
this.resolveInitPromise = resolve
});
})
window.addEventListener('message', this.eventListener)
const message = new TopicMessage(TopicMessageType.TopicGet, this.name, this.version);
const message = new TopicMessage(TopicMessageType.TopicGet, this.name, this.version)
window.postMessage(message, '*')
}

Expand All @@ -36,16 +44,6 @@ export class Topic<T> implements Subscribable<T>{
return (<any>this.asObservable()).subscribe(observerOrNext, error, complete)
}

/**
* This function does not offer read after write consistency!
* This means you cannot call it directly after publish, because the new value will not be there yet!
* This function will return undefined unti the isInitialized promise is resolved.
* @returns the current value of the topic in a synchronous way
*/
getValue(): T | undefined {
return this.isInit ? (<TopicDataMessage<T>>this.data.value).data : undefined
}

pipe(): Observable<T>
pipe<A>(op1: UnaryFunction<Observable<T>, A>): A
pipe<A, B>(op1: UnaryFunction<Observable<T>, A>, op2: UnaryFunction<A, B>): B
Expand Down Expand Up @@ -131,7 +129,7 @@ export class Topic<T> implements Subscribable<T>{
}

publish(value: T) {
const message = new TopicDataMessage<T>(TopicMessageType.TopicNext, this.name, this.version, value);
const message = new TopicDataMessage<T>(TopicMessageType.TopicNext, this.name, this.version, value)
window.postMessage(message, '*')
}

Expand Down
3 changes: 3 additions & 0 deletions libs/integration-interface/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ export * from './lib/topics/current-page/v1/current-page.topic'
export * from './lib/topics/global-error/v1/global-error.topic'
export * from './lib/topics/global-loading/v1/global-loading.topic'
export * from './lib/topics/current-theme/v1/current-theme.topic'
export * from './lib/topics/user-profile/v1/user-profile.topic'
export * from './lib/topics/configuration/v1/configuration.topic'
export * from './lib/topics/current-portal/v1/current-portal.topic'
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SyncableTopic } from '@onecx/accelerator'

export interface Config { [key: string]: string }

export class ConfigurationTopic extends SyncableTopic<Config> {
constructor() {
super('configuration', 1)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SyncableTopic } from '@onecx/accelerator'
import { Portal } from './portal.model'

export class CurrentPortalTopic extends SyncableTopic<Portal> {
constructor() {
super('currentPortal', 1)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface MicrofrontendRegistration {
creationDate?: string
creationUser?: string
modificationDate?: string
modificationUser?: string
id?: string
appId?: string
mfeId: string
baseUrl: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { MicrofrontendRegistration } from './mfe-portal-registration.model'

export interface Portal {
id?: string
portalName: string
description?: string
themeId?: string
themeName?: string
footerLabel?: string
homePage?: string
baseUrl: string
companyName?: string
portalRoles?: string[]
imageUrls?: string[]
address?: {
city?: string
country?: string
postalCode?: string
street?: string
streetNo?: string
}
phoneNumber?: string
rssFeedUrl?: string
subjectLinks?: [
{
label?: string
url?: string
}
]
microfrontendRegistrations: Array<MicrofrontendRegistration>
logoUrl?: string
userUploaded?: boolean
logoSmallImageUrl?: string
}
Loading
Loading