-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[Endpoint] Policy List UI route and initial view #56918
Changes from all commits
7b91e2f
4822fd2
91db958
9e29674
9a45a8c
5cc2f9d
ad57e4c
515099e
c2f1769
ab82e1d
e656b0c
e3675c1
01a5e2e
8c66c60
a735e16
c04f61f
1cb9d2c
8e18b5a
0492ff9
f296fd5
ee6aa46
438cb77
5d09b57
da416d4
d7c9ea4
a1b5a77
cd5488d
5d276d1
78bc8b4
8812f32
31676c0
13920fd
2d58030
546a193
b14f88a
15193b1
1fc8d65
04974e1
adfa203
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/* | ||
* 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 styled from 'styled-components'; | ||
|
||
export const TruncateText = styled.div` | ||
overflow: hidden; | ||
white-space: nowrap; | ||
text-overflow: ellipsis; | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* 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 { PolicyData } from '../../types'; | ||
|
||
interface ServerReturnedPolicyListData { | ||
type: 'serverReturnedPolicyListData'; | ||
payload: { | ||
policyItems: PolicyData[]; | ||
total: number; | ||
pageSize: number; | ||
pageIndex: number; | ||
}; | ||
} | ||
|
||
interface UserPaginatedPolicyListTable { | ||
type: 'userPaginatedPolicyListTable'; | ||
payload: { | ||
pageSize: number; | ||
pageIndex: number; | ||
}; | ||
} | ||
|
||
export type PolicyListAction = ServerReturnedPolicyListData | UserPaginatedPolicyListTable; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
// !!!! Should be deleted when https://github.com/elastic/endpoint-app-team/issues/150 | ||
// is implemented | ||
|
||
const dateOffsets = [ | ||
0, | ||
1000, | ||
300000, // 5 minutes | ||
3.6e6, // 1 hour | ||
86340000, // 23h, 59m | ||
9e7, // 25h | ||
9e7 * 5, // 5d | ||
]; | ||
|
||
const randomNumbers = [5, 50, 500, 5000, 50000]; | ||
|
||
const getRandomDateIsoString = () => { | ||
const randomIndex = Math.floor(Math.random() * Math.floor(dateOffsets.length)); | ||
return new Date(Date.now() - dateOffsets[randomIndex]).toISOString(); | ||
}; | ||
|
||
const getRandomNumber = () => { | ||
const randomIndex = Math.floor(Math.random() * Math.floor(randomNumbers.length)); | ||
return randomNumbers[randomIndex]; | ||
}; | ||
|
||
export const getFakeDatasourceApiResponse = async (page: number, pageSize: number) => { | ||
await new Promise(resolve => setTimeout(resolve, 500)); | ||
|
||
// Emulates the API response - see PR: | ||
// https://github.com/elastic/kibana/pull/56567/files#diff-431549a8739efe0c56763f164c32caeeR25 | ||
return { | ||
items: Array.from({ length: pageSize }, (x, i) => ({ | ||
name: `policy with some protections ${i + 1}`, | ||
total: getRandomNumber(), | ||
pending: getRandomNumber(), | ||
failed: getRandomNumber(), | ||
created_by: `admin ABC`, | ||
created: getRandomDateIsoString(), | ||
updated_by: 'admin 123', | ||
updated: getRandomDateIsoString(), | ||
})), | ||
success: true, | ||
total: pageSize * 10, | ||
page, | ||
perPage: pageSize, | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* 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 { PolicyListState } from '../../types'; | ||
import { applyMiddleware, createStore, Dispatch, Store } from 'redux'; | ||
import { AppAction } from '../action'; | ||
import { policyListReducer } from './reducer'; | ||
import { policyListMiddlewareFactory } from './middleware'; | ||
import { coreMock } from '../../../../../../../../src/core/public/mocks'; | ||
import { CoreStart } from 'kibana/public'; | ||
import { selectIsLoading } from './selectors'; | ||
|
||
describe('policy list store concerns', () => { | ||
const sleep = () => new Promise(resolve => setTimeout(resolve, 1000)); | ||
let fakeCoreStart: jest.Mocked<CoreStart>; | ||
let store: Store<PolicyListState>; | ||
let getState: typeof store['getState']; | ||
let dispatch: Dispatch<AppAction>; | ||
|
||
beforeEach(() => { | ||
fakeCoreStart = coreMock.createStart({ basePath: '/mock' }); | ||
store = createStore( | ||
policyListReducer, | ||
applyMiddleware(policyListMiddlewareFactory(fakeCoreStart)) | ||
); | ||
getState = store.getState; | ||
dispatch = store.dispatch; | ||
}); | ||
|
||
test('it sets `isLoading` when `userNavigatedToPage`', async () => { | ||
expect(selectIsLoading(getState())).toBe(false); | ||
dispatch({ type: 'userNavigatedToPage', payload: 'policyListPage' }); | ||
expect(selectIsLoading(getState())).toBe(true); | ||
await sleep(); | ||
expect(selectIsLoading(getState())).toBe(false); | ||
}); | ||
|
||
test('it sets `isLoading` when `userPaginatedPolicyListTable`', async () => { | ||
expect(selectIsLoading(getState())).toBe(false); | ||
dispatch({ | ||
type: 'userPaginatedPolicyListTable', | ||
payload: { | ||
pageSize: 10, | ||
pageIndex: 1, | ||
}, | ||
}); | ||
expect(selectIsLoading(getState())).toBe(true); | ||
await sleep(); | ||
expect(selectIsLoading(getState())).toBe(false); | ||
}); | ||
|
||
test('it resets state on `userNavigatedFromPage` action', async () => { | ||
dispatch({ | ||
type: 'serverReturnedPolicyListData', | ||
payload: { | ||
policyItems: [], | ||
pageIndex: 20, | ||
pageSize: 50, | ||
total: 200, | ||
}, | ||
}); | ||
dispatch({ type: 'userNavigatedFromPage', payload: 'policyListPage' }); | ||
expect(getState()).toEqual({ | ||
policyItems: [], | ||
isLoading: false, | ||
pageIndex: 0, | ||
pageSize: 10, | ||
total: 0, | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/* | ||
* 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 { policyListReducer } from './reducer'; | ||
export { PolicyListAction } from './action'; | ||
export { policyListMiddlewareFactory } from './middleware'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
* 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 { MiddlewareFactory, PolicyListState } from '../../types'; | ||
|
||
export const policyListMiddlewareFactory: MiddlewareFactory<PolicyListState> = coreStart => { | ||
return ({ getState, dispatch }) => next => async action => { | ||
next(action); | ||
|
||
if ( | ||
(action.type === 'userNavigatedToPage' && action.payload === 'policyListPage') || | ||
action.type === 'userPaginatedPolicyListTable' | ||
) { | ||
const state = getState(); | ||
let pageSize: number; | ||
let pageIndex: number; | ||
|
||
if (action.type === 'userPaginatedPolicyListTable') { | ||
pageSize = action.payload.pageSize; | ||
pageIndex = action.payload.pageIndex; | ||
} else { | ||
pageSize = state.pageSize; | ||
pageIndex = state.pageIndex; | ||
} | ||
|
||
// Need load data from API and remove fake data below | ||
// Refactor tracked via: https://github.com/elastic/endpoint-app-team/issues/150 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice |
||
const { getFakeDatasourceApiResponse } = await import('./fake_data'); | ||
const { items: policyItems, total } = await getFakeDatasourceApiResponse(pageIndex, pageSize); | ||
|
||
dispatch({ | ||
type: 'serverReturnedPolicyListData', | ||
payload: { | ||
policyItems, | ||
pageIndex, | ||
pageSize, | ||
total, | ||
}, | ||
}); | ||
} | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* 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 { Reducer } from 'redux'; | ||
import { PolicyListState } from '../../types'; | ||
import { AppAction } from '../action'; | ||
|
||
const initialPolicyListState = (): PolicyListState => { | ||
return { | ||
policyItems: [], | ||
isLoading: false, | ||
pageIndex: 0, | ||
pageSize: 10, | ||
total: 0, | ||
}; | ||
}; | ||
|
||
export const policyListReducer: Reducer<PolicyListState, AppAction> = ( | ||
state = initialPolicyListState(), | ||
action | ||
) => { | ||
if (action.type === 'serverReturnedPolicyListData') { | ||
return { | ||
...state, | ||
...action.payload, | ||
isLoading: false, | ||
}; | ||
} | ||
|
||
if ( | ||
action.type === 'userPaginatedPolicyListTable' || | ||
(action.type === 'userNavigatedToPage' && action.payload === 'policyListPage') | ||
) { | ||
return { | ||
...state, | ||
isLoading: true, | ||
}; | ||
} | ||
|
||
if (action.type === 'userNavigatedFromPage' && action.payload === 'policyListPage') { | ||
return initialPolicyListState(); | ||
} | ||
|
||
return state; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* | ||
* 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 { PolicyListState } from '../../types'; | ||
|
||
export const selectPolicyItems = (state: PolicyListState) => state.policyItems; | ||
|
||
export const selectPageIndex = (state: PolicyListState) => state.pageIndex; | ||
|
||
export const selectPageSize = (state: PolicyListState) => state.pageSize; | ||
|
||
export const selectTotal = (state: PolicyListState) => state.total; | ||
|
||
export const selectIsLoading = (state: PolicyListState) => state.isLoading; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,4 +11,9 @@ interface UserNavigatedToPage { | |
readonly payload: PageId; | ||
} | ||
|
||
export type RoutingAction = UserNavigatedToPage; | ||
interface UserNavigatedFromPage { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @peluja1012 would like your feedback here if you get a chance. I added this Routing action, which is dispatched when the component that used There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is good. Though in our next PR we are adding a "routing" middleware, which keeps track of url changes. In the alerts page, we removed |
||
readonly type: 'userNavigatedFromPage'; | ||
readonly payload: PageId; | ||
} | ||
|
||
export type RoutingAction = UserNavigatedToPage | UserNavigatedFromPage; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this cause the mock core start to return response?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't actually make the API call at this time in the middleware, so not yet.
But to your question: yes, you can setup the mocked interface to return a specific response. Here is an example in Candace's PR - https://github.com/elastic/kibana/pull/55623/files#diff-a4d4b2d400e6092b6e6a5ca7a6d9a8eeR69