From 1b6fe604f4250577a65d6b46c885fa620a049b01 Mon Sep 17 00:00:00 2001 From: Jeff Herald Date: Mon, 21 Dec 2020 15:39:32 -0500 Subject: [PATCH 01/10] feat(kubernetes): Raw resources UI MVP --- .../modules/core/src/config/settings.ts | 1 + .../src/presentation/icons/iconsByName.ts | 2 + .../presentation/icons/vectors/spMenuK8s.svg | 6 + app/scripts/modules/kubernetes/src/index.ts | 1 + .../kubernetes/src/kubernetes.module.ts | 19 +- .../rawResource/component/K8sResources.tsx | 111 +++++++ .../component/K8sResourcesFilters.tsx | 108 ++++++ .../rawResource/component/RawResourceUtils.ts | 9 + .../component/group/RawResouceGroup.tsx | 41 +++ .../component/group/RawResource.less | 81 +++++ .../component/group/RawResource.tsx | 59 ++++ .../component/group/RawResourceDetails.tsx | 232 +++++++++++++ .../component/group/RawResourceGroups.tsx | 51 +++ .../rawResource/controller/FiltersPubSub.ts | 29 ++ .../kubernetes/src/rawResource/index.ts | 1 + .../src/rawResource/model/resource.ts | 18 + .../src/rawResource/rawResource.dataSource.ts | 39 +++ .../src/rawResource/rawResource.module.ts | 8 + .../src/rawResource/rawResource.states.ts | 61 ++++ package.json | 2 +- settings.js | 2 + yarn.lock | 307 +----------------- 22 files changed, 879 insertions(+), 309 deletions(-) create mode 100644 app/scripts/modules/core/src/presentation/icons/vectors/spMenuK8s.svg create mode 100644 app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.tsx create mode 100644 app/scripts/modules/kubernetes/src/rawResource/component/K8sResourcesFilters.tsx create mode 100644 app/scripts/modules/kubernetes/src/rawResource/component/RawResourceUtils.ts create mode 100644 app/scripts/modules/kubernetes/src/rawResource/component/group/RawResouceGroup.tsx create mode 100644 app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.less create mode 100644 app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.tsx create mode 100644 app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceDetails.tsx create mode 100644 app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceGroups.tsx create mode 100644 app/scripts/modules/kubernetes/src/rawResource/controller/FiltersPubSub.ts create mode 100644 app/scripts/modules/kubernetes/src/rawResource/index.ts create mode 100644 app/scripts/modules/kubernetes/src/rawResource/model/resource.ts create mode 100644 app/scripts/modules/kubernetes/src/rawResource/rawResource.dataSource.ts create mode 100644 app/scripts/modules/kubernetes/src/rawResource/rawResource.module.ts create mode 100644 app/scripts/modules/kubernetes/src/rawResource/rawResource.states.ts diff --git a/app/scripts/modules/core/src/config/settings.ts b/app/scripts/modules/core/src/config/settings.ts index 983c98ce8e0..eaf7b96c9ef 100644 --- a/app/scripts/modules/core/src/config/settings.ts +++ b/app/scripts/modules/core/src/config/settings.ts @@ -47,6 +47,7 @@ export interface IFeatures { snapshots?: boolean; savePipelinesStageEnabled?: boolean; functions?: boolean; + kubernetesRawResources?: boolean; } export interface IDockerInsightSettings { diff --git a/app/scripts/modules/core/src/presentation/icons/iconsByName.ts b/app/scripts/modules/core/src/presentation/icons/iconsByName.ts index ff3fec5e289..a3d4af32965 100644 --- a/app/scripts/modules/core/src/presentation/icons/iconsByName.ts +++ b/app/scripts/modules/core/src/presentation/icons/iconsByName.ts @@ -66,6 +66,7 @@ import { ReactComponent as spMenuCanaryConfig } from './vectors/spMenuCanaryConf import { ReactComponent as spMenuCanaryReport } from './vectors/spMenuCanaryReport.svg'; import { ReactComponent as spMenuClusters } from './vectors/spMenuClusters.svg'; import { ReactComponent as spMenuConfig } from './vectors/spMenuConfig.svg'; +import { ReactComponent as spMenuK8s } from './vectors/spMenuK8s.svg'; import { ReactComponent as spMenuLoadBalancers } from './vectors/spMenuLoadBalancers.svg'; import { ReactComponent as spMenuMeme } from './vectors/spMenuMeme.svg'; import { ReactComponent as spMenuPager } from './vectors/spMenuPager.svg'; @@ -215,6 +216,7 @@ export const iconsByName = { spMenuCanaryReport, spMenuClusters, spMenuConfig, + spMenuK8s, spMenuLoadBalancers, spMenuMeme, spMenuPager, diff --git a/app/scripts/modules/core/src/presentation/icons/vectors/spMenuK8s.svg b/app/scripts/modules/core/src/presentation/icons/vectors/spMenuK8s.svg new file mode 100644 index 00000000000..2e07b390643 --- /dev/null +++ b/app/scripts/modules/core/src/presentation/icons/vectors/spMenuK8s.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/scripts/modules/kubernetes/src/index.ts b/app/scripts/modules/kubernetes/src/index.ts index 6d89fb7dc83..a254ff93d24 100644 --- a/app/scripts/modules/kubernetes/src/index.ts +++ b/app/scripts/modules/kubernetes/src/index.ts @@ -4,3 +4,4 @@ export * from './serverGroup'; export * from './securityGroup'; export * from './manifest'; export * from './loadBalancer'; +export * from './rawResource'; diff --git a/app/scripts/modules/kubernetes/src/kubernetes.module.ts b/app/scripts/modules/kubernetes/src/kubernetes.module.ts index fe1fb13371c..f3a17c8309d 100644 --- a/app/scripts/modules/kubernetes/src/kubernetes.module.ts +++ b/app/scripts/modules/kubernetes/src/kubernetes.module.ts @@ -1,6 +1,11 @@ import { module } from 'angular'; -import { CloudProviderRegistry, STAGE_ARTIFACT_SELECTOR_COMPONENT_REACT, YAML_EDITOR_COMPONENT } from '@spinnaker/core'; +import { + CloudProviderRegistry, + STAGE_ARTIFACT_SELECTOR_COMPONENT_REACT, + SETTINGS, + YAML_EDITOR_COMPONENT, +} from '@spinnaker/core'; import { KUBERNETES_MANIFEST_DELETE_CTRL } from './manifest/delete/delete.controller'; import { KUBERNETES_MANIFEST_SCALE_CTRL } from './manifest/scale/scale.controller'; @@ -38,6 +43,8 @@ import { KUBERNETES_DISABLE_MANIFEST_STAGE } from './pipelines/stages/traffic/di import { KubernetesSecurityGroupReader } from './securityGroup/securityGroup.reader'; import { KUBERNETES_ROLLING_RESTART } from './manifest/rollout/RollingRestart'; +import { KUBERNETS_RAW_RESOURCE_MODULE } from './rawResource'; + import kubernetesLogo from './logo/kubernetes.logo.svg'; import './validation/applicationName.validator'; @@ -54,7 +61,7 @@ templates.keys().forEach(function (key) { export const KUBERNETES_MODULE = 'spinnaker.kubernetes'; -module(KUBERNETES_MODULE, [ +const requires = [ KUBERNETES_INSTANCE_DETAILS_CTRL, KUBERNETES_LOAD_BALANCER_DETAILS_CTRL, KUBERNETES_SECURITY_GROUP_DETAILS_CTRL, @@ -91,7 +98,13 @@ module(KUBERNETES_MODULE, [ KUBERNETES_DISABLE_MANIFEST_STAGE, STAGE_ARTIFACT_SELECTOR_COMPONENT_REACT, KUBERNETES_ROLLING_RESTART, -]).config(() => { +]; + +if (SETTINGS.feature.kubernetesRawResources) { + requires.push(KUBERNETS_RAW_RESOURCE_MODULE); +} + +module(KUBERNETES_MODULE, requires).config(() => { CloudProviderRegistry.registerProvider('kubernetes', { name: 'Kubernetes', logo: { diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.tsx b/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.tsx new file mode 100644 index 00000000000..cc1f8793581 --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.tsx @@ -0,0 +1,111 @@ +import { Application, ApplicationDataSource } from '@spinnaker/core'; +import React from 'react'; +import { FiltersPubSub } from '../controller/FiltersPubSub'; +import { KUBERNETS_RAW_RESOURCE_DATA_SOURCE_KEY } from '../rawResource.dataSource'; +import { RawResource } from './group/RawResource'; +import { RawResourceGroups } from './group/RawResourceGroups'; +import { IK8sResourcesFiltersState } from './K8sResourcesFilters'; + +export interface IK8sResourcesProps { + app: Application; +} + +interface IK8sResourcesState { + groupBy: string; + filters: IK8sResourcesFiltersState; + rawResources: IApiKubernetesResource[]; +} + +export class K8sResources extends React.Component { + private dataSource: ApplicationDataSource; + private filterPubSub: FiltersPubSub = FiltersPubSub.getInstance(this.props.app.name); + + constructor(props: IK8sResourcesProps) { + super(props); + this.dataSource = this.props.app.getDataSource(KUBERNETS_RAW_RESOURCE_DATA_SOURCE_KEY); + this.state = { + groupBy: 'none', + filters: null, + rawResources: [], + }; + + this.filterPubSub.subscribe(this.onFilterChange.bind(this)); + } + + public onFilterChange(message: IK8sResourcesFiltersState) { + this.setState({ ...this.state, filters: message }); + } + + public async componentDidMount() { + await this.dataSource.ready(); + + this.setState({ + ...this.state, + groupBy: this.state.groupBy, + rawResources: await this.dataSource.data.sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1)), + }); + } + + public onGroupByChange(event: React.ChangeEvent) { + this.setState({ + ...this.state, + groupBy: event.target.value, + }); + } + + public render() { + return ( +
+
+
+ + +
+
+ {this.state.groupBy === 'none' ? ( + <> + {...this.state.rawResources + .filter((resource) => this.matchFilters(resource)) + .map((resource) => )} + + ) : ( + this.matchFilters(resource))} + groupBy={this.state.groupBy} + > + )} +
+ ); + } + + private matchFilters(resource: IApiKubernetesResource) { + if (this.state.filters == null) { + return true; + } + const accountMatch = + Object.values(this.state.filters.accounts).every((x) => !x) || this.state.filters.accounts[resource.account]; + const kindMatch = + Object.values(this.state.filters.kinds).every((x) => !x) || this.state.filters.kinds[resource.kind]; + const namespaceMatch = + Object.values(this.state.filters.namespaces).every((x) => !x) || + this.state.filters.namespaces[resource.namespace]; + return accountMatch && kindMatch && namespaceMatch; + } +} diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/K8sResourcesFilters.tsx b/app/scripts/modules/kubernetes/src/rawResource/component/K8sResourcesFilters.tsx new file mode 100644 index 00000000000..519cf91f5dd --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/component/K8sResourcesFilters.tsx @@ -0,0 +1,108 @@ +import { Application, ApplicationDataSource, FilterSection, FilterCheckbox } from '@spinnaker/core'; +import React from 'react'; +import { FiltersPubSub } from '../controller/FiltersPubSub'; +import { RawResourceUtils } from './RawResourceUtils'; +import { KUBERNETS_RAW_RESOURCE_DATA_SOURCE_KEY } from '../rawResource.dataSource'; + +export interface IK8sResourcesFiltersProps { + app: Application; +} + +export interface IK8sResourcesFiltersState { + accounts: Record; + kinds: Record; + namespaces: Record; + displayNamespaces: Record; +} + +export class K8sResourcesFilters extends React.Component { + private dataSource: ApplicationDataSource; + private filterPubSub: FiltersPubSub = FiltersPubSub.getInstance(this.props.app.name); + + constructor(props: IK8sResourcesFiltersProps) { + super(props); + this.dataSource = this.props.app.getDataSource(KUBERNETS_RAW_RESOURCE_DATA_SOURCE_KEY); + + this.state = { + accounts: {}, + kinds: {}, + namespaces: {}, + displayNamespaces: {}, + }; + } + + public async componentDidMount() { + await this.dataSource.ready(); + const ns = Object.assign({}, ...this.dataSource.data.map((resource) => ({ [resource.namespace]: false }))); + const displayNs = { ...ns }; + if ('' in displayNs) { + delete displayNs['']; + displayNs[RawResourceUtils.GLOBAL_LABEL] = false; + } + this.setState({ + accounts: Object.assign({}, ...this.dataSource.data.map((resource) => ({ [resource.account]: false }))), + kinds: Object.assign({}, ...this.dataSource.data.map((resource) => ({ [resource.kind]: false }))), + namespaces: ns, + displayNamespaces: displayNs, + }); + } + + public render() { + return ( +
+ + {...Object.keys(this.state.kinds) + .sort((a, b) => (a.toLowerCase() > b.toLowerCase() ? 1 : -1)) + .map((key) => ( + + ))} + + + {...Object.keys(this.state.accounts) + .sort((a, b) => (a.toLowerCase() > b.toLowerCase() ? 1 : -1)) + .map((key) => ( + + ))} + + + {...Object.keys(this.state.displayNamespaces) + .sort((a, b) => (a.toLowerCase() > b.toLowerCase() ? 1 : -1)) + .map((key) => ( + + ))} + +
+ ); + } + + private onNsCheckbox() { + for (const p in this.state.displayNamespaces) { + if (p == RawResourceUtils.GLOBAL_LABEL) { + this.state.namespaces[''] = this.state.displayNamespaces[p]; + } + this.state.namespaces[p] = this.state.displayNamespaces[p]; + } + this.setState(this.state); + this.filterPubSub.publish(this.state); + } + + private onCheckbox() { + this.setState(this.state); + this.filterPubSub.publish(this.state); + } +} diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/RawResourceUtils.ts b/app/scripts/modules/kubernetes/src/rawResource/component/RawResourceUtils.ts new file mode 100644 index 00000000000..792829aa57f --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/component/RawResourceUtils.ts @@ -0,0 +1,9 @@ +export class RawResourceUtils { + static namespaceDisplayName(ns: string): string { + if (ns === null || ns == '') { + return RawResourceUtils.GLOBAL_LABEL; + } + return ns; + } + static GLOBAL_LABEL = '(global)'; +} diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResouceGroup.tsx b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResouceGroup.tsx new file mode 100644 index 00000000000..8b7223b125b --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResouceGroup.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import './RawResource.less'; + +interface IRawResourceGroupProps { + title: string; + resources?: IApiKubernetesResource[]; +} + +interface IRawResourceGroupState { + open: boolean; +} + +export class RawResourceGroup extends React.Component { + constructor(props: IRawResourceGroupProps) { + super(props); + + this.state = { + open: true, + }; + } + + public render() { + return ( +
+
+ +
+

{this.props.title}

+
+
+
{this.props.children}
+
+ ); + } + + private onHeaderClick() { + this.setState({ + open: !this.state.open, + }); + } +} diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.less b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.less new file mode 100644 index 00000000000..fd0e2dd8a45 --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.less @@ -0,0 +1,81 @@ +.raw-resource-card { + background-color: white; + padding: 1em; + margin-top: 1em; + margin-left: 3em; + border: thin solid lightgrey; +} + +.raw-resource-column { + display: flex; +} + +.raw-resource-details { + color: grey; + display: flex; +} + +.raw-resource-details-column { + margin-right: 5em; +} + +.raw-resource-details-column-label { + margin-right: 1em; +} + +.raw-resource-group { + margin-top: 2em; +} + +.raw-resource-group-header { + display: flex; + height: 37px; + padding: 0; + color: var(--color-primary); + + .group-title { + display: inline-block; + padding-left: 1em; + } + + .pipeline-toggle { + background-color: var(--color-alabaster); + display: inline-block; + padding: 10px 10px 10px 0; + height: 36px; + flex: 0 0 auto; + } + + .shadowed { + position: relative; + display: flex; + } +} + +.raw-resource-title { + margin: 0; +} + +.clickable-row { + background-color: var(--color-white); + border: 1px solid var(--color-alto); + margin: 5px 15px 3px; + position: relative; + z-index: 1; + + &.active { + border-color: var(--color-accent-g1); + background-color: var(--color-accent-g3); + } + + &:hover { + border-color: var(--color-accent-g1); + box-shadow: 0 0 4px 2px var(--color-accent-g1); + z-index: 2; + } + + &.managed { + margin: 5px; + border-style: dashed; + } +} diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.tsx b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.tsx new file mode 100644 index 00000000000..ff93d075e17 --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { UISref, UISrefActive } from '@uirouter/react'; +import { CloudProviderLogo } from '@spinnaker/core'; +import './RawResource.less'; +import { RawResourceUtils } from '../RawResourceUtils'; + +interface IRawResourceProps { + resource: IApiKubernetesResource; +} + +interface IRawResourceState {} + +export class RawResource extends React.Component { + constructor(props: IRawResourceProps) { + super(props); + } + + public render() { + const key = + this.props.resource.account + + '-' + + this.props.resource.namespace + + '-' + + this.props.resource.kind + + '-' + + this.props.resource.displayName; + const params = { + account: this.props.resource.account, + name: this.props.resource.name, + region: this.props.resource.region, + }; + return ( + + +
+

+ + {this.props.resource.kind} {this.props.resource.displayName} +

+
+
+
account:
+
{this.props.resource.account}
+
+
+
namespace:
+
{RawResourceUtils.namespaceDisplayName(this.props.resource.namespace)}
+
+
+
apiVersion:
+
{this.props.resource.apiVersion}
+
+
+
+
+
+ ); + } +} diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceDetails.tsx b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceDetails.tsx new file mode 100644 index 00000000000..1417d116b26 --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceDetails.tsx @@ -0,0 +1,232 @@ +import React from 'react'; +import ReactGA from 'react-ga'; +import { Subject } from 'rxjs'; +import { get } from 'lodash'; +import { DateTime } from 'luxon'; +import { UISref } from '@uirouter/react'; +import { Dropdown } from 'react-bootstrap'; +import { + AccountTag, + CollapsibleSection, + CloudProviderLogo, + ConfirmationModalService, + Overridable, + IManifest, + ManifestWriter, + Spinner, + timestamp, +} from '@spinnaker/core'; +import { KubernetesManifestCommandBuilder } from '../../../manifest/manifestCommandBuilder.service'; +import { ManifestWizard } from '../../../manifest/wizard/ManifestWizard'; +import { KubernetesManifestService } from '../../../manifest/manifest.service'; +import { ManifestEvents } from '../../../pipelines/stages/deployManifest/manifestStatus/ManifestEvents'; +import { ManifestLabels } from '../../../manifest/ManifestLabels'; +import { ManifestCondition } from '../../../manifest/status/ManifestCondition'; +import { RawResourceUtils } from '../RawResourceUtils'; + +export interface IRawResourceDetailsProps { + app: any; + $stateParams: { + account: string; + application: string; + name: string; + region: string; + }; +} + +export interface IRawResourcesDetailState { + account: string; + application: string; + name: string; + region: string; + loading: boolean; + manifest?: IManifest; +} + +Overridable('rawResource.details'); +export class RawResourceDetails extends React.Component { + constructor(props: IRawResourceDetailsProps) { + super(props); + this.state = { + account: null, + application: null, + name: null, + region: null, + loading: true, + manifest: null, + }; + } + private destroy$ = new Subject(); + private props$ = new Subject(); + private unsubscribeManifest?: () => void; + + public componentDidMount() { + this.setState({ + account: this.props.$stateParams.account, + application: this.props.$stateParams.application, + name: this.props.$stateParams.name, + region: this.props.$stateParams.region, + }); + const params = { + account: this.props.$stateParams.account, + location: this.props.$stateParams.region == '' ? '_' : this.props.$stateParams.region, + name: this.props.$stateParams.name, + }; + this.unsubscribeManifest = KubernetesManifestService.subscribe(this.props.app, params, (manifest) => { + if (this.unsubscribeManifest != null) { + this.setState({ manifest, loading: false }); + } + }); + } + + public componentWillReceiveProps(nextProps: IRawResourceDetailsProps) { + this.props$.next(nextProps); + } + + public componentWillUnmount() { + this.destroy$.next(); + if (this.unsubscribeManifest) { + this.unsubscribeManifest(); + this.unsubscribeManifest = null; + } + } + + private editRawResource() { + const { app } = this.props; + const { account, application, manifest } = this.state; + + KubernetesManifestCommandBuilder.buildNewManifestCommand( + app, + manifest.manifest, + { app: application }, + account, + ).then((builtCommand) => { + ManifestWizard.show({ title: 'Edit Manifest', application: app, command: builtCommand }); + }); + } + + private deletedRawResource = () => { + const { app } = this.props; + const { account, manifest } = this.state; + const name = get(manifest, ['manifest', 'metadata', 'name'], null); + const kind = get(manifest, ['manifest', 'kind']); + const taskMonitor = { + application: app, + title: 'Deleting ' + name, + }; + + const submitMethod = () => { + ReactGA.event({ category: 'RawResource', action: 'Delete clicked' }); + const command = { + manifestName: this.state.name, + location: this.state.region, + account: this.state.account, + reason: null as string, + cloudProvider: 'kubernetes', + options: { + cascading: false, + }, + }; + return ManifestWriter.deleteManifest(command, this.props.app); + }; + + ConfirmationModalService.confirm({ + header: 'Are you sure you want to delete the ' + kind + ' ' + name + '?', + buttonText: 'Delete', + account: account, + taskMonitorConfig: taskMonitor, + submitMethod, + }); + }; + + private handleEditClick = (): void => { + ReactGA.event({ category: 'RawResource', action: 'Edit clicked' }); + this.editRawResource(); + }; + + public render() { + const { account, region, manifest } = this.state; + const kind = get(manifest, ['manifest', 'kind'], null); + const apiVersion = get(manifest, ['manifest', 'apiVersion'], null); + const metadata = get(manifest, ['manifest', 'metadata'], null); + const name = get(metadata, ['name'], null); + const creationUnixMs = + get(metadata, 'creationTimestamp') && DateTime.fromISO(metadata.creationTimestamp).toMillis(); + + return ( +
+
+
+ + + +
+ {this.state.loading ? ( +
+ +
+ ) : ( +
+ +

+ {kind} {name} +

+
+ )} + {!this.state.loading && ( +
+ + + {kind} Actions + + +
  • + Edit +
  • +
  • + Delete +
  • +
    +
    +
    + )} +
    + {!this.state.loading && ( +
    + +
    +
    Created
    +
    {timestamp(creationUnixMs)}
    +
    Account
    +
    + +
    +
    API Version
    +
    {apiVersion}
    +
    Kind
    +
    {kind}
    +
    Namespace
    +
    {RawResourceUtils.namespaceDisplayName(region)}
    +
    +
    + +
      + {get(manifest, ['manifest', 'status', 'conditions'], []).map((condition) => ( +
    • + +
    • + ))} +
    +
    + + + + + + +
    + )} +
    + ); + } +} diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceGroups.tsx b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceGroups.tsx new file mode 100644 index 00000000000..54de61902a0 --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceGroups.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { RawResourceGroup } from './RawResouceGroup'; +import { RawResource } from './RawResource'; +import { RawResourceUtils } from '../RawResourceUtils'; + +interface IRawResourceGroupsProps { + resources: IApiKubernetesResource[]; + groupBy: string; +} + +interface IRawResourceGroupsState {} + +export class RawResourceGroups extends React.Component { + constructor(props: IRawResourceGroupsProps) { + super(props); + } + + private buildGroupByModel(resources: IApiKubernetesResource[]) { + const values = resources.map((r: Record) => String(r[this.props.groupBy])); + return Object.assign( + {}, + ...values.map((value) => ({ + [value]: resources.filter((r: Record) => String(r[this.props.groupBy]) === value), + })), + ) as Record; + } + + private groupTitle(title: string): string { + if (this.props.groupBy == 'namespace') { + return RawResourceUtils.namespaceDisplayName(title); + } + return title; + } + + public render() { + const groups = this.buildGroupByModel(this.props.resources); + return ( +
    + {...Object.entries(groups).map((entry) => { + return entry[0] !== 'undefined' ? ( + + {...entry[1].map((resource) => )} + + ) : ( + <> + ); + })} +
    + ); + } +} diff --git a/app/scripts/modules/kubernetes/src/rawResource/controller/FiltersPubSub.ts b/app/scripts/modules/kubernetes/src/rawResource/controller/FiltersPubSub.ts new file mode 100644 index 00000000000..f8928004739 --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/controller/FiltersPubSub.ts @@ -0,0 +1,29 @@ +import { IK8sResourcesFiltersState } from '../component/K8sResourcesFilters'; + +export interface ISubcriber { + notify(message: IK8sResourcesFiltersState): void; +} + +export class FiltersPubSub { + private subcribers: ISubcriber[] = []; + private static intances: Record = {}; + + private constructor() {} + + public static getInstance(name: string) { + let instance = FiltersPubSub.intances[name]; + if (!FiltersPubSub.intances[name]) { + FiltersPubSub.intances[name] = new FiltersPubSub(); + instance = FiltersPubSub.intances[name]; + } + return instance; + } + + public subscribe(subcriber: (message: IK8sResourcesFiltersState) => void) { + this.subcribers.push({ notify: subcriber }); + } + + public publish(message: IK8sResourcesFiltersState) { + this.subcribers.forEach((sub) => sub.notify(message)); + } +} diff --git a/app/scripts/modules/kubernetes/src/rawResource/index.ts b/app/scripts/modules/kubernetes/src/rawResource/index.ts new file mode 100644 index 00000000000..2e2321d8a9a --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/index.ts @@ -0,0 +1 @@ +export * from './rawResource.module'; diff --git a/app/scripts/modules/kubernetes/src/rawResource/model/resource.ts b/app/scripts/modules/kubernetes/src/rawResource/model/resource.ts new file mode 100644 index 00000000000..0384625abca --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/model/resource.ts @@ -0,0 +1,18 @@ +interface IResource { + name: String; + labels: { + [key: string]: String; + }; +} + +interface IApiKubernetesResource { + account: string; + apiVersion: string; + displayName: string; + kind: string; + labels: Record; + moniker: Record; + name: string; + namespace: string; + region: string; +} diff --git a/app/scripts/modules/kubernetes/src/rawResource/rawResource.dataSource.ts b/app/scripts/modules/kubernetes/src/rawResource/rawResource.dataSource.ts new file mode 100644 index 00000000000..cd706107c5e --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/rawResource.dataSource.ts @@ -0,0 +1,39 @@ +import { IPromise, IQService, module } from 'angular'; +import { REST, Application, ApplicationDataSourceRegistry, INFRASTRUCTURE_KEY } from '@spinnaker/core'; + +export const KUBERNETS_RAW_RESOURCE_DATA_SOURCE = 'spinnaker.core.rawresource.dataSource'; +export const KUBERNETS_RAW_RESOURCE_DATA_SOURCE_KEY = 'k8s'; +const KUBERNETS_RAW_RESOURCE_DATA_SOURCE_SREF = `.insight.${KUBERNETS_RAW_RESOURCE_DATA_SOURCE_KEY}`; + +type ApiK8sResource = any; + +const fetchK8sResources = ($q: IQService) => (application: Application): IPromise => + REST('applications') + .path(application.name, 'rawResources') + .get() + .then((kubernetesResources: IApiKubernetesResource[]) => $q.resolve(kubernetesResources)); + +const formatK8sResources = ($q: IQService) => (_: Application, result: ApiK8sResource): IPromise => + $q.resolve(result); + +module(KUBERNETS_RAW_RESOURCE_DATA_SOURCE, []).run([ + '$q', + ($q: IQService) => { + ApplicationDataSourceRegistry.registerDataSource({ + key: KUBERNETS_RAW_RESOURCE_DATA_SOURCE_KEY, + label: 'Kubernetes', + category: INFRASTRUCTURE_KEY, + sref: KUBERNETS_RAW_RESOURCE_DATA_SOURCE_SREF, + primary: true, + icon: 'fas fa-xs fa-fw fa-th-large', + iconName: 'spMenuK8s', + loader: fetchK8sResources($q), + onLoad: formatK8sResources($q), + providerField: 'cloudProvider', + credentialsField: 'account', + regionField: 'region', + description: 'Collections of kubernetes resources', + defaultData: [], + }); + }, +]); diff --git a/app/scripts/modules/kubernetes/src/rawResource/rawResource.module.ts b/app/scripts/modules/kubernetes/src/rawResource/rawResource.module.ts new file mode 100644 index 00000000000..0f1ae6980e7 --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/rawResource.module.ts @@ -0,0 +1,8 @@ +import { module } from 'angular'; + +import { KUBERNETS_RAW_RESOURCE_DATA_SOURCE } from './rawResource.dataSource'; +import { KUBERNETS_RAW_RESOURCE_STATES } from './rawResource.states'; + +export const KUBERNETS_RAW_RESOURCE_MODULE = 'spinnaker.kubernetes.rawresource'; + +module(KUBERNETS_RAW_RESOURCE_MODULE, [KUBERNETS_RAW_RESOURCE_DATA_SOURCE, KUBERNETS_RAW_RESOURCE_STATES]); diff --git a/app/scripts/modules/kubernetes/src/rawResource/rawResource.states.ts b/app/scripts/modules/kubernetes/src/rawResource/rawResource.states.ts new file mode 100644 index 00000000000..a4892cdcbce --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/rawResource.states.ts @@ -0,0 +1,61 @@ +import { module } from 'angular'; +//import { StateParams } from '@uirouter/angularjs'; +import { ApplicationStateProvider, APPLICATION_STATE_PROVIDER, INestedState } from '@spinnaker/core'; +import { K8sResources } from './component/K8sResources'; +import { RawResourceDetails } from './component/group/RawResourceDetails'; +import { K8sResourcesFilters } from './component/K8sResourcesFilters'; + +export interface IKubernetesRawResourceStateParams { + account: string; + name: string; + region: string; +} + +export const KUBERNETS_RAW_RESOURCE_STATES = 'spinnaker.kubernetes.rawresource.states'; +module(KUBERNETS_RAW_RESOURCE_STATES, [APPLICATION_STATE_PROVIDER]).config([ + 'applicationStateProvider', + (applicationStateProvider: ApplicationStateProvider) => { + const rawResourceDetails: INestedState = { + name: 'rawResourceDetails', + url: '/rawResourceDetails/:account/:region/:name', + views: { + 'detail@../insight': { + component: RawResourceDetails, + $type: 'react', + }, + }, + resolve: { + account: ['$stateParams', ($stateParams: IKubernetesRawResourceStateParams) => $stateParams.account], + name: ['$stateParams', ($stateParams: IKubernetesRawResourceStateParams) => $stateParams.name], + region: ['$stateParams', ($stateParams: IKubernetesRawResourceStateParams) => $stateParams.region], + }, + data: { + pageTitleDetails: { + title: 'Raw Resource Details', + nameParam: 'name', + accountParam: 'account', + }, + history: { + type: 'rawResources', + }, + }, + }; + const kubernetes: INestedState = { + url: `/kubernetes`, + name: 'k8s', + views: { + nav: { component: K8sResourcesFilters, $type: 'react' }, + master: { component: K8sResources, $type: 'react' }, + }, + data: { + pageTitleSection: { + title: 'Kubernetes', + }, + }, + children: [], + }; + + applicationStateProvider.addInsightState(kubernetes); + applicationStateProvider.addInsightDetailState(rawResourceDetails); + }, +]); diff --git a/package.json b/package.json index 763fa4700fd..d4c39c2635f 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "@storybook/addon-storysource": "^6.0.26", "@storybook/preset-typescript": "^3.0.0", "@storybook/react": "^6.0.21", - "@svgr/webpack": "^5.2.0", + "@svgr/webpack": "^5.4.0", "@types/angular": "1.6.26", "@types/angular-mocks": "1.5.10", "@types/angular-ui-bootstrap": "^0.13.41", diff --git a/settings.js b/settings.js index faca5a8b730..a1b8f85b90d 100644 --- a/settings.js +++ b/settings.js @@ -30,6 +30,7 @@ const reduxLoggerEnabled = process.env.REDUX_LOGGER === 'true'; const templatesEnabled = process.env.TEMPLATES_ENABLED === 'true'; const useClassicFirewallLabels = process.env.USE_CLASSIC_FIREWALL_LABELS === 'true'; const functionsEnabled = process.env.FUNCTIONS_ENABLED === 'true' ? true : false; +const k8sRawResourcesEnabled = process.env.K8S_RAW_RESOURCES_ENABLED === 'true' ? true : false; window.spinnakerSettings = { authEnabled: authEnabled, @@ -91,6 +92,7 @@ window.spinnakerSettings = { slack: false, snapshots: false, functions: functionsEnabled, + kubernetesRawResources: k8sRawResourcesEnabled, }, gateUrl: apiHost, gitSources: ['stash', 'github', 'bitbucket', 'gitlab'], diff --git a/yarn.lock b/yarn.lock index c01e379dee3..9ff7a1051ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,15 +39,6 @@ invariant "^2.2.4" semver "^5.5.0" -"@babel/compat-data@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.8.6.tgz#7eeaa0dfa17e50c7d9c0832515eee09b56f04e35" - integrity sha512-CurCIKPTkS25Mb8mz267vU95vy+TyUpnctEX2lV33xWNmHAfjruztgiPBbXZRh3xZZy1CYvGx6XfxyTVS+sk7Q== - dependencies: - browserslist "^4.8.5" - invariant "^2.2.4" - semver "^5.5.0" - "@babel/compat-data@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.6.tgz#3f604c40e420131affe6f2c8052e9a275ae2049b" @@ -79,7 +70,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.4.5", "@babel/core@^7.7.5": +"@babel/core@^7.7.5": version "7.8.7" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.7.tgz#b69017d221ccdeb203145ae9da269d72cf102f3b" integrity sha512-rBlqF3Yko9cynC5CCFy6+K/w2N+Sq/ff2BPy+Krp7rHlABIr5epbA7OxVeKoMHB39LZOp1UY5SuLjy6uWi35yA== @@ -216,15 +207,6 @@ "@babel/types" "^7.8.3" esutils "^2.0.0" -"@babel/helper-call-delegate@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.8.7.tgz#28a279c2e6c622a6233da548127f980751324cab" - integrity sha512-doAA5LAKhsFCR0LAFIf+r2RSMmC+m8f/oQ+URnUET/rWeEzC0yTRmAGyWkD4sSu3xwbS7MYQ2u+xlt1V5R56KQ== - dependencies: - "@babel/helper-hoist-variables" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.7" - "@babel/helper-compilation-targets@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz#804ae8e3f04376607cc791b9d47d540276332bd2" @@ -236,17 +218,6 @@ levenary "^1.1.1" semver "^5.5.0" -"@babel/helper-compilation-targets@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" - integrity sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw== - dependencies: - "@babel/compat-data" "^7.8.6" - browserslist "^4.9.1" - invariant "^2.2.4" - levenary "^1.1.1" - semver "^5.5.0" - "@babel/helper-compilation-targets@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz#1e05b7ccc9d38d2f8b40b458b380a04dcfadd38a" @@ -459,19 +430,6 @@ "@babel/types" "^7.11.0" lodash "^4.17.19" -"@babel/helper-module-transforms@^7.8.3": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.8.6.tgz#6a13b5eecadc35692047073a64e42977b97654a4" - integrity sha512-RDnGJSR5EFBJjG3deY0NiL0K9TO8SXxS9n/MPsbPK/s9LbQymuLNtlzvDiNS7IpecuL45cMeLVkA+HfmlrnkRg== - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-simple-access" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/template" "^7.8.6" - "@babel/types" "^7.8.6" - lodash "^4.17.13" - "@babel/helper-module-transforms@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" @@ -880,14 +838,6 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-transform-parameters" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz#eb5ae366118ddca67bed583b53d7554cad9951bb" - integrity sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-proposal-object-rest-spread@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz#7a093586fcb18b08266eb1a7177da671ac575b63" @@ -922,14 +872,6 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0" "@babel/plugin-syntax-optional-chaining" "^7.8.0" -"@babel/plugin-proposal-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz#ae10b3214cb25f7adb1f3bc87ba42ca10b7e2543" - integrity sha512-QIoIR9abkVn+seDE3OjA08jWcs3eZ9+wJCKSRgo3WdEU2csFYgdScb+8qHB3+WXsGJD55u+5hWCISI7ejXS+kg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-proposal-optional-chaining@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" @@ -1184,20 +1126,6 @@ "@babel/helper-split-export-declaration" "^7.10.4" globals "^11.1.0" -"@babel/plugin-transform-classes@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.6.tgz#77534447a477cbe5995ae4aee3e39fbc8090c46d" - integrity sha512-k9r8qRay/R6v5aWZkrEclEhKO6mc1CCQr2dLsVHBmOQiMpN6I2bpjX3vgnldUWeEI1GHVNByULVxZ4BdP4Hmdg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-define-map" "^7.8.3" - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-split-export-declaration" "^7.8.3" - globals "^11.1.0" - "@babel/plugin-transform-classes@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz#800597ddb8aefc2c293ed27459c1fcc935a26c2c" @@ -1233,13 +1161,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-destructuring@^7.8.3": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz#fadb2bc8e90ccaf5658de6f8d4d22ff6272a2f4b" - integrity sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-transform-destructuring@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz#72c97cf5f38604aea3abf3b935b0e17b1db76a50" @@ -1308,13 +1229,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-for-of@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.6.tgz#a051bd1b402c61af97a27ff51b468321c7c2a085" - integrity sha512-M0pw4/1/KI5WAxPsdcUL/w2LJ7o89YHN3yLkzNjg7Yl15GlVGgzHyCU+FMeAxevHGsLVmUqbirlUIKTafPmzdw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-transform-for-of@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" @@ -1375,15 +1289,6 @@ "@babel/helper-plugin-utils" "^7.10.4" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-amd@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.8.3.tgz#65606d44616b50225e76f5578f33c568a0b876a5" - integrity sha512-MadJiU3rLKclzT5kBH4yxdry96odTUwuqrZM+GllFI/VhxfPz+k9MshJM+MwhfkCdxxclSbSBbUGciBngR+kEQ== - dependencies: - "@babel/helper-module-transforms" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - "@babel/plugin-transform-modules-amd@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz#8539ec42c153d12ea3836e0e3ac30d5aae7b258e" @@ -1403,16 +1308,6 @@ "@babel/helper-simple-access" "^7.10.4" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.8.3.tgz#df251706ec331bd058a34bdd72613915f82928a5" - integrity sha512-JpdMEfA15HZ/1gNuB9XEDlZM1h/gF/YOH7zaZzQu2xCFRfwc01NXBMHHSTT6hRjlXJJs5x/bfODM3LiCk94Sxg== - dependencies: - "@babel/helper-module-transforms" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-simple-access" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - "@babel/plugin-transform-modules-commonjs@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz#64b7474a4279ee588cacd1906695ca721687c277" @@ -1433,16 +1328,6 @@ "@babel/helper-plugin-utils" "^7.10.4" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.8.3.tgz#d8bbf222c1dbe3661f440f2f00c16e9bb7d0d420" - integrity sha512-8cESMCJjmArMYqa9AO5YuMEkE4ds28tMpZcGZB/jl3n0ZzlsxOAi3mC+SKypTfT8gjMupCnd3YiXCkMjj2jfOg== - dependencies: - "@babel/helper-hoist-variables" "^7.8.3" - "@babel/helper-module-transforms" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - "@babel/plugin-transform-modules-systemjs@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz#207f1461c78a231d5337a92140e52422510d81a4" @@ -1461,14 +1346,6 @@ "@babel/helper-module-transforms" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-modules-umd@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.8.3.tgz#592d578ce06c52f5b98b02f913d653ffe972661a" - integrity sha512-evhTyWhbwbI3/U6dZAnx/ePoV7H6OUG+OjiJFHmhr9FPn0VShjwC2kdxqIuQ/+1P50TMrneGzMeyMTFOjKSnAw== - dependencies: - "@babel/helper-module-transforms" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-transform-modules-umd@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" @@ -1529,15 +1406,6 @@ "@babel/helper-get-function-arity" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-parameters@^7.8.7": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.8.tgz#0381de466c85d5404565243660c4496459525daf" - integrity sha512-hC4Ld/Ulpf1psQciWWwdnUspQoQco2bMzSrwU6TmzRlvoYQe4rQFy9vnCZDTlVeCQj0JPfL+1RX0V8hCJvkgBA== - dependencies: - "@babel/helper-call-delegate" "^7.8.7" - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-transform-parameters@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz#173b265746f5e15b2afe527eeda65b73623a0795" @@ -1560,14 +1428,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-react-constant-elements@^7.0.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.8.3.tgz#784c25294bddaad2323eb4ff0c9f4a3f6c87d6bc" - integrity sha512-glrzN2U+egwRfkNFtL34xIBYTxbbUF2qJTP8HD3qETBBqzAWSeNB821X0GjU06+dNpq/UyCIjI72FmGE5NNkQQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-transform-react-constant-elements@^7.9.0": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.10.4.tgz#0f485260bf1c29012bb973e7e404749eaac12c9e" @@ -1801,69 +1661,6 @@ "@babel/helper-create-regexp-features-plugin" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" -"@babel/preset-env@^7.4.5": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.8.7.tgz#1fc7d89c7f75d2d70c2b6768de6c2e049b3cb9db" - integrity sha512-BYftCVOdAYJk5ASsznKAUl53EMhfBbr8CJ1X+AJLfGPscQkwJFiaV/Wn9DPH/7fzm2v6iRYJKYHSqyynTGw0nw== - dependencies: - "@babel/compat-data" "^7.8.6" - "@babel/helper-compilation-targets" "^7.8.7" - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-proposal-async-generator-functions" "^7.8.3" - "@babel/plugin-proposal-dynamic-import" "^7.8.3" - "@babel/plugin-proposal-json-strings" "^7.8.3" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-proposal-object-rest-spread" "^7.8.3" - "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" - "@babel/plugin-proposal-optional-chaining" "^7.8.3" - "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" - "@babel/plugin-syntax-async-generators" "^7.8.0" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-json-strings" "^7.8.0" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - "@babel/plugin-transform-arrow-functions" "^7.8.3" - "@babel/plugin-transform-async-to-generator" "^7.8.3" - "@babel/plugin-transform-block-scoped-functions" "^7.8.3" - "@babel/plugin-transform-block-scoping" "^7.8.3" - "@babel/plugin-transform-classes" "^7.8.6" - "@babel/plugin-transform-computed-properties" "^7.8.3" - "@babel/plugin-transform-destructuring" "^7.8.3" - "@babel/plugin-transform-dotall-regex" "^7.8.3" - "@babel/plugin-transform-duplicate-keys" "^7.8.3" - "@babel/plugin-transform-exponentiation-operator" "^7.8.3" - "@babel/plugin-transform-for-of" "^7.8.6" - "@babel/plugin-transform-function-name" "^7.8.3" - "@babel/plugin-transform-literals" "^7.8.3" - "@babel/plugin-transform-member-expression-literals" "^7.8.3" - "@babel/plugin-transform-modules-amd" "^7.8.3" - "@babel/plugin-transform-modules-commonjs" "^7.8.3" - "@babel/plugin-transform-modules-systemjs" "^7.8.3" - "@babel/plugin-transform-modules-umd" "^7.8.3" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" - "@babel/plugin-transform-new-target" "^7.8.3" - "@babel/plugin-transform-object-super" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.8.7" - "@babel/plugin-transform-property-literals" "^7.8.3" - "@babel/plugin-transform-regenerator" "^7.8.7" - "@babel/plugin-transform-reserved-words" "^7.8.3" - "@babel/plugin-transform-shorthand-properties" "^7.8.3" - "@babel/plugin-transform-spread" "^7.8.3" - "@babel/plugin-transform-sticky-regex" "^7.8.3" - "@babel/plugin-transform-template-literals" "^7.8.3" - "@babel/plugin-transform-typeof-symbol" "^7.8.4" - "@babel/plugin-transform-unicode-regex" "^7.8.3" - "@babel/types" "^7.8.7" - browserslist "^4.8.5" - core-js-compat "^3.6.2" - invariant "^2.2.2" - levenary "^1.1.1" - semver "^5.5.0" - "@babel/preset-env@^7.9.5": version "7.11.5" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.11.5.tgz#18cb4b9379e3e92ffea92c07471a99a2914e4272" @@ -3304,21 +3101,11 @@ resolve-from "^5.0.0" store2 "^2.7.1" -"@svgr/babel-plugin-add-jsx-attribute@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.0.1.tgz#744853b6533e83ce712d41d1873200b3e0a0b6fc" - integrity sha512-av76JbSudaN2CUOGuKQp5BVqLFidtojg4ApRTg1PBOVsskXK2ORwKnBYhIu0JLA6ynmuNDprlHNCD6IwLiYidw== - "@svgr/babel-plugin-add-jsx-attribute@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" integrity sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg== -"@svgr/babel-plugin-remove-jsx-attribute@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.0.1.tgz#5df0ccf99e4745e105f76059025986d4da8443cd" - integrity sha512-oXyByaDQEK4Ut/eC75698MDKnaadbWmp/LS2w22cZAaoObCkkiwYYgZTZ+bvb3moo//AxvKkBtNrlz6+xBb9ZQ== - "@svgr/babel-plugin-remove-jsx-attribute@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz#6b2c770c95c874654fd5e1d5ef475b78a0a962ef" @@ -3334,60 +3121,26 @@ resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz#0b221fc57f9fcd10e91fe219e2cd0dd03145a897" integrity sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ== -"@svgr/babel-plugin-svg-dynamic-title@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.0.1.tgz#221abd28898130c6466b018bf19e905648100c38" - integrity sha512-IbFiDBvq5WpANPndjEom1Y9k1pHCNfJs87jCN1Lt8NEA7yrNVPSoAjBVmmfi0aVBERfp8IT/lgjn2a/S85lXGg== - "@svgr/babel-plugin-svg-dynamic-title@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz#139b546dd0c3186b6e5db4fefc26cb0baea729d7" integrity sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg== -"@svgr/babel-plugin-svg-em-dimensions@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.0.1.tgz#bd77607634deb1d970dbd8c0b1397b41e28c59dd" - integrity sha512-7kL9LtqCm1ca+zAbBsrD4ME3EQeVcRxkdrf2GsbKPgkzWJ+399vS4VqCP462+WvFRbG13jSwpNCrvhekdyvXsA== - "@svgr/babel-plugin-svg-em-dimensions@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz#6543f69526632a133ce5cabab965deeaea2234a0" integrity sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw== -"@svgr/babel-plugin-transform-react-native-svg@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.0.1.tgz#8c593dcfe17179f5a87734a76c9174aac91d2a2b" - integrity sha512-ITG1jJ0zHQ4yft6ISQqlMW4fHIzsrSB/FmrMxAcJtkTjh9M2/9M8wfKxQya9NnTfZ5WMSlQjXMQNZmGQsuxRrw== - "@svgr/babel-plugin-transform-react-native-svg@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz#00bf9a7a73f1cad3948cdab1f8dfb774750f8c80" integrity sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q== -"@svgr/babel-plugin-transform-svg-component@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.2.0.tgz#96bda84be927f01fe37dfaf69572c2357fd97ac6" - integrity sha512-t4dq0cNr7c8cuUu1Cwehai/0iXO3dV5876r2QRaLdgQF3C6XOK2vdTvNOwcJ3uRa92revSC3kGL8v8WgJrecRg== - "@svgr/babel-plugin-transform-svg-component@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.4.0.tgz#a2212b4d018e6075a058bb7e220a66959ef7a03c" integrity sha512-zLl4Fl3NvKxxjWNkqEcpdSOpQ3LGVH2BNFQ6vjaK6sFo2IrSznrhURIPI0HAphKiiIwNYjAfE0TNoQDSZv0U9A== -"@svgr/babel-preset@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-5.2.0.tgz#8b1624662efac8a82e2b0f3054dafede9fbff89f" - integrity sha512-sr7486h+SddMU1VgFrajXx/Ws0a1QPzX4wUBM1LgG2PHeZpnm+fQs2MXQNdnfoXRwo7C5mH2I4QDiRVR/49BEg== - dependencies: - "@svgr/babel-plugin-add-jsx-attribute" "^5.0.1" - "@svgr/babel-plugin-remove-jsx-attribute" "^5.0.1" - "@svgr/babel-plugin-remove-jsx-empty-expression" "^5.0.1" - "@svgr/babel-plugin-replace-jsx-attribute-value" "^5.0.1" - "@svgr/babel-plugin-svg-dynamic-title" "^5.0.1" - "@svgr/babel-plugin-svg-em-dimensions" "^5.0.1" - "@svgr/babel-plugin-transform-react-native-svg" "^5.0.1" - "@svgr/babel-plugin-transform-svg-component" "^5.2.0" - "@svgr/babel-preset@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-5.4.0.tgz#da21854643e1c4ad2279239baa7d5a8b128c1f15" @@ -3402,15 +3155,6 @@ "@svgr/babel-plugin-transform-react-native-svg" "^5.4.0" "@svgr/babel-plugin-transform-svg-component" "^5.4.0" -"@svgr/core@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@svgr/core/-/core-5.2.0.tgz#9b12191478960bb6e9a0bc9f8f58c39bbcfc8c82" - integrity sha512-vuODnJ0owj/oFi2bzskuSEk6TGuYoMV9hmvBhGuE1QktzMAAjOr0LnvUN5u2eGB6ilGdI7yqUKrZtQ0Tw44mrA== - dependencies: - "@svgr/plugin-jsx" "^5.2.0" - camelcase "^5.3.1" - cosmiconfig "^6.0.0" - "@svgr/core@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/core/-/core-5.4.0.tgz#655378ee43679eb94fee3d4e1976e38252dff8e7" @@ -3420,13 +3164,6 @@ camelcase "^6.0.0" cosmiconfig "^6.0.0" -"@svgr/hast-util-to-babel-ast@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.0.1.tgz#0dce60a955465f0bd86bcd9c523b21472d6531d3" - integrity sha512-G7UHNPNhLyDK5p6RJvSh4TRpHszTxG8jPp5lAxC6Ez6O6rj1plEAjrCDdYj50mvilUuT9IKjqn87F8+agpKaSw== - dependencies: - "@babel/types" "^7.4.4" - "@svgr/hast-util-to-babel-ast@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.4.0.tgz#bb5d002e428f510aa5b53ec0a02377a95b367715" @@ -3434,16 +3171,6 @@ dependencies: "@babel/types" "^7.9.5" -"@svgr/plugin-jsx@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-5.2.0.tgz#f81578e5b5ff8728ab72419207f7f73655684eff" - integrity sha512-z1HWitE5sCNgaXqBGrmCnnnvR/BRTq9B/lsgZ+T8OWABzZHhezqjjDUvkyyyBb3Y+0xExWg5aTh2jxqk7GR9tg== - dependencies: - "@babel/core" "^7.7.5" - "@svgr/babel-preset" "^5.2.0" - "@svgr/hast-util-to-babel-ast" "^5.0.1" - svg-parser "^2.0.2" - "@svgr/plugin-jsx@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-5.4.0.tgz#ab47504c55615833c6db70fca2d7e489f509787c" @@ -3454,15 +3181,6 @@ "@svgr/hast-util-to-babel-ast" "^5.4.0" svg-parser "^2.0.2" -"@svgr/plugin-svgo@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-5.2.0.tgz#84162f191a40bd9725a2ff9afdc6cd3ba7e59719" - integrity sha512-cyqWx026uO3heGG/55j5zfJLtS5sl0dWYawN1JotOqpJDyyR7rraTsnydpwwsOoz0YpESjVjAkXOAfd41lBY9Q== - dependencies: - cosmiconfig "^6.0.0" - merge-deep "^3.0.2" - svgo "^1.2.2" - "@svgr/plugin-svgo@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-5.4.0.tgz#45d9800b7099a6f7b4d85ebac89ab9abe8592f64" @@ -3472,20 +3190,6 @@ merge-deep "^3.0.2" svgo "^1.2.2" -"@svgr/webpack@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-5.2.0.tgz#80fda19af0caf79b715ae916d780bd8aa4c9b9d2" - integrity sha512-y9tMjTtqrvC998aCOgsEP8pb+bdg2RR1v8i+sjT31m4Xb8HGd905K7/GdSwEMM2nlTFbxSUQPZRRvfaJwAvghA== - dependencies: - "@babel/core" "^7.4.5" - "@babel/plugin-transform-react-constant-elements" "^7.0.0" - "@babel/preset-env" "^7.4.5" - "@babel/preset-react" "^7.0.0" - "@svgr/core" "^5.2.0" - "@svgr/plugin-jsx" "^5.2.0" - "@svgr/plugin-svgo" "^5.2.0" - loader-utils "^1.2.3" - "@svgr/webpack@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-5.4.0.tgz#b68bc86e29cf007292b96ced65f80971175632e0" @@ -5354,13 +5058,6 @@ babel-plugin-apply-mdx-type-prop@1.6.18: "@babel/helper-plugin-utils" "7.10.4" "@mdx-js/util" "1.6.18" -babel-plugin-dynamic-import-node@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" - integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== - dependencies: - object.assign "^4.1.0" - babel-plugin-dynamic-import-node@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" @@ -6054,7 +5751,7 @@ browserslist@^4.11.1, browserslist@^4.12.0: node-releases "^1.1.53" pkg-up "^2.0.0" -browserslist@^4.8.3, browserslist@^4.8.5, browserslist@^4.9.1: +browserslist@^4.8.3: version "4.9.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.9.1.tgz#01ffb9ca31a1aef7678128fc6a2253316aa7287c" integrity sha512-Q0DnKq20End3raFulq6Vfp1ecB9fh8yUNV55s8sekaDDeqBaCtWlRHCUdaWyUeSSBJM7IbM6HcsyaeYqgeDhnw== From 6f14fa79af89033a81c4f28ebefa4a02dc1f7715 Mon Sep 17 00:00:00 2001 From: Jeff Herald Date: Thu, 7 Jan 2021 12:55:51 -0500 Subject: [PATCH 02/10] feat(kubernetes): Raw resources UI MVP Address PR feedback from @christopherthielen --- .../rawResource/component/K8sResources.less | 9 ++ .../rawResource/component/K8sResources.tsx | 92 +++++++-------- .../component/K8sResourcesFilters.tsx | 15 +-- .../rawResource/component/RawResourceUtils.ts | 14 ++- .../component/group/RawResouceGroup.tsx | 6 +- .../component/group/RawResource.less | 108 +++++++++--------- .../component/group/RawResource.tsx | 18 +-- .../component/group/RawResourceDetails.tsx | 14 +-- .../component/group/RawResourceGroups.tsx | 12 +- .../rawResource/controller/FiltersPubSub.ts | 20 ++-- .../src/rawResource/rawResource.dataSource.ts | 13 +-- 11 files changed, 173 insertions(+), 148 deletions(-) create mode 100644 app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.less diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.less b/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.less new file mode 100644 index 00000000000..708abe2456c --- /dev/null +++ b/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.less @@ -0,0 +1,9 @@ +.K8sResources { + width: 100%; + .StandardFieldLayout { + width: 25%; + .StandardFieldLayout_Label { + min-width: 72px; + } + } +} diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.tsx b/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.tsx index cc1f8793581..1b815a289b0 100644 --- a/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.tsx +++ b/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.tsx @@ -1,10 +1,12 @@ -import { Application, ApplicationDataSource } from '@spinnaker/core'; +import { Application, ApplicationDataSource, FormikFormField, ReactSelectInput } from '@spinnaker/core'; import React from 'react'; import { FiltersPubSub } from '../controller/FiltersPubSub'; import { KUBERNETS_RAW_RESOURCE_DATA_SOURCE_KEY } from '../rawResource.dataSource'; import { RawResource } from './group/RawResource'; import { RawResourceGroups } from './group/RawResourceGroups'; import { IK8sResourcesFiltersState } from './K8sResourcesFilters'; +import { Formik } from 'formik'; +import './K8sResources.less'; export interface IK8sResourcesProps { app: Application; @@ -19,6 +21,7 @@ interface IK8sResourcesState { export class K8sResources extends React.Component { private dataSource: ApplicationDataSource; private filterPubSub: FiltersPubSub = FiltersPubSub.getInstance(this.props.app.name); + private sub = this.onFilterChange.bind(this); constructor(props: IK8sResourcesProps) { super(props); @@ -29,7 +32,11 @@ export class K8sResources extends React.Component (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1)), + rawResources: await this.dataSource.data.sort((a, b) => a.name.localeCompare(b.name)), }); } - public onGroupByChange(event: React.ChangeEvent) { - this.setState({ - ...this.state, - groupBy: event.target.value, - }); - } + public groupByChanged = (groupBy: string): void => { + this.setState({ ...this.state, groupBy: groupBy.toLowerCase() }); + }; public render() { + const opts = ['None', 'Account', 'Kind', 'Namespace']; return ( -
    -
    -
    - - -
    -
    - {this.state.groupBy === 'none' ? ( - <> - {...this.state.rawResources - .filter((resource) => this.matchFilters(resource)) - .map((resource) => )} - - ) : ( - this.matchFilters(resource))} - groupBy={this.state.groupBy} - > - )} +
    +
    + null} + initialValues={{ groupBy: 'None' }} + render={() => { + return ( + ( + + )} + /> + ); + }} + /> +
    +
    + {this.state.groupBy === 'none' ? ( + <> + {...this.state.rawResources + .filter((resource) => this.matchFilters(resource)) + .map((resource) => )} + + ) : ( + this.matchFilters(resource))} + groupBy={this.state.groupBy} + > + )} +
    ); } diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/K8sResourcesFilters.tsx b/app/scripts/modules/kubernetes/src/rawResource/component/K8sResourcesFilters.tsx index 519cf91f5dd..0e9d32f961d 100644 --- a/app/scripts/modules/kubernetes/src/rawResource/component/K8sResourcesFilters.tsx +++ b/app/scripts/modules/kubernetes/src/rawResource/component/K8sResourcesFilters.tsx @@ -52,7 +52,7 @@ export class K8sResourcesFilters extends React.Component {...Object.keys(this.state.kinds) - .sort((a, b) => (a.toLowerCase() > b.toLowerCase() ? 1 : -1)) + .sort((a, b) => a.localeCompare(b)) .map((key) => ( {...Object.keys(this.state.accounts) - .sort((a, b) => (a.toLowerCase() > b.toLowerCase() ? 1 : -1)) + .sort((a, b) => a.localeCompare(b)) .map((key) => ( {...Object.keys(this.state.displayNamespaces) - .sort((a, b) => (a.toLowerCase() > b.toLowerCase() ? 1 : -1)) + .sort((a, b) => a.localeCompare(b)) .map((key) => ( -
    +
    +

    {this.props.title}

    -
    {this.props.children}
    +
    {this.props.children}
    ); } diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.less b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.less index fd0e2dd8a45..9674b6ffa13 100644 --- a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.less +++ b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.less @@ -1,39 +1,65 @@ -.raw-resource-card { - background-color: white; - padding: 1em; - margin-top: 1em; - margin-left: 3em; - border: thin solid lightgrey; -} +.RawResource { + &.card { + background-color: white; + padding: 1em; + margin-top: 1em; + margin-left: 3em; + border: thin solid lightgrey; + } -.raw-resource-column { - display: flex; -} + &.clickable-row { + background-color: var(--color-white); + border: 1px solid var(--color-alto); + margin: 5px 15px 3px; + position: relative; + z-index: 1; -.raw-resource-details { - color: grey; - display: flex; -} + &.active { + border-color: var(--color-accent-g1); + background-color: var(--color-accent-g3); + } -.raw-resource-details-column { - margin-right: 5em; -} + &:hover { + border-color: var(--color-accent-g1); + box-shadow: 0 0 4px 2px var(--color-accent-g1); + z-index: 2; + } + + &.managed { + margin: 5px; + border-style: dashed; + } + } + + .details { + color: grey; + display: flex; + + .column { + margin-right: 5em; -.raw-resource-details-column-label { - margin-right: 1em; + .title { + margin-right: 1em; + } + } + } + + .title { + margin: 0; + } } -.raw-resource-group { +.RawResourceGroup { margin-top: 2em; -} -.raw-resource-group-header { - display: flex; - height: 37px; - padding: 0; - color: var(--color-primary); + .header { + display: flex; + height: 37px; + padding: 0; + color: var(--color-primary); + } - .group-title { + .title { display: inline-block; padding-left: 1em; } @@ -51,31 +77,3 @@ display: flex; } } - -.raw-resource-title { - margin: 0; -} - -.clickable-row { - background-color: var(--color-white); - border: 1px solid var(--color-alto); - margin: 5px 15px 3px; - position: relative; - z-index: 1; - - &.active { - border-color: var(--color-accent-g1); - background-color: var(--color-accent-g3); - } - - &:hover { - border-color: var(--color-accent-g1); - box-shadow: 0 0 4px 2px var(--color-accent-g1); - z-index: 2; - } - - &.managed { - margin: 5px; - border-style: dashed; - } -} diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.tsx b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.tsx index ff93d075e17..9bed06a1259 100644 --- a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.tsx +++ b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResource.tsx @@ -32,22 +32,22 @@ export class RawResource extends React.Component -
    -

    +
    +

    {this.props.resource.kind} {this.props.resource.displayName}

    -
    -
    -
    account:
    +
    +
    +
    account:
    {this.props.resource.account}
    -
    -
    namespace:
    +
    +
    namespace:
    {RawResourceUtils.namespaceDisplayName(this.props.resource.namespace)}
    -
    -
    apiVersion:
    +
    +
    apiVersion:
    {this.props.resource.apiVersion}
    diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceDetails.tsx b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceDetails.tsx index 1417d116b26..ae672029d6b 100644 --- a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceDetails.tsx +++ b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceDetails.tsx @@ -21,7 +21,7 @@ import { ManifestWizard } from '../../../manifest/wizard/ManifestWizard'; import { KubernetesManifestService } from '../../../manifest/manifest.service'; import { ManifestEvents } from '../../../pipelines/stages/deployManifest/manifestStatus/ManifestEvents'; import { ManifestLabels } from '../../../manifest/ManifestLabels'; -import { ManifestCondition } from '../../../manifest/status/ManifestCondition'; +import { IKubernetesManifestCondition, ManifestCondition } from '../../../manifest/status/ManifestCondition'; import { RawResourceUtils } from '../RawResourceUtils'; export interface IRawResourceDetailsProps { @@ -43,7 +43,7 @@ export interface IRawResourcesDetailState { manifest?: IManifest; } -Overridable('rawResource.details'); +Overridable('k8s.rawResource.details'); export class RawResourceDetails extends React.Component { constructor(props: IRawResourceDetailsProps) { super(props); @@ -146,10 +146,10 @@ export class RawResourceDetails extends React.Component
      - {get(manifest, ['manifest', 'status', 'conditions'], []).map((condition) => ( + {(manifest?.manifest?.status?.conditions ?? []).map((condition: IKubernetesManifestCondition) => (
    • diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceGroups.tsx b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceGroups.tsx index 54de61902a0..10419720ae4 100644 --- a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceGroups.tsx +++ b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceGroups.tsx @@ -35,11 +35,13 @@ export class RawResourceGroups extends React.Component - {...Object.entries(groups).map((entry) => { - return entry[0] !== 'undefined' ? ( - - {...entry[1].map((resource) => )} +
      + {...Object.entries(groups).map(([key, value]) => { + return key !== 'undefined' ? ( + + {...value.map((resource) => ( + + ))} ) : ( <> diff --git a/app/scripts/modules/kubernetes/src/rawResource/controller/FiltersPubSub.ts b/app/scripts/modules/kubernetes/src/rawResource/controller/FiltersPubSub.ts index f8928004739..2c6e575813d 100644 --- a/app/scripts/modules/kubernetes/src/rawResource/controller/FiltersPubSub.ts +++ b/app/scripts/modules/kubernetes/src/rawResource/controller/FiltersPubSub.ts @@ -1,11 +1,9 @@ import { IK8sResourcesFiltersState } from '../component/K8sResourcesFilters'; -export interface ISubcriber { - notify(message: IK8sResourcesFiltersState): void; -} +type ISubscriber = (message: IK8sResourcesFiltersState) => void; export class FiltersPubSub { - private subcribers: ISubcriber[] = []; + private subscribers: ISubscriber[] = []; private static intances: Record = {}; private constructor() {} @@ -19,11 +17,19 @@ export class FiltersPubSub { return instance; } - public subscribe(subcriber: (message: IK8sResourcesFiltersState) => void) { - this.subcribers.push({ notify: subcriber }); + public subscribe(subscriber: ISubscriber) { + this.subscribers.push(subscriber); + return () => (this.subscribers = this.subscribers.filter((x) => x !== subscriber)); + } + + public unsubscribe(subscriber: ISubscriber) { + const index = this.subscribers.indexOf(subscriber); + if (index > -1) { + this.subscribers.splice(index, 1); + } } public publish(message: IK8sResourcesFiltersState) { - this.subcribers.forEach((sub) => sub.notify(message)); + this.subscribers.forEach((sub) => sub(message)); } } diff --git a/app/scripts/modules/kubernetes/src/rawResource/rawResource.dataSource.ts b/app/scripts/modules/kubernetes/src/rawResource/rawResource.dataSource.ts index cd706107c5e..ec782b4f2a9 100644 --- a/app/scripts/modules/kubernetes/src/rawResource/rawResource.dataSource.ts +++ b/app/scripts/modules/kubernetes/src/rawResource/rawResource.dataSource.ts @@ -1,4 +1,4 @@ -import { IPromise, IQService, module } from 'angular'; +import { IQService, module } from 'angular'; import { REST, Application, ApplicationDataSourceRegistry, INFRASTRUCTURE_KEY } from '@spinnaker/core'; export const KUBERNETS_RAW_RESOURCE_DATA_SOURCE = 'spinnaker.core.rawresource.dataSource'; @@ -7,13 +7,10 @@ const KUBERNETS_RAW_RESOURCE_DATA_SOURCE_SREF = `.insight.${KUBERNETS_RAW_RESOUR type ApiK8sResource = any; -const fetchK8sResources = ($q: IQService) => (application: Application): IPromise => - REST('applications') - .path(application.name, 'rawResources') - .get() - .then((kubernetesResources: IApiKubernetesResource[]) => $q.resolve(kubernetesResources)); +const fetchK8sResources = (application: Application): PromiseLike => + REST('applications').path(application.name, 'rawResources').get(); -const formatK8sResources = ($q: IQService) => (_: Application, result: ApiK8sResource): IPromise => +const formatK8sResources = ($q: IQService) => (_: Application, result: ApiK8sResource): PromiseLike => $q.resolve(result); module(KUBERNETS_RAW_RESOURCE_DATA_SOURCE, []).run([ @@ -27,7 +24,7 @@ module(KUBERNETS_RAW_RESOURCE_DATA_SOURCE, []).run([ primary: true, icon: 'fas fa-xs fa-fw fa-th-large', iconName: 'spMenuK8s', - loader: fetchK8sResources($q), + loader: fetchK8sResources, onLoad: formatK8sResources($q), providerField: 'cloudProvider', credentialsField: 'account', From 85d544aeae1d7eda4f7e8f1dbd0b26596f1c6804 Mon Sep 17 00:00:00 2001 From: Jeff Herald Date: Mon, 11 Jan 2021 15:08:12 -0500 Subject: [PATCH 03/10] feat(kubernetes): Raw resources UI MVP Address final PR feedback from @christopherthielen --- .../rawResource/component/K8sResources.tsx | 37 ++++++++----------- .../component/K8sResourcesFilters.tsx | 10 ++--- .../component/group/RawResource.tsx | 17 ++------- .../component/group/RawResourceDetails.tsx | 4 +- 4 files changed, 26 insertions(+), 42 deletions(-) diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.tsx b/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.tsx index 1b815a289b0..abadae4a187 100644 --- a/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.tsx +++ b/app/scripts/modules/kubernetes/src/rawResource/component/K8sResources.tsx @@ -1,12 +1,12 @@ -import { Application, ApplicationDataSource, FormikFormField, ReactSelectInput } from '@spinnaker/core'; +import { Application, ApplicationDataSource, FormField, ReactSelectInput } from '@spinnaker/core'; import React from 'react'; import { FiltersPubSub } from '../controller/FiltersPubSub'; import { KUBERNETS_RAW_RESOURCE_DATA_SOURCE_KEY } from '../rawResource.dataSource'; import { RawResource } from './group/RawResource'; import { RawResourceGroups } from './group/RawResourceGroups'; import { IK8sResourcesFiltersState } from './K8sResourcesFilters'; -import { Formik } from 'formik'; import './K8sResources.less'; +import { RawResourceUtils } from './RawResourceUtils'; export interface IK8sResourcesProps { app: Application; @@ -53,8 +53,8 @@ export class K8sResources extends React.Component { - this.setState({ ...this.state, groupBy: groupBy.toLowerCase() }); + private groupByChanged = (e: React.ChangeEvent): void => { + this.setState({ groupBy: e.target.value.toLowerCase() }); }; public render() { @@ -62,29 +62,24 @@ export class K8sResources extends React.Component
      - null} - initialValues={{ groupBy: 'None' }} - render={() => { - return ( - ( - - )} - /> - ); - }} + ( + + )} />
      -
      +
      {this.state.groupBy === 'none' ? ( <> {...this.state.rawResources .filter((resource) => this.matchFilters(resource)) - .map((resource) => )} + .map((resource) => ( + + ))} ) : ( -
      +

      {this.props.resource.kind} {this.props.resource.displayName} diff --git a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceDetails.tsx b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceDetails.tsx index ae672029d6b..d83a054eb4b 100644 --- a/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceDetails.tsx +++ b/app/scripts/modules/kubernetes/src/rawResource/component/group/RawResourceDetails.tsx @@ -108,8 +108,8 @@ export class RawResourceDetails extends React.Component { const { app } = this.props; const { account, manifest } = this.state; - const name = get(manifest, ['manifest', 'metadata', 'name'], null); - const kind = get(manifest, ['manifest', 'kind']); + const name = manifest?.manifest?.metadata?.name ?? null; + const kind = manifest?.manifest?.kind; const taskMonitor = { application: app, title: 'Deleting ' + name, From 22ec587690d6f0f468cc89e3e4ec8d08c4c78f7c Mon Sep 17 00:00:00 2001 From: AbdulRahman AlHamali Date: Thu, 25 Mar 2021 15:06:51 -0400 Subject: [PATCH 04/10] allow ignore failure --- .../modules/core/src/domain/IOrchestratedItem.ts | 1 + .../modules/core/src/help/help.contents.ts | 6 +++++- .../orchestratedItem.transformer.ts | 3 +++ .../config/stages/common/executionSummary.html | 16 +++++++++++++++- .../core/src/pipeline/config/stages/stage.html | 1 + .../pipeline/details/stageSummary.component.ts | 8 ++++++++ 6 files changed, 33 insertions(+), 2 deletions(-) diff --git a/app/scripts/modules/core/src/domain/IOrchestratedItem.ts b/app/scripts/modules/core/src/domain/IOrchestratedItem.ts index f342e6e638d..89f1de975ea 100644 --- a/app/scripts/modules/core/src/domain/IOrchestratedItem.ts +++ b/app/scripts/modules/core/src/domain/IOrchestratedItem.ts @@ -27,5 +27,6 @@ export interface IOrchestratedItem extends ITimedItem { isCanceled: boolean; isSuspended: boolean; isPaused: boolean; + isHalted: boolean; runningTime: string; } diff --git a/app/scripts/modules/core/src/help/help.contents.ts b/app/scripts/modules/core/src/help/help.contents.ts index 41fbbbd4e66..7a70addfeed 100644 --- a/app/scripts/modules/core/src/help/help.contents.ts +++ b/app/scripts/modules/core/src/help/help.contents.ts @@ -79,6 +79,10 @@ const helpContents: { [key: string]: string } = {

      When this option is enabled, stage will only execute when the supplied expression evaluates true.

      The expression does not need to be wrapped in \${ and }.

      If this expression evaluates to false, the stages following this stage will still execute.

      `, + 'pipeline.config.allowIgnoreFailure': ` +

      When this option is enabled, users will be able to manually ignore the stage if it failed.

      +

      You should use this only for stages that other stages don't closely depend on.

      +

      For example, if later stages depend on the outputs of this stage, you should not allow that option.

      `, 'pipeline.config.checkPreconditions.failPipeline': `

      Checked - the overall pipeline will fail whenever this precondition is false.

      Unchecked - the overall pipeline will continue executing but this particular branch will stop.

      `, @@ -357,7 +361,7 @@ const helpContents: { [key: string]: string } = { 'pipeline.config.trigger.runAsUser': "The current user must have access to the specified service account, and the service account must have access to the current application. Otherwise, you'll receive an 'Access is denied' error.", 'pipeline.config.trigger.authorizedUser': - "The current user must have the permission to approve the manual judgment stage. Otherwise, you'll not be able continue to the next pipeline stage.", + "The current user must have the permission to approve the manual judgment stage. Otherwise, you'll not be able continue to the next pipeline stage.", 'pipeline.config.script.repoUrl': '

      Path to the repo hosting the scripts in Stash. (e.g. CDL/mimir-scripts). Leave empty to use the default.

      ', 'pipeline.config.script.repoBranch': diff --git a/app/scripts/modules/core/src/orchestratedItem/orchestratedItem.transformer.ts b/app/scripts/modules/core/src/orchestratedItem/orchestratedItem.transformer.ts index 54eb42f14de..4c8e40408c7 100644 --- a/app/scripts/modules/core/src/orchestratedItem/orchestratedItem.transformer.ts +++ b/app/scripts/modules/core/src/orchestratedItem/orchestratedItem.transformer.ts @@ -83,6 +83,9 @@ export class OrchestratedItemTransformer { isPaused: { get: (): boolean => item.status === 'PAUSED', }, + isHalted: { + get: (): boolean => ['TERMINAL', 'CANCELED', 'STOPPED'].includes(item.status), + }, status: { // Returns either SUCCEEDED, RUNNING, FAILED, CANCELED, or NOT_STARTED get: (): string => this.normalizeStatus(item), diff --git a/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html b/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html index f31d3c02fbc..51531c4de54 100644 --- a/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html +++ b/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html @@ -2,7 +2,7 @@
      Stage details: {{stageSummary.name || stageSummary.type }} -
      +

    @@ -49,6 +59,10 @@
    + Failure ignored manually by {{stage.ignoreFailureDetails.by}} — {{stage.ignoreFailureDetails.time | + timestamp}} Previous exception: {{stage.ignoreFailureDetails.previousException}} +
    diff --git a/app/scripts/modules/core/src/pipeline/config/stages/stage.html b/app/scripts/modules/core/src/pipeline/config/stages/stage.html index 4e39f4c955a..960bca3bc96 100644 --- a/app/scripts/modules/core/src/pipeline/config/stages/stage.html +++ b/app/scripts/modules/core/src/pipeline/config/stages/stage.html @@ -131,6 +131,7 @@

    > + Date: Thu, 25 Mar 2021 15:07:13 -0400 Subject: [PATCH 05/10] add the directive --- .../allowIgnoreFailure.directive.html | 13 +++++++++++++ .../allowIgnoreFailure.directive.js | 16 ++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.html create mode 100644 app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.js diff --git a/app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.html b/app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.html new file mode 100644 index 00000000000..38ae6430972 --- /dev/null +++ b/app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.html @@ -0,0 +1,13 @@ +
    +
    +
    +
    + +
    +
    +
    +
    diff --git a/app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.js b/app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.js new file mode 100644 index 00000000000..6e432013d7f --- /dev/null +++ b/app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.js @@ -0,0 +1,16 @@ +'use strict'; + +import { module } from 'angular'; + +export const CORE_PIPELINE_CONFIG_STAGES_ALLOWIGNOREFAILURE_ALLOWIGNOREFAILURE_DIRECTIVE = + 'spinnaker.core.pipeline.stage.allowIgnoreFailure.directive'; +export const name = CORE_PIPELINE_CONFIG_STAGES_ALLOWIGNOREFAILURE_ALLOWIGNOREFAILURE_DIRECTIVE; // for backwards compatibility +module(CORE_PIPELINE_CONFIG_STAGES_ALLOWIGNOREFAILURE_ALLOWIGNOREFAILURE_DIRECTIVE, []).component( + 'allowIgnoreFailure', + { + bindings: { + stage: '<', + }, + templateUrl: require('./allowIgnoreFailure.directive.html'), + }, +); From a2f156f41987d3d71ccffbc93390f2dad84c4e20 Mon Sep 17 00:00:00 2001 From: AbdulRahman AlHamali Date: Thu, 25 Mar 2021 15:23:38 -0400 Subject: [PATCH 06/10] import allow ignore failure module --- .../pipeline/config/stages/stage.module.js | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/app/scripts/modules/core/src/pipeline/config/stages/stage.module.js b/app/scripts/modules/core/src/pipeline/config/stages/stage.module.js index 7c18a29de81..a05878cb447 100644 --- a/app/scripts/modules/core/src/pipeline/config/stages/stage.module.js +++ b/app/scripts/modules/core/src/pipeline/config/stages/stage.module.js @@ -21,6 +21,7 @@ import { OVERRIDE_TIMEOUT_COMPONENT } from './overrideTimeout/overrideTimeout.mo import { ApplicationReader } from 'core/application/service/ApplicationReader'; import { CORE_PIPELINE_CONFIG_STAGES_OPTIONALSTAGE_OPTIONALSTAGE_DIRECTIVE } from './optionalStage/optionalStage.directive'; import { CORE_PIPELINE_CONFIG_STAGES_FAILONFAILEDEXPRESSIONS_FAILONFAILEDEXPRESSIONS_DIRECTIVE } from './failOnFailedExpressions/failOnFailedExpressions.directive'; +import { CORE_PIPELINE_CONFIG_STAGES_ALLOWIGNOREFAILURE_ALLOWIGNOREFAILURE_DIRECTIVE } from './allowIgnoreFailure/allowIgnoreFailure.directive'; import { CORE_PIPELINE_CONFIG_STAGES_COMMON_STAGECONFIGFIELD_STAGECONFIGFIELD_DIRECTIVE } from './common/stageConfigField/stageConfigField.directive'; export const CORE_PIPELINE_CONFIG_STAGES_STAGE_MODULE = 'spinnaker.core.pipeline.config.stage'; @@ -34,6 +35,7 @@ module(CORE_PIPELINE_CONFIG_STAGES_STAGE_MODULE, [ CORE_PIPELINE_CONFIG_STAGES_OPTIONALSTAGE_OPTIONALSTAGE_DIRECTIVE, CORE_PIPELINE_CONFIG_STAGES_FAILONFAILEDEXPRESSIONS_FAILONFAILEDEXPRESSIONS_DIRECTIVE, CORE_PIPELINE_CONFIG_STAGES_COMMON_STAGECONFIGFIELD_STAGECONFIGFIELD_DIRECTIVE, + CORE_PIPELINE_CONFIG_STAGES_ALLOWIGNOREFAILURE_ALLOWIGNOREFAILURE_DIRECTIVE, ]) .directive('pipelineConfigStage', function () { return { @@ -135,29 +137,29 @@ module(CORE_PIPELINE_CONFIG_STAGES_STAGE_MODULE, [ }); }; - $scope.getApplicationPermissions = function() { - ApplicationReader.getApplicationPermissions($scope.application.name).then(result => { - appPermissions = result; - if (appPermissions) { - const readArray = appPermissions.READ || []; - const writeArray = appPermissions.WRITE || []; - const executeArray = appPermissions.EXECUTE || []; - appRoles = _.union(readArray, writeArray, executeArray); - appRoles = Array.from(new Set(appRoles)); - $scope.updateAvailableStageRoles(); - } - }); + $scope.getApplicationPermissions = function () { + ApplicationReader.getApplicationPermissions($scope.application.name).then((result) => { + appPermissions = result; + if (appPermissions) { + const readArray = appPermissions.READ || []; + const writeArray = appPermissions.WRITE || []; + const executeArray = appPermissions.EXECUTE || []; + appRoles = _.union(readArray, writeArray, executeArray); + appRoles = Array.from(new Set(appRoles)); + $scope.updateAvailableStageRoles(); + } + }); }; - $scope.updateAvailableStageRoles = function() { - $scope.options.stageRoles = appRoles.map(function(value, index) { - return { - name: value, - roleId: value, - id: index, - available: true, - }; - }); + $scope.updateAvailableStageRoles = function () { + $scope.options.stageRoles = appRoles.map(function (value, index) { + return { + name: value, + roleId: value, + id: index, + available: true, + }; + }); }; this.editStageJson = () => { From 728aec06f619807ce003071870e48c23dfe3b51c Mon Sep 17 00:00:00 2001 From: AbdulRahman AlHamali Date: Thu, 25 Mar 2021 15:39:16 -0400 Subject: [PATCH 07/10] add ignore action --- .../details/stageSummary.component.ts | 26 +++++++++++++++++++ .../src/pipeline/service/execution.service.ts | 4 +++ 2 files changed, 30 insertions(+) diff --git a/app/scripts/modules/core/src/pipeline/details/stageSummary.component.ts b/app/scripts/modules/core/src/pipeline/details/stageSummary.component.ts index bd67293658b..bbb9fec08a8 100644 --- a/app/scripts/modules/core/src/pipeline/details/stageSummary.component.ts +++ b/app/scripts/modules/core/src/pipeline/details/stageSummary.component.ts @@ -109,6 +109,32 @@ export class StageSummaryController implements IController { return topLevelStage; } + public openIgnoreStageFailureModal(): void { + ConfirmationModalService.confirm({ + header: 'Really ignore this failure?', + buttonText: 'Ignore', + askForReason: true, + submitJustWithReason: true, + body: `
    + Warning: Ignoring this failure may have unpredictable results. +
      +
    • Downstream stages that depend on the outputs of this stage may fail or behave unexpectedly.
    • +
    +
    + `, + submitMethod: (reason: string) => + this.executionService + .ignoreStageFailureInExecution(this.execution.id, this.stage.id, reason) + .then(() => + this.executionService.waitUntilExecutionMatches(this.execution.id, (execution) => { + const updatedStage = execution.stages.find((stage) => stage.id === this.stage.id); + return updatedStage && updatedStage.status === 'FAILED_CONTINUE'; + }), + ) + .then((updated) => this.executionService.updateExecution(this.application, updated)), + }); + } + public openManualSkipStageModal(): void { const topLevelStage = this.getTopLevelStage(); ConfirmationModalService.confirm({ diff --git a/app/scripts/modules/core/src/pipeline/service/execution.service.ts b/app/scripts/modules/core/src/pipeline/service/execution.service.ts index d0cd1822101..d3dcf1655b2 100644 --- a/app/scripts/modules/core/src/pipeline/service/execution.service.ts +++ b/app/scripts/modules/core/src/pipeline/service/execution.service.ts @@ -529,6 +529,10 @@ export class ExecutionService { return REST('/pipelines').path(executionId, 'stages', stageId).patch(data); } + public ignoreStageFailureInExecution(executionId: string, stageId: string, reason: string): PromiseLike { + return REST('/pipelines').path(executionId, 'stages', stageId, 'ignoreFailure').put({ reason }); + } + private stringifyExecution(execution: IExecution): string { const transient = { ...execution }; transient.stages = transient.stages.filter((s) => s.status !== 'SUCCEEDED' && s.status !== 'NOT_STARTED'); From 124187f58088e4063dd4580a0158697a0a508278 Mon Sep 17 00:00:00 2001 From: AbdulRahman AlHamali Date: Thu, 25 Mar 2021 15:53:15 -0400 Subject: [PATCH 08/10] fix reason --- .../core/src/pipeline/details/stageSummary.component.ts | 2 +- .../modules/core/src/pipeline/service/execution.service.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/scripts/modules/core/src/pipeline/details/stageSummary.component.ts b/app/scripts/modules/core/src/pipeline/details/stageSummary.component.ts index bbb9fec08a8..1c2f7f01ad1 100644 --- a/app/scripts/modules/core/src/pipeline/details/stageSummary.component.ts +++ b/app/scripts/modules/core/src/pipeline/details/stageSummary.component.ts @@ -122,7 +122,7 @@ export class StageSummaryController implements IController { `, - submitMethod: (reason: string) => + submitMethod: (reason: object) => this.executionService .ignoreStageFailureInExecution(this.execution.id, this.stage.id, reason) .then(() => diff --git a/app/scripts/modules/core/src/pipeline/service/execution.service.ts b/app/scripts/modules/core/src/pipeline/service/execution.service.ts index d3dcf1655b2..e3eb8a60b61 100644 --- a/app/scripts/modules/core/src/pipeline/service/execution.service.ts +++ b/app/scripts/modules/core/src/pipeline/service/execution.service.ts @@ -529,8 +529,8 @@ export class ExecutionService { return REST('/pipelines').path(executionId, 'stages', stageId).patch(data); } - public ignoreStageFailureInExecution(executionId: string, stageId: string, reason: string): PromiseLike { - return REST('/pipelines').path(executionId, 'stages', stageId, 'ignoreFailure').put({ reason }); + public ignoreStageFailureInExecution(executionId: string, stageId: string, reason: object): PromiseLike { + return REST('/pipelines').path(executionId, 'stages', stageId, 'ignoreFailure').put(reason); } private stringifyExecution(execution: IExecution): string { From 2ed7cd6a4c55ab14c3b1752ec5efb2e4878ff960 Mon Sep 17 00:00:00 2001 From: AbdulRahman AlHamali Date: Thu, 25 Mar 2021 16:05:03 -0400 Subject: [PATCH 09/10] fix display of details --- .../src/pipeline/config/stages/common/executionSummary.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html b/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html index 51531c4de54..443fe768031 100644 --- a/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html +++ b/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html @@ -60,8 +60,9 @@
    - Failure ignored manually by {{stage.ignoreFailureDetails.by}} — {{stage.ignoreFailureDetails.time | - timestamp}} Previous exception: {{stage.ignoreFailureDetails.previousException}} + Failure ignored manually by {{stage.context.ignoreFailureDetails.by}} — + {{stage.context.ignoreFailureDetails.time | timestamp}} Previous exception: + {{stage.context.ignoreFailureDetails.previousException}}
    From 53551e509b1d45462fbfb8338d1772b7983a7e78 Mon Sep 17 00:00:00 2001 From: AbdulRahman AlHamali Date: Thu, 25 Mar 2021 16:14:18 -0400 Subject: [PATCH 10/10] optional display of exception --- .../src/pipeline/config/stages/common/executionSummary.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html b/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html index 443fe768031..dc4003026d6 100644 --- a/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html +++ b/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html @@ -61,8 +61,8 @@
    Failure ignored manually by {{stage.context.ignoreFailureDetails.by}} — - {{stage.context.ignoreFailureDetails.time | timestamp}} Previous exception: - {{stage.context.ignoreFailureDetails.previousException}} + {{stage.context.ignoreFailureDetails.time | timestamp}} {{stage.context.ignoreFailureDetails.previousException? + 'Previous exception:' + stage.context.ignoreFailureDetails.previousException : ''}}