Skip to content
This repository has been archived by the owner on Jan 24, 2023. It is now read-only.

Commit

Permalink
Helm: Allow users to view and use repositories from Helm Hub (#463)
Browse files Browse the repository at this point in the history
* Improve presentation and fix issue with development versions

* WIP

* Add support for Helm Upgrade and Helm history

* Remove console logging

* Add comment

* Minor tweaks following self-review

* WIP

* fix install button

* Fix actual helm install

* Remove helm repos view
- need to add endpoint type specific actions to endpoints list view (helm sync)

* Show per endpoint type actions in endpoints table
- use for helm sync

* Tidy up registration step

* Tidy up provider interceptor pattern

* Tidy up services, fix charts list filter by repo/helm hub

* Add endpoint unRegisterable, make helm types sub types, tidy up

* Remove debug logging

* Fix whitespace

* Minor tidy ups

* Fix compile issue

* Fixed versions again, and bugs following subtype split

* Fix unit tests

* Add db migration script to update existing helm endpoints with repo subtype

* Fix front-end unit tests

* Always show upgrade button

* Minor entity store type updates

* Convert unRegisterable to registeredLimit

* Changes following review

* Fix display of helm type in favourite cards
- favourite cards are NOT by subtype, so parent type must have label info

* Multiple Fixes
- Fix display of disconected text & box in endpoint favourite card
- Don't go out to fetch helm schema if there's none defined
- Fixes following merge

* Align icons in workload summary page

* Fix lint failures

* Address PR feedback

* Revert

* Remove description when checking for similar charts

* Only show button when the helm chart is available

* Improve types, start work on helm hub upgrade

* Fixes following merge

* Remove TODOs

Co-authored-by: Neil MacDougall <[email protected]>
Co-authored-by: Neil MacDougall <[email protected]>
  • Loading branch information
3 people committed Sep 11, 2020
1 parent c44204a commit 7991978
Show file tree
Hide file tree
Showing 60 changed files with 1,127 additions and 622 deletions.
3 changes: 0 additions & 3 deletions src/frontend/packages/core/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,6 @@ const storeDebugImports = environment.production ? [] : [
})
class AppStoreDebugModule { }

/**
* `HttpXsrfTokenExtractor` which retrieves the token from a cookie.
*/

@NgModule({
declarations: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { first, map } from 'rxjs/operators';
import { combineLatest, filter, first, map } from 'rxjs/operators';

import { RouterNav } from '../../../../../../store/src/actions/router.actions';
import { GeneralEntityAppState } from '../../../../../../store/src/app-state';
import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog';
import {
StratosCatalogEndpointEntity,
} from '../../../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity';
import { IStratosEndpointDefinition } from '../../../../../../store/src/entity-catalog/entity-catalog.types';
import { selectSessionData } from '../../../../../../store/src/reducers/auth.reducer';
import { stratosEntityCatalog } from '../../../../../../store/src/stratos-entity-catalog';
import { BASE_REDIRECT_QUERY } from '../../../../shared/components/stepper/stepper.types';
import { TileConfigManager } from '../../../../shared/components/tile/tile-selector.helpers';
import { ITileConfig, ITileData } from '../../../../shared/components/tile/tile-selector.types';
Expand All @@ -17,6 +21,10 @@ interface ICreateEndpointTilesData extends ITileData {
parentType: string;
}

type EndpointsByType = {
[endpointType: string]: number,
};

@Component({
selector: 'app-create-endpoint-base-step',
templateUrl: './create-endpoint-base-step.component.html',
Expand Down Expand Up @@ -67,7 +75,7 @@ export class CreateEndpointBaseStepComponent {
}
// Both A & B are equal. Unlikely.
return 0;
}
};

get selectedTile() {
return this.pSelectedTile;
Expand All @@ -83,13 +91,15 @@ export class CreateEndpointBaseStepComponent {
}));
}
}
constructor(public store: Store<GeneralEntityAppState>, ) {
constructor(public store: Store<GeneralEntityAppState>,) {
// Need to filter the endpoint types on the tech preview flag
this.tileSelectorConfig$ = store.select(selectSessionData()).pipe(
combineLatest(this.getEndpointTypesByCount()),
first(),
map(sessionData => {
map(([sessionData, endpointTypesByCount]) => {
const techPreviewIsEnabled = sessionData.config.enableTechPreview || false;
return entityCatalog.getAllEndpointTypes(techPreviewIsEnabled)
.filter(endpoint => this.filterByEndpointCount(endpoint, endpointTypesByCount))
.sort((endpointA, endpointB) => this.sortEndpointTiles(endpointA.definition, endpointB.definition))
.map(catalogEndpoint => {
const endpoint = catalogEndpoint.definition;
Expand All @@ -112,4 +122,38 @@ export class CreateEndpointBaseStepComponent {
);
}

private getEndpointDefinitionKey = (type: string, subType: string): string => type + '_sep_' + subType;
private getEndpointTypesByCount = (): Observable<EndpointsByType> =>
stratosEntityCatalog.endpoint.store.getAll.getPaginationService().entities$.pipe(
filter(endpoints => !!endpoints),
map(endpoints => {
const endpointsByType: { [endpointType: string]: number; } = {};
return endpoints.reduce((res, endpoint) => {
const type = this.getEndpointDefinitionKey(endpoint.cnsi_type, endpoint.sub_type);
if (!res[type]) {
res[type] = 0;
}
res[type]++;
return res;
}, endpointsByType);
}),
);
private filterByEndpointCount = (endpoint: StratosCatalogEndpointEntity, endpointTypesByCount: EndpointsByType) => {
// No limit applied, always show endpoint
if (typeof endpoint.definition.registeredLimit !== 'number') {
return true;
}
// Zero limit, never show endpoint
if (endpoint.definition.registeredLimit === 0) {
return false;
}

// Check that the limit is not exceeded by endpoints already registered
const type = endpoint.definition.parentType ?
this.getEndpointDefinitionKey(endpoint.definition.parentType, endpoint.definition.type) :
this.getEndpointDefinitionKey(endpoint.definition.type, '');
const count = endpointTypesByCount[type] || 0;
return count < endpoint.definition.registeredLimit;
};

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
font-size: 40px;
height: 40px;
margin-right: 12px;
text-align: center;
width: 40px;
}
&__label {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@
font-size: 14px;
font-weight: normal;
margin-right: 8px;
margin-top: -2px;
padding: 2px 4px;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<mat-card class="meta-card {{status$ && statusBackground? (status$ | async): '' }}" [ngClass]="{'meta-card-pointer': clickAction}"
(click)="clickAction ? clickAction() : null">
<mat-card class="meta-card {{status$ && statusBackground? (status$ | async): '' }}"
[ngClass]="{'meta-card-pointer': clickAction}" (click)="clickAction ? clickAction() : null">
<div *ngIf="isDeleting$ | async" class="meta-card__deleting-overlay">
<div class="meta-card__deleting-overlay-inner">
<div class="meta-card__deleting-text">Deleting</div>
Expand All @@ -9,7 +9,8 @@
<app-card-status *ngIf="status$" [status$]="status$">
</app-card-status>
<mat-card-header class="meta-card__header" *ngIf="title">
<div [ngClass]="statusIconByTitle ? 'meta-card__header-container__title--with-icon' : 'meta-card__header-container__title'">
<div
[ngClass]="statusIconByTitle ? 'meta-card__header-container__title--with-icon' : 'meta-card__header-container__title'">
<div
[ngClass]="statusIconByTitle ? 'meta-card__header-container__title__content--with-icon' : 'meta-card__header-container__title__content'">
<ng-container *ngTemplateOutlet="title.content"></ng-container>
Expand All @@ -18,20 +19,24 @@
<ng-container *ngTemplateOutlet="statusIconTmple"></ng-container>
</div>
</div>
<app-entity-favorite-star [confirmRemoval]="confirmFavoriteRemoval" class="meta-card__favorite" *ngIf="favorite" [favorite]="favorite">
<app-entity-favorite-star [confirmRemoval]="confirmFavoriteRemoval" class="meta-card__favorite" *ngIf="favorite"
[favorite]="favorite">
</app-entity-favorite-star>
<div class="meta-card__header-container__actions" *ngIf="actionMenu && (showMenu$ | async)" appClickStopPropagation>
<button mat-icon-button class="meta-card__header__button" color="basic" [matMenuTriggerFor]="menu" [disabled]="isDeleting$ | async">
<button mat-icon-button class="meta-card__header__button" color="basic" [matMenuTriggerFor]="menu"
[disabled]="isDeleting$ | async">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu class="meta-card__header__popup" #menu="matMenu" xPosition="before" >
<mat-menu class="meta-card__header__popup" #menu="matMenu" xPosition="before">
<ng-container *ngFor="let menuItem of actionMenu">
<button class="meta-card__header__popup__btn" [disabled]="menuItem.disabled | async" mat-menu-item *ngIf="menuItem.can | async"
(click)="menuItem.action()">
<mat-icon *ngIf="menuItem.icon">{{menuItem.icon}}</mat-icon>
<span>{{menuItem.label}}</span>
</button>
<div *ngIf="menuItem.separator" class="meta-card__header__popup-separator"></div>
<ng-container *ngIf="menuItem.can | async">
<button *ngIf="!menuItem.separator" class="meta-card__header__popup__btn"
[disabled]="menuItem.disabled | async" mat-menu-item (click)="menuItem.action()">
<mat-icon *ngIf="menuItem.icon">{{menuItem.icon}}</mat-icon>
<span>{{menuItem.label}}</span>
</button>
<div *ngIf="menuItem.separator" class="meta-card__header__popup-separator"></div>
</ng-container>
</ng-container>
</mat-menu>
</div>
Expand All @@ -48,6 +53,7 @@
</mat-card-content>
</mat-card>
<ng-template #statusIconTmple>
<app-application-state-icon *ngIf="statusIcon && status$" [status]="status$ | async" matTooltip="{{statusIconTooltip}}">
<app-application-state-icon *ngIf="statusIcon && status$" [status]="status$ | async"
matTooltip="{{statusIconTooltip}}">
</app-application-state-icon>
</ng-template>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, ContentChild, ContentChildren, Input, OnDestroy, QueryList } from '@angular/core';
import { combineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
import { combineLatest, Observable, of as observableOf, of, Subscription } from 'rxjs';
import { first, map, tap } from 'rxjs/operators';

import { FavoritesConfigMapper } from '../../../../../../../../store/src/favorite-config-mapper';
Expand All @@ -13,7 +13,7 @@ import { MetaCardItemComponent } from '../meta-card-item/meta-card-item.componen
import { MetaCardTitleComponent } from '../meta-card-title/meta-card-title.component';


export function createMetaCardMenuItemSeparator() {
export function createMetaCardMenuItemSeparator(): MenuItem {
return {
label: '-',
separator: true,
Expand Down Expand Up @@ -86,15 +86,18 @@ export class MetaCardComponent implements OnDestroy {
this.pActionMenu = actionMenu.map(menuItem => {
if (!menuItem.can) {
menuItem.separator = menuItem.label === '-';
menuItem.can = observableOf(!menuItem.separator);
menuItem.can = of(true);
}
if (!menuItem.disabled) {
menuItem.disabled = observableOf(false);
}
return menuItem;
});

this.showMenu$ = combineLatest(actionMenu.map(menuItem => menuItem.can)).pipe(
const nonSeparators = actionMenu
.filter(menuItem => !menuItem.separator)
.map(menuItem => menuItem.can);
this.showMenu$ = combineLatest(nonSeparators).pipe(
map(cans => cans.some(can => can))
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,18 @@ export class EndpointCardComponent extends CardCell<EndpointModel> implements On
@Input('dataSource')
set dataSource(ds: BaseEndpointsDataSource) {
this.pDs = ds;

// Don't show card menu if the ds only provides a single endpoint type (for instance the cf endpoint page)
if (ds && !ds.dsEndpointType && !this.cardMenu) {
this.cardMenu = this.endpointListHelper.endpointActions().map(endpointAction => ({
label: endpointAction.label,
action: () => endpointAction.action(this.pRow),
can: endpointAction.createVisible(this.rowObs)
}));
this.cardMenu = this.endpointListHelper.endpointActions(true).map(endpointAction => {
const separator = endpointAction.label === '-';
return {
label: endpointAction.label,
action: () => endpointAction.action(this.pRow),
can: endpointAction.createVisible ? endpointAction.createVisible(this.rowObs) : null,
separator
};
});

// Add a copy address to clipboard
this.cardMenu.push(createMetaCardMenuItemSeparator());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ComponentFactoryResolver, ComponentRef, Injectable, ViewContainerRef } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { combineLatest, Observable } from 'rxjs';
import { combineLatest, Observable, of } from 'rxjs';
import { map, pairwise } from 'rxjs/operators';

import { RouterNav } from '../../../../../../../store/src/actions/router.actions';
Expand All @@ -19,6 +19,7 @@ import {
import { SnackBarService } from '../../../../services/snackbar.service';
import { ConfirmationDialogConfig } from '../../../confirmation-dialog.config';
import { ConfirmationDialogService } from '../../../confirmation-dialog.service';
import { createMetaCardMenuItemSeparator } from '../../list-cards/meta-card/meta-card-base/meta-card.component';
import { IListAction } from '../../list.component.types';
import { TableCellCustom } from '../../list.types';

Expand All @@ -37,6 +38,27 @@ function isEndpointListDetailsComponent(obj: any): EndpointListDetailsComponent
return obj ? obj.isEndpointListDetailsComponent ? obj as EndpointListDetailsComponent : null : null;
}

/**
* Combine the result of all createVisibles functions for the given actions
*/
function combineCreateVisibles(
customActions: IListAction<EndpointModel>[]
): (row$: Observable<EndpointModel>) => Observable<boolean> {
const createVisiblesFns = customActions
.map(action => action.createVisible)
.filter(createVisible => !!createVisible);
if (createVisiblesFns.length === 0) {
return () => of(false);
} else {
return (row$: Observable<EndpointModel>) => {
const createVisibles = createVisiblesFns.map(createVisible => createVisible(row$));
return combineLatest(createVisibles).pipe(
map(allRes => allRes.some(res => res))
);
};
}
}

@Injectable()
export class EndpointListHelper {
constructor(
Expand All @@ -48,7 +70,23 @@ export class EndpointListHelper {
private snackBarService: SnackBarService,
) { }

endpointActions(): IListAction<EndpointModel>[] {
endpointActions(includeSeparators = false): IListAction<EndpointModel>[] {
// Add any additional actions that are per endpoint type
const customActions = entityCatalog.getAllEndpointTypes()
.map(endpoint => endpoint.definition.endpointListActions)
.filter(endpointListActions => !!endpointListActions)
.map(endpointListActions => endpointListActions(this.store))
.reduce((res, actions) => res.concat(actions), []);

if (includeSeparators && customActions.length) {
// Only show the separator if we have custom actions to separate AND at least one is visible
const createVisibleFn = combineCreateVisibles(customActions);
customActions.splice(0, 0, {
...createMetaCardMenuItemSeparator(),
createVisible: createVisibleFn
});
}

return [
{
action: (item) => {
Expand Down Expand Up @@ -126,7 +164,8 @@ export class EndpointListHelper {
label: 'Edit endpoint',
description: 'Edit the endpoint',
createVisible: () => this.currentUserPermissionsService.can(StratosCurrentUserPermissions.ENDPOINT_REGISTER)
}
},
...customActions
];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';

import { ITileConfig } from '../tile/tile-selector.types';


Expand All @@ -12,6 +13,9 @@ export class TileSelectorComponent {
public hiddenOptions: ITileConfig[] = [];
public showingMore = false;
@Input() set options(options: ITileConfig[]) {
if (!options) {
return;
}
const groupedOptions = options.reduce((grouped, option) => {
if (option.hidden) {
grouped.hidden.push(option);
Expand All @@ -20,9 +24,9 @@ export class TileSelectorComponent {
}
return grouped;
}, {
show: [],
hidden: []
});
show: [],
hidden: []
});
this.pOptions = groupedOptions.show;
this.hiddenOptions = groupedOptions.hidden;
}
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/packages/core/xsrf.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { tap } from 'rxjs/operators';

const STRATOS_XSRF_HEADER_NAME = 'X-XSRF-Token';

/**
* `HttpXsrfTokenExtractor` which retrieves the token from a cookie.
*/
@Injectable()
export class HttpXsrfHeaderExtractor implements HttpXsrfTokenExtractor {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class EntityCatalog {
return Array.from(this.endpoints.values());
}

public getAllEndpointTypes(techPreviewEnabled = false) {
public getAllEndpointTypes(techPreviewEnabled = false): StratosCatalogEndpointEntity[] {
const baseEndpoints = Array.from(this.endpoints.values())
.filter(item => !item.definition.techPreview || item.definition.techPreview && techPreviewEnabled);
return baseEndpoints.reduce((allEndpoints, baseEndpoint) => {
Expand Down
Loading

0 comments on commit 7991978

Please sign in to comment.