diff --git a/src/renderer/components/+apps-releases/release.store.ts b/src/renderer/components/+apps-releases/release.store.ts index 59e8d9a5bc..0ed52a56c7 100644 --- a/src/renderer/components/+apps-releases/release.store.ts +++ b/src/renderer/components/+apps-releases/release.store.ts @@ -1,5 +1,5 @@ import isEqual from "lodash/isEqual"; -import { action, IReactionDisposer, observable, reaction, when } from "mobx"; +import { action, observable, reaction, when } from "mobx"; import { autobind } from "../../utils"; import { HelmRelease, helmReleasesApi, IReleaseCreatePayload, IReleaseUpdatePayload } from "../../api/endpoints/helm-releases.api"; import { ItemStore } from "../../item.store"; @@ -10,57 +10,56 @@ import { Notifications } from "../notifications"; @autobind() export class ReleaseStore extends ItemStore { - @observable releaseSecrets: Secret[] = []; - @observable secretWatcher: IReactionDisposer; + releaseSecrets = observable.map(); constructor() { super(); when(() => secretsStore.isLoaded, () => { - this.releaseSecrets = this.getReleaseSecrets(); + this.releaseSecrets.replace(this.getReleaseSecrets()); }); } - watch() { - this.secretWatcher = reaction(() => secretsStore.items.toJS(), () => { + watchAssociatedSecrets(): (() => void) { + return reaction(() => secretsStore.items.toJS(), () => { if (this.isLoading) return; - const secrets = this.getReleaseSecrets(); - const amountChanged = secrets.length !== this.releaseSecrets.length; - const labelsChanged = this.releaseSecrets.some(item => { - const secret = secrets.find(secret => secret.getId() == item.getId()); - - if (!secret) return; - - return !isEqual(item.getLabels(), secret.getLabels()); - }); + const newSecrets = this.getReleaseSecrets(); + const amountChanged = newSecrets.length !== this.releaseSecrets.size; + const labelsChanged = newSecrets.some(([id, secret]) => ( + !isEqual(secret.getLabels(), this.releaseSecrets.get(id)?.getLabels()) + )); if (amountChanged || labelsChanged) { this.loadFromContextNamespaces(); } - this.releaseSecrets = [...secrets]; + this.releaseSecrets.replace(newSecrets); }); } - unwatch() { - this.secretWatcher(); + watchSelecteNamespaces(): (() => void) { + return reaction(() => namespaceStore.context.contextNamespaces, namespaces => { + this.loadAll(namespaces); + }); } - getReleaseSecrets() { - return secretsStore.getByLabel({ owner: "helm" }); + private getReleaseSecrets() { + return secretsStore + .getByLabel({ owner: "helm" }) + .map(s => [s.getId(), s] as const); } getReleaseSecret(release: HelmRelease) { - const labels = { + return secretsStore.getByLabel({ owner: "helm", name: release.getName() - }; - - return secretsStore.getByLabel(labels) - .filter(secret => secret.getNs() == release.getNs())[0]; + }) + .find(secret => secret.getNs() == release.getNs()); } @action async loadAll(namespaces: string[]) { + console.log("reloading releases", namespaces); this.isLoading = true; + this.isLoaded = false; try { const items = await this.loadItems(namespaces); @@ -79,17 +78,18 @@ export class ReleaseStore extends ItemStore { } async loadFromContextNamespaces(): Promise { - return this.loadAll(namespaceStore.contextNamespaces); + return this.loadAll(namespaceStore.context.contextNamespaces); } async loadItems(namespaces: string[]) { - const isLoadingAll = namespaceStore.allowedNamespaces.every(ns => namespaces.includes(ns)); - const noAccessibleNamespaces = namespaceStore.context.cluster.accessibleNamespaces.length === 0; + const isLoadingAll = namespaceStore.context.allNamespaces?.length > 1 + && namespaceStore.context.cluster.accessibleNamespaces.length === 0 + && namespaceStore.context.allNamespaces.every(ns => namespaces.includes(ns)); - if (isLoadingAll && noAccessibleNamespaces) { + if (isLoadingAll) { return helmReleasesApi.list(); } else { - return Promise + return Promise // load resources per namespace .all(namespaces.map(namespace => helmReleasesApi.list(namespace))) .then(items => items.flat()); } diff --git a/src/renderer/components/+apps-releases/releases.tsx b/src/renderer/components/+apps-releases/releases.tsx index 71cf3d954f..8b8f4ea048 100644 --- a/src/renderer/components/+apps-releases/releases.tsx +++ b/src/renderer/components/+apps-releases/releases.tsx @@ -2,7 +2,7 @@ import "./releases.scss"; import React, { Component } from "react"; import kebabCase from "lodash/kebabCase"; -import { observer } from "mobx-react"; +import { disposeOnUnmount, observer } from "mobx-react"; import { RouteComponentProps } from "react-router"; import { releaseStore } from "./release.store"; import { IReleaseRouteParams, releaseURL } from "./release.route"; @@ -30,14 +30,11 @@ interface Props extends RouteComponentProps { @observer export class HelmReleases extends Component { - componentDidMount() { - // Watch for secrets associated with releases and react to their changes - releaseStore.watch(); - } - - componentWillUnmount() { - releaseStore.unwatch(); + disposeOnUnmount(this, [ + releaseStore.watchAssociatedSecrets(), + releaseStore.watchSelecteNamespaces(), + ]); } get selectedRelease() { @@ -49,21 +46,16 @@ export class HelmReleases extends Component { } showDetails = (item: HelmRelease) => { - if (!item) { - navigation.merge(releaseURL()); - } - else { - navigation.merge(releaseURL({ - params: { - name: item.getName(), - namespace: item.getNs() - } - })); - } + navigation.merge(releaseURL({ + params: { + name: item.getName(), + namespace: item.getNs() + } + })); }; hideDetails = () => { - this.showDetails(null); + navigation.merge(releaseURL()); }; renderRemoveDialogMessage(selectedItems: HelmRelease[]) { @@ -114,30 +106,22 @@ export class HelmReleases extends Component { { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, { title: "Updated", className: "updated", sortBy: columnId.updated, id: columnId.updated }, ]} - renderTableContents={(release: HelmRelease) => { - const version = release.getVersion(); - - return [ - release.getName(), - release.getNs(), - release.getChart(), - release.getRevision(), - <> - {version} - , - release.appVersion, - { title: release.getStatus(), className: kebabCase(release.getStatus()) }, - release.getUpdated(), - ]; - }} - renderItemMenu={(release: HelmRelease) => { - return ( - - ); - }} + renderTableContents={(release: HelmRelease) => [ + release.getName(), + release.getNs(), + release.getChart(), + release.getRevision(), + release.getVersion(), + release.appVersion, + { title: release.getStatus(), className: kebabCase(release.getStatus()) }, + release.getUpdated(), + ]} + renderItemMenu={(release: HelmRelease) => ( + + )} customizeRemoveDialog={(selectedItems: HelmRelease[]) => ({ message: this.renderRemoveDialogMessage(selectedItems) })} diff --git a/src/renderer/components/context.ts b/src/renderer/components/context.ts index e8c9b1327d..5233faba83 100755 --- a/src/renderer/components/context.ts +++ b/src/renderer/components/context.ts @@ -4,8 +4,8 @@ import { namespaceStore } from "./+namespaces/namespace.store"; export interface ClusterContext { cluster?: Cluster; - allNamespaces?: string[]; // available / allowed namespaces from cluster.ts - contextNamespaces?: string[]; // selected by user (see: namespace-select.tsx) + allNamespaces: string[]; // available / allowed namespaces from cluster.ts + contextNamespaces: string[]; // selected by user (see: namespace-select.tsx) } export const clusterContext: ClusterContext = { diff --git a/src/renderer/components/item-object-list/item-list-layout.tsx b/src/renderer/components/item-object-list/item-list-layout.tsx index 5c7db4cd41..88df0bc093 100644 --- a/src/renderer/components/item-object-list/item-list-layout.tsx +++ b/src/renderer/components/item-object-list/item-list-layout.tsx @@ -293,6 +293,10 @@ export class ItemListLayout extends React.Component { } renderNoItems() { + if (!this.isReady) { + return ; + } + if (this.filters.length > 0) { return ( @@ -309,6 +313,14 @@ export class ItemListLayout extends React.Component { return ; } + renderItems() { + if (this.props.virtual) { + return null; + } + + return this.items.map(item => this.getRow(item.getId())); + } + renderHeaderContent(placeholders: IHeaderPlaceholders): ReactNode { const { isSearchable, searchFilters } = this.props; const { title, filters, search, info } = placeholders; @@ -339,7 +351,7 @@ export class ItemListLayout extends React.Component { ); } - return allItemsCount <= 1 ? `${allItemsCount} item` : `${allItemsCount} items`; + return allItemsCount === 1 ? `${allItemsCount} item` : `${allItemsCount} items`; } renderHeader() { @@ -415,37 +427,29 @@ export class ItemListLayout extends React.Component { store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, detailsItem, tableProps = {}, tableId } = this.props; - const { isReady, removeItemsDialog, items } = this; + const { removeItemsDialog, items } = this; const { selectedItems } = store; const selectedItemId = detailsItem && detailsItem.getId(); return (
- {!isReady && ( - - )} - {isReady && ( - - {this.renderTableHeader()} - { - !virtual && items.map(item => this.getRow(item.getId())) - } -
- - )} + + {this.renderTableHeader()} + {this.renderItems()} +