Skip to content

Commit

Permalink
Btsymbala/trusted app deletion (#77316) (#78299)
Browse files Browse the repository at this point in the history
* Moved the DeleteTrustedAppsRequestParams to common folder to be able to use it on the client.

* Added trusted app deletion API to the client service layer.

* Made default data type for async resource state.

* Added guard for stale state.

* Added timestamp to the list data to be used to refresh list when it's modified.

* Separated out base type for resource state change actions.

* Added action for outdating list data.

* Moved the refresh condition inside the middleware case function and added timestamping data.

* Added state, actions, reducers and middleware for deletion dialog.

* Added actions column and deletion action.

* Added trusted app deletion dialog.

* Changed to not have deletonDialog as optional in store.

* Changed the store to contain the full entry in the dialog state and changed the modal message to indicate the trusted app name.

* Extracted notifications component and enhanced error display.

* Added success message and unified messages a bit.

* Complete coverage with tests.

* Removed unused variable in translations.

* Fixed tests because of outdated snapshots and inproper mocking of htmlIdGenerator.

* Fixed code review comments.

* Fixed type error.
  • Loading branch information
efreeti authored Sep 24, 2020
1 parent dbda39b commit eebb6af
Show file tree
Hide file tree
Showing 31 changed files with 3,848 additions and 339 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@

import { TypeOf } from '@kbn/config-schema';
import {
DeleteTrustedAppsRequestSchema,
GetTrustedAppsRequestSchema,
PostTrustedAppCreateRequestSchema,
} from '../schema/trusted_apps';

/** API request params for deleting Trusted App entry */
export type DeleteTrustedAppsRequestParams = TypeOf<typeof DeleteTrustedAppsRequestSchema.params>;

/** API request params for retrieving a list of Trusted Apps */
export type GetTrustedAppsListRequest = TypeOf<typeof GetTrustedAppsRequestSchema.query>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,7 @@ export const getPolicyDetailPath = (policyId: string, search?: string) => {
})}${appendSearch(search)}`;
};

const isDefaultOrMissing = (
value: number | string | undefined,
defaultValue: number | undefined
) => {
const isDefaultOrMissing = <T>(value: T | undefined, defaultValue: T) => {
return value === undefined || value === defaultValue;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,26 @@
*/

import { HttpStart } from 'kibana/public';

import {
TRUSTED_APPS_CREATE_API,
TRUSTED_APPS_DELETE_API,
TRUSTED_APPS_LIST_API,
} from '../../../../../common/endpoint/constants';

import {
DeleteTrustedAppsRequestParams,
GetTrustedListAppsResponse,
GetTrustedAppsListRequest,
PostTrustedAppCreateRequest,
PostTrustedAppCreateResponse,
} from '../../../../../common/endpoint/types/trusted_apps';

import { resolvePathVariables } from './utils';

export interface TrustedAppsService {
getTrustedAppsList(request: GetTrustedAppsListRequest): Promise<GetTrustedListAppsResponse>;
deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise<void>;
createTrustedApp(request: PostTrustedAppCreateRequest): Promise<PostTrustedAppCreateResponse>;
}

Expand All @@ -30,6 +37,10 @@ export class TrustedAppsHttpService implements TrustedAppsService {
});
}

async deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise<void> {
return this.http.delete<void>(resolvePathVariables(TRUSTED_APPS_DELETE_API, request));
}

async createTrustedApp(request: PostTrustedAppCreateRequest) {
return this.http.post<PostTrustedAppCreateResponse>(TRUSTED_APPS_CREATE_API, {
body: JSON.stringify(request),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { resolvePathVariables } from './utils';

describe('utils', () => {
describe('resolvePathVariables', () => {
it('should resolve defined variables', () => {
expect(resolvePathVariables('/segment1/{var1}/segment2', { var1: 'value1' })).toBe(
'/segment1/value1/segment2'
);
});

it('should not resolve undefined variables', () => {
expect(resolvePathVariables('/segment1/{var1}/segment2', {})).toBe(
'/segment1/{var1}/segment2'
);
});

it('should ignore unused variables', () => {
expect(resolvePathVariables('/segment1/{var1}/segment2', { var2: 'value2' })).toBe(
'/segment1/{var1}/segment2'
);
});

it('should replace multiple variable occurences', () => {
expect(resolvePathVariables('/{var1}/segment1/{var1}', { var1: 'value1' })).toBe(
'/value1/segment1/value1'
);
});

it('should replace multiple variables', () => {
const path = resolvePathVariables('/{var1}/segment1/{var2}', {
var1: 'value1',
var2: 'value2',
});

expect(path).toBe('/value1/segment1/value2');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export const resolvePathVariables = (path: string, variables: { [K: string]: string | number }) =>
Object.keys(variables).reduce((acc, paramName) => {
return acc.replace(new RegExp(`\{${paramName}\}`, 'g'), String(variables[paramName]));
}, path);
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
isLoadingResourceState,
isLoadedResourceState,
isFailedResourceState,
isStaleResourceState,
getLastLoadedResourceState,
getCurrentResourceError,
isOutdatedResourceState,
Expand Down Expand Up @@ -137,6 +138,24 @@ describe('AsyncResourceState', () => {
expect(isFailedResourceState(failedResourceStateInitially)).toBe(true);
});
});

describe('isStaleResourceState()', () => {
it('returns true for UninitialisedResourceState', () => {
expect(isStaleResourceState(uninitialisedResourceState)).toBe(true);
});

it('returns false for LoadingResourceState', () => {
expect(isStaleResourceState(loadingResourceStateInitially)).toBe(false);
});

it('returns true for LoadedResourceState', () => {
expect(isStaleResourceState(loadedResourceState)).toBe(true);
});

it('returns true for FailedResourceState', () => {
expect(isStaleResourceState(failedResourceStateInitially)).toBe(true);
});
});
});

describe('functions', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface UninitialisedResourceState {
* @param Data - type of the data that is referenced by resource state
* @param Error - type of the error that can happen during attempt to update data
*/
export interface LoadingResourceState<Data, Error = ServerApiError> {
export interface LoadingResourceState<Data = null, Error = ServerApiError> {
type: 'LoadingResourceState';
previousState: StaleResourceState<Data, Error>;
}
Expand All @@ -46,7 +46,7 @@ export interface LoadingResourceState<Data, Error = ServerApiError> {
*
* @param Data - type of the data that is referenced by resource state
*/
export interface LoadedResourceState<Data> {
export interface LoadedResourceState<Data = null> {
type: 'LoadedResourceState';
data: Data;
}
Expand All @@ -59,7 +59,7 @@ export interface LoadedResourceState<Data> {
* @param Data - type of the data that is referenced by resource state
* @param Error - type of the error that can happen during attempt to update data
*/
export interface FailedResourceState<Data, Error = ServerApiError> {
export interface FailedResourceState<Data = null, Error = ServerApiError> {
type: 'FailedResourceState';
error: Error;
lastLoadedState?: LoadedResourceState<Data>;
Expand All @@ -71,7 +71,7 @@ export interface FailedResourceState<Data, Error = ServerApiError> {
* @param Data - type of the data that is referenced by resource state
* @param Error - type of the error that can happen during attempt to update data
*/
export type StaleResourceState<Data, Error = ServerApiError> =
export type StaleResourceState<Data = null, Error = ServerApiError> =
| UninitialisedResourceState
| LoadedResourceState<Data>
| FailedResourceState<Data, Error>;
Expand All @@ -82,7 +82,7 @@ export type StaleResourceState<Data, Error = ServerApiError> =
* @param Data - type of the data that is referenced by resource state
* @param Error - type of the error that can happen during attempt to update data
*/
export type AsyncResourceState<Data, Error = ServerApiError> =
export type AsyncResourceState<Data = null, Error = ServerApiError> =
| UninitialisedResourceState
| LoadingResourceState<Data, Error>
| LoadedResourceState<Data>
Expand All @@ -106,6 +106,13 @@ export const isFailedResourceState = <Data, Error>(
state: Immutable<AsyncResourceState<Data, Error>>
): state is Immutable<FailedResourceState<Data, Error>> => state.type === 'FailedResourceState';

export const isStaleResourceState = <Data, Error>(
state: Immutable<AsyncResourceState<Data, Error>>
): state is Immutable<StaleResourceState<Data, Error>> =>
isUninitialisedResourceState(state) ||
isLoadedResourceState(state) ||
isFailedResourceState(state);

// Set of functions to work with AsyncResourceState

export const getLastLoadedResourceState = <Data, Error>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { ServerApiError } from '../../../../common/types';
import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types/trusted_apps';
import { AsyncResourceState } from '.';
import { TrustedAppsUrlParams } from '../types';
import { ServerApiError } from '../../../../common/types';

export interface PaginationInfo {
index: number;
Expand All @@ -18,6 +18,7 @@ export interface TrustedAppsListData {
items: TrustedApp[];
totalItemsCount: number;
paginationInfo: PaginationInfo;
timestamp: number;
}

/** Store State when an API request has been sent to create a new trusted app entry */
Expand All @@ -42,8 +43,14 @@ export interface TrustedAppsListPageState {
listView: {
currentListResourceState: AsyncResourceState<TrustedAppsListData>;
currentPaginationInfo: PaginationInfo;
freshDataTimestamp: number;
show: TrustedAppsUrlParams['show'] | undefined;
};
deletionDialog: {
entry?: TrustedApp;
confirmed: boolean;
submissionResourceState: AsyncResourceState;
};
createView:
| undefined
| TrustedAppCreatePending
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { Action } from 'redux';

import { TrustedApp } from '../../../../../common/endpoint/types';
import {
AsyncResourceState,
TrustedAppCreateFailure,
Expand All @@ -12,12 +15,30 @@ import {
TrustedAppsListData,
} from '../state';

export interface TrustedAppsListResourceStateChanged {
type: 'trustedAppsListResourceStateChanged';
export type TrustedAppsListDataOutdated = Action<'trustedAppsListDataOutdated'>;

interface ResourceStateChanged<T, D = null> extends Action<T> {
payload: { newState: AsyncResourceState<D> };
}

export type TrustedAppsListResourceStateChanged = ResourceStateChanged<
'trustedAppsListResourceStateChanged',
TrustedAppsListData
>;

export type TrustedAppDeletionSubmissionResourceStateChanged = ResourceStateChanged<
'trustedAppDeletionSubmissionResourceStateChanged'
>;

export type TrustedAppDeletionDialogStarted = Action<'trustedAppDeletionDialogStarted'> & {
payload: {
newState: AsyncResourceState<TrustedAppsListData>;
entry: TrustedApp;
};
}
};

export type TrustedAppDeletionDialogConfirmed = Action<'trustedAppDeletionDialogConfirmed'>;

export type TrustedAppDeletionDialogClosed = Action<'trustedAppDeletionDialogClosed'>;

export interface UserClickedSaveNewTrustedAppButton {
type: 'userClickedSaveNewTrustedAppButton';
Expand All @@ -35,7 +56,12 @@ export interface ServerReturnedCreateTrustedAppFailure {
}

export type TrustedAppsPageAction =
| TrustedAppsListDataOutdated
| TrustedAppsListResourceStateChanged
| TrustedAppDeletionSubmissionResourceStateChanged
| TrustedAppDeletionDialogStarted
| TrustedAppDeletionDialogConfirmed
| TrustedAppDeletionDialogClosed
| UserClickedSaveNewTrustedAppButton
| ServerReturnedCreateTrustedAppSuccess
| ServerReturnedCreateTrustedAppFailure;
Loading

0 comments on commit eebb6af

Please sign in to comment.