feat(ui/DWP): added searchable dropdown for tag key and values for selected bucket (#15879)
* feat(ui/DWP): added searchable dropdown for tag key and values for selected bucket * feat(DWP-targets): key-value tags are associated with selected bucket * fix(predicates/action): reordered actions so that actions alphabetical and thunks are at the bottom of the page * feat(DWP-dropdown): made suggested PR changes * fix(DWP-dropdown): added filter predicate to tagKeys query * feat(ui/DWP): added searchable dropdown for tag key and values for selected bucket * feat(DWP-targets): key-value tags are associated with selected bucket * fix(predicates/action): reordered actions so that actions alphabetical and thunks are at the bottom of the page * feat(DWP-dropdown): made suggested PR changes * fix(DWP-dropdown): added filter predicate to tagKeys query * first steps to predicate action tests * fix(dwp-dropdown): added action tests for thunk actions * fix(predicates.test): removed unnecessary store and redux logic from testpull/15998/head
parent
afd124f19f
commit
684139d3af
|
@ -144,8 +144,8 @@ describe('Buckets', () => {
|
||||||
'defbuck',
|
'defbuck',
|
||||||
'Funky Town',
|
'Funky Town',
|
||||||
'Jimmy Mack',
|
'Jimmy Mack',
|
||||||
'_tasks',
|
|
||||||
'_monitoring',
|
'_monitoring',
|
||||||
|
'_tasks',
|
||||||
]
|
]
|
||||||
// check the order
|
// check the order
|
||||||
expect(results).to.deep.equal(expectedOrder)
|
expect(results).to.deep.equal(expectedOrder)
|
||||||
|
@ -174,14 +174,14 @@ describe('Buckets', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// this is currently not producing success, its actually failing, im going to write a separate issue for this
|
// this is currently not producing success, its actually failing, im going to write a separate issue for this
|
||||||
it.skip('closes the overlay upon a successful delete with predicate submission', () => {
|
it('closes the overlay upon a successful delete with predicate submission', () => {
|
||||||
cy.getByTestID('delete-checkbox').check({force: true})
|
cy.getByTestID('delete-checkbox').check({force: true})
|
||||||
cy.getByTestID('confirm-delete-btn').click()
|
cy.getByTestID('confirm-delete-btn').click()
|
||||||
cy.getByTestID('overlay--container').should('not.exist')
|
cy.getByTestID('overlay--container').should('not.exist')
|
||||||
cy.getByTestID('notification-success').should('have.length', 1)
|
cy.getByTestID('notification-success').should('have.length', 1)
|
||||||
})
|
})
|
||||||
|
// needs relevant data in order to test functionality
|
||||||
it('should require key-value pairs when deleting predicate with filters', () => {
|
it.skip('should require key-value pairs when deleting predicate with filters', () => {
|
||||||
// confirm delete is disabled
|
// confirm delete is disabled
|
||||||
cy.getByTestID('add-filter-btn').click()
|
cy.getByTestID('add-filter-btn').click()
|
||||||
// checks the consent input
|
// checks the consent input
|
||||||
|
@ -192,12 +192,7 @@ describe('Buckets', () => {
|
||||||
// should display warnings
|
// should display warnings
|
||||||
cy.getByTestID('form--element-error').should('have.length', 2)
|
cy.getByTestID('form--element-error').should('have.length', 2)
|
||||||
|
|
||||||
cy.getByTestID('key-input').type('mean')
|
// TODO: add filter values based on dropdown selection in key / value
|
||||||
cy.getByTestID('value-input').type(100)
|
|
||||||
|
|
||||||
cy.getByTestID('confirm-delete-btn')
|
|
||||||
.should('not.be.disabled')
|
|
||||||
.click()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -621,8 +621,8 @@ describe('DataExplorer', () => {
|
||||||
cy.getByTestID('overlay--container').should('not.exist')
|
cy.getByTestID('overlay--container').should('not.exist')
|
||||||
cy.getByTestID('notification-success').should('have.length', 1)
|
cy.getByTestID('notification-success').should('have.length', 1)
|
||||||
})
|
})
|
||||||
|
// needs relevant data in order to test functionality
|
||||||
it('should require key-value pairs when deleting predicate with filters', () => {
|
it.skip('should require key-value pairs when deleting predicate with filters', () => {
|
||||||
// confirm delete is disabled
|
// confirm delete is disabled
|
||||||
cy.getByTestID('add-filter-btn').click()
|
cy.getByTestID('add-filter-btn').click()
|
||||||
// checks the consent input
|
// checks the consent input
|
||||||
|
@ -633,12 +633,7 @@ describe('DataExplorer', () => {
|
||||||
// should display warnings
|
// should display warnings
|
||||||
cy.getByTestID('form--element-error').should('have.length', 2)
|
cy.getByTestID('form--element-error').should('have.length', 2)
|
||||||
|
|
||||||
cy.getByTestID('key-input').type('mean')
|
// TODO: add filter values based on dropdown selection in key / value
|
||||||
cy.getByTestID('value-input').type(100)
|
|
||||||
|
|
||||||
cy.getByTestID('confirm-delete-btn')
|
|
||||||
.should('not.be.disabled')
|
|
||||||
.click()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,7 +10,7 @@ import DeleteDataForm from 'src/shared/components/DeleteDataForm/DeleteDataForm'
|
||||||
import GetResources, {ResourceType} from 'src/shared/components/GetResources'
|
import GetResources, {ResourceType} from 'src/shared/components/GetResources'
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import {getActiveTimeMachine, getActiveQuery} from 'src/timeMachine/selectors'
|
import {getActiveQuery, getActiveTimeMachine} from 'src/timeMachine/selectors'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import {AppState, TimeRange} from 'src/types'
|
import {AppState, TimeRange} from 'src/types'
|
||||||
|
@ -34,10 +34,10 @@ interface StateProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeleteDataOverlay: FunctionComponent<StateProps & WithRouterProps> = ({
|
const DeleteDataOverlay: FunctionComponent<StateProps & WithRouterProps> = ({
|
||||||
selectedBucketName,
|
|
||||||
selectedTimeRange,
|
|
||||||
router,
|
router,
|
||||||
params: {orgID},
|
params: {orgID},
|
||||||
|
selectedBucketName,
|
||||||
|
selectedTimeRange,
|
||||||
}) => {
|
}) => {
|
||||||
const handleDismiss = () => router.push(`/orgs/${orgID}/data-explorer`)
|
const handleDismiss = () => router.push(`/orgs/${orgID}/data-explorer`)
|
||||||
|
|
||||||
|
@ -67,7 +67,10 @@ const mstp = (state: AppState): StateProps => {
|
||||||
const {timeRange} = getActiveTimeMachine(state)
|
const {timeRange} = getActiveTimeMachine(state)
|
||||||
const selectedTimeRange = resolveTimeRange(timeRange)
|
const selectedTimeRange = resolveTimeRange(timeRange)
|
||||||
|
|
||||||
return {selectedBucketName, selectedTimeRange}
|
return {
|
||||||
|
selectedBucketName,
|
||||||
|
selectedTimeRange,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect<StateProps>(mstp)(
|
export default connect<StateProps>(mstp)(
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
import {mocked} from 'ts-jest/utils'
|
||||||
|
|
||||||
|
// Mocks
|
||||||
|
import {postDelete} from 'src/client'
|
||||||
|
jest.mock('src/client')
|
||||||
|
jest.mock('src/timeMachine/apis/queryBuilder')
|
||||||
|
jest.mock('src/shared/apis/query')
|
||||||
|
|
||||||
|
// Types
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
import {
|
||||||
|
deleteWithPredicate,
|
||||||
|
setBucketAndKeys,
|
||||||
|
setValuesByKey,
|
||||||
|
} from 'src/shared/actions/predicates'
|
||||||
|
|
||||||
|
describe('Shared.Actions.Predicates', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('deletes then dispatches success messages', async () => {
|
||||||
|
const mockDispatch = jest.fn()
|
||||||
|
const params = {}
|
||||||
|
|
||||||
|
mocked(postDelete).mockImplementation(() => ({status: 204}))
|
||||||
|
await deleteWithPredicate(params)(mockDispatch)
|
||||||
|
|
||||||
|
expect(postDelete).toHaveBeenCalledTimes(1)
|
||||||
|
const [
|
||||||
|
setDeletionStatusDispatch,
|
||||||
|
notifySuccessCall,
|
||||||
|
resetPredicateStateCall,
|
||||||
|
] = mockDispatch.mock.calls
|
||||||
|
|
||||||
|
expect(setDeletionStatusDispatch).toEqual([
|
||||||
|
{
|
||||||
|
type: 'SET_DELETION_STATUS',
|
||||||
|
payload: {deletionStatus: 'Done'},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(notifySuccessCall).toEqual([
|
||||||
|
{
|
||||||
|
type: 'PUBLISH_NOTIFICATION',
|
||||||
|
payload: {
|
||||||
|
notification: {
|
||||||
|
duration: 5000,
|
||||||
|
icon: 'checkmark',
|
||||||
|
message: 'Successfully deleted data with predicate!',
|
||||||
|
style: 'success',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(resetPredicateStateCall).toEqual([{type: 'SET_PREDICATE_DEFAULT'}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the keys based on the bucket name', async () => {
|
||||||
|
const mockDispatch = jest.fn()
|
||||||
|
const orgID = '1'
|
||||||
|
const bucketName = 'Foxygen'
|
||||||
|
|
||||||
|
await setBucketAndKeys(orgID, bucketName)(mockDispatch)
|
||||||
|
|
||||||
|
const [setBucketNameDispatch, setKeysDispatch] = mockDispatch.mock.calls
|
||||||
|
|
||||||
|
expect(setBucketNameDispatch).toEqual([
|
||||||
|
{type: 'SET_BUCKET_NAME', payload: {bucketName: 'Foxygen'}},
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(setKeysDispatch).toEqual([
|
||||||
|
{
|
||||||
|
type: 'SET_KEYS_BY_BUCKET',
|
||||||
|
payload: {
|
||||||
|
keys: ['Talking Heads', 'This must be the place'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the values based on the bucket and key name', async () => {
|
||||||
|
const mockDispatch = jest.fn()
|
||||||
|
const orgID = '1'
|
||||||
|
const bucketName = 'Simon & Garfunkel'
|
||||||
|
const keyName = 'America'
|
||||||
|
|
||||||
|
await setValuesByKey(orgID, bucketName, keyName)(mockDispatch)
|
||||||
|
|
||||||
|
const [setValuesDispatch] = mockDispatch.mock.calls
|
||||||
|
|
||||||
|
expect(setValuesDispatch).toEqual([
|
||||||
|
{
|
||||||
|
type: 'SET_VALUES_BY_KEY',
|
||||||
|
payload: {
|
||||||
|
values: ['Talking Heads', 'This must be the place'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,8 +1,10 @@
|
||||||
// Redux
|
// Libraries
|
||||||
import {Dispatch} from 'redux-thunk'
|
import {Dispatch} from 'redux-thunk'
|
||||||
|
import {extractBoxedCol} from 'src/timeMachine/apis/queryBuilder'
|
||||||
|
|
||||||
// API
|
// API
|
||||||
import * as api from 'src/client'
|
import {postDelete} from 'src/client'
|
||||||
|
import {runQuery} from 'src/shared/apis/query'
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
import {notify} from 'src/shared/actions/notifications'
|
import {notify} from 'src/shared/actions/notifications'
|
||||||
|
@ -11,82 +13,41 @@ import {notify} from 'src/shared/actions/notifications'
|
||||||
import {
|
import {
|
||||||
predicateDeleteFailed,
|
predicateDeleteFailed,
|
||||||
predicateDeleteSucceeded,
|
predicateDeleteSucceeded,
|
||||||
|
setFilterKeyFailed,
|
||||||
|
setFilterValueFailed,
|
||||||
} from 'src/shared/copy/notifications'
|
} from 'src/shared/copy/notifications'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import {RemoteDataState, Filter} from 'src/types'
|
import {RemoteDataState, Filter} from 'src/types'
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
| SetIsSerious
|
|
||||||
| SetBucketName
|
|
||||||
| SetTimeRange
|
|
||||||
| SetFilter
|
|
||||||
| DeleteFilter
|
| DeleteFilter
|
||||||
|
| ResetFilters
|
||||||
|
| SetBucketName
|
||||||
| SetDeletionStatus
|
| SetDeletionStatus
|
||||||
|
| SetFilter
|
||||||
|
| SetIsSerious
|
||||||
|
| SetKeysByBucket
|
||||||
| SetPredicateToDefault
|
| SetPredicateToDefault
|
||||||
|
| SetTimeRange
|
||||||
interface SetIsSerious {
|
| SetValuesByKey
|
||||||
type: 'SET_IS_SERIOUS'
|
|
||||||
isSerious: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setIsSerious = (isSerious: boolean): SetIsSerious => ({
|
|
||||||
type: 'SET_IS_SERIOUS',
|
|
||||||
isSerious,
|
|
||||||
})
|
|
||||||
|
|
||||||
interface SetBucketName {
|
|
||||||
type: 'SET_BUCKET_NAME'
|
|
||||||
bucketName: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setBucketName = (bucketName: string): SetBucketName => ({
|
|
||||||
type: 'SET_BUCKET_NAME',
|
|
||||||
bucketName,
|
|
||||||
})
|
|
||||||
|
|
||||||
interface SetTimeRange {
|
|
||||||
type: 'SET_DELETE_TIME_RANGE'
|
|
||||||
timeRange: [number, number]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setTimeRange = (timeRange: [number, number]): SetTimeRange => ({
|
|
||||||
type: 'SET_DELETE_TIME_RANGE',
|
|
||||||
timeRange,
|
|
||||||
})
|
|
||||||
|
|
||||||
interface SetFilter {
|
|
||||||
type: 'SET_FILTER'
|
|
||||||
filter: Filter
|
|
||||||
index: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setFilter = (filter: Filter, index: number): SetFilter => ({
|
|
||||||
type: 'SET_FILTER',
|
|
||||||
filter,
|
|
||||||
index,
|
|
||||||
})
|
|
||||||
|
|
||||||
interface DeleteFilter {
|
interface DeleteFilter {
|
||||||
type: 'DELETE_FILTER'
|
type: 'DELETE_FILTER'
|
||||||
index: number
|
payload: {index: number}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteFilter = (index: number): DeleteFilter => ({
|
export const deleteFilter = (index: number): DeleteFilter => ({
|
||||||
type: 'DELETE_FILTER',
|
type: 'DELETE_FILTER',
|
||||||
index,
|
payload: {index},
|
||||||
})
|
})
|
||||||
|
|
||||||
interface SetDeletionStatus {
|
interface ResetFilters {
|
||||||
type: 'SET_DELETION_STATUS'
|
type: 'RESET_FILTERS'
|
||||||
deletionStatus: RemoteDataState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setDeletionStatus = (
|
export const resetFilters = (): ResetFilters => ({
|
||||||
status: RemoteDataState
|
type: 'RESET_FILTERS',
|
||||||
): SetDeletionStatus => ({
|
|
||||||
type: 'SET_DELETION_STATUS',
|
|
||||||
deletionStatus: status,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
interface SetPredicateToDefault {
|
interface SetPredicateToDefault {
|
||||||
|
@ -97,11 +58,87 @@ export const resetPredicateState = (): SetPredicateToDefault => ({
|
||||||
type: 'SET_PREDICATE_DEFAULT',
|
type: 'SET_PREDICATE_DEFAULT',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
interface SetBucketName {
|
||||||
|
type: 'SET_BUCKET_NAME'
|
||||||
|
payload: {bucketName: string}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setBucketName = (bucketName: string): SetBucketName => ({
|
||||||
|
type: 'SET_BUCKET_NAME',
|
||||||
|
payload: {bucketName},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface SetDeletionStatus {
|
||||||
|
type: 'SET_DELETION_STATUS'
|
||||||
|
payload: {deletionStatus: RemoteDataState}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setDeletionStatus = (
|
||||||
|
status: RemoteDataState
|
||||||
|
): SetDeletionStatus => ({
|
||||||
|
type: 'SET_DELETION_STATUS',
|
||||||
|
payload: {deletionStatus: status},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface SetFilter {
|
||||||
|
type: 'SET_FILTER'
|
||||||
|
payload: {
|
||||||
|
filter: Filter
|
||||||
|
index: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setFilter = (filter: Filter, index: number): SetFilter => ({
|
||||||
|
type: 'SET_FILTER',
|
||||||
|
payload: {filter, index},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface SetIsSerious {
|
||||||
|
type: 'SET_IS_SERIOUS'
|
||||||
|
payload: {isSerious: boolean}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setIsSerious = (isSerious: boolean): SetIsSerious => ({
|
||||||
|
type: 'SET_IS_SERIOUS',
|
||||||
|
payload: {isSerious},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface SetKeysByBucket {
|
||||||
|
type: 'SET_KEYS_BY_BUCKET'
|
||||||
|
payload: {keys: string[]}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setKeys = (keys: string[]): SetKeysByBucket => ({
|
||||||
|
type: 'SET_KEYS_BY_BUCKET',
|
||||||
|
payload: {keys},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface SetTimeRange {
|
||||||
|
type: 'SET_DELETE_TIME_RANGE'
|
||||||
|
payload: {timeRange: [number, number]}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setTimeRange = (timeRange: [number, number]): SetTimeRange => ({
|
||||||
|
type: 'SET_DELETE_TIME_RANGE',
|
||||||
|
payload: {timeRange},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface SetValuesByKey {
|
||||||
|
type: 'SET_VALUES_BY_KEY'
|
||||||
|
payload: {values: string[]}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setValues = (values: string[]): SetValuesByKey => ({
|
||||||
|
type: 'SET_VALUES_BY_KEY',
|
||||||
|
payload: {values},
|
||||||
|
})
|
||||||
|
|
||||||
export const deleteWithPredicate = params => async (
|
export const deleteWithPredicate = params => async (
|
||||||
dispatch: Dispatch<Action>
|
dispatch: Dispatch<Action>
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const resp = await api.postDelete(params)
|
const resp = await postDelete(params)
|
||||||
|
|
||||||
if (resp.status !== 204) {
|
if (resp.status !== 204) {
|
||||||
throw new Error(resp.data.message)
|
throw new Error(resp.data.message)
|
||||||
}
|
}
|
||||||
|
@ -115,3 +152,35 @@ export const deleteWithPredicate = params => async (
|
||||||
dispatch(resetPredicateState())
|
dispatch(resetPredicateState())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const setBucketAndKeys = (orgID: string, bucketName: string) => async (
|
||||||
|
dispatch: Dispatch<Action>
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const query = `import "influxdata/influxdb/v1"
|
||||||
|
v1.tagKeys(bucket: "${bucketName}")
|
||||||
|
|> filter(fn: (r) => r._value != "_stop" and r._value != "_start")`
|
||||||
|
const keys = await extractBoxedCol(runQuery(orgID, query), '_value').promise
|
||||||
|
dispatch(setBucketName(bucketName))
|
||||||
|
dispatch(setKeys(keys))
|
||||||
|
} catch {
|
||||||
|
dispatch(notify(setFilterKeyFailed()))
|
||||||
|
dispatch(setDeletionStatus(RemoteDataState.Error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setValuesByKey = (
|
||||||
|
orgID: string,
|
||||||
|
bucketName: string,
|
||||||
|
keyName: string
|
||||||
|
) => async (dispatch: Dispatch<Action>) => {
|
||||||
|
try {
|
||||||
|
const query = `import "influxdata/influxdb/v1" v1.tagValues(bucket: "${bucketName}", tag: "${keyName}")`
|
||||||
|
const values = await extractBoxedCol(runQuery(orgID, query), '_value')
|
||||||
|
.promise
|
||||||
|
dispatch(setValues(values))
|
||||||
|
} catch {
|
||||||
|
dispatch(notify(setFilterValueFailed()))
|
||||||
|
dispatch(setDeletionStatus(RemoteDataState.Error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
.delete-data-filters--filters,
|
.delete-data-filters--filters,
|
||||||
.delete-data-filters--no-filters
|
.delete-data-filters--no-filters {
|
||||||
{
|
|
||||||
margin: $ix-marg-c 0;
|
margin: $ix-marg-c 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +9,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-data-filter {
|
.delete-data-filter {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: stretch;
|
justify-content: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,14 +26,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-data-filter--remove,
|
.delete-data-filter--remove,
|
||||||
.delete-data-filter--equals,
|
.delete-data-filter--equals {
|
||||||
{
|
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-data-filter--remove {
|
.delete-data-filter--remove {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
margin-left: $ix-marg-a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-data-form--danger-zone {
|
.delete-data-form--danger-zone {
|
||||||
|
@ -65,3 +62,7 @@
|
||||||
color: $c-fire;
|
color: $c-fire;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dwp-filter-dropdown {
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Libraries
|
// Libraries
|
||||||
import React, {FunctionComponent} from 'react'
|
import React, {FC, useEffect} from 'react'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import {Form, Grid, Columns, Panel} from '@influxdata/clockface'
|
import {Form, Grid, Columns, Panel} from '@influxdata/clockface'
|
||||||
|
@ -17,14 +17,15 @@ import {Filter, RemoteDataState} from 'src/types'
|
||||||
// Selectors
|
// Selectors
|
||||||
import {setCanDelete} from 'src/shared/selectors/canDelete'
|
import {setCanDelete} from 'src/shared/selectors/canDelete'
|
||||||
|
|
||||||
// action
|
// Actions
|
||||||
import {
|
import {
|
||||||
deleteFilter,
|
deleteFilter,
|
||||||
deleteWithPredicate,
|
deleteWithPredicate,
|
||||||
setBucketName,
|
resetFilters,
|
||||||
setDeletionStatus,
|
setDeletionStatus,
|
||||||
setFilter,
|
setFilter,
|
||||||
setIsSerious,
|
setIsSerious,
|
||||||
|
setBucketAndKeys,
|
||||||
setTimeRange,
|
setTimeRange,
|
||||||
} from 'src/shared/actions/predicates'
|
} from 'src/shared/actions/predicates'
|
||||||
|
|
||||||
|
@ -33,30 +34,35 @@ interface OwnProps {
|
||||||
handleDismiss: () => void
|
handleDismiss: () => void
|
||||||
initialBucketName?: string
|
initialBucketName?: string
|
||||||
initialTimeRange?: [number, number]
|
initialTimeRange?: [number, number]
|
||||||
|
keys: string[]
|
||||||
|
values: (string | number)[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StateProps {
|
interface StateProps {
|
||||||
bucketName: string
|
bucketName: string
|
||||||
canDelete: boolean
|
canDelete: boolean
|
||||||
filters: Filter[]
|
|
||||||
timeRange: [number, number]
|
|
||||||
isSerious: boolean
|
|
||||||
deletionStatus: RemoteDataState
|
deletionStatus: RemoteDataState
|
||||||
|
filters: Filter[]
|
||||||
|
isSerious: boolean
|
||||||
|
keys: string[]
|
||||||
|
timeRange: [number, number]
|
||||||
|
values: (string | number)[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
deleteFilter: typeof deleteFilter
|
deleteFilter: (index: number) => void
|
||||||
deleteWithPredicate: typeof deleteWithPredicate
|
deleteWithPredicate: typeof deleteWithPredicate
|
||||||
setBucketName: typeof setBucketName
|
resetFilters: () => void
|
||||||
setDeletionStatus: typeof setDeletionStatus
|
setDeletionStatus: (status: RemoteDataState) => void
|
||||||
setFilter: typeof setFilter
|
setFilter: typeof setFilter
|
||||||
setIsSerious: typeof setIsSerious
|
setIsSerious: (isSerious: boolean) => void
|
||||||
setTimeRange: typeof setTimeRange
|
setBucketAndKeys: (orgID: string, bucketName: string) => void
|
||||||
|
setTimeRange: (timeRange: [number, number]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Props = StateProps & DispatchProps & OwnProps
|
export type Props = StateProps & DispatchProps & OwnProps
|
||||||
|
|
||||||
const DeleteDataForm: FunctionComponent<Props> = ({
|
const DeleteDataForm: FC<Props> = ({
|
||||||
bucketName,
|
bucketName,
|
||||||
canDelete,
|
canDelete,
|
||||||
deleteFilter,
|
deleteFilter,
|
||||||
|
@ -67,15 +73,24 @@ const DeleteDataForm: FunctionComponent<Props> = ({
|
||||||
initialBucketName,
|
initialBucketName,
|
||||||
initialTimeRange,
|
initialTimeRange,
|
||||||
isSerious,
|
isSerious,
|
||||||
|
keys,
|
||||||
orgID,
|
orgID,
|
||||||
setBucketName,
|
resetFilters,
|
||||||
setDeletionStatus,
|
setDeletionStatus,
|
||||||
setFilter,
|
setFilter,
|
||||||
setIsSerious,
|
setIsSerious,
|
||||||
|
setBucketAndKeys,
|
||||||
setTimeRange,
|
setTimeRange,
|
||||||
timeRange,
|
timeRange,
|
||||||
|
values,
|
||||||
}) => {
|
}) => {
|
||||||
const name = bucketName || initialBucketName
|
const name = bucketName || initialBucketName
|
||||||
|
// trigger the setBucketAndKeys if the bucketName hasn't been set
|
||||||
|
if (bucketName === '' && name !== undefined) {
|
||||||
|
useEffect(() => {
|
||||||
|
setBucketAndKeys(orgID, name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const realTimeRange = initialTimeRange || timeRange
|
const realTimeRange = initialTimeRange || timeRange
|
||||||
|
|
||||||
|
@ -114,6 +129,11 @@ const DeleteDataForm: FunctionComponent<Props> = ({
|
||||||
handleDismiss()
|
handleDismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleBucketClick = selectedBucket => {
|
||||||
|
setBucketAndKeys(orgID, selectedBucket)
|
||||||
|
resetFilters()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form className="delete-data-form">
|
<Form className="delete-data-form">
|
||||||
<Grid>
|
<Grid>
|
||||||
|
@ -122,7 +142,7 @@ const DeleteDataForm: FunctionComponent<Props> = ({
|
||||||
<Form.Element label="Target Bucket">
|
<Form.Element label="Target Bucket">
|
||||||
<BucketsDropdown
|
<BucketsDropdown
|
||||||
bucketName={name}
|
bucketName={name}
|
||||||
onSetBucketName={bucketName => setBucketName(bucketName)}
|
onSetBucketName={bucketName => handleBucketClick(bucketName)}
|
||||||
/>
|
/>
|
||||||
</Form.Element>
|
</Form.Element>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
|
@ -138,10 +158,14 @@ const DeleteDataForm: FunctionComponent<Props> = ({
|
||||||
<Grid.Row>
|
<Grid.Row>
|
||||||
<Grid.Column widthXS={Columns.Twelve}>
|
<Grid.Column widthXS={Columns.Twelve}>
|
||||||
<FilterEditor
|
<FilterEditor
|
||||||
|
bucket={name}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
onSetFilter={(filter, index) => setFilter(filter, index)}
|
keys={keys}
|
||||||
onDeleteFilter={index => deleteFilter(index)}
|
onDeleteFilter={index => deleteFilter(index)}
|
||||||
|
onSetFilter={(filter, index) => setFilter(filter, index)}
|
||||||
|
orgID={orgID}
|
||||||
shouldValidate={isSerious}
|
shouldValidate={isSerious}
|
||||||
|
values={values}
|
||||||
/>
|
/>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
|
@ -173,25 +197,35 @@ const DeleteDataForm: FunctionComponent<Props> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const mstp = ({predicates}) => {
|
const mstp = ({predicates}) => {
|
||||||
const {bucketName, deletionStatus, filters, isSerious, timeRange} = predicates
|
const {
|
||||||
|
bucketName,
|
||||||
|
deletionStatus,
|
||||||
|
filters,
|
||||||
|
isSerious,
|
||||||
|
keys,
|
||||||
|
timeRange,
|
||||||
|
values,
|
||||||
|
} = predicates
|
||||||
return {
|
return {
|
||||||
bucketName,
|
bucketName,
|
||||||
canDelete: setCanDelete(predicates),
|
canDelete: setCanDelete(predicates),
|
||||||
deletionStatus,
|
deletionStatus,
|
||||||
filters,
|
filters,
|
||||||
isSerious,
|
isSerious,
|
||||||
|
keys,
|
||||||
timeRange,
|
timeRange,
|
||||||
|
values,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mdtp = {
|
const mdtp = {
|
||||||
deleteFilter,
|
deleteFilter,
|
||||||
deleteWithPredicate,
|
deleteWithPredicate,
|
||||||
setBucketName,
|
resetFilters,
|
||||||
setDeletionStatus,
|
setDeletionStatus,
|
||||||
setFilter,
|
setFilter,
|
||||||
setIsSerious,
|
setIsSerious,
|
||||||
|
setBucketAndKeys,
|
||||||
setTimeRange,
|
setTimeRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,17 +9,25 @@ import FilterRow from 'src/shared/components/DeleteDataForm/FilterRow'
|
||||||
import {Filter} from 'src/types'
|
import {Filter} from 'src/types'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
bucket: string
|
||||||
filters: Filter[]
|
filters: Filter[]
|
||||||
onSetFilter: (filter: Filter, index: number) => any
|
keys: string[]
|
||||||
onDeleteFilter: (index: number) => any
|
onDeleteFilter: (index: number) => any
|
||||||
|
onSetFilter: (filter: Filter, index: number) => any
|
||||||
|
orgID: string
|
||||||
shouldValidate: boolean
|
shouldValidate: boolean
|
||||||
|
values: (string | number)[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterEditor: FunctionComponent<Props> = ({
|
const FilterEditor: FunctionComponent<Props> = ({
|
||||||
|
bucket,
|
||||||
filters,
|
filters,
|
||||||
onSetFilter,
|
keys,
|
||||||
onDeleteFilter,
|
onDeleteFilter,
|
||||||
|
onSetFilter,
|
||||||
|
orgID,
|
||||||
shouldValidate,
|
shouldValidate,
|
||||||
|
values,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="delete-data-filters">
|
<div className="delete-data-filters">
|
||||||
|
@ -37,11 +45,15 @@ const FilterEditor: FunctionComponent<Props> = ({
|
||||||
<div className="delete-data-filters--filters">
|
<div className="delete-data-filters--filters">
|
||||||
{filters.map((filter, i) => (
|
{filters.map((filter, i) => (
|
||||||
<FilterRow
|
<FilterRow
|
||||||
|
bucket={bucket}
|
||||||
key={i}
|
key={i}
|
||||||
|
keys={keys}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
onChange={filter => onSetFilter(filter, i)}
|
onChange={filter => onSetFilter(filter, i)}
|
||||||
onDelete={() => onDeleteFilter(i)}
|
onDelete={() => onDeleteFilter(i)}
|
||||||
|
orgID={orgID}
|
||||||
shouldValidate={shouldValidate}
|
shouldValidate={shouldValidate}
|
||||||
|
values={values}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,25 +5,44 @@ import {
|
||||||
ButtonShape,
|
ButtonShape,
|
||||||
Form,
|
Form,
|
||||||
IconFont,
|
IconFont,
|
||||||
Input,
|
|
||||||
SelectDropdown,
|
SelectDropdown,
|
||||||
} from '@influxdata/clockface'
|
} from '@influxdata/clockface'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import SearchableDropdown from 'src/shared/components/SearchableDropdown'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import {Filter} from 'src/types'
|
import {Filter} from 'src/types'
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
import {setValuesByKey} from 'src/shared/actions/predicates'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
bucket: string
|
||||||
filter: Filter
|
filter: Filter
|
||||||
|
keys: string[]
|
||||||
onChange: (filter: Filter) => any
|
onChange: (filter: Filter) => any
|
||||||
onDelete: () => any
|
onDelete: () => any
|
||||||
|
orgID: string
|
||||||
shouldValidate: boolean
|
shouldValidate: boolean
|
||||||
|
values: (string | number)[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterRow: FC<Props> = ({
|
interface DispatchProps {
|
||||||
|
setValuesByKey: (orgID: string, bucketName: string, keyName: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const FilterRow: FC<Props & DispatchProps> = ({
|
||||||
|
bucket,
|
||||||
filter: {key, equality, value},
|
filter: {key, equality, value},
|
||||||
|
keys,
|
||||||
onChange,
|
onChange,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
orgID,
|
||||||
|
setValuesByKey,
|
||||||
shouldValidate,
|
shouldValidate,
|
||||||
|
values,
|
||||||
}) => {
|
}) => {
|
||||||
const keyErrorMessage =
|
const keyErrorMessage =
|
||||||
shouldValidate && key.trim() === '' ? 'Key cannot be empty' : null
|
shouldValidate && key.trim() === '' ? 'Key cannot be empty' : null
|
||||||
|
@ -32,8 +51,12 @@ const FilterRow: FC<Props> = ({
|
||||||
const valueErrorMessage =
|
const valueErrorMessage =
|
||||||
shouldValidate && value.trim() === '' ? 'Value cannot be empty' : null
|
shouldValidate && value.trim() === '' ? 'Value cannot be empty' : null
|
||||||
|
|
||||||
const onChangeKey = e => onChange({key: e.target.value, equality, value})
|
const onChangeKey = input => onChange({key: input, equality, value})
|
||||||
const onChangeValue = e => onChange({key, equality, value: e.target.value})
|
const onKeySelect = input => {
|
||||||
|
setValuesByKey(orgID, bucket, input)
|
||||||
|
onChange({key: input, equality, value})
|
||||||
|
}
|
||||||
|
const onChangeValue = input => onChange({key, equality, value: input})
|
||||||
const onChangeEquality = e => onChange({key, equality: e, value})
|
const onChangeEquality = e => onChange({key, equality: e, value})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -43,7 +66,19 @@ const FilterRow: FC<Props> = ({
|
||||||
required={true}
|
required={true}
|
||||||
errorMessage={keyErrorMessage}
|
errorMessage={keyErrorMessage}
|
||||||
>
|
>
|
||||||
<Input onChange={onChangeKey} value={key} testID="key-input" />
|
<SearchableDropdown
|
||||||
|
className="dwp-filter-dropdown"
|
||||||
|
searchTerm={key}
|
||||||
|
emptyText="No Tags Found"
|
||||||
|
searchPlaceholder="Search keys..."
|
||||||
|
selectedOption={key}
|
||||||
|
onSelect={onKeySelect}
|
||||||
|
onChangeSearchTerm={onChangeKey}
|
||||||
|
testID="dwp-filter-key-input"
|
||||||
|
buttonTestID="tag-selector--dropdown-button"
|
||||||
|
menuTestID="tag-selector--dropdown-menu"
|
||||||
|
options={keys}
|
||||||
|
/>
|
||||||
</Form.Element>
|
</Form.Element>
|
||||||
<Form.Element
|
<Form.Element
|
||||||
label="Equality Filter"
|
label="Equality Filter"
|
||||||
|
@ -51,6 +86,7 @@ const FilterRow: FC<Props> = ({
|
||||||
errorMessage={equalityErrorMessage}
|
errorMessage={equalityErrorMessage}
|
||||||
>
|
>
|
||||||
<SelectDropdown
|
<SelectDropdown
|
||||||
|
className="dwp-filter-dropdown"
|
||||||
options={['=', '!=']}
|
options={['=', '!=']}
|
||||||
selectedOption={equality}
|
selectedOption={equality}
|
||||||
onSelect={onChangeEquality}
|
onSelect={onChangeEquality}
|
||||||
|
@ -61,7 +97,19 @@ const FilterRow: FC<Props> = ({
|
||||||
required={true}
|
required={true}
|
||||||
errorMessage={valueErrorMessage}
|
errorMessage={valueErrorMessage}
|
||||||
>
|
>
|
||||||
<Input onChange={onChangeValue} value={value} testID="value-input" />
|
<SearchableDropdown
|
||||||
|
className="dwp-filter-dropdown"
|
||||||
|
searchTerm={value}
|
||||||
|
emptyText="No Tags Found"
|
||||||
|
searchPlaceholder="Search values..."
|
||||||
|
selectedOption={value}
|
||||||
|
onSelect={onChangeValue}
|
||||||
|
onChangeSearchTerm={onChangeValue}
|
||||||
|
testID="dwp-filter-value-input"
|
||||||
|
buttonTestID="tag-selector--dropdown-button"
|
||||||
|
menuTestID="tag-selector--dropdown-menu"
|
||||||
|
options={values}
|
||||||
|
/>
|
||||||
</Form.Element>
|
</Form.Element>
|
||||||
<Button
|
<Button
|
||||||
className="delete-data-filter--remove"
|
className="delete-data-filter--remove"
|
||||||
|
@ -73,4 +121,9 @@ const FilterRow: FC<Props> = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FilterRow
|
const mdtp = {setValuesByKey}
|
||||||
|
|
||||||
|
export default connect<{}, DispatchProps>(
|
||||||
|
null,
|
||||||
|
mdtp
|
||||||
|
)(FilterRow)
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React, {FunctionComponent} from 'react'
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import {withRouter, WithRouterProps} from 'react-router'
|
import {withRouter, WithRouterProps} from 'react-router'
|
||||||
import {Overlay} from '@influxdata/clockface'
|
import {Overlay} from '@influxdata/clockface'
|
||||||
|
import {get} from 'lodash'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import DeleteDataForm from 'src/shared/components/DeleteDataForm/DeleteDataForm'
|
import DeleteDataForm from 'src/shared/components/DeleteDataForm/DeleteDataForm'
|
||||||
|
@ -10,28 +11,36 @@ import DeleteDataForm from 'src/shared/components/DeleteDataForm/DeleteDataForm'
|
||||||
// Types
|
// Types
|
||||||
import {Bucket, AppState} from 'src/types'
|
import {Bucket, AppState} from 'src/types'
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import {getActiveQuery} from 'src/timeMachine/selectors'
|
||||||
|
|
||||||
interface StateProps {
|
interface StateProps {
|
||||||
buckets: Bucket[]
|
buckets: Bucket[]
|
||||||
|
selectedBucketName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeleteDataOverlay: FunctionComponent<StateProps & WithRouterProps> = ({
|
const DeleteDataOverlay: FunctionComponent<StateProps & WithRouterProps> = ({
|
||||||
|
buckets,
|
||||||
router,
|
router,
|
||||||
params: {orgID, bucketID},
|
params: {orgID, bucketID},
|
||||||
buckets,
|
selectedBucketName,
|
||||||
}) => {
|
}) => {
|
||||||
const handleDismiss = () =>
|
const handleDismiss = () =>
|
||||||
router.push(`/orgs/${orgID}/load-data/buckets/${bucketID}`)
|
router.push(`/orgs/${orgID}/load-data/buckets/${bucketID}`)
|
||||||
const bucketName = buckets.find(bucket => bucket.id === bucketID).name
|
// separated find logic and name logic since directly routing the a delete-data
|
||||||
|
// endpoint was crashing the app because the bucket is undefined until the component mounts
|
||||||
|
const bucket = buckets.find(bucket => bucket.id === bucketID)
|
||||||
|
const bucketName = bucket && bucket.name ? bucket.name : ''
|
||||||
|
const initialBucketName = selectedBucketName || bucketName
|
||||||
return (
|
return (
|
||||||
<Overlay visible={true}>
|
<Overlay visible={true}>
|
||||||
<Overlay.Container maxWidth={600}>
|
<Overlay.Container maxWidth={600}>
|
||||||
<Overlay.Header title="Delete Data" onDismiss={handleDismiss} />
|
<Overlay.Header title="Delete Data" onDismiss={handleDismiss} />
|
||||||
<Overlay.Body>
|
<Overlay.Body>
|
||||||
<DeleteDataForm
|
<DeleteDataForm
|
||||||
initialBucketName={bucketName}
|
|
||||||
orgID={orgID}
|
|
||||||
handleDismiss={handleDismiss}
|
handleDismiss={handleDismiss}
|
||||||
|
initialBucketName={initialBucketName}
|
||||||
|
orgID={orgID}
|
||||||
/>
|
/>
|
||||||
</Overlay.Body>
|
</Overlay.Body>
|
||||||
</Overlay.Container>
|
</Overlay.Container>
|
||||||
|
@ -40,7 +49,12 @@ const DeleteDataOverlay: FunctionComponent<StateProps & WithRouterProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const mstp = (state: AppState): StateProps => {
|
const mstp = (state: AppState): StateProps => {
|
||||||
return {buckets: state.buckets.list}
|
const activeQuery = getActiveQuery(state)
|
||||||
|
const selectedBucketName = get(activeQuery, 'builderConfig.buckets.0')
|
||||||
|
return {
|
||||||
|
buckets: state.buckets.list,
|
||||||
|
selectedBucketName,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect<StateProps>(mstp)(
|
export default connect<StateProps>(mstp)(
|
||||||
|
|
|
@ -28,7 +28,7 @@ interface Props {
|
||||||
buttonTestID: string
|
buttonTestID: string
|
||||||
menuTheme: DropdownMenuTheme
|
menuTheme: DropdownMenuTheme
|
||||||
menuTestID: string
|
menuTestID: string
|
||||||
options: string[]
|
options: (string | number)[]
|
||||||
emptyText: string
|
emptyText: string
|
||||||
style?: CSSProperties
|
style?: CSSProperties
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ export default class SearchableDropdown extends Component<Props> {
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const filteredOptions = options.filter(option =>
|
const filteredOptions = options.filter(option =>
|
||||||
option.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase())
|
`${option}`.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase())
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!filteredOptions.length) {
|
if (!filteredOptions.length) {
|
||||||
|
|
|
@ -568,6 +568,16 @@ export const predicateDeleteFailed = (): Notification => ({
|
||||||
message: 'Failed to delete data with predicate',
|
message: 'Failed to delete data with predicate',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const setFilterKeyFailed = (): Notification => ({
|
||||||
|
...defaultErrorNotification,
|
||||||
|
message: 'Failed to set the filter key tag',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const setFilterValueFailed = (): Notification => ({
|
||||||
|
...defaultErrorNotification,
|
||||||
|
message: 'Failed to set the filter value tag',
|
||||||
|
})
|
||||||
|
|
||||||
export const bucketCreateSuccess = (): Notification => ({
|
export const bucketCreateSuccess = (): Notification => ({
|
||||||
...defaultSuccessNotification,
|
...defaultSuccessNotification,
|
||||||
message: 'Bucket was successfully created',
|
message: 'Bucket was successfully created',
|
||||||
|
|
|
@ -12,10 +12,12 @@ export const HOUR_MS = 1000 * 60 * 60
|
||||||
|
|
||||||
export const initialState: PredicatesState = {
|
export const initialState: PredicatesState = {
|
||||||
bucketName: '',
|
bucketName: '',
|
||||||
timeRange: [recently - HOUR_MS, recently],
|
deletionStatus: RemoteDataState.NotStarted,
|
||||||
filters: [],
|
filters: [],
|
||||||
isSerious: false,
|
isSerious: false,
|
||||||
deletionStatus: RemoteDataState.NotStarted,
|
keys: [],
|
||||||
|
timeRange: [recently - HOUR_MS, recently],
|
||||||
|
values: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const predicatesReducer = (
|
export const predicatesReducer = (
|
||||||
|
@ -23,43 +25,54 @@ export const predicatesReducer = (
|
||||||
action: Action
|
action: Action
|
||||||
): PredicatesState => {
|
): PredicatesState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case 'RESET_FILTERS':
|
||||||
|
return {...state, filters: []}
|
||||||
|
|
||||||
case 'SET_IS_SERIOUS':
|
case 'SET_IS_SERIOUS':
|
||||||
return {...state, isSerious: action.isSerious}
|
return {...state, isSerious: action.payload.isSerious}
|
||||||
|
|
||||||
case 'SET_BUCKET_NAME':
|
case 'SET_BUCKET_NAME':
|
||||||
return {...state, bucketName: action.bucketName}
|
return {...state, bucketName: action.payload.bucketName}
|
||||||
|
|
||||||
case 'SET_DELETE_TIME_RANGE':
|
case 'SET_DELETE_TIME_RANGE':
|
||||||
return {...state, timeRange: action.timeRange}
|
return {...state, timeRange: action.payload.timeRange}
|
||||||
|
|
||||||
case 'SET_FILTER':
|
case 'SET_FILTER':
|
||||||
if (action.index >= state.filters.length) {
|
if (action.payload.index >= state.filters.length) {
|
||||||
return {...state, filters: [...state.filters, action.filter]}
|
return {...state, filters: [...state.filters, action.payload.filter]}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
filters: state.filters.map((filter, i) =>
|
filters: state.filters.map((filter, i) =>
|
||||||
i === action.index ? action.filter : filter
|
i === action.payload.index ? action.payload.filter : filter
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'DELETE_FILTER':
|
case 'DELETE_FILTER':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
filters: state.filters.filter((_, i) => i !== action.index),
|
filters: state.filters.filter((_, i) => i !== action.payload.index),
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'SET_DELETION_STATUS':
|
case 'SET_DELETION_STATUS':
|
||||||
return {...state, deletionStatus: action.deletionStatus}
|
return {...state, deletionStatus: action.payload.deletionStatus}
|
||||||
|
|
||||||
|
case 'SET_KEYS_BY_BUCKET':
|
||||||
|
return {...state, keys: action.payload.keys}
|
||||||
|
|
||||||
|
case 'SET_VALUES_BY_KEY':
|
||||||
|
return {...state, values: action.payload.values}
|
||||||
|
|
||||||
case 'SET_PREDICATE_DEFAULT':
|
case 'SET_PREDICATE_DEFAULT':
|
||||||
return {
|
return {
|
||||||
bucketName: '',
|
bucketName: '',
|
||||||
timeRange: [recently - HOUR_MS, recently],
|
deletionStatus: RemoteDataState.NotStarted,
|
||||||
filters: [],
|
filters: [],
|
||||||
isSerious: false,
|
isSerious: false,
|
||||||
deletionStatus: RemoteDataState.NotStarted,
|
keys: [],
|
||||||
|
timeRange: [recently - HOUR_MS, recently],
|
||||||
|
values: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -12,3 +12,8 @@ export const findValues = (_: string) => ({
|
||||||
promise: Promise.resolve(['tv1', 'tv2']),
|
promise: Promise.resolve(['tv1', 'tv2']),
|
||||||
cancel: () => {},
|
cancel: () => {},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const extractBoxedCol = (_: string) => ({
|
||||||
|
promise: Promise.resolve(['Talking Heads', 'This must be the place']),
|
||||||
|
cancel: () => {},
|
||||||
|
})
|
||||||
|
|
|
@ -105,7 +105,7 @@ export function findValues({
|
||||||
return extractBoxedCol(runQuery(orgID, query), '_value')
|
return extractBoxedCol(runQuery(orgID, query), '_value')
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractBoxedCol(
|
export function extractBoxedCol(
|
||||||
resp: CancelBox<RunQueryResult>,
|
resp: CancelBox<RunQueryResult>,
|
||||||
colName: string
|
colName: string
|
||||||
): CancelBox<string[]> {
|
): CancelBox<string[]> {
|
||||||
|
@ -120,7 +120,7 @@ function extractBoxedCol(
|
||||||
return {promise, cancel: resp.cancel}
|
return {promise, cancel: resp.cancel}
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractCol(csv: string, colName: string): string[] {
|
export function extractCol(csv: string, colName: string): string[] {
|
||||||
const tables = parseResponse(csv)
|
const tables = parseResponse(csv)
|
||||||
const data = get(tables, '0.data', [])
|
const data = get(tables, '0.data', [])
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@ import {connect} from 'react-redux'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import {
|
import {
|
||||||
Input,
|
|
||||||
FlexBox,
|
|
||||||
ComponentSize,
|
|
||||||
FlexDirection,
|
|
||||||
AlignItems,
|
AlignItems,
|
||||||
|
ComponentSize,
|
||||||
|
FlexBox,
|
||||||
|
FlexDirection,
|
||||||
|
Input,
|
||||||
} from '@influxdata/clockface'
|
} from '@influxdata/clockface'
|
||||||
import SearchableDropdown from 'src/shared/components/SearchableDropdown'
|
import SearchableDropdown from 'src/shared/components/SearchableDropdown'
|
||||||
import WaitingText from 'src/shared/components/WaitingText'
|
import WaitingText from 'src/shared/components/WaitingText'
|
||||||
|
|
|
@ -2,8 +2,10 @@ import {Filter, RemoteDataState} from 'src/types'
|
||||||
|
|
||||||
export interface PredicatesState {
|
export interface PredicatesState {
|
||||||
bucketName: string
|
bucketName: string
|
||||||
timeRange: [number, number]
|
deletionStatus: RemoteDataState
|
||||||
filters: Filter[]
|
filters: Filter[]
|
||||||
isSerious: boolean
|
isSerious: boolean
|
||||||
deletionStatus: RemoteDataState
|
keys: string[]
|
||||||
|
timeRange: [number, number]
|
||||||
|
values: string[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {MeState} from 'src/shared/reducers/me'
|
||||||
import {NoteEditorState} from 'src/dashboards/reducers/notes'
|
import {NoteEditorState} from 'src/dashboards/reducers/notes'
|
||||||
import {DataLoadingState} from 'src/dataLoaders/reducers'
|
import {DataLoadingState} from 'src/dataLoaders/reducers'
|
||||||
import {OnboardingState} from 'src/onboarding/reducers'
|
import {OnboardingState} from 'src/onboarding/reducers'
|
||||||
|
import {PredicatesState} from 'src/types'
|
||||||
import {VariablesState, VariableEditorState} from 'src/variables/reducers'
|
import {VariablesState, VariableEditorState} from 'src/variables/reducers'
|
||||||
import {LabelsState} from 'src/labels/reducers'
|
import {LabelsState} from 'src/labels/reducers'
|
||||||
import {BucketsState} from 'src/buckets/reducers'
|
import {BucketsState} from 'src/buckets/reducers'
|
||||||
|
@ -30,38 +31,39 @@ import {NotificationRulesState} from 'src/alerting/reducers/notifications/rules'
|
||||||
import {NotificationEndpointsState} from 'src/alerting/reducers/notifications/endpoints'
|
import {NotificationEndpointsState} from 'src/alerting/reducers/notifications/endpoints'
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
VERSION: string
|
|
||||||
labels: LabelsState
|
|
||||||
buckets: BucketsState
|
|
||||||
telegrafs: TelegrafsState
|
|
||||||
links: Links
|
|
||||||
app: AppPresentationState
|
app: AppPresentationState
|
||||||
ranges: RangeState
|
|
||||||
autoRefresh: AutoRefreshState
|
autoRefresh: AutoRefreshState
|
||||||
views: ViewsState
|
buckets: BucketsState
|
||||||
|
checks: ChecksState
|
||||||
|
cloud: {limits: LimitsState}
|
||||||
dashboards: DashboardsState
|
dashboards: DashboardsState
|
||||||
|
dataLoading: DataLoadingState
|
||||||
|
endpoints: NotificationEndpointsState
|
||||||
|
labels: LabelsState
|
||||||
|
links: Links
|
||||||
|
me: MeState
|
||||||
|
members: MembersState
|
||||||
|
noteEditor: NoteEditorState
|
||||||
notifications: Notification[]
|
notifications: Notification[]
|
||||||
timeMachines: TimeMachinesState
|
onboarding: OnboardingState
|
||||||
routing: RouterState
|
|
||||||
tasks: TasksState
|
|
||||||
timeRange: TimeRange
|
|
||||||
orgs: OrgsState
|
orgs: OrgsState
|
||||||
overlays: OverlayState
|
overlays: OverlayState
|
||||||
me: MeState
|
predicates: PredicatesState
|
||||||
onboarding: OnboardingState
|
ranges: RangeState
|
||||||
noteEditor: NoteEditorState
|
routing: RouterState
|
||||||
dataLoading: DataLoadingState
|
rules: NotificationRulesState
|
||||||
|
scrapers: ScrapersState
|
||||||
|
tasks: TasksState
|
||||||
|
telegrafs: TelegrafsState
|
||||||
|
templates: TemplatesState
|
||||||
|
timeMachines: TimeMachinesState
|
||||||
|
timeRange: TimeRange
|
||||||
|
tokens: AuthorizationsState
|
||||||
|
userSettings: UserSettingsState
|
||||||
variables: VariablesState
|
variables: VariablesState
|
||||||
variableEditor: VariableEditorState
|
variableEditor: VariableEditorState
|
||||||
tokens: AuthorizationsState
|
VERSION: string
|
||||||
templates: TemplatesState
|
views: ViewsState
|
||||||
scrapers: ScrapersState
|
|
||||||
userSettings: UserSettingsState
|
|
||||||
members: MembersState
|
|
||||||
cloud: {limits: LimitsState}
|
|
||||||
checks: ChecksState
|
|
||||||
rules: NotificationRulesState
|
|
||||||
endpoints: NotificationEndpointsState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetState = () => AppState
|
export type GetState = () => AppState
|
||||||
|
|
Loading…
Reference in New Issue