Skip to content

Commit

Permalink
Merge pull request #3255 from cloudfoundry-incubator/user-favorites
Browse files Browse the repository at this point in the history
User favorites
  • Loading branch information
richard-cox authored Feb 15, 2019
2 parents 26c66a1 + 7a9658f commit 96af329
Show file tree
Hide file tree
Showing 141 changed files with 6,270 additions and 2,542 deletions.
4,613 changes: 2,313 additions & 2,300 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
"@angular/common": "^6.1.1",
"@angular/compiler": "^6.1.1",
"@angular/core": "^6.1.1",
"@angular/forms": "^6.1.1",
"@angular/flex-layout": "^6.0.0-beta.16",
"@angular/forms": "^6.1.1",
"@angular/http": "^6.1.1",
"@angular/material": "^6.1.0",
"@angular/material-moment-adapter": "^6.4.7",
Expand All @@ -60,7 +60,6 @@
"@ngrx/store-devtools": "^6.0.1",
"@swimlane/ngx-charts": "^9.0.0",
"angular2-virtual-scroll": "^0.3.1",
"stratos-angular6-json-schema-form": "1.0.3",
"core-js": "^2.5.7",
"hammerjs": "^2.0.8",
"js-yaml": "^3.11.0",
Expand All @@ -72,6 +71,7 @@
"rxjs": "^6.2.0",
"rxjs-spy": "^7.0.2",
"rxjs-websockets": "~6.0.0",
"stratos-angular6-json-schema-form": "1.0.3",
"ts-md5": "^1.2.4",
"web-animations-js": "^2.3.1",
"xterm": "^3.5.0",
Expand Down Expand Up @@ -124,4 +124,4 @@
"tslint": "~5.10.0",
"typescript": "~2.9.0"
}
}
}
236 changes: 231 additions & 5 deletions src/frontend/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,54 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { Params, RouterStateSnapshot } from '@angular/router';
import { Params, RouterStateSnapshot, RouteReuseStrategy } from '@angular/router';
import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';

import { Store } from '@ngrx/store';
import { debounceTime, withLatestFrom } from 'rxjs/operators';
import { AppComponent } from './app.component';
import { RouteModule } from './app.routing';
import { IAppFavMetadata, IOrgFavMetadata, ISpaceFavMetadata } from './cf-favourite-types';
import { IApp, IOrganization, ISpace } from './core/cf-api.types';
import { CoreModule } from './core/core.module';
import { CurrentUserPermissions } from './core/current-user-permissions.config';
import { CurrentUserPermissionsService } from './core/current-user-permissions.service';
import { DynamicExtensionRoutes } from './core/extension/dynamic-extension-routes';
import { ExtensionService } from './core/extension/extension-service';
import { getGitHubAPIURL, GITHUB_API_URL } from './core/github.helpers';
import { UserFavoriteManager } from './core/user-favorite-manager';
import { CustomImportModule } from './custom-import.module';
import { AboutModule } from './features/about/about.module';
import { createGetApplicationAction } from './features/applications/application.service';
import { ApplicationsModule } from './features/applications/applications.module';
import { DashboardModule } from './features/dashboard/dashboard.module';
import { getFullEndpointApiUrl, initEndpointExtensions } from './features/endpoints/endpoint-helpers';
import { HomeModule } from './features/home/home.module';
import { LoginModule } from './features/login/login.module';
import { NoEndpointsNonAdminComponent } from './features/no-endpoints-non-admin/no-endpoints-non-admin.component';
import { ServiceCatalogModule } from './features/service-catalog/service-catalog.module';
import { SetupModule } from './features/setup/setup.module';
import { LoggedInService } from './logged-in.service';
import { ApplicationStateService } from './shared/components/application-state/application-state.service';
import { favoritesConfigMapper } from './shared/components/favorites-meta-card/favorite-config-mapper';
import { SharedModule } from './shared/shared.module';
import { GetAllEndpoints } from './store/actions/endpoint.actions';
import { GetOrganization } from './store/actions/organization.actions';
import { RouterNav } from './store/actions/router.actions';
import { GetSpace } from './store/actions/space.actions';
import { UpdateUserFavoriteMetadataAction } from './store/actions/user-favourites-actions/update-user-favorite-metadata-action';
import { AppState } from './store/app-state';
import { applicationSchemaKey, endpointSchemaKey, organizationSchemaKey, spaceSchemaKey } from './store/helpers/entity-factory';
import { getAPIRequestDataState } from './store/selectors/api.selectors';
import { AppStoreModule } from './store/store.module';
import { APIResource } from './store/types/api.types';
import { EndpointModel } from './store/types/endpoint.types';
import { IRequestDataState } from './store/types/entity.types';
import { IEndpointFavMetadata, IFavoriteMetadata, UserFavorite } from './store/types/user-favorites.types';
import { XSRFModule } from './xsrf.module';
import { initEndpointExtensions } from './features/endpoints/endpoint-helpers';
import { recentlyVisitedSelector } from './store/selectors/recently-visitied.selectors';
import { SetRecentlyVisitedEntityAction } from './store/actions/recently-visited.actions';
import { CustomReuseStrategy } from './route-reuse-stragegy';


// Create action for router navigation. See
// - https://github.com/ngrx/platform/issues/68
Expand Down Expand Up @@ -86,14 +111,215 @@ export class CustomRouterStateSerializer
ExtensionService,
DynamicExtensionRoutes,
{ provide: GITHUB_API_URL, useFactory: getGitHubAPIURL },
{ provide: RouterStateSerializer, useClass: CustomRouterStateSerializer } // Create action for router navigation
{ provide: RouterStateSerializer, useClass: CustomRouterStateSerializer }, // Create action for router navigation
{ provide: RouteReuseStrategy, useClass: CustomReuseStrategy }
],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(private ext: ExtensionService) {
private userFavoriteManager: UserFavoriteManager;
constructor(
ext: ExtensionService,
private permissionService: CurrentUserPermissionsService,
private appStateService: ApplicationStateService,
private store: Store<AppState>,
) {
ext.init();
// Init Auth Types and Endpoint Types provided by extensions
initEndpointExtensions(ext);
// Once the CF modules become an extension point, these should be moved to a CF specific module
this.registerCfFavoriteMappers();
this.userFavoriteManager = new UserFavoriteManager(store);
const allFavs$ = this.userFavoriteManager.getAllFavorites();
const recents$ = this.store.select(recentlyVisitedSelector);
const debouncedApiRequestData$ = this.store.select(getAPIRequestDataState).pipe(debounceTime(2000));
debouncedApiRequestData$.pipe(
withLatestFrom(allFavs$)
).subscribe(
([entities, [favoriteGroups, favorites]]) => {
Object.keys(favoriteGroups).forEach(endpointId => {
const favoriteGroup = favoriteGroups[endpointId];
if (!favoriteGroup.ethereal) {
const endpointFavorite = favorites[endpointId];
this.syncFavorite(endpointFavorite, entities);
}
favoriteGroup.entitiesIds.forEach(id => {
const favorite = favorites[id];
this.syncFavorite(favorite, entities);
});
});
}
);

debouncedApiRequestData$.pipe(
withLatestFrom(recents$)
).subscribe(
([entities, recents]) => {
Object.values(recents.entities).forEach(recentEntity => {
const mapper = favoritesConfigMapper.getMapperFunction(recentEntity);
if (entities[recentEntity.entityType] && entities[recentEntity.entityType][recentEntity.entityId]) {
const entity = entities[recentEntity.entityType][recentEntity.entityId];
const entityToMetadata = favoritesConfigMapper.getEntityMetadata(recentEntity, entity);
const name = mapper(entityToMetadata).name;
if (name && name !== recentEntity.name) {
this.store.dispatch(new SetRecentlyVisitedEntityAction({
...recentEntity,
name
}));
}
}
});
}
);
}

private syncFavorite(favorite: UserFavorite<IFavoriteMetadata>, entities: IRequestDataState) {
if (favorite) {
const entity = entities[favorite.entityType][favorite.entityId || favorite.endpointId];
if (entity) {
const newMetadata = favoritesConfigMapper.getEntityMetadata(favorite, entity);
if (this.metadataHasChanged(favorite.metadata, newMetadata)) {
this.store.dispatch(new UpdateUserFavoriteMetadataAction({
...favorite,
metadata: newMetadata
}));
}
}
}
}

private metadataHasChanged(oldMeta: IFavoriteMetadata, newMeta: IFavoriteMetadata) {
if ((!oldMeta && newMeta) || (oldMeta && !newMeta)) {
return true;
}
const oldKeys = Object.keys(oldMeta);
const newKeys = Object.keys(newMeta);
const oldValues = Object.values(oldMeta);
const newValues = Object.values(newMeta);
if (oldKeys.length !== newKeys.length) {
return true;
}
if (oldKeys.sort().join(',') !== newKeys.sort().join(',')) {
return true;
}
if (oldValues.sort().join(',') !== newValues.sort().join(',')) {
return true;
}
return false;
}

private registerCfFavoriteMappers() {
const endpointType = 'cf';

this.registerCfEndpointMapper(endpointType);
this.registerCfApplicationMapper(endpointType);
this.registerCfSpaceMapper(endpointType);
this.registerCfOrgMapper(endpointType);
}
private registerCfEndpointMapper(endpointType: string) {
favoritesConfigMapper.registerFavoriteConfig<EndpointModel, IEndpointFavMetadata>({
endpointType,
entityType: endpointSchemaKey
},
'Cloud Foundry',
(endpoint: IEndpointFavMetadata) => ({
type: endpointType,
routerLink: `/cloud-foundry/${endpoint.guid}`,
lines: [
['Address', endpoint.address],
['User', endpoint.user],
['Admin', endpoint.admin]
],
name: endpoint.name,
menuItems: [
{
label: 'Deploy application',
action: () => this.store.dispatch(new RouterNav({ path: ['applications/deploy'], query: { endpointGuid: endpoint.guid } })),
can: this.permissionService.can(CurrentUserPermissions.APPLICATION_CREATE)
}
]
}),
() => new GetAllEndpoints(false),
endpoint => ({
name: endpoint.name,
guid: endpoint.guid,
address: getFullEndpointApiUrl(endpoint),
user: endpoint.user ? endpoint.user.name : undefined,
admin: endpoint.user ? endpoint.user.admin ? 'Yes' : 'No' : undefined
})
);
}

private registerCfApplicationMapper(endpointType: string) {
favoritesConfigMapper.registerFavoriteConfig<APIResource<IApp>, IAppFavMetadata>({
endpointType,
entityType: applicationSchemaKey
},
'Application',
(app: IAppFavMetadata) => {
return {
type: applicationSchemaKey,
routerLink: `/applications/${app.cfGuid}/${app.guid}/summary`,
name: app.name
};
},
favorite => createGetApplicationAction(favorite.entityId, favorite.endpointId),
app => ({
guid: app.metadata.guid,
cfGuid: app.entity.cfGuid,
name: app.entity.name,
})
);
}

private registerCfSpaceMapper(endpointType: string) {
favoritesConfigMapper.registerFavoriteConfig<APIResource<ISpace>, ISpaceFavMetadata>({
endpointType,
entityType: spaceSchemaKey
},
'Space',
(space: ISpaceFavMetadata) => {
return {
type: spaceSchemaKey,
routerLink: `/cloud-foundry/${space.cfGuid}/organizations/${space.orgGuid}/spaces/${space.guid}/summary`,
name: space.name
};
},
favorite => new GetSpace(favorite.entityId, favorite.endpointId),
space => ({
guid: space.metadata.guid,
orgGuid: space.entity.organization_guid ? space.entity.organization_guid : space.entity.organization.metadata.guid,
name: space.entity.name,
cfGuid: space.entity.cfGuid,
})
);

}
private registerCfOrgMapper(endpointType: string) {

favoritesConfigMapper.registerFavoriteConfig<APIResource<IOrganization>, IOrgFavMetadata>({
endpointType,
entityType: organizationSchemaKey
},
'Organization',
(org: IOrgFavMetadata) => ({
type: organizationSchemaKey,
routerLink: `/cloud-foundry/${org.cfGuid}/organizations/${org.guid}`,
name: org.name
}),
favorite => new GetOrganization(favorite.entityId, favorite.endpointId),
org => ({
guid: org.metadata.guid,
status: this.getOrgStatus(org),
name: org.entity.name,
cfGuid: org.entity.cfGuid,
})
);
}
private getOrgStatus(org: APIResource<IOrganization>) {
if (!org || !org.entity || !org.entity.status) {
return 'Unknown';
}
return org.entity.status.charAt(0).toUpperCase() + org.entity.status.slice(1);
}
}
16 changes: 7 additions & 9 deletions src/frontend/app/app.routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ import { UpgradePageComponent } from './features/setup/upgrade-page/upgrade-page
import { SharedModule } from './shared/shared.module';
import { PageNotFoundComponentComponent } from './core/page-not-found-component/page-not-found-component.component';
import { DomainMismatchComponent } from './features/setup/domain-mismatch/domain-mismatch.component';
import { environment } from '../environments/environment';
import { CustomRoutingImportModule } from './custom-import.module';

const appRoutes: Routes = [
{ path: '', redirectTo: 'applications', pathMatch: 'full' },
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'uaa', component: ConsoleUaaWizardComponent },
{ path: 'upgrade', component: UpgradePageComponent },
{ path: 'domainMismatch', component: DomainMismatchComponent },
Expand All @@ -29,19 +28,18 @@ const appRoutes: Routes = [
canActivate: [AuthGuardService, EndpointsService],
children: [
{
path: 'dashboard', component: HomePageComponent,
path: 'home', component: HomePageComponent,
data: {
stratosNavigation: {
text: 'Dashboard',
matIcon: 'assessment',
// Experimental - only show in development
hidden: observableOf(environment.production),
text: 'Home',
matIcon: 'home',
position: 10
}
}
},
{
path: 'applications', loadChildren: 'app/features/applications/applications.module#ApplicationsModule',
path: 'applications',
loadChildren: 'app/features/applications/applications.module#ApplicationsModule',
data: {
stratosNavigation: {
text: 'Applications',
Expand Down Expand Up @@ -120,7 +118,7 @@ const appRoutes: Routes = [
CommonModule,
CoreModule,
SharedModule,
RouterModule.forRoot(appRoutes),
RouterModule.forRoot(appRoutes, { onSameUrlNavigation: 'reload' }),
CustomRoutingImportModule,
]
})
Expand Down
22 changes: 22 additions & 0 deletions src/frontend/app/cf-favourite-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IFavoriteMetadata } from './store/types/user-favorites.types';

export interface ISpaceFavMetadata extends IFavoriteMetadata {
guid: string;
orgGuid: string;
name: string;
cfGuid: string;
}

export interface IOrgFavMetadata extends IFavoriteMetadata {
guid: string;
status: string;
name: string;
cfGuid: string;
}


export interface IAppFavMetadata extends IFavoriteMetadata {
guid: string;
cfGuid: string;
name: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Directive, HostListener } from '@angular/core';
})
export class ClickStopPropagationDirective {
@HostListener('click', ['$event'])
public onClick(event: any): void {
public onClick(event: any) {
event.stopPropagation();
}
}
Loading

0 comments on commit 96af329

Please sign in to comment.