Skip to content

Commit

Permalink
feat(cc-kv-explorer): add error state when fetching keys fails
Browse files Browse the repository at this point in the history
  • Loading branch information
pdesoyres-cc committed Jan 27, 2025
1 parent 1274365 commit 040431c
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 76 deletions.
162 changes: 116 additions & 46 deletions src/components/cc-kv-explorer/cc-kv-explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import '../cc-select/cc-select.js';
* @typedef {import('./cc-kv-explorer.types.js').CcKvExplorerStateLoadingKeys} CcKvExplorerStateLoadingKeys
* @typedef {import('./cc-kv-explorer.types.js').CcKvExplorerStateFiltering} CcKvExplorerStateFiltering
* @typedef {import('./cc-kv-explorer.types.js').CcKvExplorerStateRefreshing} CcKvExplorerStateRefreshing
* @typedef {import('./cc-kv-explorer.types.js').CcKvExplorerStateErrorKeys} CcKvExplorerStateErrorKeys
* @typedef {import('./cc-kv-explorer.types.js').CcKvExplorerDetailState} CcKvExplorerDetailState
* @typedef {import('./cc-kv-explorer.types.js').CcKvExplorerKeyAddFormState} CcKvExplorerKeyAddFormState
* @typedef {import('./cc-kv-explorer.types.js').CcKvKeyFilter} CcKvKeyFilter
Expand Down Expand Up @@ -291,6 +292,22 @@ export class CcKvExplorer extends LitElement {
}
}

/**
* @param {CcKvExplorerStateErrorKeys} state
*/
_getErrorKeyMessage(state) {
switch (state.action) {
case 'loading':
return i18n('cc-kv-explorer.error.fetch-keys.loading');
case 'loading-more':
return i18n('cc-kv-explorer.error.fetch-keys.loading-more');
case 'filtering':
return i18n('cc-kv-explorer.error.fetch-keys.filtering');
case 'refreshing':
return i18n('cc-kv-explorer.error.fetch-keys.refreshing');
}
}

/**
* @param {'already-used'|null} errorCode
*/
Expand Down Expand Up @@ -384,6 +401,21 @@ export class CcKvExplorer extends LitElement {
dispatchCustomEvent(this, 'refresh-keys');
}

_onErrorKeysRetryButtonClick() {
if (this.state.type === 'error-keys') {
switch (this.state.action) {
case 'loading':
case 'refreshing':
case 'loading-more':
dispatchCustomEvent(this, 'refresh-keys');
break;
case 'filtering':
this._filterFormRef.value.requestSubmit();
break;
}
}
}

