Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pool management - delete #8550

Merged

Conversation

GowthamShanmugam
Copy link
Contributor

Signed-off-by: Gowtham Shanmugasundaram gshanmug@redhat.com

@openshift-ci-robot openshift-ci-robot added the component/ceph Related to ceph-storage-plugin label Apr 4, 2021

export type OcsStorageClassResource = StorageClassResourceKind & {
parameters: {
pool: string;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

existing storage kind dont have StorageClassResourceKind dont have a parameter, also parameter will change dynamically, i am maintaining our OCS specific StorageClassResourceKind

@GowthamShanmugam
Copy link
Contributor Author

GowthamShanmugam commented Apr 4, 2021

delete_pool_icon_2

delete_pool_icon

@GowthamShanmugam GowthamShanmugam force-pushed the pool_delete branch 2 times, most recently from 1d4d086 to 27dc27d Compare April 4, 2021 16:47
@openshift-ci-robot openshift-ci-robot added the kind/i18n Indicates issue or PR relates to internationalization or has content that needs to be translated label Apr 4, 2021
@GowthamShanmugam GowthamShanmugam force-pushed the pool_delete branch 5 times, most recently from c690455 to 0e7cf8c Compare April 5, 2021 13:28
@GowthamShanmugam
Copy link
Contributor Author

/retest

1 similar comment
@GowthamShanmugam
Copy link
Contributor Author

/retest

"Finish": "Finish",
"Delete Block Pool": "Delete Block Pool",
"{state.poolStatus === POOL_PROGRESS.BOUNDED ? (\n <p>\n <strong>{state.poolName}</strong> cannot be deleted. When a pool is bounded to\n PVC it cannot be deleted. Please detach all the resources from storage class(es){' '}\n <strong>{scNames.join(', ')}</strong>.{' '}\n <Button variant=\"link\" isInline onClick={pvcLink}>\n Go to PVC list\n </Button>\n </p>\n ) : (\n <p>\n Deleting <strong>{state.poolName}</strong> will remove all the saved data of\n this pool. Are you sure want to delete?\n </p>\n )}": "{state.poolStatus === POOL_PROGRESS.BOUNDED ? (\n <p>\n <strong>{state.poolName}</strong> cannot be deleted. When a pool is bounded to\n PVC it cannot be deleted. Please detach all the resources from storage class(es){' '}\n <strong>{scNames.join(', ')}</strong>.{' '}\n <Button variant=\"link\" isInline onClick={pvcLink}>\n Go to PVC list\n </Button>\n </p>\n ) : (\n <p>\n Deleting <strong>{state.poolName}</strong> will remove all the saved data of\n this pool. Are you sure want to delete?\n </p>\n )}",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably by mistake ?

Comment on lines 137 to 127
<Trans t={t} ns="ceph-storage-plugin">
{state.poolStatus === POOL_PROGRESS.BOUNDED ? (
<p>
<strong>{state.poolName}</strong> cannot be deleted. When a pool is bounded to
PVC it cannot be deleted. Please detach all the resources from storage class(es){' '}
<strong>{scNames.join(', ')}</strong>.{' '}
<Button variant="link" isInline onClick={pvcLink}>
Go to PVC list
</Button>
</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<Trans t={t} ns="ceph-storage-plugin">
{state.poolStatus === POOL_PROGRESS.BOUNDED ? (
<p>
<strong>{state.poolName}</strong> cannot be deleted. When a pool is bounded to
PVC it cannot be deleted. Please detach all the resources from storage class(es){' '}
<strong>{scNames.join(', ')}</strong>.{' '}
<Button variant="link" isInline onClick={pvcLink}>
Go to PVC list
</Button>
</p>
{state.poolStatus === POOL_PROGRESS.BOUNDED ? (
<Trans t={t} ns="ceph-storage-plugin">
<p>
<strong>{{state.poolName}}</strong> cannot be deleted. When a pool is bounded to
PVC it cannot be deleted. Please detach all the resources from storage class(es){' '}
<strong>{{scNames.join(', ')}}</strong>.{' '}
<Button variant="link" isInline onClick={pvcLink}>
Go to PVC list
</Button>
</p>
</Trans>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

Comment on lines 148 to 132
<p>
Deleting <strong>{state.poolName}</strong> will remove all the saved data of
this pool. Are you sure want to delete?
</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<p>
Deleting <strong>{state.poolName}</strong> will remove all the saved data of
this pool. Are you sure want to delete?
</p>
<Trans t={t} ns="ceph-storage-plugin">
<p>
Deleting <strong>{{state.poolName}}</strong> will remove all the saved data of
this pool. Are you sure want to delete?
</p>
</Trans>

this pool. Are you sure want to delete?
</p>
)}
</Trans>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
</Trans>

Comment on lines 88 to 87
(scNameList: string[]) => {
const pvcScNames = pvcResources?.items?.map((pvcConfig) => pvcConfig?.spec?.storageClassName);
// intersection of scNames and pvcScNames
const usedScNames = scNameList.filter((scName) => pvcScNames.includes(scName));
if (usedScNames.length) {
dispatch({ type: BlockPoolActionType.SET_POOL_STATUS, payload: POOL_PROGRESS.BOUNDED });
setScNames(usedScNames);
}
},
[pvcResources],
);

// find storage class using pool
React.useEffect(() => {
if (scLoaded && pvcLoaded)
checkPvcStatus(
scResources?.items
?.filter((scConfig) => state.poolName === scConfig?.parameters?.pool)
.map((scConfig) => scConfig?.metadata?.name) || [],
);
}, [scResources, scLoaded, pvcResources, pvcLoaded, state.poolName, checkPvcStatus]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can find a lot of these helpers in selectors/index.ts . You can leverage those and you should always create pure functions is possible.
For instance -> getPVStorageClass for
const pvcScNames = pvcResources?.items?.map((pvcConfig) => pvcConfig?.spec?.storageClassName);
And set the state setScNames in useEffect

@GowthamShanmugam GowthamShanmugam force-pushed the pool_delete branch 3 times, most recently from 41c0ff9 to 36e3168 Compare April 6, 2021 11:49
onClick: () => void;
action: 'Create' | 'Save' | 'Delete';
onPoolCreation?: (name: string) => void;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this footer is shared by create | delete | update module so no more extra option required

@GowthamShanmugam
Copy link
Contributor Author

@afreen23 Changes done

Comment on lines 94 to 95
export const getPvcNames = (pvcResources: ListKind<K8sResourceKind>): string[] =>
pvcResources?.items?.map((pvcConfig) => getStorageClassName(pvcConfig));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its giving you sc names and not pvc names.
No need to create a new utility just use it directly in the component as follows:

Suggested change
export const getPvcNames = (pvcResources: ListKind<K8sResourceKind>): string[] =>
pvcResources?.items?.map((pvcConfig) => getStorageClassName(pvcConfig));
const scNames = pvcResources?.items?.map(getStorageClassName);

Comment on lines 85 to 92
export const getSCNamesUsingPool = (
scData: ListKind<K8sResourceKind>,
poolName: string,
): string[] =>
scData.items?.reduce((scNames, sc) => {
if (_.get(sc, 'parameters.pool') === poolName) scNames.push(sc.metadata?.name);
return scNames;
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not put this here, if its not going to be used by components other than pool.

Suggested change
export const getSCNamesUsingPool = (
scData: ListKind<K8sResourceKind>,
poolName: string,
): string[] =>
scData.items?.reduce((scNames, sc) => {
if (_.get(sc, 'parameters.pool') === poolName) scNames.push(sc.metadata?.name);
return scNames;
}, []);
export const getSCNamesUsingPool = (
scData: ListKind<K8sResourceKind>,
poolName: string,
): string[] =>
scData.items?.reduce((scNames, sc) => {
if (sc?.parameters?.pool === poolName) scNames.push(sc.metadata?.name);
return scNames;
}, []);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! got the point

Comment on lines 70 to 77
const pvcScNames: string[] = getPvcNames(pvcResources) || [];
// intersection of scNames and pvcScNames
const usedScNames = scNameList.filter((scName) => pvcScNames.includes(scName));
if (usedScNames.length) {
dispatch({ type: BlockPoolActionType.SET_POOL_STATUS, payload: POOL_PROGRESS.BOUNDED });
setScNames(usedScNames);
}
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you can move this logic to use effect below itself.
You dont need callback since pvcResources is a dependency there as well. Hence not providing the expected optimization.

@@ -63,17 +63,20 @@ export const BlockPoolModalFooter = (props: BlockPoolModalFooterProps) => {
</Button>
<Button
type="button"
variant="primary"
variant={action !== 'Delete' ? 'primary' : 'danger'}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to create an enum of all button texts.
Import and use pf ButtonVariant enum.

Suggested change
variant={action !== 'Delete' ? 'primary' : 'danger'}
variant={action !== ButtonText.Delete ? ButtonVariant.primary' : ButtonVariant.danger'}

@GowthamShanmugam
Copy link
Contributor Author

/retest

@@ -29,8 +29,8 @@ export const getCephPVs = (pvsData: K8sResourceKind[] = []): K8sResourceKind[] =
);
});

const getPVStorageClass = (pv: K8sResourceKind) => _.get(pv, 'spec.storageClassName');
const getStorageClassName = (pvc: K8sResourceKind) =>
export const getPVStorageClass = (pv: K8sResourceKind) => _.get(pv, 'spec.storageClassName');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const getPVStorageClass = (pv: K8sResourceKind) => _.get(pv, 'spec.storageClassName');
const getPVStorageClass = (pv: K8sResourceKind) => _.get(pv, 'spec.storageClassName');

Comment on lines 74 to 76
const pvcScNames: string[] = pvcResources.items?.map((pvcConfig) =>
getStorageClassName(pvcConfig),
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const pvcScNames: string[] = pvcResources.items?.map((pvcConfig) =>
getStorageClassName(pvcConfig),
);
const pvcScNames: string[] = pvcResources.items?.map(getStorageClassName);


React.useEffect(() => {
if (scLoaded && pvcLoaded && state.poolStatus !== POOL_PROGRESS.NOTALLOWED) {
const scNameList: string[] = scResources.items?.reduce((scList, sc) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const scNameList: string[] = scResources.items?.reduce((scList, sc) => {
const poolScNames: string[] = scResources.items?.reduce((scList, sc) => {

@GowthamShanmugam GowthamShanmugam force-pushed the pool_delete branch 3 times, most recently from ddc7a5c to 92c06ce Compare April 7, 2021 08:43
Copy link
Contributor

@afreen23 afreen23 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets make the go to pvc list the primary action instead pf close, also add cancel.

<p>
<strong>{{ poolName }}</strong> cannot be deleted. When a pool is bounded to PVC
it cannot be deleted. Please detach all the resources from storage class(es){' '}
<strong>{{ scNames }}</strong>.{' '}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont you want to destructure all array elements separated by comma and the last element by and?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i accidentally removed the change, let me check how to add and for last element

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image


export const commaSeparatedString = (text: string[], t: TFunction): string =>
[text.slice(0, -1).join(', '), text.slice(-1)[0]].join(
text.length < 2 ? '' : ` ${t('ceph-storage-plugin~and ')}`,
Copy link
Contributor Author

@GowthamShanmugam GowthamShanmugam Apr 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i uded ` ${t('ceph-storage-plugin~and ')}` is working

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For translation, its better to not put any spaces with strings. Also there is a chance of accidental miss while modifying.
Also explicit indication of spaces misses the chance of removal while making any changes.

Suggested change
text.length < 2 ? '' : ` ${t('ceph-storage-plugin~and ')}`,
text.length < 2 ? '' : ` ${t('ceph-storage-plugin~and')} `,

@GowthamShanmugam GowthamShanmugam force-pushed the pool_delete branch 4 times, most recently from 45c211f to 01856e4 Compare April 8, 2021 18:15
],
[POOL_PROGRESS.NOTREADY]: [
{
id: 'modal-finish-action',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

few states have similar prop but i don't want to create a new function to pass the prop, I feel let it be separate, so we can do changes in future easily

state={state}
dispatch={dispatch}
onClick={onClick}
redirectToPvcList={redirectToPvcList}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally your footer should be generic to take two values vs onClick, redirect, actionTextButtonMapping.

  1. onSubmit - for primary text
  2. onCancel - for secondary text
    I dont think you need an extra enum - actionTextButtonMapping

type: ButtonType.submit,
variant: ButtonVariant.primary,
onClick: () => {
redirectToPvcList();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
redirectToPvcList();
history.push('/k8s/all-namespaces/persistentvolumeclaims');

handlePromise(k8sKill(CephBlockPoolModel, blockPoolConfig), () => close());
};

const redirectToPvcList = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the error you are getting ? I assume you can call it there too.

closeLabel: string;
actionButtonTextMapping: ActionButtonTextMapping;
confirmAction: () => void;
redirectToPvcList?: () => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
redirectToPvcList?: () => void;

actionLabel,
closeLabel,
confirmAction,
redirectToPvcList,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
redirectToPvcList,

cancel={cancel}
close={close}
actionLabel={t('ceph-storage-plugin~Save')}
closeLabel={t('ceph-storage-plugin~Close')}
actionButtonTextMapping={ActionButtonTextMapping.UPDATE}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
actionButtonTextMapping={ActionButtonTextMapping.UPDATE}
primaryAction={FooterPrimaryActions.UPDATE}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

@@ -274,3 +276,9 @@ export type Payload = K8sResourceCommon & {
[key: string]: any;
};
};

export type OcsStorageClassResource = StorageClassResourceKind & {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export type OcsStorageClassResource = StorageClassResourceKind & {
export type OcsStorageClassKind = StorageClassResourceKind & {

@@ -208,3 +208,9 @@ export const checkRequiredValues = (
volumeType: string,
isPoolManagementSupported: boolean,
): boolean => !poolName || !replicaSize || (isPoolManagementSupported && !volumeType);

export enum ActionButtonTextMapping {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export enum ActionButtonTextMapping {
export enum FooterPrimaryActions {

</Button>
</ActionGroup>
) : (
const modalFooterButtonFactory: ModalFooterButtonFactory = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const modalFooterButtonFactory: ModalFooterButtonFactory = {
const footerButtonsFactory: FooterButtonFactory = {

label: t('ceph-storage-plugin~Cancel'),
type: ButtonType.button,
variant: ButtonVariant.secondary,
onClick: () => cancel(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onClick: () => cancel(),
onClick: cancel

import { ModalComponentProps } from '@console/internal/components/factory/modal';
import { history } from '@console/internal/components/utils/router';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it works after import history from the correct place

cancel={props.cancel}
close={props.close}
actionLabel={t('ceph-storage-plugin~Create')}
closeLabel={t('ceph-storage-plugin~Finish')}
footerPrimaryAction={FooterPrimaryActions.CREATE}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: since its footer component's prop

Suggested change
footerPrimaryAction={FooterPrimaryActions.CREATE}
primaryAction={FooterPrimaryActions.CREATE}

}, [scResources, scLoaded, pvcResources, pvcLoaded, state.poolStatus, poolName, t]);

// Delete block pool
const onClick = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const onClick = () => {
const deletePool = () => {

Comment on lines 149 to 150
kind?: string;
blockPoolConfig?: StoragePoolKind;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why all props optional ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I cant find where kind is used ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind is optional as per extension design we have to receive both, but kind is optional

};

type ModalFooterButtonFactory = {
[stauts: string]: ButtonProps[];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[stauts: string]: ButtonProps[];
[status: POOL_PROGRESS]: ButtonProps[];

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it should be status in POOL_PROGRESS

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reason i used string is, i used default string

Comment on lines 176 to 177
disable?: boolean;
onClick: (e?: React.FormEvent<EventTarget>) => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
disable?: boolean;
onClick: (e?: React.FormEvent<EventTarget>) => void;
onClick: (e?: React.FormEvent<EventTarget>) => void;
disable?: boolean;

cancel,
close,
} = props;
const { state, dispatch, onSubmit, footerPrimaryAction, cancel, close } = props;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this footer be used for create block pool as well ?
Just to have one footer - BlockPoolFooter rather than one for modals and one for create.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can but it needs some hack for alignment, l thought it will confuse others, anyway, let me think what I can do here

};

type FooterButtonFactory = {
[stauts in POOL_PROGRESS | 'default']?: ButtonProps[];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[stauts in POOL_PROGRESS | 'default']?: ButtonProps[];
[status in POOL_PROGRESS | 'default']?: ButtonProps[];

Comment on lines 143 to 144
const actionContentAlign = (buttons: JSX.Element[]) =>
className.includes('actions--left') ? buttons.reverse() : buttons;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why reversing buttons order ?

cancel={onClose}
onSubmit={createPool}
primaryAction={FooterPrimaryActions.CREATE}
className="pf-c-form pf-c-form__actions--left"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why pf-c-form on button actions ?

Signed-off-by: Gowtham Shanmugasundaram <gshanmug@redhat.com>
Copy link
Contributor

@afreen23 afreen23 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@openshift-ci-robot openshift-ci-robot added the lgtm Indicates that a PR is ready to be merged. label Apr 9, 2021
@openshift-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: afreen23, GowthamShanmugam

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci-robot openshift-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Apr 9, 2021
@GowthamShanmugam
Copy link
Contributor Author

/retest

1 similar comment
@GowthamShanmugam
Copy link
Contributor Author

/retest

@openshift-bot
Copy link
Contributor

/retest

Please review the full test history for this PR and help us cut down flakes.

@GowthamShanmugam
Copy link
Contributor Author

/retest

@afreen23
Copy link
Contributor

afreen23 commented Apr 9, 2021

/test e2e-gcp-console

@openshift-bot
Copy link
Contributor

/retest

Please review the full test history for this PR and help us cut down flakes.

@openshift-merge-robot openshift-merge-robot merged commit b99922d into openshift:master Apr 10, 2021
@spadgett spadgett added this to the v4.8 milestone May 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. component/ceph Related to ceph-storage-plugin kind/i18n Indicates issue or PR relates to internationalization or has content that needs to be translated lgtm Indicates that a PR is ready to be merged.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants