mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Helm components should always use version information (#949)
* clean up code to catch rejections Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
d20f890ddb
commit
5401c99298
@ -49,4 +49,4 @@
|
|||||||
.chart-description {
|
.chart-description {
|
||||||
margin-top: $margin * 2;
|
margin-top: $margin * 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import "./helm-chart-details.scss";
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { HelmChart, helmChartsApi } from "../../api/endpoints/helm-charts.api";
|
import { HelmChart, helmChartsApi } from "../../api/endpoints/helm-charts.api";
|
||||||
import { t, Trans } from "@lingui/macro";
|
import { t, Trans } from "@lingui/macro";
|
||||||
import { autorun, observable } from "mobx";
|
import { observable, toJS } from "mobx";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Drawer, DrawerItem } from "../drawer";
|
import { Drawer, DrawerItem } from "../drawer";
|
||||||
import { autobind, stopPropagation } from "../../utils";
|
import { autobind, stopPropagation } from "../../utils";
|
||||||
import { MarkdownViewer } from "../markdown-viewer";
|
import { MarkdownViewer } from "../markdown-viewer";
|
||||||
@ -25,39 +25,41 @@ interface Props {
|
|||||||
export class HelmChartDetails extends Component<Props> {
|
export class HelmChartDetails extends Component<Props> {
|
||||||
@observable chartVersions: HelmChart[];
|
@observable chartVersions: HelmChart[];
|
||||||
@observable selectedChart: HelmChart;
|
@observable selectedChart: HelmChart;
|
||||||
@observable description: string = null;
|
@observable readme: string = null;
|
||||||
|
@observable error: string = null;
|
||||||
|
|
||||||
private chartPromise: CancelablePromise<{ readme: string; versions: HelmChart[] }>;
|
private chartPromise: CancelablePromise<{ readme: string; versions: HelmChart[] }>;
|
||||||
|
|
||||||
@disposeOnUnmount
|
async componentDidMount() {
|
||||||
chartSelector = autorun(async () => {
|
const { chart: { name, repo, version } } = this.props
|
||||||
if (!this.props.chart) return;
|
|
||||||
this.chartVersions = null;
|
|
||||||
this.selectedChart = null;
|
|
||||||
this.description = null;
|
|
||||||
this.loadChartData();
|
|
||||||
this.chartPromise.then(data => {
|
|
||||||
this.description = data.readme;
|
|
||||||
this.chartVersions = data.versions;
|
|
||||||
this.selectedChart = data.versions[0];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
loadChartData(version?: string) {
|
try {
|
||||||
const { chart: { name, repo } } = this.props;
|
const { readme, versions } = await (this.chartPromise = helmChartsApi.get(repo, name, version))
|
||||||
if (this.chartPromise) this.chartPromise.cancel();
|
this.readme = readme
|
||||||
this.chartPromise = helmChartsApi.get(repo, name, version);
|
this.chartVersions = versions
|
||||||
|
this.selectedChart = versions[0]
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.chartPromise?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
onVersionChange(opt: SelectOption) {
|
async onVersionChange({ value: version }: SelectOption) {
|
||||||
const version = opt.value;
|
|
||||||
this.selectedChart = this.chartVersions.find(chart => chart.version === version);
|
this.selectedChart = this.chartVersions.find(chart => chart.version === version);
|
||||||
this.description = null;
|
this.readme = null;
|
||||||
this.loadChartData(version);
|
|
||||||
this.chartPromise.then(data => {
|
try {
|
||||||
this.description = data.readme
|
this.chartPromise?.cancel();
|
||||||
});
|
const { chart: { name, repo } } = this.props;
|
||||||
|
const { readme } = await (this.chartPromise = helmChartsApi.get(repo, name, version))
|
||||||
|
this.readme = readme;
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
@ -79,7 +81,7 @@ export class HelmChartDetails extends Component<Props> {
|
|||||||
<div className="intro-contents box grow">
|
<div className="intro-contents box grow">
|
||||||
<div className="description flex align-center justify-space-between">
|
<div className="description flex align-center justify-space-between">
|
||||||
{selectedChart.getDescription()}
|
{selectedChart.getDescription()}
|
||||||
<Button primary label={_i18n._(t`Install`)} onClick={this.install}/>
|
<Button primary label={_i18n._(t`Install`)} onClick={this.install} />
|
||||||
</div>
|
</div>
|
||||||
<DrawerItem name={_i18n._(t`Version`)} className="version" onClick={stopPropagation}>
|
<DrawerItem name={_i18n._(t`Version`)} className="version" onClick={stopPropagation}>
|
||||||
<Select
|
<Select
|
||||||
@ -95,12 +97,12 @@ export class HelmChartDetails extends Component<Props> {
|
|||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name={_i18n._(t`Maintainers`)} className="maintainers">
|
<DrawerItem name={_i18n._(t`Maintainers`)} className="maintainers">
|
||||||
{selectedChart.getMaintainers().map(({ name, email, url }) =>
|
{selectedChart.getMaintainers().map(({ name, email, url }) =>
|
||||||
<a key={name} href={url ? url : `mailto:${email}`} target="_blank">{name}</a>
|
<a key={name} href={url || `mailto:${email}`} target="_blank">{name}</a>
|
||||||
)}
|
)}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
{selectedChart.getKeywords().length > 0 && (
|
{selectedChart.getKeywords().length > 0 && (
|
||||||
<DrawerItem name={_i18n._(t`Keywords`)} labelsOnly>
|
<DrawerItem name={_i18n._(t`Keywords`)} labelsOnly>
|
||||||
{selectedChart.getKeywords().map(key => <Badge key={key} label={key}/>)}
|
{selectedChart.getKeywords().map(key => <Badge key={key} label={key} />)}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -108,14 +110,35 @@ export class HelmChartDetails extends Component<Props> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderReadme() {
|
||||||
|
if (this.readme === null) {
|
||||||
|
return <Spinner center />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="chart-description">
|
||||||
|
<MarkdownViewer markdown={this.readme} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
if (this.selectedChart === null || this.description === null) return <Spinner center/>;
|
if (!this.selectedChart) {
|
||||||
|
return <Spinner center />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.error) {
|
||||||
|
return (
|
||||||
|
<div className="box grow">
|
||||||
|
<p className="error">{this.error}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="box grow">
|
<div className="box grow">
|
||||||
{this.renderIntroduction()}
|
{this.renderIntroduction()}
|
||||||
<div className="chart-description">
|
{this.renderReadme()}
|
||||||
<MarkdownViewer markdown={this.description}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -135,4 +158,4 @@ export class HelmChartDetails extends Component<Props> {
|
|||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -72,7 +72,7 @@ export class HelmCharts extends Component<Props> {
|
|||||||
(items: HelmChart[]) => items.filter(item => !item.deprecated)
|
(items: HelmChart[]) => items.filter(item => !item.deprecated)
|
||||||
]}
|
]}
|
||||||
customizeHeader={() => (
|
customizeHeader={() => (
|
||||||
<SearchInput placeholder={_i18n._(t`Search Helm Charts`)}/>
|
<SearchInput placeholder={_i18n._(t`Search Helm Charts`)} />
|
||||||
)}
|
)}
|
||||||
renderTableHeader={[
|
renderTableHeader={[
|
||||||
{ className: "icon" },
|
{ className: "icon" },
|
||||||
@ -99,11 +99,13 @@ export class HelmCharts extends Component<Props> {
|
|||||||
detailsItem={this.selectedChart}
|
detailsItem={this.selectedChart}
|
||||||
onDetails={this.showDetails}
|
onDetails={this.showDetails}
|
||||||
/>
|
/>
|
||||||
<HelmChartDetails
|
{this.selectedChart && (
|
||||||
chart={this.selectedChart}
|
<HelmChartDetails
|
||||||
hideDetails={this.hideDetails}
|
chart={this.selectedChart}
|
||||||
/>
|
hideDetails={this.hideDetails}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { t } from "@lingui/macro";
|
|||||||
import { HelmChart, helmChartsApi } from "../../api/endpoints/helm-charts.api";
|
import { HelmChart, helmChartsApi } from "../../api/endpoints/helm-charts.api";
|
||||||
import { IReleaseUpdateDetails } from "../../api/endpoints/helm-releases.api";
|
import { IReleaseUpdateDetails } from "../../api/endpoints/helm-releases.api";
|
||||||
import { _i18n } from "../../i18n";
|
import { _i18n } from "../../i18n";
|
||||||
|
import { Notifications } from "../notifications";
|
||||||
|
|
||||||
export interface IChartInstallData {
|
export interface IChartInstallData {
|
||||||
name: string;
|
name: string;
|
||||||
@ -27,45 +28,50 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
|||||||
});
|
});
|
||||||
autorun(() => {
|
autorun(() => {
|
||||||
const { selectedTab, isOpen } = dockStore;
|
const { selectedTab, isOpen } = dockStore;
|
||||||
if (!isInstallChartTab(selectedTab)) return;
|
if (isInstallChartTab(selectedTab) && isOpen) {
|
||||||
if (isOpen) {
|
this.loadData()
|
||||||
this.loadData();
|
.catch(err => Notifications.error(String(err)))
|
||||||
}
|
}
|
||||||
}, { delay: 250 })
|
}, { delay: 250 })
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async loadData(tabId = dockStore.selectedTabId) {
|
async loadData(tabId = dockStore.selectedTabId) {
|
||||||
const { values } = this.getData(tabId);
|
const promises = []
|
||||||
const versions = this.versions.getData(tabId);
|
|
||||||
return Promise.all([
|
if (!this.getData(tabId).values) {
|
||||||
!versions && this.loadVersions(tabId),
|
promises.push(this.loadValues(tabId))
|
||||||
!values && this.loadValues(tabId),
|
}
|
||||||
])
|
|
||||||
|
if (!this.versions.getData(tabId)) {
|
||||||
|
promises.push(this.loadVersions(tabId))
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises)
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async loadVersions(tabId: TabId) {
|
async loadVersions(tabId: TabId) {
|
||||||
const { repo, name } = this.getData(tabId);
|
const { repo, name, version } = this.getData(tabId);
|
||||||
this.versions.clearData(tabId); // reset
|
this.versions.clearData(tabId); // reset
|
||||||
const charts = await helmChartsApi.get(repo, name);
|
const charts = await helmChartsApi.get(repo, name, version);
|
||||||
const versions = charts.versions.map(chartVersion => chartVersion.version);
|
const versions = charts.versions.map(chartVersion => chartVersion.version);
|
||||||
this.versions.setData(tabId, versions);
|
this.versions.setData(tabId, versions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async loadValues(tabId: TabId) {
|
async loadValues(tabId: TabId) {
|
||||||
const data = this.getData(tabId);
|
const data = this.getData(tabId)
|
||||||
const { repo, name, version } = data;
|
const { repo, name, version } = data
|
||||||
let values = "";
|
|
||||||
const fetchValues = async (retry = 1, maxRetries = 3) => {
|
// This loop is for "retrying" the "getValues" call
|
||||||
values = await helmChartsApi.getValues(repo, name, version);
|
for (const _ of Array(3)) {
|
||||||
if (values || retry == maxRetries) return;
|
const values = await helmChartsApi.getValues(repo, name, version)
|
||||||
await fetchValues(retry + 1);
|
if (values) {
|
||||||
};
|
this.setData(tabId, { ...data, values })
|
||||||
this.setData(tabId, { ...data, values: undefined }); // reset
|
return
|
||||||
await fetchValues();
|
}
|
||||||
this.setData(tabId, { ...data, values });
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -107,15 +107,15 @@ export class InstallChart extends Component<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { tabId, chartData, values, versions, install } = this;
|
const { tabId, chartData, values, versions, install } = this;
|
||||||
if (!chartData || chartData.values === undefined || !versions) {
|
if (chartData?.values === undefined || !versions) {
|
||||||
return <Spinner center/>;
|
return <Spinner center />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.releaseDetails) {
|
if (this.releaseDetails) {
|
||||||
return (
|
return (
|
||||||
<div className="InstallChartDone flex column gaps align-center justify-center">
|
<div className="InstallChartDone flex column gaps align-center justify-center">
|
||||||
<p>
|
<p>
|
||||||
<Icon material="check" big sticker/>
|
<Icon material="check" big sticker />
|
||||||
</p>
|
</p>
|
||||||
<p><Trans>Installation complete!</Trans></p>
|
<p><Trans>Installation complete!</Trans></p>
|
||||||
<div className="flex gaps align-center">
|
<div className="flex gaps align-center">
|
||||||
@ -144,7 +144,7 @@ export class InstallChart extends Component<Props> {
|
|||||||
const panelControls = (
|
const panelControls = (
|
||||||
<div className="install-controls flex gaps align-center">
|
<div className="install-controls flex gaps align-center">
|
||||||
<span><Trans>Chart</Trans></span>
|
<span><Trans>Chart</Trans></span>
|
||||||
<Badge label={`${repo}/${name}`} title={_i18n._(t`Repo/Name`)}/>
|
<Badge label={`${repo}/${name}`} title={_i18n._(t`Repo/Name`)} />
|
||||||
<span><Trans>Version</Trans></span>
|
<span><Trans>Version</Trans></span>
|
||||||
<Select
|
<Select
|
||||||
className="chart-version"
|
className="chart-version"
|
||||||
@ -191,4 +191,4 @@ export class InstallChart extends Component<Props> {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,10 +8,16 @@ import { JsonApiErrorParsed } from "../../api/json-api";
|
|||||||
export type IMessageId = string | number;
|
export type IMessageId = string | number;
|
||||||
export type IMessage = React.ReactNode | React.ReactNode[] | JsonApiErrorParsed;
|
export type IMessage = React.ReactNode | React.ReactNode[] | JsonApiErrorParsed;
|
||||||
|
|
||||||
|
export enum NotificationStatus {
|
||||||
|
OK = "ok",
|
||||||
|
ERROR = "error",
|
||||||
|
INFO = "info",
|
||||||
|
}
|
||||||
|
|
||||||
export interface INotification {
|
export interface INotification {
|
||||||
id?: IMessageId;
|
id?: IMessageId;
|
||||||
message: IMessage;
|
message: IMessage;
|
||||||
status?: "ok" | "error" | "info";
|
status?: NotificationStatus;
|
||||||
timeout?: number; // auto-hiding timeout in milliseconds, 0 = no hide
|
timeout?: number; // auto-hiding timeout in milliseconds, 0 = no hide
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { reaction } from "mobx";
|
|||||||
import { disposeOnUnmount, observer } from "mobx-react"
|
import { disposeOnUnmount, observer } from "mobx-react"
|
||||||
import { JsonApiErrorParsed } from "../../api/json-api";
|
import { JsonApiErrorParsed } from "../../api/json-api";
|
||||||
import { cssNames, prevDefault } from "../../utils";
|
import { cssNames, prevDefault } from "../../utils";
|
||||||
import { IMessage, INotification, notificationsStore } from "./notifications.store";
|
import { IMessage, INotification, notificationsStore, NotificationStatus } from "./notifications.store";
|
||||||
import { Animate } from "../animate";
|
import { Animate } from "../animate";
|
||||||
import { Icon } from "../icon"
|
import { Icon } from "../icon"
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ export class Notifications extends React.Component {
|
|||||||
notificationsStore.add({
|
notificationsStore.add({
|
||||||
message: message,
|
message: message,
|
||||||
timeout: 2500,
|
timeout: 2500,
|
||||||
status: "ok"
|
status: NotificationStatus.OK
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,13 +25,13 @@ export class Notifications extends React.Component {
|
|||||||
notificationsStore.add({
|
notificationsStore.add({
|
||||||
message: message,
|
message: message,
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
status: "error"
|
status: NotificationStatus.ERROR
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static info(message: IMessage, customOpts: Partial<INotification> = {}) {
|
static info(message: IMessage, customOpts: Partial<INotification> = {}) {
|
||||||
return notificationsStore.add({
|
return notificationsStore.add({
|
||||||
status: "info",
|
status: NotificationStatus.INFO,
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
message: message,
|
message: message,
|
||||||
...customOpts,
|
...customOpts,
|
||||||
@ -78,7 +78,7 @@ export class Notifications extends React.Component {
|
|||||||
onMouseLeave={() => addAutoHideTimer(notification)}
|
onMouseLeave={() => addAutoHideTimer(notification)}
|
||||||
onMouseEnter={() => removeAutoHideTimer(notification)}>
|
onMouseEnter={() => removeAutoHideTimer(notification)}>
|
||||||
<div className="box center">
|
<div className="box center">
|
||||||
<Icon material="info_outline"/>
|
<Icon material="info_outline" />
|
||||||
</div>
|
</div>
|
||||||
<div className="message box grow">{msgText}</div>
|
<div className="message box grow">{msgText}</div>
|
||||||
<div className="box center">
|
<div className="box center">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user