From f542ccf25e16ade5219f35a1b58973475a7f276b Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Fri, 29 Aug 2025 13:33:33 +0300 Subject: [PATCH 1/5] WIP navbar: add connection switcher --- frontend/src/app/app.component.css | 8 ++++++++ frontend/src/app/app.component.html | 20 ++++++++++++------- frontend/src/app/app.component.ts | 15 ++++++++++++-- .../connections-list.component.ts | 4 ++-- .../src/app/services/connections.service.ts | 4 ++++ 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/frontend/src/app/app.component.css b/frontend/src/app/app.component.css index 5246c021b..3336c8d08 100644 --- a/frontend/src/app/app.component.css +++ b/frontend/src/app/app.component.css @@ -55,6 +55,10 @@ } } +.nav-bar__slash { + color: #fff; +} + .nav-bar__button { --mdc-text-button-label-text-color: rgba(255, 255, 255, 1) !important; --mat-mdc-button-persistent-ripple-color: transparent !important; @@ -136,6 +140,10 @@ padding: 0 8px; } +.connection_active { + background: var(--color-accentedPalette-100); +} + .menu { display: flex; align-items: center; diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 70052f9de..ed94dc362 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -81,17 +81,23 @@ Rocketadmin logo + / + + + + {{connection.connection.title || connection.connection.database}} + + demo diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 8aa9767e5..82e87a2c9 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -168,12 +168,13 @@ export class AppComponent { if (!res.isTemporary && res.expires) { const expirationTime = new Date(res.expires); if (expirationTime) { - localStorage.setItem('token_expiration', expirationTime.toISOString()); - expirationToken = expirationTime.toISOString(); - } + localStorage.setItem('token_expiration', expirationTime.toISOString()); + expirationToken = expirationTime.toISOString(); + } this.router.navigate(['/connections-list']); + console.log('App component, user logged in, initializing app'); this.initializeUserSession(); const expirationInterval = differenceInMilliseconds(expirationTime, new Date()); @@ -184,7 +185,7 @@ export class AppComponent { } // app initialization if user is logged in (session restoration) - if (expirationToken) { + else if (expirationToken) { const expirationTime = expirationToken ? new Date(expirationToken) : null; const currantTime = new Date(); @@ -192,6 +193,7 @@ export class AppComponent { const expirationInterval = differenceInMilliseconds(expirationTime, currantTime); console.log('expirationInterval', expirationInterval); if (expirationInterval > 0) { + console.log('App component, session restoration'); this.initializeUserSession(); setTimeout(() => { @@ -233,6 +235,10 @@ export class AppComponent { return this._connections.currentTab; } + get ownUserConnections() { + return this._connections.ownConnectionsList; + } + initializeUserSession() { this._user.fetchUser() .subscribe((res: User) => { @@ -254,10 +260,14 @@ export class AppComponent { 'mode': 'demo' }); - this._connections.fetchConnections() - .subscribe((res: any) => { - this.connections = res.connections.filter(connectionItem => !connectionItem.connection.isTestConnection); - }) + // this._connections.fetchConnections() + // .subscribe((res: any) => { + // this.connections = res.filter(connectionItem => !connectionItem.isTestConnection); + // }) + + this._connections.cast.subscribe( () => { + this._connections.fetchConnections().subscribe(); + }); this._company.getWhiteLabelProperties(res.company.id).subscribe( whiteLabelSettings => { this.whiteLabelSettings.logo = whiteLabelSettings.logo; diff --git a/frontend/src/app/components/connections-list/connections-list.component.html b/frontend/src/app/components/connections-list/connections-list.component.html index 08fa9c07d..2a6becd5e 100644 --- a/frontend/src/app/components/connections-list/connections-list.component.html +++ b/frontend/src/app/components/connections-list/connections-list.component.html @@ -17,11 +17,11 @@

{{companyN -
diff --git a/frontend/src/app/components/connections-list/connections-list.component.ts b/frontend/src/app/components/connections-list/connections-list.component.ts index 5043b9c25..c9102e737 100644 --- a/frontend/src/app/components/connections-list/connections-list.component.ts +++ b/frontend/src/app/components/connections-list/connections-list.component.ts @@ -38,8 +38,8 @@ import { take } from 'rxjs/operators'; }) export class ConnectionsListComponent implements OnInit { - public connections: ConnectionItem[] = null; - public testConnections: ConnectionItem[] = null; + public connections: Connection[] = null; + // public testConnections: Connection[] = null; public titles: Object; public displayedCardCount: number = 3; public connectionsListCollapsed: boolean = true; @@ -58,6 +58,14 @@ export class ConnectionsListComponent implements OnInit { return this._userService.isDemo; } + get ownConnections() { + return this._connectionsServise.ownConnectionsList; + } + + get testConnections() { + return this._connectionsServise.testConnectionsList; + } + ngOnInit(): void { this._companyService.getCurrentTabTitle() .pipe(take(1)) @@ -72,18 +80,22 @@ export class ConnectionsListComponent implements OnInit { this.companyName = res.name; }) }); - this._connectionsServise.fetchConnections() - .subscribe((res: any) => { - this.setConnections(res); - }) - } - setConnections(res) { - this.connections = res.connections.filter(connectionItem => !connectionItem.connection.isTestConnection); - this.testConnections = res.connections.filter(connectionItem => connectionItem.connection.isTestConnection); - this.titles = Object.assign({}, ...res.connections.map((connectionItem) => ({[connectionItem.connection.id]: this.getTitle(connectionItem.connection)}))); + // this._connectionsServise.cast.subscribe((connections) => { + // // this.setConnections(connections); + // console.log('ConnectionsListComponent connections', connections); + // this.connections = connections.filter(connectionItem => !connectionItem.isTestConnection); + // this.testConnections = connections.filter(connectionItem => connectionItem.isTestConnection); + // this.titles = Object.assign({}, ...connections.map((connectionItem) => ({[connectionItem.id]: this.getTitle(connectionItem)}))); + // }); } + // setConnections(connections: Connection[]) { + // this.connections = connections.filter(connectionItem => !connectionItem.isTestConnection); + // this.testConnections = connections.filter(connectionItem => connectionItem.isTestConnection); + // this.titles = Object.assign({}, ...connections.map((connectionItem) => ({[connectionItem.id]: this.getTitle(connectionItem)}))); + // } + getTitle(connection: Connection) { if (!connection.title && connection.masterEncryption) return 'Untitled encrypted connection' return connection.title || connection.database diff --git a/frontend/src/app/services/connections.service.ts b/frontend/src/app/services/connections.service.ts index f1e726e7a..4b07f90f7 100644 --- a/frontend/src/app/services/connections.service.ts +++ b/frontend/src/app/services/connections.service.ts @@ -58,9 +58,14 @@ export class ConnectionsService { public companyName: string; public isCustomAccentedColor: boolean; public defaultDisplayTable: string; + public ownConnections: Connection[] = null; + public testConnections: Connection[] = null; private connectionNameSubject: BehaviorSubject = new BehaviorSubject('Rocketadmin'); private connectionSigningKeySubject: BehaviorSubject = new BehaviorSubject(null); + private connectionsSubject: BehaviorSubject = new BehaviorSubject([]); + + public cast = this.connectionsSubject.asObservable(); constructor( private _http: HttpClient, @@ -134,6 +139,14 @@ export class ConnectionsService { return tabs; } + get ownConnectionsList() { + return this.ownConnections; + } + + get testConnectionsList() { + return this.testConnections; + } + getCurrentConnectionTitle() { return this.connectionNameSubject.asObservable(); } @@ -182,7 +195,7 @@ export class ConnectionsService { return accessLevel === 'edit' || accessLevel === 'readonly' } - defineConnecrionType(connection) { + defineConnectionType(connection) { if (connection.type && connection.type.startsWith('agent_')) { connection.type = connection.type.slice(6); connection.connectionType = ConnectionType.Agent; @@ -197,10 +210,12 @@ export class ConnectionsService { .pipe( map(res => { const connections = res.connections.map(connectionItem => { - const connection = this.defineConnecrionType(connectionItem.connection); + const connection = this.defineConnectionType(connectionItem.connection); return {...connectionItem, connection}; - }) - return {... res, connections}; + }); + this.ownConnections = connections.filter(connectionItem => !connectionItem.connection.isTestConnection); + this.testConnections = connections.filter(connectionItem => connectionItem.connection.isTestConnection); + return connections; }), catchError((err) => { console.log(err); @@ -222,7 +237,7 @@ export class ConnectionsService { return this._http.get(`/connection/one/${id}`) .pipe( map(res => { - const connection = this.defineConnecrionType(res.connection); + const connection = this.defineConnectionType(res.connection); if (res.connectionProperties) { this.connectionLogo = res.connectionProperties.logo_url; this.companyName = res.connectionProperties.company_name; @@ -291,6 +306,7 @@ export class ConnectionsService { }) .pipe( map((res: any) => { + this.connectionsSubject.next(null); this._masterPassword.checkMasterPassword(connection.masterEncryption, res.id, masterKey); this._notifications.showSuccessSnackbar('Connection was added successfully.'); return res; @@ -352,6 +368,7 @@ export class ConnectionsService { return this._http.put(`/connection/delete/${id}`, metadata) .pipe( map(() => { + this.connectionsSubject.next(null); this._notifications.showSuccessSnackbar('Connection has been deleted successfully.'); }), catchError((err) => { From a7fcfd12b15a55e7406acadf66fd5f8468f524b7 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Tue, 2 Sep 2025 16:16:37 +0300 Subject: [PATCH 4/5] home: update own connections cards style, fix display titles. --- frontend/src/app/app.component.css | 5 +++ frontend/src/app/app.component.html | 12 ++++-- frontend/src/app/app.component.ts | 4 ++ .../connection-settings.component.html | 4 +- .../connections-list.component.ts | 19 --------- .../demo-connections.component.html | 2 +- .../own-connections.component.css | 27 +++++-------- .../own-connections.component.html | 40 ++++++------------- .../src/app/services/connections.service.ts | 13 +++++- 9 files changed, 52 insertions(+), 74 deletions(-) diff --git a/frontend/src/app/app.component.css b/frontend/src/app/app.component.css index 25eaa04ac..736de31db 100644 --- a/frontend/src/app/app.component.css +++ b/frontend/src/app/app.component.css @@ -57,6 +57,7 @@ .nav-bar__slash { color: #fff; + margin-bottom: -4px; } .nav-bar__button{ @@ -79,6 +80,10 @@ } } +.nav-bar__buttonConnectionsMenu { + margin-bottom: -4px; +} + .nav-bar__account-button ::ng-deep .mat-badge-content { top: 14px; left: 32px; diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 53a4d6d02..b87357d03 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -83,11 +83,15 @@ / - {{connection.connection.title || connection.connection.database}} + {{connection.displayTitle}} diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 82e87a2c9..642a6bb9d 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -227,6 +227,10 @@ export class AppComponent { return this._connections.currentConnectionName; } + get isTestConnection() { + return this._connections.currentConnection.isTestConnection; + } + get visibleTabs() { return this._connections.visibleTabs; } diff --git a/frontend/src/app/components/connection-settings/connection-settings.component.html b/frontend/src/app/components/connection-settings/connection-settings.component.html index 12ee0f9d0..c8e11cf6e 100644 --- a/frontend/src/app/components/connection-settings/connection-settings.component.html +++ b/frontend/src/app/components/connection-settings/connection-settings.component.html @@ -56,7 +56,7 @@

Rocketadmin can not find any tables

- +
diff --git a/frontend/src/app/components/connections-list/connections-list.component.ts b/frontend/src/app/components/connections-list/connections-list.component.ts index c9102e737..af77e2cfa 100644 --- a/frontend/src/app/components/connections-list/connections-list.component.ts +++ b/frontend/src/app/components/connections-list/connections-list.component.ts @@ -80,24 +80,5 @@ export class ConnectionsListComponent implements OnInit { this.companyName = res.name; }) }); - - // this._connectionsServise.cast.subscribe((connections) => { - // // this.setConnections(connections); - // console.log('ConnectionsListComponent connections', connections); - // this.connections = connections.filter(connectionItem => !connectionItem.isTestConnection); - // this.testConnections = connections.filter(connectionItem => connectionItem.isTestConnection); - // this.titles = Object.assign({}, ...connections.map((connectionItem) => ({[connectionItem.id]: this.getTitle(connectionItem)}))); - // }); - } - - // setConnections(connections: Connection[]) { - // this.connections = connections.filter(connectionItem => !connectionItem.isTestConnection); - // this.testConnections = connections.filter(connectionItem => connectionItem.isTestConnection); - // this.titles = Object.assign({}, ...connections.map((connectionItem) => ({[connectionItem.id]: this.getTitle(connectionItem)}))); - // } - - getTitle(connection: Connection) { - if (!connection.title && connection.masterEncryption) return 'Untitled encrypted connection' - return connection.title || connection.database } } diff --git a/frontend/src/app/components/connections-list/demo-connections/demo-connections.component.html b/frontend/src/app/components/connections-list/demo-connections/demo-connections.component.html index 4c6a99712..dfe3867d4 100644 --- a/frontend/src/app/components/connections-list/demo-connections/demo-connections.component.html +++ b/frontend/src/app/components/connections-list/demo-connections/demo-connections.component.html @@ -12,7 +12,7 @@

Try out a demo admin panel

class="testConnectionLink__icon" [alt]="testConnectionItem.connection.type"> diff --git a/frontend/src/app/components/connections-list/own-connections/own-connections.component.css b/frontend/src/app/components/connections-list/own-connections/own-connections.component.css index 122030c2e..c048ddb25 100644 --- a/frontend/src/app/components/connections-list/own-connections/own-connections.component.css +++ b/frontend/src/app/components/connections-list/own-connections/own-connections.component.css @@ -205,6 +205,7 @@ .connectionItem { flex: 0 0 calc((100% - 40px)/3); + max-width: calc((100% - 40px)/3); } @media (width <= 600px) { @@ -219,8 +220,7 @@ border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 4px; color: inherit; - height: 148px; - padding: 20px 12px; + padding: 12px; text-decoration: none; width: 100%; transition: border 200ms, box-shadow 200ms; @@ -250,39 +250,30 @@ .connectionLogoPreview { flex-shrink: 0; display: flex; - /* flex-direction: column; */ - /* align-items: flex-start; */ align-items: center; - /* justify-content: center; */ - gap: 4px; + gap: 12px; background-color: var(--color-primaryPalette-500); border-radius: 2px; color: #fff; font-size: 20px; font-weight: 900; - height: 52px; - margin-bottom: 4px; + height: 72px; padding: 8px; } .connectionLogoPreview__logo { - height: 100%; + height: 85%; object-fit: contain; } -.connectionLogoPreview .connectionLogoPreview__name { - margin-bottom: 0; - font-size: 20px; -} - .connectionInfo { - display: grid; - grid-template-columns: auto 40px; - align-items: center; + display: flex; + flex-direction: column; } .connectionInfo .connectionInfo__connectionTitle { - margin-bottom: 0; + margin-top: -4px; + margin-bottom: -2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/frontend/src/app/components/connections-list/own-connections/own-connections.component.html b/frontend/src/app/components/connections-list/own-connections/own-connections.component.html index 41ebec7dd..560e80c2e 100644 --- a/frontend/src/app/components/connections-list/own-connections/own-connections.component.html +++ b/frontend/src/app/components/connections-list/own-connections/own-connections.component.html @@ -24,36 +24,20 @@

-
- + - - {{connectionItem.connection.connection_properties?.company_name}} - -
-
- -
-
- - {{connectionItem.connection.connection_properties?.company_name}} - -
-
- - - Rocketadmin - -
-
-

{{ connectionItem.connection.title }}

- {{ connectionItem.connection.type }} - arrow_forward + + + +
+

{{ connectionItem.displayTitle }}

+ {{ connectionItem.connection.type }} +
diff --git a/frontend/src/app/services/connections.service.ts b/frontend/src/app/services/connections.service.ts index 4b07f90f7..d585fabfd 100644 --- a/frontend/src/app/services/connections.service.ts +++ b/frontend/src/app/services/connections.service.ts @@ -101,7 +101,7 @@ export class ConnectionsService { } get currentConnectionName() { - return this.connection.title || this.connection.database; + return this.defineConnectionTitle(this.connection); } get logo() { @@ -205,13 +205,21 @@ export class ConnectionsService { return connection; } + defineConnectionTitle(connection: Connection) { + if (!connection.title && connection.masterEncryption) return 'Untitled encrypted connection'; + if (!connection.title && !connection.database) return 'Untitled connection'; + return connection.title || connection.database; + } + fetchConnections() { return this._http.get('/connections') .pipe( map(res => { const connections = res.connections.map(connectionItem => { const connection = this.defineConnectionType(connectionItem.connection); - return {...connectionItem, connection}; + const displayTitle = this.defineConnectionTitle(connectionItem.connection); + console.log('displayTitle', displayTitle); + return {...connectionItem, connection, displayTitle}; }); this.ownConnections = connections.filter(connectionItem => !connectionItem.connection.isTestConnection); this.testConnections = connections.filter(connectionItem => connectionItem.connection.isTestConnection); @@ -343,6 +351,7 @@ export class ConnectionsService { map(res => { this._masterPassword.checkMasterPassword(connection.masterEncryption, connection.id, masterKey); this._notifications.showSuccessSnackbar('Connection has been updated successfully.'); + this.connectionsSubject.next(null); return res; }), catchError((err) => { From 1767961d6a5877c5b79d272765636a3a8734f394 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Tue, 2 Sep 2025 19:20:00 +0300 Subject: [PATCH 5/5] update unit tests --- frontend/src/app/app.component.spec.ts | 20 +- frontend/src/app/app.component.ts | 2 +- .../connections-list.component.spec.ts | 194 ------------------ .../demo-connections.component.spec.ts | 93 ++++++++- .../demo-connections.component.ts | 18 +- .../app/services/connections.service.spec.ts | 12 +- 6 files changed, 128 insertions(+), 211 deletions(-) diff --git a/frontend/src/app/app.component.spec.ts b/frontend/src/app/app.component.spec.ts index a5a3b97ec..62999964f 100644 --- a/frontend/src/app/app.component.spec.ts +++ b/frontend/src/app/app.component.spec.ts @@ -61,15 +61,22 @@ describe('AppComponent', () => { }; const mockUiSettingsService = { - getUiSettings: jasmine.createSpy('getUiSettings'), - updateGlobalSetting: jasmine.createSpy('updateGlobalSetting') + getUiSettings: jasmine.createSpy('getUiSettings').and.returnValue(of({globalSettings: {lastFeatureNotificationId: 'default-id'}})), + updateGlobalSetting: jasmine.createSpy('updateGlobalSetting').and.returnValue(of({})) }; + const connectionsCast = new Subject(); + const mockConnectionsService = { isCustomAccentedColor: true, connectionID: '123', visibleTabs: ['dashboard'], - currentTab: 'dashboard' + currentTab: 'dashboard', + currentConnection: { + isTestConnection: false + }, + cast: connectionsCast, + fetchConnections: jasmine.createSpy('fetchConnections').and.returnValue(of([])) }; const mockTablesService = { @@ -139,7 +146,7 @@ describe('AppComponent', () => { it('should set userLoggedIn and logo on user session initialization', fakeAsync(() => { mockCompanyService.getWhiteLabelProperties.and.returnValue(of({logo: 'data:png;base64,some-base64-data'})); - mockUiSettingsService.getUiSettings.and.returnValue(of({settings: {globalSettings: {lastFeatureNotificationId: 'old-id'}}})); + mockUiSettingsService.getUiSettings.and.returnValue(of({globalSettings: {lastFeatureNotificationId: 'old-id'}})); app.initializeUserSession(); tick(); @@ -151,7 +158,7 @@ describe('AppComponent', () => { it('should render custom logo in navbar if it is set', fakeAsync(() => { mockCompanyService.getWhiteLabelProperties.and.returnValue(of({logo: 'data:png;base64,some-base64-data'})); - mockUiSettingsService.getUiSettings.and.returnValue(of({settings: {globalSettings: {lastFeatureNotificationId: 'old-id'}}})); + mockUiSettingsService.getUiSettings.and.returnValue(of({globalSettings: {lastFeatureNotificationId: 'old-id'}})); app.initializeUserSession(); tick(); @@ -165,7 +172,7 @@ describe('AppComponent', () => { it('should render the link to Connetions list that contains the custom logo in the navbar', fakeAsync(() => { mockCompanyService.getWhiteLabelProperties.and.returnValue(of({logo: null})); - mockUiSettingsService.getUiSettings.and.returnValue(of({settings: {globalSettings: {lastFeatureNotificationId: 'old-id'}}})); + mockUiSettingsService.getUiSettings.and.returnValue(of({globalSettings: {lastFeatureNotificationId: 'old-id'}})); app.initializeUserSession(); tick(); @@ -246,6 +253,7 @@ describe('AppComponent', () => { it('should handle user login flow when cast emits user with expires', fakeAsync(() => { mockCompanyService.getWhiteLabelProperties.and.returnValue(of({logo: '', favicon: ''})); + mockUiSettingsService.getUiSettings.and.returnValue(of({globalSettings: {lastFeatureNotificationId: 'old-id'}})); const expirationDate = new Date(Date.now() + 10_000); // 10s from now app['currentFeatureNotificationId'] = 'some-id'; diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 642a6bb9d..a0e7c2d36 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -228,7 +228,7 @@ export class AppComponent { } get isTestConnection() { - return this._connections.currentConnection.isTestConnection; + return this._connections.currentConnection?.isTestConnection || false; } get visibleTabs() { diff --git a/frontend/src/app/components/connections-list/connections-list.component.spec.ts b/frontend/src/app/components/connections-list/connections-list.component.spec.ts index 8e97dbf49..6c3fbeb93 100644 --- a/frontend/src/app/components/connections-list/connections-list.component.spec.ts +++ b/frontend/src/app/components/connections-list/connections-list.component.spec.ts @@ -40,198 +40,4 @@ describe('ConnectionsListComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); - - it('should sort connections on test and own, and set object of titles', () => { - const fakeConnections = { - "connections": [ - { - "connection": { - "id": "9176a5d1-624f-48af-b5a2-f0a08e15252f", - "title": "new test agent", - "masterEncryption": false, - "type": "agent_mysql", - "host": null, - "port": null, - "username": null, - "database": null, - "schema": null, - "sid": null, - "createdAt": "2021-09-03T14:45:21.154Z", - "updatedAt": "2021-09-03T14:45:21.154Z", - "ssh": false, - "sshHost": null, - "sshPort": null, - "sshUsername": null, - "ssl": false, - "cert": null, - "isTestConnection": false - }, - "accessLevel": "edit" - }, - { - "connection": { - "id": "944b546a-5141-4c89-b0da-39bcc904ffea", - "title": "Test connection to MSSQL", - "masterEncryption": false, - "type": "agent_mssql", - "host": "U2FsdGVkX18Aqg8bUNwW/QQyadQRimuU6wSuerla/aM2+7902zH1PCNIuBYwnvKK88NNyjiuaxdXC44S5zApDabiqyG75Tj9jaxRntrKElU=", - "port": 1433, - "username": "U2FsdGVkX1/p7kOB3MpSmBvOlMcVaw2rwM1T/bd+b/c=", - "database": "U2FsdGVkX18KzyNPZrNUi734AbAG/ON5ijxj4k3rC2A=", - "schema": null, - "sid": null, - "createdAt": "2021-09-03T10:29:48.168Z", - "updatedAt": "2021-09-03T14:50:26.085Z", - "ssh": false, - "sshHost": null, - "sshPort": null, - "sshUsername": null, - "ssl": false, - "cert": null, - "isTestConnection": true - }, - "accessLevel": "edit" - }, - { - "connection": { - "id": "5f80922d-f4bf-4a91-a7d3-d3f38dfcaa4e", - "title": "Test connection to OracleDB", - "masterEncryption": false, - "type": "agent_oracledb", - "host": "U2FsdGVkX1/TdsnH5xIQaCvFXU8CpkJMKnSX1l+5JVQ6+WrFk83OeZ629tHLtVMG04Ht+H6kpstKZ01PrqZfBhM2OvLL2rw0bImen/TGFuw=", - "port": 1521, - "username": "U2FsdGVkX19oQClMIPyFNxEYeSPNLJIDpnYPSWKfyWU=", - "database": "U2FsdGVkX19NJkB1M3ydah9qjZQZBcnnLU03T0Y7PeY=", - "schema": null, - "sid": "ORCL", - "createdAt": "2021-09-03T10:29:48.150Z", - "updatedAt": "2021-09-03T14:45:51.582Z", - "ssh": false, - "sshHost": null, - "sshPort": null, - "sshUsername": null, - "ssl": false, - "cert": null, - "isTestConnection": true - }, - "accessLevel": "edit" - } - ], - "connectionsCount": 3 - }; - - component.setConnections(fakeConnections); - fixture.detectChanges(); - - expect(component.connections).toEqual([ - { - "connection": { - "id": "9176a5d1-624f-48af-b5a2-f0a08e15252f", - "title": "new test agent", - "masterEncryption": false, - "type": "agent_mysql", - "host": null, - "port": null, - "username": null, - "database": null, - "schema": null, - "sid": null, - "createdAt": "2021-09-03T14:45:21.154Z", - "updatedAt": "2021-09-03T14:45:21.154Z", - "ssh": false, - "sshHost": null, - "sshPort": null, - "sshUsername": null, - "ssl": false, - "cert": null, - "isTestConnection": false - }, - "accessLevel": "edit" - } - ] as any); - expect(component.testConnections).toEqual([ - { - "connection": { - "id": "944b546a-5141-4c89-b0da-39bcc904ffea", - "title": "Test connection to MSSQL", - "masterEncryption": false, - "type": "agent_mssql", - "host": "U2FsdGVkX18Aqg8bUNwW/QQyadQRimuU6wSuerla/aM2+7902zH1PCNIuBYwnvKK88NNyjiuaxdXC44S5zApDabiqyG75Tj9jaxRntrKElU=", - "port": 1433, - "username": "U2FsdGVkX1/p7kOB3MpSmBvOlMcVaw2rwM1T/bd+b/c=", - "database": "U2FsdGVkX18KzyNPZrNUi734AbAG/ON5ijxj4k3rC2A=", - "schema": null, - "sid": null, - "createdAt": "2021-09-03T10:29:48.168Z", - "updatedAt": "2021-09-03T14:50:26.085Z", - "ssh": false, - "sshHost": null, - "sshPort": null, - "sshUsername": null, - "ssl": false, - "cert": null, - "isTestConnection": true - }, - "accessLevel": "edit" - }, - { - "connection": { - "id": "5f80922d-f4bf-4a91-a7d3-d3f38dfcaa4e", - "title": "Test connection to OracleDB", - "masterEncryption": false, - "type": "agent_oracledb", - "host": "U2FsdGVkX1/TdsnH5xIQaCvFXU8CpkJMKnSX1l+5JVQ6+WrFk83OeZ629tHLtVMG04Ht+H6kpstKZ01PrqZfBhM2OvLL2rw0bImen/TGFuw=", - "port": 1521, - "username": "U2FsdGVkX19oQClMIPyFNxEYeSPNLJIDpnYPSWKfyWU=", - "database": "U2FsdGVkX19NJkB1M3ydah9qjZQZBcnnLU03T0Y7PeY=", - "schema": null, - "sid": "ORCL", - "createdAt": "2021-09-03T10:29:48.150Z", - "updatedAt": "2021-09-03T14:45:51.582Z", - "ssh": false, - "sshHost": null, - "sshPort": null, - "sshUsername": null, - "ssl": false, - "cert": null, - "isTestConnection": true - }, - "accessLevel": "edit" - } - ] as any); - expect(component.titles).toEqual({ - "9176a5d1-624f-48af-b5a2-f0a08e15252f": "new test agent", - "944b546a-5141-4c89-b0da-39bcc904ffea": "Test connection to MSSQL", - "5f80922d-f4bf-4a91-a7d3-d3f38dfcaa4e": "Test connection to OracleDB" - }) - }); - - it('should get connection title if it is pointed', () => { - const connection = { - title: 'SQL connection' - } - - const connectionTitle = component.getTitle(connection as any); - expect(connectionTitle).toEqual('SQL connection'); - }); - - it('should get db name as connection title if it is not pointed', () => { - const connection = { - database: 'testDB' - } - - const connectionTitle = component.getTitle(connection as any); - expect(connectionTitle).toEqual('testDB'); - }); - - it('should get Untitled encrypted connection as connection title if it is not pointed and connection is encriped', () => { - const connection = { - database: 'testDB', - masterEncryption: true - } - - const connectionTitle = component.getTitle(connection as any); - expect(connectionTitle).toEqual('Untitled encrypted connection'); - expect(connectionTitle).not.toEqual('testDB'); - }); }); diff --git a/frontend/src/app/components/connections-list/demo-connections/demo-connections.component.spec.ts b/frontend/src/app/components/connections-list/demo-connections/demo-connections.component.spec.ts index b15e5029c..53d22a18f 100644 --- a/frontend/src/app/components/connections-list/demo-connections/demo-connections.component.spec.ts +++ b/frontend/src/app/components/connections-list/demo-connections/demo-connections.component.spec.ts @@ -1,23 +1,114 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DemoConnectionsComponent } from './demo-connections.component'; +import { ConnectionItem, DBtype, ConnectionType } from 'src/app/models/connection'; +import { AccessLevel } from 'src/app/models/user'; +import { provideRouter } from '@angular/router'; +import { Angulartics2Module } from 'angulartics2'; describe('DemoConnectionsComponent', () => { let component: DemoConnectionsComponent; let fixture: ComponentFixture; + const mockTestConnections: ConnectionItem[] = [ + { + connection: { + id: '1', + title: 'MySQL Test DB', + type: DBtype.MySQL, + database: 'test_db', + host: 'localhost', + port: '3306', + username: 'root', + password: 'password', + schema: null, + sid: null, + ssl: false, + ssh: false, + connectionType: ConnectionType.Direct, + masterEncryption: false, + azure_encryption: false, + isTestConnection: true, + cert: '' + }, + accessLevel: AccessLevel.None + }, + { + connection: { + id: '2', + title: 'PostgreSQL Test DB', + type: DBtype.Postgres, + database: 'test_db', + host: 'localhost', + port: '5432', + username: 'postgres', + password: 'password', + schema: null, + sid: null, + ssl: false, + ssh: false, + connectionType: ConnectionType.Direct, + masterEncryption: false, + azure_encryption: false, + isTestConnection: true, + cert: '' + }, + accessLevel: AccessLevel.None + } + ]; + beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [DemoConnectionsComponent] + imports: [ + Angulartics2Module.forRoot({}), + DemoConnectionsComponent + ], + providers: [provideRouter([])] }) .compileComponents(); fixture = TestBed.createComponent(DemoConnectionsComponent); component = fixture.componentInstance; + component.testConnections = mockTestConnections; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should order test databases according to supportedOrderedDatabases', () => { + // Call the method directly + const orderedConnections = component.orderTestDatabases(); + + // Verify the result is an array + expect(Array.isArray(orderedConnections)).toBe(true); + + // Verify the connections are in the right order + expect(orderedConnections.length).toBe(2); + expect(orderedConnections[0].connection.type).toBe(DBtype.MySQL); + expect(orderedConnections[1].connection.type).toBe(DBtype.Postgres); + }); + + it('should handle empty or null testConnections', () => { + // Set testConnections to null + component.testConnections = null; + fixture.detectChanges(); + + // Call the method + const result = component.orderTestDatabases(); + + // Verify it returns an empty array + expect(result).toEqual([]); + + // Set testConnections to empty array + component.testConnections = []; + fixture.detectChanges(); + + // Call the method again + const resultForEmpty = component.orderTestDatabases(); + + // Verify it returns an empty array + expect(resultForEmpty).toEqual([]); + }); }); diff --git a/frontend/src/app/components/connections-list/demo-connections/demo-connections.component.ts b/frontend/src/app/components/connections-list/demo-connections/demo-connections.component.ts index e72be0a8f..250c65e57 100644 --- a/frontend/src/app/components/connections-list/demo-connections/demo-connections.component.ts +++ b/frontend/src/app/components/connections-list/demo-connections/demo-connections.component.ts @@ -19,7 +19,7 @@ import { RouterModule } from '@angular/router'; styleUrl: './demo-connections.component.css' }) export class DemoConnectionsComponent implements OnInit { - @Input() testConnections: ConnectionItem[] = null; + @Input() testConnections: ConnectionItem[] = []; @Input() isDemo: boolean = false; public testDatabasesNames = supportedDatabasesTitles; @@ -34,15 +34,25 @@ export class DemoConnectionsComponent implements OnInit { } ngOnInit() { - this.testConnections = this.orderTestDatabases(); + if (this.testConnections && this.testConnections.length > 0) { + this.testConnections = this.orderTestDatabases(); + } } orderTestDatabases() { + if (!this.testConnections || !Array.isArray(this.testConnections) || this.testConnections.length === 0) { + return []; + } + const orderMap = new Map(supportedOrderedDatabases.map((db, index) => [db, index])); return [...this.testConnections].sort((a, b) => { - const typeA = a.connection.type; - const typeB = b.connection.type; + const typeA = a.connection?.type; + const typeB = b.connection?.type; + + if (!typeA || !typeB) { + return 0; + } const indexA = orderMap.has(typeA) ? orderMap.get(typeA) : Infinity; const indexB = orderMap.has(typeB) ? orderMap.get(typeB) : Infinity; diff --git a/frontend/src/app/services/connections.service.spec.ts b/frontend/src/app/services/connections.service.spec.ts index 6d731e2ab..639f78102 100644 --- a/frontend/src/app/services/connections.service.spec.ts +++ b/frontend/src/app/services/connections.service.spec.ts @@ -174,7 +174,7 @@ describe('ConnectionsService', () => { }) it('should define a type of connections without agent_ prefix', () => { - const connection = service.defineConnecrionType(connectionCredsNetwork); + const connection = service.defineConnectionType(connectionCredsNetwork); expect(connection).toEqual({ "title": "Test connection via SSH tunnel to mySQL", @@ -200,7 +200,7 @@ describe('ConnectionsService', () => { }) it('should define a type of connections with agent_ prefix', () => { - const connection = service.defineConnecrionType({ + const connection = service.defineConnectionType({ "title": "Test connection via SSH tunnel to mySQL", "masterEncryption": false, "type": "agent_mysql", @@ -287,7 +287,8 @@ describe('ConnectionsService', () => { "cert": null, "isTestConnection": true }, - "accessLevel": "edit" + "accessLevel": "edit", + "displayTitle": "Test connection to OracleDB" }, { "connection": { @@ -311,14 +312,15 @@ describe('ConnectionsService', () => { "cert": null, "isTestConnection": true }, - "accessLevel": "readonly" + "accessLevel": "readonly", + "displayTitle": "Test connection via SSH tunnel to mySQL" } ], "connectionsCount": 2 } service.fetchConnections().subscribe(connectionData => { - expect(connectionData).toEqual(connectionsList); + expect(connectionData).toEqual(connectionsList.connections); isSubscribeCalled = true; });