/**
* @param {CustomEvent<CcKvKeyType>} event
*/
Expand Down Expand Up @@ -511,9 +543,7 @@ export class CcKvExplorer extends LitElement {

return html`
<div class="wrapper">
${this._renderFilterBar(this.state)}
<div class="keys">${this._renderKeysHeader(this.state)} ${this._renderKeys(this.state)}</div>
${this._renderDetail()}
${this._renderFilterBar(this.state)} ${this._renderKeys(this.state)} ${this._renderDetail()}
<cc-kv-terminal-beta
class="terminal"
.state=${this.terminalState}
Expand All @@ -524,18 +554,20 @@ export class CcKvExplorer extends LitElement {
}

/**
* @param {CcKvExplorerStateLoaded | CcKvExplorerStateLoadingKeys | CcKvExplorerStateFiltering| CcKvExplorerStateRefreshing} state
* @param {CcKvExplorerStateLoaded | CcKvExplorerStateLoadingKeys | CcKvExplorerStateFiltering| CcKvExplorerStateRefreshing | CcKvExplorerStateErrorKeys} state
* @return {TemplateResult}
*/
_renderFilterBar(state) {
const isError = state.type === 'error-keys';
const isFetching = state.type === 'loading-keys' || state.type === 'filtering' || state.type === 'refreshing';
const isFiltering = state.type === 'filtering';
const isReadonly = isError || isFetching;

/** @type {Array<CcKvKeyType | 'all'>} */
const redisFilters = ['all', ...this.supportedTypes];
const kvFilterOptions = redisFilters
// todo: remove this filter and replace by a proper `readonly` property once `<cc-select>` supports it.
.filter((f) => !isFetching || f !== this._filterType)
// todo: remove this filter and replace by a proper `readonly` property once `<cc-select>` supports it. (cf https://github.com/CleverCloud/clever-components/issues/1299)
.filter((f) => (isReadonly ? f === this._filterType : true))
.map((f) => ({ label: this._getKeyFilterLabel(f), value: f }));

return html`
Expand All @@ -553,14 +585,14 @@ export class CcKvExplorer extends LitElement {
name="pattern"
inline
label=${i18n('cc-kv-explorer.filter.by-pattern')}
?readonly=${isFetching}
?readonly=${isReadonly}
></cc-input-text>
<cc-button
type="submit"
.icon=${iconFilter}
hide-text
outlined
?disabled=${isFetching && !isFiltering}
?disabled=${isReadonly && !isFiltering}
?waiting=${isFiltering}
>
${i18n('cc-kv-explorer.filter.apply')}
Expand All @@ -570,10 +602,68 @@ export class CcKvExplorer extends LitElement {
}

/**
* @param {CcKvExplorerStateLoaded | CcKvExplorerStateLoadingKeys | CcKvExplorerStateFiltering | CcKvExplorerStateRefreshing} state
* @param {CcKvExplorerStateLoaded | CcKvExplorerStateLoadingKeys | CcKvExplorerStateFiltering | CcKvExplorerStateRefreshing | CcKvExplorerStateErrorKeys} state
* @return {TemplateResult}
*/
_renderKeys(state) {
if (state.type === 'error-keys') {
return html`<div class="keys error">
<cc-notice intent="danger">
<div slot="message">
<span>${this._getErrorKeyMessage(state)}</span>&nbsp;<cc-button
link
@cc-button:click=${this._onErrorKeysRetryButtonClick}
>${i18n('cc-kv-explorer.error.fetch-keys.retry')}</cc-button
>
</div>
</cc-notice>
</div>`;
}

return html`<div class="keys">${this._renderKeysHeader(state)} ${this._renderKeysList(state)}</div>`;
}

/**
* @param {CcKvExplorerStateLoaded | CcKvExplorerStateLoadingKeys | CcKvExplorerStateFiltering | CcKvExplorerStateRefreshing} state
* @return {TemplateResult}
*/
_renderKeysHeader(state) {
const isFetching = state.type === 'loading-keys' || state.type === 'filtering' || state.type === 'refreshing';
const skeleton = state.type === 'loading-keys' || state.type === 'refreshing';
const isRefreshing = state.type === 'refreshing';

return html`<div class="keys-header">
<div>
<span class=${classMap({ skeleton })}>
${i18n('cc-kv-explorer.keys.header.total', { total: state.total })}
</span>
</div>
<cc-button
${ref(this._addButtonRef)}
a11y-name=${i18n('cc-kv-explorer.keys.header.add-key.a11y')}
.icon=${iconAdd}
primary
@cc-button:click=${this._onAddButtonClick}
>${i18n('cc-kv-explorer.keys.header.add-key')}</cc-button
>
<cc-button
.icon=${iconRefresh}
hide-text
outlined
?disabled=${isFetching && !isRefreshing}
?waiting=${isRefreshing}
@cc-button:click=${this._onRefreshKeysButtonClick}
>${i18n('cc-kv-explorer.keys.header.refresh')}</cc-button
>
</div>`;
}

/**
* @param {CcKvExplorerStateLoaded | CcKvExplorerStateLoadingKeys | CcKvExplorerStateFiltering | CcKvExplorerStateRefreshing} state
* @return {TemplateResult}
*/
_renderKeysList(state) {
if (state.type === 'loaded' && state.keys.length === 0) {
return html`
<div class="keys-list-empty">
Expand Down Expand Up @@ -625,42 +715,6 @@ export class CcKvExplorer extends LitElement {
`;
}

/**
* @param {CcKvExplorerStateLoaded | CcKvExplorerStateLoadingKeys | CcKvExplorerStateFiltering | CcKvExplorerStateRefreshing} state
* @return {TemplateResult}
*/
_renderKeysHeader(state) {
const isFetching = state.type === 'loading-keys' || state.type === 'filtering' || state.type === 'refreshing';
const skeleton = state.type === 'loading-keys' || state.type === 'refreshing';
const isRefreshing = state.type === 'refreshing';

return html`<div class="keys-header">
<div>
<span class=${classMap({ skeleton })}>
${i18n('cc-kv-explorer.keys.header.total', { total: state.total })}
</span>
</div>
<cc-button
${ref(this._addButtonRef)}
a11y-name=${i18n('cc-kv-explorer.keys.header.add-key.a11y')}
.icon=${iconAdd}
primary
@cc-button:click=${this._onAddButtonClick}
>${i18n('cc-kv-explorer.keys.header.add-key')}</cc-button
>
<cc-button
.icon=${iconRefresh}
hide-text
outlined
?disabled=${isFetching && !isRefreshing}
?waiting=${isRefreshing}
@cc-button:click=${this._onRefreshKeysButtonClick}
>${i18n('cc-kv-explorer.keys.header.refresh')}</cc-button
>
</div>`;
}

/**
* @param {object} _
* @param {CcKvKeyState} _.keyState
Expand Down Expand Up @@ -711,7 +765,12 @@ export class CcKvExplorer extends LitElement {
_renderDetail() {
switch (this.detailState.type) {
case 'hidden':
if (this.state.type === 'loading' || this.state.type === 'error' || this.state.total === 0) {
if (
this.state.type === 'loading' ||
this.state.type === 'error' ||
this.state.type === 'error-keys' ||
this.state.total === 0
) {
return null;
}
return html`<div class="detail-empty">
Expand Down Expand Up @@ -979,6 +1038,17 @@ export class CcKvExplorer extends LitElement {
overflow: hidden;
}
.keys.error {
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
}
.keys.error cc-button {
vertical-align: baseline;
}
.keys-header {
align-items: center;
background-color: var(--cc-color-bg-neutral);
Expand Down
8 changes: 4 additions & 4 deletions src/components/cc-kv-explorer/cc-kv-explorer.smart.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ defineSmartComponent({
await keysCtrl.filter(type, pattern);
} catch (e) {
console.error(e);
notifyError(i18n('cc-kv-explorer.error.fetch-keys'));
updateComponent('state', { type: 'error-keys', action: 'filtering' });
}
},
);
Expand All @@ -94,7 +94,7 @@ defineSmartComponent({
await keysCtrl.refresh();
} catch (e) {
console.error(e);
notifyError(i18n('cc-kv-explorer.error.fetch-keys'));
updateComponent('state', { type: 'error-keys', action: 'refreshing' });
}
});

Expand All @@ -103,7 +103,7 @@ defineSmartComponent({
await keysCtrl.loadMore();
} catch (e) {
console.error(e);
notifyError(i18n('cc-kv-explorer.error.fetch-keys'));
updateComponent('state', { type: 'error-keys', action: 'loading-more' });
}
});

Expand Down Expand Up @@ -433,7 +433,7 @@ defineSmartComponent({
}
} catch (e) {
console.error(e);
notifyError(i18n('cc-kv-explorer.error.fetch-keys'));
updateComponent('state', { type: 'error-keys', action: 'loading' });
}

/**
Expand Down
48 changes: 48 additions & 0 deletions src/components/cc-kv-explorer/cc-kv-explorer.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,54 @@ export const refreshingKeys = makeStory(conf, {
],
});

export const errorLoadingKeys = makeStory(conf, {
/** @type {Array<Partial<CcKvExplorer>>} */
items: [
{
state: {
type: 'error-keys',
action: 'loading',
},
},
],
});

export const errorLoadingMoreKeys = makeStory(conf, {
/** @type {Array<Partial<CcKvExplorer>>} */
items: [
{
state: {
type: 'error-keys',
action: 'loading-more',
},
},
],
});

export const errorFilteringKeys = makeStory(conf, {
/** @type {Array<Partial<CcKvExplorer>>} */
items: [
{
state: {
type: 'error-keys',
action: 'filtering',
},
},
],
});

export const errorRefreshingKeys = makeStory(conf, {
/** @type {Array<Partial<CcKvExplorer>>} */
items: [
{
state: {
type: 'error-keys',
action: 'refreshing',
},
},
],
});

export const loadingKeyString = makeStory(conf, {
/** @type {Array<Partial<CcKvExplorer>>} */
items: [
Expand Down
8 changes: 7 additions & 1 deletion src/components/cc-kv-explorer/cc-kv-explorer.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export type CcKvExplorerState =
| CcKvExplorerStateLoadingKeys
| CcKvExplorerStateFiltering
| CcKvExplorerStateRefreshing
| CcKvExplorerStateLoaded;
| CcKvExplorerStateLoaded
| CcKvExplorerStateErrorKeys;

export interface CcKvExplorerStateLoading {
type: 'loading';
Expand Down Expand Up @@ -43,6 +44,11 @@ export interface CcKvExplorerStateLoaded {
total: number;
}

export interface CcKvExplorerStateErrorKeys {
type: 'error-keys';
action: 'loading' | 'loading-more' | 'filtering' | 'refreshing';
}

//-- filter ---

export type CcKvKeyFilter = {
Expand Down
Loading

0 comments on commit 040431c

Please sign in to comment.