Skip to content

Commit

Permalink
Add Confirm modal when deleting
Browse files Browse the repository at this point in the history
This was missed because I copied App Search and it was an included there. Once I had an API, I realized I needed to have the confirm modal, as deleting the API key is destructive
  • Loading branch information
scottybollinger committed Dec 3, 2021
1 parent 01476a8 commit e52e145
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ describe('ApiKeysLogic', () => {
},
activeApiTokenRawName: '',
apiKeyFormVisible: false,
apiTokenNameToDelete: '',
deleteModalVisible: false,
formErrors: [],
};

Expand Down Expand Up @@ -121,6 +123,30 @@ describe('ApiKeysLogic', () => {
});
});

describe('deleteModalVisible', () => {
const tokenName = 'my-token';

it('should set deleteModalVisible to true and set apiTokenNameToDelete', () => {
ApiKeysLogic.actions.showDeleteModal(tokenName);

expect(ApiKeysLogic.values).toEqual({
...values,
deleteModalVisible: true,
apiTokenNameToDelete: tokenName,
});
});

it('should set deleteModalVisible to false and reset apiTokenNameToDelete', () => {
mount({
deleteModalVisible: true,
apiTokenNameToDelete: tokenName,
});
ApiKeysLogic.actions.hideDeleteModal();

expect(ApiKeysLogic.values).toEqual(values);
});
});

describe('formErrors', () => {
it('should reset `formErrors`', () => {
mount({
Expand Down Expand Up @@ -419,7 +445,8 @@ describe('ApiKeysLogic', () => {
jest.spyOn(ApiKeysLogic.actions, 'fetchApiKeys').mockImplementationOnce(() => {});
http.delete.mockReturnValue(Promise.resolve());

ApiKeysLogic.actions.deleteApiKey(tokenName);
ApiKeysLogic.actions.showDeleteModal(tokenName);
ApiKeysLogic.actions.deleteApiKey();
expect(http.delete).toHaveBeenCalledWith(
`/internal/workplace_search/api_keys/${tokenName}`
);
Expand All @@ -431,7 +458,7 @@ describe('ApiKeysLogic', () => {

itShowsServerErrorAsFlashMessage(http.delete, () => {
mount();
ApiKeysLogic.actions.deleteApiKey(tokenName);
ApiKeysLogic.actions.deleteApiKey();
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ interface ApiKeysLogicActions {
resetApiKeys(): { value: boolean };
fetchApiKeys(): void;
onPaginate(newPageIndex: number): { newPageIndex: number };
deleteApiKey(tokenName: string): string;
deleteApiKey(): void;
onApiFormSubmit(): void;
showDeleteModal(tokenName: string): string;
hideDeleteModal(): void;
}

interface ApiKeysLogicValues {
Expand All @@ -56,6 +58,8 @@ interface ApiKeysLogicValues {
meta: Meta;
nameInputBlurred: boolean;
apiKeyFormVisible: boolean;
deleteModalVisible: boolean;
apiTokenNameToDelete: string;
}

export const ApiKeysLogic = kea<MakeLogicType<ApiKeysLogicValues, ApiKeysLogicActions>>({
Expand All @@ -71,7 +75,9 @@ export const ApiKeysLogic = kea<MakeLogicType<ApiKeysLogicValues, ApiKeysLogicAc
resetApiKeys: false,
fetchApiKeys: true,
onPaginate: (newPageIndex) => ({ newPageIndex }),
deleteApiKey: (tokenName) => tokenName,
deleteApiKey: true,
showDeleteModal: (tokenName) => tokenName,
hideDeleteModal: true,
onApiFormSubmit: () => null,
}),
reducers: () => ({
Expand Down Expand Up @@ -125,6 +131,22 @@ export const ApiKeysLogic = kea<MakeLogicType<ApiKeysLogicValues, ApiKeysLogicAc
onApiTokenCreateSuccess: () => false,
},
],
deleteModalVisible: [
false,
{
showDeleteModal: () => true,
hideDeleteModal: () => false,
fetchApiKeys: () => false,
},
],
apiTokenNameToDelete: [
'',
{
showDeleteModal: (_, tokenName) => tokenName,
hideDeleteModal: () => '',
fetchApiKeys: () => '',
},
],
formErrors: [
[],
{
Expand Down Expand Up @@ -156,13 +178,15 @@ export const ApiKeysLogic = kea<MakeLogicType<ApiKeysLogicValues, ApiKeysLogicAc
flashAPIErrors(e);
}
},
deleteApiKey: async (tokenName) => {
deleteApiKey: async () => {
const { apiTokenNameToDelete } = values;

try {
const { http } = HttpLogic.values;
await http.delete(`/internal/workplace_search/api_keys/${tokenName}`);
await http.delete(`/internal/workplace_search/api_keys/${apiTokenNameToDelete}`);

actions.fetchApiKeys();
flashSuccessToast(DELETE_MESSAGE(tokenName));
flashSuccessToast(DELETE_MESSAGE(apiTokenNameToDelete));
} catch (e) {
flashAPIErrors(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ import React from 'react';

import { shallow } from 'enzyme';

import { EuiBasicTable, EuiCopy } from '@elastic/eui';
import { EuiBasicTable, EuiCopy, EuiConfirmModal } from '@elastic/eui';

import { HiddenText } from '../../../../shared/hidden_text';

import { ApiKey } from './api_key';
import { ApiKeysList } from './api_keys_list';

describe('ApiKeysList', () => {
const showDeleteModal = jest.fn();
const hideDeleteModal = jest.fn();
const deleteApiKey = jest.fn();
const onPaginate = jest.fn();
const apiToken = {
Expand All @@ -40,7 +42,7 @@ describe('ApiKeysList', () => {

beforeEach(() => {
setMockValues(values);
setMockActions({ deleteApiKey, onPaginate });
setMockActions({ deleteApiKey, onPaginate, showDeleteModal, hideDeleteModal });
});

it('renders', () => {
Expand Down Expand Up @@ -86,6 +88,18 @@ describe('ApiKeysList', () => {
});
});

it('handles confirmModal submission', () => {
setMockValues({
...values,
deleteModalVisible: true,
});
const wrapper = shallow(<ApiKeysList />);
const modal = wrapper.find(EuiConfirmModal);
modal.prop('onConfirm')!({} as any);

expect(deleteApiKey).toHaveBeenCalled();
});

describe('columns', () => {
let columns: any[];

Expand Down Expand Up @@ -168,11 +182,11 @@ describe('ApiKeysList', () => {
name: 'some-name',
};

it('calls deleteApiKey when clicked', () => {
it('calls showDeleteModal when clicked', () => {
const action = columns[2].actions[0];
action.onClick(token);

expect(deleteApiKey).toHaveBeenCalledWith('some-name');
expect(showDeleteModal).toHaveBeenCalledWith('some-name');
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import React from 'react';

import { useActions, useValues } from 'kea';

import { EuiBasicTable, EuiBasicTableColumn, EuiCopy } from '@elastic/eui';
import { EuiBasicTable, EuiBasicTableColumn, EuiCopy, EuiConfirmModal } from '@elastic/eui';

import { DELETE_BUTTON_LABEL } from '../../../../shared/constants';
import { DELETE_BUTTON_LABEL, CANCEL_BUTTON_LABEL } from '../../../../shared/constants';
import { HiddenText } from '../../../../shared/hidden_text';
import { convertMetaToPagination, handlePageChange } from '../../../../shared/table_pagination';
import { ApiToken } from '../../../types';
Expand All @@ -22,13 +22,29 @@ import {
COPIED_TOOLTIP,
NAME_TITLE,
KEY_TITLE,
API_KEYS_CONFIRM_DELETE_TITLE,
API_KEYS_CONFIRM_DELETE_LABEL,
} from '../constants';

import { ApiKey } from './api_key';

export const ApiKeysList: React.FC = () => {
const { deleteApiKey, onPaginate } = useActions(ApiKeysLogic);
const { apiTokens, meta, dataLoading } = useValues(ApiKeysLogic);
const { deleteApiKey, onPaginate, showDeleteModal, hideDeleteModal } = useActions(ApiKeysLogic);
const { apiTokens, meta, dataLoading, deleteModalVisible } = useValues(ApiKeysLogic);

const deleteModal = (
<EuiConfirmModal
title={API_KEYS_CONFIRM_DELETE_TITLE}
onCancel={hideDeleteModal}
onConfirm={deleteApiKey}
cancelButtonText={CANCEL_BUTTON_LABEL}
confirmButtonText={DELETE_BUTTON_LABEL}
buttonColor="danger"
defaultFocusedButton="confirm"
>
<p>{API_KEYS_CONFIRM_DELETE_LABEL}</p>
</EuiConfirmModal>
);

const columns: Array<EuiBasicTableColumn<ApiToken>> = [
{
Expand Down Expand Up @@ -71,22 +87,25 @@ export const ApiKeysList: React.FC = () => {
type: 'icon',
icon: 'trash',
color: 'danger',
onClick: (token: ApiToken) => deleteApiKey(token.name),
onClick: (token: ApiToken) => showDeleteModal(token.name),
},
],
},
];

return (
<EuiBasicTable
columns={columns}
items={apiTokens}
loading={dataLoading}
pagination={{
...convertMetaToPagination(meta),
hidePerPageOptions: true,
}}
onChange={handlePageChange(onPaginate)}
/>
<>
{deleteModalVisible && deleteModal}
<EuiBasicTable
columns={columns}
items={apiTokens}
loading={dataLoading}
pagination={{
...convertMetaToPagination(meta),
hidePerPageOptions: true,
}}
onChange={handlePageChange(onPaginate)}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,17 @@ export const API_KEYS_EMPTY_BUTTON_LABEL = i18n.translate(
defaultMessage: 'Learn about API keys',
}
);

export const API_KEYS_CONFIRM_DELETE_TITLE = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.apiKeys.confirmDeleteTitle',
{
defaultMessage: 'Delete API key',
}
);

export const API_KEYS_CONFIRM_DELETE_LABEL = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.apiKeys.confirmDeleteLabel',
{
defaultMessage: 'Are you sure you want to delete this API key? This action cannot be undone.',
}
);

0 comments on commit e52e145

Please sign in to comment.