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

Btsymbala/trusted app deletion #77316

Merged
merged 22 commits into from
Sep 23, 2020
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ad77e55
Moved the DeleteTrustedAppsRequestParams to common folder to be able …
efreeti Sep 14, 2020
211b777
Added trusted app deletion API to the client service layer.
efreeti Sep 14, 2020
9010aa7
Made default data type for async resource state.
efreeti Sep 14, 2020
024d9fc
Added guard for stale state.
efreeti Sep 14, 2020
c7b12c0
Added timestamp to the list data to be used to refresh list when it's…
efreeti Sep 14, 2020
7e3f341
Separated out base type for resource state change actions.
efreeti Sep 14, 2020
db9c856
Added action for outdating list data.
efreeti Sep 14, 2020
48c2711
Moved the refresh condition inside the middleware case function and a…
efreeti Sep 14, 2020
f018a3d
Added state, actions, reducers and middleware for deletion dialog.
efreeti Sep 14, 2020
d2e8550
Added actions column and deletion action.
efreeti Sep 14, 2020
94f0c62
Added trusted app deletion dialog.
efreeti Sep 14, 2020
9ba2b5b
Changed to not have deletonDialog as optional in store.
efreeti Sep 14, 2020
080e828
Changed the store to contain the full entry in the dialog state and c…
efreeti Sep 15, 2020
105be7c
Extracted notifications component and enhanced error display.
efreeti Sep 15, 2020
0da760b
Added success message and unified messages a bit.
efreeti Sep 15, 2020
769474c
Complete coverage with tests.
efreeti Sep 17, 2020
90aa0bf
Removed unused variable in translations.
efreeti Sep 17, 2020
0d8754a
Fixed tests because of outdated snapshots and inproper mocking of htm…
efreeti Sep 17, 2020
2cd4538
Pulled in master.
efreeti Sep 17, 2020
3a4730f
Pulled in master.
efreeti Sep 23, 2020
7d5d26c
Fixed code review comments.
efreeti Sep 23, 2020
f6a933c
Fixed type error.
efreeti Sep 23, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>;
export interface GetTrustedListAppsResponse {
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