diff --git a/ui/cypress/e2e/buckets.test.ts b/ui/cypress/e2e/buckets.test.ts index 086cfa2884..fe6b70e6f9 100644 --- a/ui/cypress/e2e/buckets.test.ts +++ b/ui/cypress/e2e/buckets.test.ts @@ -144,8 +144,8 @@ describe('Buckets', () => { 'defbuck', 'Funky Town', 'Jimmy Mack', - '_tasks', '_monitoring', + '_tasks', ] // check the order 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 - 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('confirm-delete-btn').click() cy.getByTestID('overlay--container').should('not.exist') cy.getByTestID('notification-success').should('have.length', 1) }) - - it('should require key-value pairs when deleting predicate with filters', () => { + // needs relevant data in order to test functionality + it.skip('should require key-value pairs when deleting predicate with filters', () => { // confirm delete is disabled cy.getByTestID('add-filter-btn').click() // checks the consent input @@ -192,12 +192,7 @@ describe('Buckets', () => { // should display warnings cy.getByTestID('form--element-error').should('have.length', 2) - cy.getByTestID('key-input').type('mean') - cy.getByTestID('value-input').type(100) - - cy.getByTestID('confirm-delete-btn') - .should('not.be.disabled') - .click() + // TODO: add filter values based on dropdown selection in key / value }) }) diff --git a/ui/cypress/e2e/explorer.test.ts b/ui/cypress/e2e/explorer.test.ts index e04fa7148c..f4a48450b2 100644 --- a/ui/cypress/e2e/explorer.test.ts +++ b/ui/cypress/e2e/explorer.test.ts @@ -621,8 +621,8 @@ describe('DataExplorer', () => { cy.getByTestID('overlay--container').should('not.exist') cy.getByTestID('notification-success').should('have.length', 1) }) - - it('should require key-value pairs when deleting predicate with filters', () => { + // needs relevant data in order to test functionality + it.skip('should require key-value pairs when deleting predicate with filters', () => { // confirm delete is disabled cy.getByTestID('add-filter-btn').click() // checks the consent input @@ -633,12 +633,7 @@ describe('DataExplorer', () => { // should display warnings cy.getByTestID('form--element-error').should('have.length', 2) - cy.getByTestID('key-input').type('mean') - cy.getByTestID('value-input').type(100) - - cy.getByTestID('confirm-delete-btn') - .should('not.be.disabled') - .click() + // TODO: add filter values based on dropdown selection in key / value }) }) }) diff --git a/ui/src/dataExplorer/components/DeleteDataOverlay.tsx b/ui/src/dataExplorer/components/DeleteDataOverlay.tsx index b3c57993da..7350456b35 100644 --- a/ui/src/dataExplorer/components/DeleteDataOverlay.tsx +++ b/ui/src/dataExplorer/components/DeleteDataOverlay.tsx @@ -10,7 +10,7 @@ import DeleteDataForm from 'src/shared/components/DeleteDataForm/DeleteDataForm' import GetResources, {ResourceType} from 'src/shared/components/GetResources' // Utils -import {getActiveTimeMachine, getActiveQuery} from 'src/timeMachine/selectors' +import {getActiveQuery, getActiveTimeMachine} from 'src/timeMachine/selectors' // Types import {AppState, TimeRange} from 'src/types' @@ -34,10 +34,10 @@ interface StateProps { } const DeleteDataOverlay: FunctionComponent = ({ - selectedBucketName, - selectedTimeRange, router, params: {orgID}, + selectedBucketName, + selectedTimeRange, }) => { const handleDismiss = () => router.push(`/orgs/${orgID}/data-explorer`) @@ -67,7 +67,10 @@ const mstp = (state: AppState): StateProps => { const {timeRange} = getActiveTimeMachine(state) const selectedTimeRange = resolveTimeRange(timeRange) - return {selectedBucketName, selectedTimeRange} + return { + selectedBucketName, + selectedTimeRange, + } } export default connect(mstp)( diff --git a/ui/src/shared/actions/predicates.test.ts b/ui/src/shared/actions/predicates.test.ts new file mode 100644 index 0000000000..707dafb2d3 --- /dev/null +++ b/ui/src/shared/actions/predicates.test.ts @@ -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'], + }, + }, + ]) + }) +}) diff --git a/ui/src/shared/actions/predicates.ts b/ui/src/shared/actions/predicates.ts index 46406937c7..167d5473a2 100644 --- a/ui/src/shared/actions/predicates.ts +++ b/ui/src/shared/actions/predicates.ts @@ -1,8 +1,10 @@ -// Redux +// Libraries import {Dispatch} from 'redux-thunk' +import {extractBoxedCol} from 'src/timeMachine/apis/queryBuilder' // API -import * as api from 'src/client' +import {postDelete} from 'src/client' +import {runQuery} from 'src/shared/apis/query' // Actions import {notify} from 'src/shared/actions/notifications' @@ -11,82 +13,41 @@ import {notify} from 'src/shared/actions/notifications' import { predicateDeleteFailed, predicateDeleteSucceeded, + setFilterKeyFailed, + setFilterValueFailed, } from 'src/shared/copy/notifications' // Types import {RemoteDataState, Filter} from 'src/types' export type Action = - | SetIsSerious - | SetBucketName - | SetTimeRange - | SetFilter | DeleteFilter + | ResetFilters + | SetBucketName | SetDeletionStatus + | SetFilter + | SetIsSerious + | SetKeysByBucket | SetPredicateToDefault - -interface SetIsSerious { - 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, -}) + | SetTimeRange + | SetValuesByKey interface DeleteFilter { type: 'DELETE_FILTER' - index: number + payload: {index: number} } export const deleteFilter = (index: number): DeleteFilter => ({ type: 'DELETE_FILTER', - index, + payload: {index}, }) -interface SetDeletionStatus { - type: 'SET_DELETION_STATUS' - deletionStatus: RemoteDataState +interface ResetFilters { + type: 'RESET_FILTERS' } -export const setDeletionStatus = ( - status: RemoteDataState -): SetDeletionStatus => ({ - type: 'SET_DELETION_STATUS', - deletionStatus: status, +export const resetFilters = (): ResetFilters => ({ + type: 'RESET_FILTERS', }) interface SetPredicateToDefault { @@ -97,11 +58,87 @@ export const resetPredicateState = (): SetPredicateToDefault => ({ 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 ( dispatch: Dispatch ) => { try { - const resp = await api.postDelete(params) + const resp = await postDelete(params) + if (resp.status !== 204) { throw new Error(resp.data.message) } @@ -115,3 +152,35 @@ export const deleteWithPredicate = params => async ( dispatch(resetPredicateState()) } } + +export const setBucketAndKeys = (orgID: string, bucketName: string) => async ( + dispatch: Dispatch +) => { + 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) => { + 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)) + } +} diff --git a/ui/src/shared/components/DeleteDataForm/DeleteDataForm.scss b/ui/src/shared/components/DeleteDataForm/DeleteDataForm.scss index c12e246e28..79762aaa9a 100644 --- a/ui/src/shared/components/DeleteDataForm/DeleteDataForm.scss +++ b/ui/src/shared/components/DeleteDataForm/DeleteDataForm.scss @@ -1,6 +1,5 @@ .delete-data-filters--filters, -.delete-data-filters--no-filters -{ +.delete-data-filters--no-filters { margin: $ix-marg-c 0; } @@ -10,7 +9,7 @@ } .delete-data-filter { - display: flex; + display: flex; justify-content: stretch; } @@ -27,14 +26,12 @@ } .delete-data-filter--remove, -.delete-data-filter--equals, -{ +.delete-data-filter--equals { margin-top: 18px; } .delete-data-filter--remove { flex: 0 0 auto; - margin-left: $ix-marg-a; } .delete-data-form--danger-zone { @@ -65,3 +62,7 @@ color: $c-fire; } } + +.dwp-filter-dropdown { + max-width: 95%; +} diff --git a/ui/src/shared/components/DeleteDataForm/DeleteDataForm.tsx b/ui/src/shared/components/DeleteDataForm/DeleteDataForm.tsx index 0109cc2937..52cb04a4cb 100644 --- a/ui/src/shared/components/DeleteDataForm/DeleteDataForm.tsx +++ b/ui/src/shared/components/DeleteDataForm/DeleteDataForm.tsx @@ -1,5 +1,5 @@ // Libraries -import React, {FunctionComponent} from 'react' +import React, {FC, useEffect} from 'react' import moment from 'moment' import {connect} from 'react-redux' import {Form, Grid, Columns, Panel} from '@influxdata/clockface' @@ -17,14 +17,15 @@ import {Filter, RemoteDataState} from 'src/types' // Selectors import {setCanDelete} from 'src/shared/selectors/canDelete' -// action +// Actions import { deleteFilter, deleteWithPredicate, - setBucketName, + resetFilters, setDeletionStatus, setFilter, setIsSerious, + setBucketAndKeys, setTimeRange, } from 'src/shared/actions/predicates' @@ -33,30 +34,35 @@ interface OwnProps { handleDismiss: () => void initialBucketName?: string initialTimeRange?: [number, number] + keys: string[] + values: (string | number)[] } interface StateProps { bucketName: string canDelete: boolean - filters: Filter[] - timeRange: [number, number] - isSerious: boolean deletionStatus: RemoteDataState + filters: Filter[] + isSerious: boolean + keys: string[] + timeRange: [number, number] + values: (string | number)[] } interface DispatchProps { - deleteFilter: typeof deleteFilter + deleteFilter: (index: number) => void deleteWithPredicate: typeof deleteWithPredicate - setBucketName: typeof setBucketName - setDeletionStatus: typeof setDeletionStatus + resetFilters: () => void + setDeletionStatus: (status: RemoteDataState) => void setFilter: typeof setFilter - setIsSerious: typeof setIsSerious - setTimeRange: typeof setTimeRange + setIsSerious: (isSerious: boolean) => void + setBucketAndKeys: (orgID: string, bucketName: string) => void + setTimeRange: (timeRange: [number, number]) => void } export type Props = StateProps & DispatchProps & OwnProps -const DeleteDataForm: FunctionComponent = ({ +const DeleteDataForm: FC = ({ bucketName, canDelete, deleteFilter, @@ -67,15 +73,24 @@ const DeleteDataForm: FunctionComponent = ({ initialBucketName, initialTimeRange, isSerious, + keys, orgID, - setBucketName, + resetFilters, setDeletionStatus, setFilter, setIsSerious, + setBucketAndKeys, setTimeRange, timeRange, + values, }) => { 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 @@ -114,6 +129,11 @@ const DeleteDataForm: FunctionComponent = ({ handleDismiss() } + const handleBucketClick = selectedBucket => { + setBucketAndKeys(orgID, selectedBucket) + resetFilters() + } + return (
@@ -122,7 +142,7 @@ const DeleteDataForm: FunctionComponent = ({ setBucketName(bucketName)} + onSetBucketName={bucketName => handleBucketClick(bucketName)} /> @@ -138,10 +158,14 @@ const DeleteDataForm: FunctionComponent = ({ setFilter(filter, index)} + keys={keys} onDeleteFilter={index => deleteFilter(index)} + onSetFilter={(filter, index) => setFilter(filter, index)} + orgID={orgID} shouldValidate={isSerious} + values={values} /> @@ -173,25 +197,35 @@ const DeleteDataForm: FunctionComponent = ({ } const mstp = ({predicates}) => { - const {bucketName, deletionStatus, filters, isSerious, timeRange} = predicates - + const { + bucketName, + deletionStatus, + filters, + isSerious, + keys, + timeRange, + values, + } = predicates return { bucketName, canDelete: setCanDelete(predicates), deletionStatus, filters, isSerious, + keys, timeRange, + values, } } const mdtp = { deleteFilter, deleteWithPredicate, - setBucketName, + resetFilters, setDeletionStatus, setFilter, setIsSerious, + setBucketAndKeys, setTimeRange, } diff --git a/ui/src/shared/components/DeleteDataForm/FilterEditor.tsx b/ui/src/shared/components/DeleteDataForm/FilterEditor.tsx index aee4f4aee4..1f5f8f94ce 100644 --- a/ui/src/shared/components/DeleteDataForm/FilterEditor.tsx +++ b/ui/src/shared/components/DeleteDataForm/FilterEditor.tsx @@ -9,17 +9,25 @@ import FilterRow from 'src/shared/components/DeleteDataForm/FilterRow' import {Filter} from 'src/types' interface Props { + bucket: string filters: Filter[] - onSetFilter: (filter: Filter, index: number) => any + keys: string[] onDeleteFilter: (index: number) => any + onSetFilter: (filter: Filter, index: number) => any + orgID: string shouldValidate: boolean + values: (string | number)[] } const FilterEditor: FunctionComponent = ({ + bucket, filters, - onSetFilter, + keys, onDeleteFilter, + onSetFilter, + orgID, shouldValidate, + values, }) => { return (
@@ -37,11 +45,15 @@ const FilterEditor: FunctionComponent = ({
{filters.map((filter, i) => ( onSetFilter(filter, i)} onDelete={() => onDeleteFilter(i)} + orgID={orgID} shouldValidate={shouldValidate} + values={values} /> ))}
diff --git a/ui/src/shared/components/DeleteDataForm/FilterRow.tsx b/ui/src/shared/components/DeleteDataForm/FilterRow.tsx index 1f28cff934..84863c43c1 100644 --- a/ui/src/shared/components/DeleteDataForm/FilterRow.tsx +++ b/ui/src/shared/components/DeleteDataForm/FilterRow.tsx @@ -5,25 +5,44 @@ import { ButtonShape, Form, IconFont, - Input, SelectDropdown, } from '@influxdata/clockface' +import {connect} from 'react-redux' + +// Components +import SearchableDropdown from 'src/shared/components/SearchableDropdown' // Types import {Filter} from 'src/types' +// Actions +import {setValuesByKey} from 'src/shared/actions/predicates' + interface Props { + bucket: string filter: Filter + keys: string[] onChange: (filter: Filter) => any onDelete: () => any + orgID: string shouldValidate: boolean + values: (string | number)[] } -const FilterRow: FC = ({ +interface DispatchProps { + setValuesByKey: (orgID: string, bucketName: string, keyName: string) => void +} + +const FilterRow: FC = ({ + bucket, filter: {key, equality, value}, + keys, onChange, onDelete, + orgID, + setValuesByKey, shouldValidate, + values, }) => { const keyErrorMessage = shouldValidate && key.trim() === '' ? 'Key cannot be empty' : null @@ -32,8 +51,12 @@ const FilterRow: FC = ({ const valueErrorMessage = shouldValidate && value.trim() === '' ? 'Value cannot be empty' : null - const onChangeKey = e => onChange({key: e.target.value, equality, value}) - const onChangeValue = e => onChange({key, equality, value: e.target.value}) + const onChangeKey = input => onChange({key: input, equality, 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}) return ( @@ -43,7 +66,19 @@ const FilterRow: FC = ({ required={true} errorMessage={keyErrorMessage} > - + = ({ errorMessage={equalityErrorMessage} > = ({ + buckets, router, params: {orgID, bucketID}, - buckets, + selectedBucketName, }) => { const handleDismiss = () => 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 ( @@ -40,7 +49,12 @@ const DeleteDataOverlay: FunctionComponent = ({ } 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(mstp)( diff --git a/ui/src/shared/components/SearchableDropdown.tsx b/ui/src/shared/components/SearchableDropdown.tsx index 11eadca4b0..15c573784a 100644 --- a/ui/src/shared/components/SearchableDropdown.tsx +++ b/ui/src/shared/components/SearchableDropdown.tsx @@ -28,7 +28,7 @@ interface Props { buttonTestID: string menuTheme: DropdownMenuTheme menuTestID: string - options: string[] + options: (string | number)[] emptyText: string style?: CSSProperties } @@ -110,7 +110,7 @@ export default class SearchableDropdown extends Component { } = this.props const filteredOptions = options.filter(option => - option.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase()) + `${option}`.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase()) ) if (!filteredOptions.length) { diff --git a/ui/src/shared/copy/notifications.ts b/ui/src/shared/copy/notifications.ts index 06900d6083..d881cb5aed 100644 --- a/ui/src/shared/copy/notifications.ts +++ b/ui/src/shared/copy/notifications.ts @@ -568,6 +568,16 @@ export const predicateDeleteFailed = (): Notification => ({ 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 => ({ ...defaultSuccessNotification, message: 'Bucket was successfully created', diff --git a/ui/src/shared/reducers/predicates.ts b/ui/src/shared/reducers/predicates.ts index 0c4521eee8..1c8f385ae3 100644 --- a/ui/src/shared/reducers/predicates.ts +++ b/ui/src/shared/reducers/predicates.ts @@ -12,10 +12,12 @@ export const HOUR_MS = 1000 * 60 * 60 export const initialState: PredicatesState = { bucketName: '', - timeRange: [recently - HOUR_MS, recently], + deletionStatus: RemoteDataState.NotStarted, filters: [], isSerious: false, - deletionStatus: RemoteDataState.NotStarted, + keys: [], + timeRange: [recently - HOUR_MS, recently], + values: [], } export const predicatesReducer = ( @@ -23,43 +25,54 @@ export const predicatesReducer = ( action: Action ): PredicatesState => { switch (action.type) { + case 'RESET_FILTERS': + return {...state, filters: []} + case 'SET_IS_SERIOUS': - return {...state, isSerious: action.isSerious} + return {...state, isSerious: action.payload.isSerious} case 'SET_BUCKET_NAME': - return {...state, bucketName: action.bucketName} + return {...state, bucketName: action.payload.bucketName} case 'SET_DELETE_TIME_RANGE': - return {...state, timeRange: action.timeRange} + return {...state, timeRange: action.payload.timeRange} case 'SET_FILTER': - if (action.index >= state.filters.length) { - return {...state, filters: [...state.filters, action.filter]} + if (action.payload.index >= state.filters.length) { + return {...state, filters: [...state.filters, action.payload.filter]} } return { ...state, filters: state.filters.map((filter, i) => - i === action.index ? action.filter : filter + i === action.payload.index ? action.payload.filter : filter ), } case 'DELETE_FILTER': return { ...state, - filters: state.filters.filter((_, i) => i !== action.index), + filters: state.filters.filter((_, i) => i !== action.payload.index), } 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': return { bucketName: '', - timeRange: [recently - HOUR_MS, recently], + deletionStatus: RemoteDataState.NotStarted, filters: [], isSerious: false, - deletionStatus: RemoteDataState.NotStarted, + keys: [], + timeRange: [recently - HOUR_MS, recently], + values: [], } default: diff --git a/ui/src/timeMachine/apis/__mocks__/queryBuilder.ts b/ui/src/timeMachine/apis/__mocks__/queryBuilder.ts index 79c07bb2b0..ff08def70e 100644 --- a/ui/src/timeMachine/apis/__mocks__/queryBuilder.ts +++ b/ui/src/timeMachine/apis/__mocks__/queryBuilder.ts @@ -12,3 +12,8 @@ export const findValues = (_: string) => ({ promise: Promise.resolve(['tv1', 'tv2']), cancel: () => {}, }) + +export const extractBoxedCol = (_: string) => ({ + promise: Promise.resolve(['Talking Heads', 'This must be the place']), + cancel: () => {}, +}) diff --git a/ui/src/timeMachine/apis/queryBuilder.ts b/ui/src/timeMachine/apis/queryBuilder.ts index d9ffd916b0..410d9423f7 100644 --- a/ui/src/timeMachine/apis/queryBuilder.ts +++ b/ui/src/timeMachine/apis/queryBuilder.ts @@ -105,7 +105,7 @@ export function findValues({ return extractBoxedCol(runQuery(orgID, query), '_value') } -function extractBoxedCol( +export function extractBoxedCol( resp: CancelBox, colName: string ): CancelBox { @@ -120,7 +120,7 @@ function extractBoxedCol( 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 data = get(tables, '0.data', []) diff --git a/ui/src/timeMachine/components/TagSelector.tsx b/ui/src/timeMachine/components/TagSelector.tsx index a3f7f58528..3fd05be093 100644 --- a/ui/src/timeMachine/components/TagSelector.tsx +++ b/ui/src/timeMachine/components/TagSelector.tsx @@ -4,11 +4,11 @@ import {connect} from 'react-redux' // Components import { - Input, - FlexBox, - ComponentSize, - FlexDirection, AlignItems, + ComponentSize, + FlexBox, + FlexDirection, + Input, } from '@influxdata/clockface' import SearchableDropdown from 'src/shared/components/SearchableDropdown' import WaitingText from 'src/shared/components/WaitingText' diff --git a/ui/src/types/predicates.ts b/ui/src/types/predicates.ts index 4c5680e191..7ac4dc6682 100644 --- a/ui/src/types/predicates.ts +++ b/ui/src/types/predicates.ts @@ -2,8 +2,10 @@ import {Filter, RemoteDataState} from 'src/types' export interface PredicatesState { bucketName: string - timeRange: [number, number] + deletionStatus: RemoteDataState filters: Filter[] isSerious: boolean - deletionStatus: RemoteDataState + keys: string[] + timeRange: [number, number] + values: string[] } diff --git a/ui/src/types/stores.ts b/ui/src/types/stores.ts index 712e9ef254..b26c3d4f7d 100644 --- a/ui/src/types/stores.ts +++ b/ui/src/types/stores.ts @@ -9,6 +9,7 @@ import {MeState} from 'src/shared/reducers/me' import {NoteEditorState} from 'src/dashboards/reducers/notes' import {DataLoadingState} from 'src/dataLoaders/reducers' import {OnboardingState} from 'src/onboarding/reducers' +import {PredicatesState} from 'src/types' import {VariablesState, VariableEditorState} from 'src/variables/reducers' import {LabelsState} from 'src/labels/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' export interface AppState { - VERSION: string - labels: LabelsState - buckets: BucketsState - telegrafs: TelegrafsState - links: Links app: AppPresentationState - ranges: RangeState autoRefresh: AutoRefreshState - views: ViewsState + buckets: BucketsState + checks: ChecksState + cloud: {limits: LimitsState} dashboards: DashboardsState + dataLoading: DataLoadingState + endpoints: NotificationEndpointsState + labels: LabelsState + links: Links + me: MeState + members: MembersState + noteEditor: NoteEditorState notifications: Notification[] - timeMachines: TimeMachinesState - routing: RouterState - tasks: TasksState - timeRange: TimeRange + onboarding: OnboardingState orgs: OrgsState overlays: OverlayState - me: MeState - onboarding: OnboardingState - noteEditor: NoteEditorState - dataLoading: DataLoadingState + predicates: PredicatesState + ranges: RangeState + routing: RouterState + rules: NotificationRulesState + scrapers: ScrapersState + tasks: TasksState + telegrafs: TelegrafsState + templates: TemplatesState + timeMachines: TimeMachinesState + timeRange: TimeRange + tokens: AuthorizationsState + userSettings: UserSettingsState variables: VariablesState variableEditor: VariableEditorState - tokens: AuthorizationsState - templates: TemplatesState - scrapers: ScrapersState - userSettings: UserSettingsState - members: MembersState - cloud: {limits: LimitsState} - checks: ChecksState - rules: NotificationRulesState - endpoints: NotificationEndpointsState + VERSION: string + views: ViewsState } export type GetState = () => AppState diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000000..fb57ccd13a --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + +