refactor(ui/views): normalization (#16616)
* refactor: move views logic to separate directory * refactor: normalize views * fix: spinners * fix: dont render views until status is done * fix(http/dashboards): view shape not returning from getDashboard * test: delete irrelevant and redundant test * fix: go tidy * test: skipping monaco test * chore: sort type exports * chore: cleanuppull/16658/head
parent
7a9b09cf1c
commit
78c1e9e19e
|
|
@ -215,18 +215,23 @@ type dashboardCellResponse struct {
|
|||
func (d *dashboardCellResponse) MarshalJSON() ([]byte, error) {
|
||||
r := struct {
|
||||
platform.Cell
|
||||
Properties platform.ViewProperties `json:"properties,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Links map[string]string `json:"links"`
|
||||
Properties json.RawMessage `json:"properties,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Links map[string]string `json:"links"`
|
||||
}{
|
||||
Cell: d.Cell,
|
||||
Links: d.Links,
|
||||
}
|
||||
|
||||
if d.Cell.View != nil {
|
||||
r.Properties = d.Cell.View.Properties
|
||||
b, err := platform.MarshalViewPropertiesJSON(d.Cell.View.Properties)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Properties = b
|
||||
r.Name = d.Cell.View.Name
|
||||
}
|
||||
|
||||
return json.Marshal(r)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -454,6 +454,7 @@ func TestService_handleGetDashboard(t *testing.T) {
|
|||
"h": 4,
|
||||
"name": "the cell name",
|
||||
"properties": {
|
||||
"shape": "chronograf-v2",
|
||||
"axes": null,
|
||||
"colors": null,
|
||||
"geom": "",
|
||||
|
|
@ -974,6 +975,7 @@ func TestService_handlePostDashboard(t *testing.T) {
|
|||
"h": 4,
|
||||
"name": "hello a view",
|
||||
"properties": {
|
||||
"shape": "chronograf-v2",
|
||||
"axes": null,
|
||||
"colors": null,
|
||||
"geom": "",
|
||||
|
|
|
|||
|
|
@ -451,7 +451,8 @@ describe('DataExplorer', () => {
|
|||
cy.getByTestID('toolbar-function').should('have.length', 1)
|
||||
})
|
||||
|
||||
it('shows the empty state when the query returns no results', () => {
|
||||
// TODO: fix flakeyness of focused() command
|
||||
it.skip('shows the empty state when the query returns no results', () => {
|
||||
cy.getByTestID('time-machine--bottom').within(() => {
|
||||
cy.get('.react-monaco-editor-container')
|
||||
.click()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {incrementCloneName} from 'src/utils/naming'
|
|||
import {reportError} from 'src/shared/utils/errors'
|
||||
import {isDurationParseable} from 'src/shared/utils/duration'
|
||||
import {checkThresholdsValid} from '../utils/checkValidate'
|
||||
import {createView} from 'src/shared/utils/view'
|
||||
import {createView} from 'src/views/helpers'
|
||||
import {getOrg} from 'src/organizations/selectors'
|
||||
|
||||
// Actions
|
||||
|
|
|
|||
|
|
@ -6,15 +6,16 @@ import {get} from 'lodash'
|
|||
import {Plot} from '@influxdata/giraffe'
|
||||
import CheckPlot from 'src/shared/components/CheckPlot'
|
||||
import EmptyQueryView, {ErrorFormat} from 'src/shared/components/EmptyQueryView'
|
||||
import TimeSeries from 'src/shared/components/TimeSeries'
|
||||
|
||||
// Types
|
||||
import {ResourceIDs} from 'src/alerting/reducers/checks'
|
||||
import {Check, TimeZone, CheckViewProperties} from 'src/types'
|
||||
import TimeSeries from 'src/shared/components/TimeSeries'
|
||||
import {createView} from 'src/shared/utils/view'
|
||||
import {Check, TimeZone, CheckViewProperties, TimeRange} from 'src/types'
|
||||
|
||||
// Utils
|
||||
import {createView} from 'src/views/helpers'
|
||||
import {checkResultsLength} from 'src/shared/utils/vis'
|
||||
import {getTimeRangeVars} from 'src/variables/utils/getTimeRangeVars'
|
||||
import {TimeRange} from 'src/types'
|
||||
|
||||
export const ResourceIDsContext = createContext<ResourceIDs>(null)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from 'src/alerting/actions/alertBuilder'
|
||||
|
||||
// Utils
|
||||
import {createView} from 'src/shared/utils/view'
|
||||
import {createView} from 'src/views/helpers'
|
||||
|
||||
// Types
|
||||
import {AppState, RemoteDataState, CheckViewProperties} from 'src/types'
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from 'src/alerting/actions/alertBuilder'
|
||||
|
||||
// Utils
|
||||
import {createView} from 'src/shared/utils/view'
|
||||
import {createView} from 'src/views/helpers'
|
||||
|
||||
// Types
|
||||
import {AppState, RemoteDataState, CheckViewProperties} from 'src/types'
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import * as schemas from 'src/schemas'
|
|||
|
||||
// Actions
|
||||
import {refreshDashboardVariableValues} from 'src/dashboards/actions/thunks'
|
||||
import {setView} from 'src/dashboards/actions/views'
|
||||
import {setView} from 'src/views/actions/creators'
|
||||
import {notify} from 'src/shared/actions/notifications'
|
||||
import {setCells, setCell, removeCell} from 'src/cells/actions/creators'
|
||||
|
||||
|
|
@ -31,10 +31,12 @@ import {
|
|||
DashboardEntities,
|
||||
ResourceType,
|
||||
CellEntities,
|
||||
View,
|
||||
ViewEntities,
|
||||
} from 'src/types'
|
||||
|
||||
// Utils
|
||||
import {getViewsForDashboard} from 'src/dashboards/selectors'
|
||||
import {getViewsForDashboard} from 'src/views/selectors'
|
||||
import {getNewDashboardCell} from 'src/dashboards/utils/cellGetters'
|
||||
import {getByID} from 'src/resources/selectors'
|
||||
|
||||
|
|
@ -112,7 +114,12 @@ export const createCellWithView = (
|
|||
|
||||
await dispatch(refreshDashboardVariableValues(dashboardID, views))
|
||||
|
||||
dispatch(setView(cellID, newView, RemoteDataState.Done))
|
||||
const normView = normalize<View, ViewEntities, string>(
|
||||
newView,
|
||||
schemas.view
|
||||
)
|
||||
|
||||
dispatch(setView(cellID, RemoteDataState.Done, normView))
|
||||
dispatch(setCell(cellID, RemoteDataState.Done, normCell))
|
||||
} catch {
|
||||
notify(copy.cellAddFailed())
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
// Libraries
|
||||
import {get, isUndefined} from 'lodash'
|
||||
import {get} from 'lodash'
|
||||
|
||||
// Actions
|
||||
import {createCellWithView} from 'src/cells/actions/thunks'
|
||||
import {updateView} from 'src/dashboards/actions/views'
|
||||
import {updateView} from 'src/views/actions/thunks'
|
||||
|
||||
// Utils
|
||||
import {createView} from 'src/shared/utils/view'
|
||||
import {getView} from 'src/dashboards/selectors'
|
||||
import {createView} from 'src/views/helpers'
|
||||
import {getByID} from 'src/resources/selectors'
|
||||
|
||||
// Types
|
||||
import {
|
||||
|
|
@ -16,10 +16,10 @@ import {
|
|||
NoteEditorMode,
|
||||
ResourceType,
|
||||
Dashboard,
|
||||
View,
|
||||
} from 'src/types'
|
||||
import {NoteEditorState} from 'src/dashboards/reducers/notes'
|
||||
import {Dispatch} from 'react'
|
||||
import {getByID} from 'src/resources/selectors'
|
||||
|
||||
export type Action =
|
||||
| CloseNoteEditorAction
|
||||
|
|
@ -113,16 +113,14 @@ export const loadNote = (id: string) => (
|
|||
dispatch: Dispatch<Action>,
|
||||
getState: GetState
|
||||
) => {
|
||||
const {
|
||||
views: {views},
|
||||
} = getState()
|
||||
const currentViewState = views[id]
|
||||
const state = getState()
|
||||
const currentViewState = getByID<View>(state, ResourceType.Views, id)
|
||||
|
||||
if (!currentViewState) {
|
||||
return
|
||||
}
|
||||
|
||||
const view = currentViewState.view
|
||||
const view = currentViewState
|
||||
|
||||
const note: string = get(view, 'properties.note', '')
|
||||
const showNoteWhenEmpty: boolean = get(
|
||||
|
|
@ -147,13 +145,9 @@ export const updateViewNote = (id: string) => (
|
|||
) => {
|
||||
const state = getState()
|
||||
const {note, showNoteWhenEmpty} = state.noteEditor
|
||||
const view: any = getView(state, id)
|
||||
const view = getByID<View>(state, ResourceType.Views, id)
|
||||
|
||||
if (!view) {
|
||||
throw new Error(`could not find view with id "${id}"`)
|
||||
}
|
||||
|
||||
if (isUndefined(view.properties.note)) {
|
||||
if (view.properties.type === 'check') {
|
||||
throw new Error(
|
||||
`view type "${view.properties.type}" does not support notes`
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,11 +21,12 @@ import {
|
|||
deleteTimeRange,
|
||||
updateTimeRangeFromQueryParams,
|
||||
} from 'src/dashboards/actions/ranges'
|
||||
import {setView, setViews} from 'src/dashboards/actions/views'
|
||||
import {setViews} from 'src/views/actions/creators'
|
||||
import {selectValue} from 'src/variables/actions/creators'
|
||||
import {getVariables, refreshVariableValues} from 'src/variables/actions/thunks'
|
||||
import {setExportTemplate} from 'src/templates/actions'
|
||||
import {checkDashboardLimits} from 'src/cloud/actions/limits'
|
||||
import {updateViewAndVariables} from 'src/views/actions/thunks'
|
||||
import * as creators from 'src/dashboards/actions/creators'
|
||||
|
||||
// Utils
|
||||
|
|
@ -35,7 +36,6 @@ import {
|
|||
extractVariablesList,
|
||||
getHydratedVariables,
|
||||
} from 'src/variables/selectors'
|
||||
import {getViewsForDashboard} from 'src/dashboards/selectors'
|
||||
import {dashboardToTemplate} from 'src/shared/utils/resourceToTemplate'
|
||||
import {exportVariables} from 'src/variables/utils/exportVariables'
|
||||
import {getSaveableView} from 'src/timeMachine/selectors'
|
||||
|
|
@ -58,8 +58,10 @@ import {
|
|||
Label,
|
||||
RemoteDataState,
|
||||
DashboardEntities,
|
||||
ViewEntities,
|
||||
ResourceType,
|
||||
} from 'src/types'
|
||||
import {CellsWithViewProperties} from 'src/client'
|
||||
|
||||
type Action = creators.Action
|
||||
|
||||
|
|
@ -261,9 +263,11 @@ export const getDashboard = (dashboardID: string) => async (
|
|||
getState: GetState
|
||||
): Promise<void> => {
|
||||
try {
|
||||
// Fetch the dashboard and all variables a user has access to
|
||||
dispatch(creators.setDashboard(dashboardID, RemoteDataState.Loading))
|
||||
|
||||
// Fetch the dashboard, views, and all variables a user has access to
|
||||
const [resp] = await Promise.all([
|
||||
api.getDashboard({dashboardID}),
|
||||
api.getDashboard({dashboardID, query: {include: 'properties'}}),
|
||||
dispatch(getVariables()),
|
||||
])
|
||||
|
||||
|
|
@ -276,21 +280,22 @@ export const getDashboard = (dashboardID: string) => async (
|
|||
schemas.dashboard
|
||||
)
|
||||
|
||||
const {cells, id}: Dashboard = normDash.entities.dashboards[normDash.result]
|
||||
const cellViews: CellsWithViewProperties = resp.data.cells || []
|
||||
const viewsData = schemas.viewsFromCells(cellViews, dashboardID)
|
||||
|
||||
// Fetch all the views in use on the dashboard
|
||||
const views = await Promise.all(
|
||||
cells.map(cellID => dashAPI.getView(id, cellID))
|
||||
const normViews = normalize<View, ViewEntities, string[]>(
|
||||
viewsData,
|
||||
schemas.arrayOfViews
|
||||
)
|
||||
|
||||
dispatch(setViews(RemoteDataState.Done, views))
|
||||
dispatch(setViews(RemoteDataState.Done, normViews))
|
||||
|
||||
// Ensure the values for the variables in use on the dashboard are populated
|
||||
await dispatch(refreshDashboardVariableValues(id, views))
|
||||
await dispatch(refreshDashboardVariableValues(dashboardID, viewsData))
|
||||
|
||||
// Now that all the necessary state has been loaded, set the dashboard
|
||||
dispatch(creators.setDashboard(dashboardID, RemoteDataState.Done, normDash))
|
||||
dispatch(updateTimeRangeFromQueryParams(id))
|
||||
dispatch(updateTimeRangeFromQueryParams(dashboardID))
|
||||
} catch (error) {
|
||||
const org = getOrg(getState())
|
||||
dispatch(push(`/orgs/${org.id}/dashboards`))
|
||||
|
|
@ -338,29 +343,6 @@ export const updateDashboard = (
|
|||
}
|
||||
}
|
||||
|
||||
export const updateView = (dashboardID: string, view: View) => async (
|
||||
dispatch,
|
||||
getState: GetState
|
||||
) => {
|
||||
const cellID = view.cellID
|
||||
|
||||
try {
|
||||
const newView = await dashAPI.updateView(dashboardID, cellID, view)
|
||||
|
||||
const views = getViewsForDashboard(getState(), dashboardID)
|
||||
|
||||
views.splice(views.findIndex(v => v.id === newView.id), 1, newView)
|
||||
|
||||
await dispatch(refreshDashboardVariableValues(dashboardID, views))
|
||||
|
||||
dispatch(setView(cellID, newView, RemoteDataState.Done))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
dispatch(notify(copy.cellUpdateFailed()))
|
||||
dispatch(setView(cellID, null, RemoteDataState.Error))
|
||||
}
|
||||
}
|
||||
|
||||
export const addDashboardLabel = (dashboardID: string, label: Label) => async (
|
||||
dispatch: Dispatch<Action | PublishNotificationAction>
|
||||
) => {
|
||||
|
|
@ -479,7 +461,7 @@ export const saveVEOView = (dashboardID: string) => async (
|
|||
|
||||
try {
|
||||
if (view.id) {
|
||||
await dispatch(updateView(dashboardID, view))
|
||||
await dispatch(updateViewAndVariables(dashboardID, view))
|
||||
} else {
|
||||
await dispatch(createCellWithView(dashboardID, view))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,115 +0,0 @@
|
|||
import {createStore} from 'redux'
|
||||
import {mocked} from 'ts-jest/utils'
|
||||
|
||||
// Mocks
|
||||
import {viewProperties} from 'mocks/dummyData'
|
||||
|
||||
import {getView} from 'src/dashboards/apis'
|
||||
jest.mock('src/dashboards/apis/index')
|
||||
|
||||
import {getView as getViewFromState} from 'src/dashboards/selectors'
|
||||
jest.mock('src/dashboards/selectors')
|
||||
|
||||
// Types
|
||||
import {RemoteDataState} from 'src/types'
|
||||
|
||||
// Reducers
|
||||
import viewsReducer from 'src/dashboards/reducers/views'
|
||||
|
||||
// Actions
|
||||
import {getViewForTimeMachine} from 'src/dashboards/actions/views'
|
||||
|
||||
const dashboardID = '04960a1f5dafe000'
|
||||
const viewID = '04960a1fbdafe000'
|
||||
const timeMachineId = 'veo'
|
||||
|
||||
const memoryUsageView = {
|
||||
viewID: viewID,
|
||||
dashboardID: dashboardID,
|
||||
id: viewID,
|
||||
links: {
|
||||
self: `/api/v2/dashboards/${dashboardID}/cells/${viewID}`,
|
||||
},
|
||||
name: 'Memory Usage',
|
||||
properties: viewProperties,
|
||||
}
|
||||
|
||||
const populatedViewState = {
|
||||
status: RemoteDataState.Done,
|
||||
views: {
|
||||
[viewID]: {
|
||||
status: RemoteDataState.Done,
|
||||
view: memoryUsageView,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const unpopulatedViewState = {
|
||||
status: RemoteDataState.Done,
|
||||
views: {},
|
||||
}
|
||||
|
||||
describe('Dashboards.Actions.getViewForTimeMachine', () => {
|
||||
let store
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
store = null
|
||||
})
|
||||
|
||||
// fix for https://github.com/influxdata/influxdb/issues/15239
|
||||
it('dispatches a SET_VIEW action and fetches the view if there is no view in the store', async () => {
|
||||
store = createStore(viewsReducer, unpopulatedViewState)
|
||||
|
||||
mocked(getViewFromState).mockImplementation(() => undefined)
|
||||
mocked(getView).mockImplementation(() => Promise.resolve(memoryUsageView))
|
||||
|
||||
const mockedDispatch = jest.fn()
|
||||
await getViewForTimeMachine(dashboardID, viewID, timeMachineId)(
|
||||
mockedDispatch,
|
||||
store.getState
|
||||
)
|
||||
|
||||
expect(mocked(getView)).toHaveBeenCalledTimes(1)
|
||||
expect(mockedDispatch).toHaveBeenCalledTimes(3)
|
||||
|
||||
const [
|
||||
setViewDispatchArguments,
|
||||
setActiveTimeMachineDispatchArguments,
|
||||
] = mockedDispatch.mock.calls
|
||||
expect(setViewDispatchArguments[0]).toEqual({
|
||||
type: 'SET_VIEW',
|
||||
payload: {id: viewID, view: null, status: RemoteDataState.Loading},
|
||||
})
|
||||
expect(setActiveTimeMachineDispatchArguments[0]).toEqual({
|
||||
type: 'SET_ACTIVE_TIME_MACHINE',
|
||||
payload: {
|
||||
activeTimeMachineID: timeMachineId,
|
||||
initialState: {view: memoryUsageView},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// fix for https://github.com/influxdata/influxdb/issues/15239
|
||||
it('does not dispatch a SET_VIEW action and does not fetch the view if there is already a view in the store', async () => {
|
||||
store = createStore(viewsReducer, populatedViewState)
|
||||
// `getViewFromState` expects dashboard-like state, which has additional keys that are beyond the scope of this spec
|
||||
mocked(getViewFromState).mockImplementation(() => memoryUsageView)
|
||||
|
||||
const mockedDispatch = jest.fn()
|
||||
await getViewForTimeMachine(dashboardID, viewID, timeMachineId)(
|
||||
mockedDispatch,
|
||||
store.getState
|
||||
)
|
||||
|
||||
expect(mocked(getView)).toHaveBeenCalledTimes(0)
|
||||
expect(mockedDispatch).toHaveBeenCalledTimes(2)
|
||||
expect(mockedDispatch).toHaveBeenCalledWith({
|
||||
type: 'SET_ACTIVE_TIME_MACHINE',
|
||||
payload: {
|
||||
activeTimeMachineID: timeMachineId,
|
||||
initialState: {view: memoryUsageView},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
// Utils
|
||||
import {getView as getViewFromState} from 'src/dashboards/selectors'
|
||||
|
||||
// APIs
|
||||
import {
|
||||
getView as getViewAJAX,
|
||||
updateView as updateViewAJAX,
|
||||
} from 'src/dashboards/apis/'
|
||||
|
||||
// Constants
|
||||
import * as copy from 'src/shared/copy/notifications'
|
||||
|
||||
// Actions
|
||||
import {notify} from 'src/shared/actions/notifications'
|
||||
import {setActiveTimeMachine} from 'src/timeMachine/actions'
|
||||
import {executeQueries} from 'src/timeMachine/actions/queries'
|
||||
|
||||
// Selectors
|
||||
import {getTimeRangeByDashboardID} from 'src/dashboards/selectors/index'
|
||||
|
||||
// Types
|
||||
import {RemoteDataState, QueryView, GetState} from 'src/types'
|
||||
import {Dispatch} from 'redux'
|
||||
import {View} from 'src/types'
|
||||
import {TimeMachineID} from 'src/types'
|
||||
|
||||
export type Action = SetViewAction | SetViewsAction | ResetViewsAction
|
||||
|
||||
export interface SetViewsAction {
|
||||
type: 'SET_VIEWS'
|
||||
payload: {
|
||||
views?: View[]
|
||||
status: RemoteDataState
|
||||
}
|
||||
}
|
||||
|
||||
export const setViews = (
|
||||
status: RemoteDataState,
|
||||
views: View[]
|
||||
): SetViewsAction => ({
|
||||
type: 'SET_VIEWS',
|
||||
payload: {views, status},
|
||||
})
|
||||
|
||||
export interface SetViewAction {
|
||||
type: 'SET_VIEW'
|
||||
payload: {
|
||||
id: string
|
||||
view: View
|
||||
status: RemoteDataState
|
||||
}
|
||||
}
|
||||
|
||||
export const setView = (
|
||||
id: string,
|
||||
view: View,
|
||||
status: RemoteDataState
|
||||
): SetViewAction => ({
|
||||
type: 'SET_VIEW',
|
||||
payload: {id, view, status},
|
||||
})
|
||||
|
||||
export interface ResetViewsAction {
|
||||
type: 'RESET_VIEWS'
|
||||
}
|
||||
|
||||
export const resetViews = (): ResetViewsAction => ({
|
||||
type: 'RESET_VIEWS',
|
||||
})
|
||||
|
||||
export const getView = (dashboardID: string, cellID: string) => async (
|
||||
dispatch: Dispatch<Action>
|
||||
): Promise<void> => {
|
||||
dispatch(setView(cellID, null, RemoteDataState.Loading))
|
||||
try {
|
||||
const view = await getViewAJAX(dashboardID, cellID)
|
||||
|
||||
dispatch(setView(cellID, view, RemoteDataState.Done))
|
||||
} catch {
|
||||
dispatch(setView(cellID, null, RemoteDataState.Error))
|
||||
}
|
||||
}
|
||||
|
||||
export const updateView = (dashboardID: string, view: View) => async (
|
||||
dispatch: Dispatch<Action>
|
||||
): Promise<View> => {
|
||||
const viewID = view.cellID
|
||||
|
||||
dispatch(setView(viewID, view, RemoteDataState.Loading))
|
||||
|
||||
try {
|
||||
const newView = await updateViewAJAX(dashboardID, viewID, view)
|
||||
|
||||
dispatch(setView(viewID, newView, RemoteDataState.Done))
|
||||
|
||||
return newView
|
||||
} catch {
|
||||
dispatch(setView(viewID, null, RemoteDataState.Error))
|
||||
}
|
||||
}
|
||||
|
||||
export const getViewForTimeMachine = (
|
||||
dashboardID: string,
|
||||
cellID: string,
|
||||
timeMachineID: TimeMachineID
|
||||
) => async (dispatch, getState: GetState): Promise<void> => {
|
||||
try {
|
||||
const state = getState()
|
||||
let view = getViewFromState(state, cellID) as QueryView
|
||||
|
||||
const timeRange = getTimeRangeByDashboardID(state, dashboardID)
|
||||
|
||||
if (!view) {
|
||||
dispatch(setView(cellID, null, RemoteDataState.Loading))
|
||||
view = (await getViewAJAX(dashboardID, cellID)) as QueryView
|
||||
}
|
||||
|
||||
dispatch(setActiveTimeMachine(timeMachineID, {view, timeRange}))
|
||||
dispatch(executeQueries(dashboardID))
|
||||
} catch (e) {
|
||||
dispatch(notify(copy.getViewFailed(e.message)))
|
||||
dispatch(setView(cellID, null, RemoteDataState.Error))
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
import * as api from 'src/client'
|
||||
|
||||
// Types
|
||||
import {Cell, View, NewView} from 'src/types'
|
||||
import {Cell, View, NewView, RemoteDataState} from 'src/types'
|
||||
|
||||
export const getView = async (
|
||||
dashboardID: string,
|
||||
|
|
@ -14,7 +14,7 @@ export const getView = async (
|
|||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
return {...resp.data, dashboardID, cellID}
|
||||
return {...resp.data, dashboardID, cellID, status: RemoteDataState.Done}
|
||||
}
|
||||
|
||||
export const updateView = async (
|
||||
|
|
@ -32,7 +32,12 @@ export const updateView = async (
|
|||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
const viewWithIDs: View = {...resp.data, dashboardID, cellID}
|
||||
const viewWithIDs: View = {
|
||||
...resp.data,
|
||||
dashboardID,
|
||||
cellID,
|
||||
status: RemoteDataState.Done,
|
||||
}
|
||||
|
||||
return viewWithIDs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import * as cellActions from 'src/cells/actions/thunks'
|
|||
import * as dashboardActions from 'src/dashboards/actions/thunks'
|
||||
import * as rangesActions from 'src/dashboards/actions/ranges'
|
||||
import * as appActions from 'src/shared/actions/app'
|
||||
import {updateViewAndVariables} from 'src/views/actions/thunks'
|
||||
import {
|
||||
setAutoRefreshInterval,
|
||||
setAutoRefreshStatus,
|
||||
|
|
@ -41,12 +42,10 @@ import {getOrg} from 'src/organizations/selectors'
|
|||
import {
|
||||
Links,
|
||||
Cell,
|
||||
View,
|
||||
TimeRange,
|
||||
AppState,
|
||||
AutoRefresh,
|
||||
AutoRefreshStatus,
|
||||
RemoteDataState,
|
||||
ResourceType,
|
||||
Dashboard,
|
||||
} from 'src/types'
|
||||
|
|
@ -63,7 +62,6 @@ interface StateProps {
|
|||
links: Links
|
||||
timeRange: TimeRange
|
||||
showVariablesControls: boolean
|
||||
views: {[cellID: string]: {view: View; status: RemoteDataState}}
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
|
|
@ -76,7 +74,7 @@ interface DispatchProps {
|
|||
handleChooseAutoRefresh: typeof setAutoRefreshInterval
|
||||
onSetAutoRefreshStatus: typeof setAutoRefreshStatus
|
||||
handleClickPresentationButton: AppActions.DelayEnablePresentationModeDispatcher
|
||||
onUpdateView: typeof dashboardActions.updateView
|
||||
onUpdateView: typeof updateViewAndVariables
|
||||
onToggleShowVariablesControls: typeof toggleShowVariablesControls
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +218,6 @@ class DashboardPage extends Component<Props> {
|
|||
const mstp = (state: AppState, {dashboardID}: OwnProps): StateProps => {
|
||||
const {
|
||||
links,
|
||||
views: {views},
|
||||
userSettings: {showVariablesControls},
|
||||
cloud: {limits},
|
||||
} = state
|
||||
|
|
@ -238,7 +235,6 @@ const mstp = (state: AppState, {dashboardID}: OwnProps): StateProps => {
|
|||
|
||||
return {
|
||||
links,
|
||||
views,
|
||||
orgName: org && org.name,
|
||||
timeRange,
|
||||
dashboardName: dashboard && dashboard.name,
|
||||
|
|
@ -258,7 +254,7 @@ const mdtp: DispatchProps = {
|
|||
updateQueryParams: rangesActions.updateQueryParams,
|
||||
updateCells: cellActions.updateCells,
|
||||
deleteCell: cellActions.deleteCell,
|
||||
onUpdateView: dashboardActions.updateView,
|
||||
onUpdateView: updateViewAndVariables,
|
||||
onToggleShowVariablesControls: toggleShowVariablesControls,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import VEOHeader from 'src/dashboards/components/VEOHeader'
|
|||
// Actions
|
||||
import {setName} from 'src/timeMachine/actions'
|
||||
import {saveVEOView} from 'src/dashboards/actions/thunks'
|
||||
import {getViewForTimeMachine} from 'src/dashboards/actions/views'
|
||||
import {getViewForTimeMachine} from 'src/views/actions/thunks'
|
||||
|
||||
// Utils
|
||||
import {getActiveTimeMachine} from 'src/timeMachine/selectors'
|
||||
|
|
|
|||
|
|
@ -185,10 +185,10 @@ class NoteEditorOverlay extends PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
const mstp = ({noteEditor, views, overlays}: AppState): StateProps => {
|
||||
const mstp = ({noteEditor, resources, overlays}: AppState): StateProps => {
|
||||
const {params} = overlays
|
||||
const {mode} = noteEditor
|
||||
const {status} = views
|
||||
const {status} = resources.views
|
||||
|
||||
const cellID = get(params, 'cellID', undefined)
|
||||
const dashboardID = get(params, 'dashboardID', undefined)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
removeDashboardLabel,
|
||||
} from 'src/dashboards/actions/thunks'
|
||||
import {createLabel as createLabelAsync} from 'src/labels/actions'
|
||||
import {resetViews} from 'src/views/actions/creators'
|
||||
|
||||
// Selectors
|
||||
import {viewableLabels} from 'src/labels/selectors'
|
||||
|
|
@ -26,7 +27,6 @@ import {AppState, Label} from 'src/types'
|
|||
|
||||
// Constants
|
||||
import {DEFAULT_DASHBOARD_NAME} from 'src/dashboards/constants'
|
||||
import {resetViews} from 'src/dashboards/actions/views'
|
||||
|
||||
// Utilities
|
||||
import {relativeTimestampFormatter} from 'src/shared/utils/relativeTimestampFormatter'
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
// Types
|
||||
import {Action} from 'src/dashboards/actions/views'
|
||||
import {RemoteDataState} from 'src/types'
|
||||
import {View} from 'src/types'
|
||||
|
||||
export interface ViewsState {
|
||||
status: RemoteDataState
|
||||
views: {
|
||||
[viewID: string]: {
|
||||
status: RemoteDataState
|
||||
view: View
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initialState = () => ({
|
||||
status: RemoteDataState.NotStarted,
|
||||
views: {},
|
||||
})
|
||||
|
||||
const viewsReducer = (
|
||||
state: ViewsState = initialState(),
|
||||
action: Action
|
||||
): ViewsState => {
|
||||
switch (action.type) {
|
||||
case 'SET_VIEWS': {
|
||||
const {status} = action.payload
|
||||
|
||||
if (!action.payload.views) {
|
||||
return {
|
||||
...state,
|
||||
status,
|
||||
}
|
||||
}
|
||||
|
||||
const views = action.payload.views.reduce<ViewsState['views']>(
|
||||
(acc, view) => ({
|
||||
...acc,
|
||||
[view.id]: {
|
||||
view,
|
||||
status: RemoteDataState.Done,
|
||||
},
|
||||
}),
|
||||
{}
|
||||
)
|
||||
|
||||
return {
|
||||
status,
|
||||
views,
|
||||
}
|
||||
}
|
||||
|
||||
case 'SET_VIEW': {
|
||||
const {id, view, status} = action.payload
|
||||
|
||||
return {
|
||||
...state,
|
||||
views: {
|
||||
...state.views,
|
||||
[id]: {view, status},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
case 'RESET_VIEWS': {
|
||||
return initialState()
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
export default viewsReducer
|
||||
|
|
@ -1,15 +1,6 @@
|
|||
import {get} from 'lodash'
|
||||
|
||||
import {
|
||||
AppState,
|
||||
View,
|
||||
Check,
|
||||
ViewType,
|
||||
RemoteDataState,
|
||||
TimeRange,
|
||||
ResourceType,
|
||||
Dashboard,
|
||||
} from 'src/types'
|
||||
import {AppState, View, Check, ViewType, TimeRange} from 'src/types'
|
||||
|
||||
import {
|
||||
getValuesForVariable,
|
||||
|
|
@ -19,15 +10,6 @@ import {
|
|||
|
||||
// Constants
|
||||
import {DEFAULT_TIME_RANGE} from 'src/shared/constants/timeRanges'
|
||||
import {getByID} from 'src/resources/selectors'
|
||||
|
||||
export const getView = (state: AppState, id: string): View => {
|
||||
return get(state, `views.views.${id}.view`)
|
||||
}
|
||||
|
||||
export const getViewStatus = (state: AppState, id: string): RemoteDataState => {
|
||||
return get(state, `views.views.${id}.status`, RemoteDataState.Loading)
|
||||
}
|
||||
|
||||
export const getTimeRangeByDashboardID = (
|
||||
state: AppState,
|
||||
|
|
@ -46,25 +28,6 @@ export const getCheckForView = (
|
|||
: null
|
||||
}
|
||||
|
||||
export const getViewsForDashboard = (
|
||||
state: AppState,
|
||||
dashboardID: string
|
||||
): View[] => {
|
||||
const dashboard = getByID<Dashboard>(
|
||||
state,
|
||||
ResourceType.Dashboards,
|
||||
dashboardID
|
||||
)
|
||||
|
||||
const cellIDs = new Set(dashboard.cells.map(cellID => cellID))
|
||||
|
||||
const views = Object.values(state.views.views)
|
||||
.map(d => d.view)
|
||||
.filter(view => view && cellIDs.has(view.cellID))
|
||||
|
||||
return views
|
||||
}
|
||||
|
||||
interface DropdownValues {
|
||||
list: {name: string; value: string}[]
|
||||
selectedKey: string
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ interface OwnProps {
|
|||
export type Props = StateProps & DispatchProps & OwnProps
|
||||
|
||||
@ErrorHandling
|
||||
class GetResources extends PureComponent<Props, StateProps> {
|
||||
class GetResource extends PureComponent<Props, StateProps> {
|
||||
public componentDidMount() {
|
||||
const {resources} = this.props
|
||||
const promises = []
|
||||
|
|
@ -67,8 +67,10 @@ class GetResources extends PureComponent<Props, StateProps> {
|
|||
<SpinnerContainer
|
||||
loading={remoteDataState}
|
||||
spinnerComponent={<TechnoSpinner />}
|
||||
/>
|
||||
{remoteDataState === RemoteDataState.Done ? children : null}
|
||||
testID="dashboard-container--spinner"
|
||||
>
|
||||
{children}
|
||||
</SpinnerContainer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -89,4 +91,4 @@ const mdtp = {
|
|||
export default connect<StateProps, DispatchProps, {}>(
|
||||
mstp,
|
||||
mdtp
|
||||
)(GetResources)
|
||||
)(GetResource)
|
||||
|
|
|
|||
|
|
@ -20,8 +20,11 @@ export const setResourceAtID = <R extends {status: RemoteDataState}>(
|
|||
return
|
||||
}
|
||||
|
||||
if (!draftState.allIDs.includes(id)) {
|
||||
draftState.allIDs.push(id)
|
||||
}
|
||||
|
||||
draftState.byID[id] = {...r, status}
|
||||
draftState.allIDs.push(id)
|
||||
draftState.byID[id].status = status
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// Libraries
|
||||
import {schema} from 'normalizr'
|
||||
import {omit} from 'lodash'
|
||||
|
||||
// Types
|
||||
import {
|
||||
|
|
@ -11,10 +12,13 @@ import {
|
|||
RemoteDataState,
|
||||
Variable,
|
||||
Dashboard,
|
||||
View,
|
||||
} from 'src/types'
|
||||
import {CellsWithViewProperties} from 'src/client'
|
||||
|
||||
// Utils
|
||||
import {addLabelDefaults} from 'src/labels/utils'
|
||||
import {defaultView} from 'src/views/helpers'
|
||||
|
||||
/* Authorizations */
|
||||
|
||||
|
|
@ -28,6 +32,30 @@ export const arrayOfAuths = [auth]
|
|||
export const bucket = new schema.Entity(ResourceType.Buckets)
|
||||
export const arrayOfBuckets = [bucket]
|
||||
|
||||
/* Views */
|
||||
|
||||
// Defines the schema for the "views" resource
|
||||
|
||||
export const viewsFromCells = (
|
||||
cells: CellsWithViewProperties,
|
||||
dashboardID: string
|
||||
): View[] => {
|
||||
return cells.map(cell => {
|
||||
const {properties, id, name} = cell
|
||||
|
||||
return {
|
||||
id,
|
||||
...defaultView(name),
|
||||
cellID: id,
|
||||
properties,
|
||||
dashboardID,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const view = new schema.Entity(ResourceType.Views)
|
||||
export const arrayOfViews = [view]
|
||||
|
||||
/* Cells */
|
||||
|
||||
// Defines the schema for the "cells" resource
|
||||
|
|
@ -37,8 +65,8 @@ export const cell = new schema.Entity(
|
|||
{
|
||||
processStrategy: (cell: Cell, parent: Dashboard) => {
|
||||
return {
|
||||
...cell,
|
||||
dashboardID: !cell.dashboardID ? parent.id : cell.dashboardID,
|
||||
...omit<Cell>(cell, 'properties'),
|
||||
dashboardID: cell.dashboardID ? cell.dashboardID : parent.id,
|
||||
status: RemoteDataState.Done,
|
||||
}
|
||||
},
|
||||
|
|
@ -53,6 +81,7 @@ export const dashboard = new schema.Entity(
|
|||
ResourceType.Dashboards,
|
||||
{
|
||||
cells: arrayOfCells,
|
||||
views: arrayOfViews,
|
||||
},
|
||||
{
|
||||
processStrategy: (dashboard: Dashboard) => addDashboardDefaults(dashboard),
|
||||
|
|
@ -172,6 +201,7 @@ export const variable = new schema.Entity(
|
|||
)
|
||||
export const arrayOfVariables = [variable]
|
||||
|
||||
// Defaults
|
||||
const addStatus = <R extends {status: RemoteDataState}>(resource: R) => {
|
||||
return resource.status ? resource.status : RemoteDataState.Done
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,17 +8,22 @@ import CellHeader from 'src/shared/components/cells/CellHeader'
|
|||
import CellContext from 'src/shared/components/cells/CellContext'
|
||||
import ViewComponent from 'src/shared/components/cells/View'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {SpinnerContainer} from '@influxdata/clockface'
|
||||
import EmptyGraphMessage from 'src/shared/components/EmptyGraphMessage'
|
||||
|
||||
// Utils
|
||||
import {getView, getViewStatus} from 'src/dashboards/selectors'
|
||||
import {getByID} from 'src/resources/selectors'
|
||||
|
||||
// Types
|
||||
import {AppState, View, Cell, TimeRange, RemoteDataState} from 'src/types'
|
||||
import {
|
||||
RemoteDataState,
|
||||
AppState,
|
||||
View,
|
||||
Cell,
|
||||
TimeRange,
|
||||
ResourceType,
|
||||
} from 'src/types'
|
||||
|
||||
interface StateProps {
|
||||
viewsStatus: RemoteDataState
|
||||
view: View
|
||||
}
|
||||
|
||||
|
|
@ -42,13 +47,11 @@ class CellComponent extends Component<Props, State> {
|
|||
return (
|
||||
<>
|
||||
<CellHeader name={this.viewName} note={this.viewNote}>
|
||||
{view && (
|
||||
<CellContext
|
||||
cell={cell}
|
||||
view={view}
|
||||
onCSVDownload={this.handleCSVDownload}
|
||||
/>
|
||||
)}
|
||||
<CellContext
|
||||
cell={cell}
|
||||
view={view}
|
||||
onCSVDownload={this.handleCSVDownload}
|
||||
/>
|
||||
</CellHeader>
|
||||
<div className="cell--view" data-testid="cell--view-empty">
|
||||
{this.view}
|
||||
|
|
@ -60,7 +63,7 @@ class CellComponent extends Component<Props, State> {
|
|||
private get viewName(): string {
|
||||
const {view} = this.props
|
||||
|
||||
if (view && view.properties.type !== 'markdown') {
|
||||
if (view && view.properties && view.properties.type !== 'markdown') {
|
||||
return view.name
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +73,7 @@ class CellComponent extends Component<Props, State> {
|
|||
private get viewNote(): string {
|
||||
const {view} = this.props
|
||||
|
||||
if (!view) {
|
||||
if (!view || !view.properties || !view.properties.type) {
|
||||
return ''
|
||||
}
|
||||
|
||||
|
|
@ -85,19 +88,18 @@ class CellComponent extends Component<Props, State> {
|
|||
}
|
||||
|
||||
private get view(): JSX.Element {
|
||||
const {timeRange, manualRefresh, view, viewsStatus} = this.props
|
||||
const {timeRange, manualRefresh, view} = this.props
|
||||
|
||||
if (!view || view.status !== RemoteDataState.Done) {
|
||||
return <EmptyGraphMessage message="Loading..." />
|
||||
}
|
||||
|
||||
return (
|
||||
<SpinnerContainer
|
||||
loading={viewsStatus}
|
||||
spinnerComponent={<EmptyGraphMessage message="Loading..." />}
|
||||
>
|
||||
<ViewComponent
|
||||
view={view}
|
||||
timeRange={timeRange}
|
||||
manualRefresh={manualRefresh}
|
||||
/>
|
||||
</SpinnerContainer>
|
||||
<ViewComponent
|
||||
view={view}
|
||||
timeRange={timeRange}
|
||||
manualRefresh={manualRefresh}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -107,11 +109,9 @@ class CellComponent extends Component<Props, State> {
|
|||
}
|
||||
|
||||
const mstp = (state: AppState, ownProps: OwnProps): StateProps => {
|
||||
const view = getView(state, ownProps.cell.id)
|
||||
const view = getByID<View>(state, ResourceType.Views, ownProps.cell.id)
|
||||
|
||||
const status = getViewStatus(state, ownProps.cell.id)
|
||||
|
||||
return {view, viewsStatus: status}
|
||||
return {view}
|
||||
}
|
||||
|
||||
export default connect<StateProps, {}, OwnProps>(
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ export const myView: View = {
|
|||
yColumn: null,
|
||||
position: 'overlaid',
|
||||
},
|
||||
status: RemoteDataState.Done,
|
||||
}
|
||||
|
||||
export const myfavelabel: Label = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import _ from 'lodash'
|
||||
import {getDeep} from 'src/utils/wrappers'
|
||||
|
||||
import {defaultBuilderConfig} from 'src/shared/utils/view'
|
||||
import {defaultBuilderConfig} from 'src/views/helpers'
|
||||
import {viewableLabels} from 'src/labels/selectors'
|
||||
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import tasksReducer from 'src/tasks/reducers'
|
|||
import rangesReducer from 'src/dashboards/reducers/ranges'
|
||||
import {dashboardsReducer} from 'src/dashboards/reducers/dashboards'
|
||||
import {cellsReducer} from 'src/cells/reducers'
|
||||
import viewsReducer from 'src/dashboards/reducers/views'
|
||||
import viewsReducer from 'src/views/reducers'
|
||||
import {timeMachinesReducer} from 'src/timeMachine/reducers'
|
||||
import {orgsReducer} from 'src/organizations/reducers'
|
||||
import overlaysReducer from 'src/overlays/reducers/overlays'
|
||||
|
|
@ -69,6 +69,7 @@ export const rootReducer = combineReducers<ReducerState>({
|
|||
resources: combineReducers({
|
||||
buckets: bucketsReducer,
|
||||
cells: cellsReducer,
|
||||
dashboards: dashboardsReducer,
|
||||
members: membersReducer,
|
||||
orgs: orgsReducer,
|
||||
scrapers: scrapersReducer,
|
||||
|
|
@ -76,7 +77,7 @@ export const rootReducer = combineReducers<ReducerState>({
|
|||
telegrafs: telegrafsReducer,
|
||||
tokens: authsReducer,
|
||||
variables: variablesReducer,
|
||||
dashboards: dashboardsReducer,
|
||||
views: viewsReducer,
|
||||
}),
|
||||
routing: routerReducer,
|
||||
rules: rulesReducer,
|
||||
|
|
@ -87,7 +88,6 @@ export const rootReducer = combineReducers<ReducerState>({
|
|||
timeMachines: timeMachinesReducer,
|
||||
userSettings: userSettingsReducer,
|
||||
variableEditor: variableEditorReducer,
|
||||
views: viewsReducer,
|
||||
VERSION: () => '',
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {getTimeRangeByDashboardID} from 'src/dashboards/selectors'
|
|||
import {getActiveQuery} from 'src/timeMachine/selectors'
|
||||
|
||||
// Utils
|
||||
import {createView} from 'src/shared/utils/view'
|
||||
import {createView} from 'src/views/helpers'
|
||||
import {createCheckQueryFromAlertBuilder} from 'src/alerting/utils/customCheck'
|
||||
|
||||
// Types
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {cloneDeep, isNumber, get, map} from 'lodash'
|
|||
import {produce} from 'immer'
|
||||
|
||||
// Utils
|
||||
import {createView, defaultViewQuery} from 'src/shared/utils/view'
|
||||
import {createView, defaultViewQuery} from 'src/views/helpers'
|
||||
import {isConfigValid, buildQuery} from 'src/timeMachine/utils/queryBuilder'
|
||||
|
||||
// Constants
|
||||
|
|
@ -22,20 +22,19 @@ import {
|
|||
TableViewProperties,
|
||||
TimeRange,
|
||||
View,
|
||||
} from 'src/types'
|
||||
import {
|
||||
ViewType,
|
||||
DashboardDraftQuery,
|
||||
BuilderConfig,
|
||||
BuilderConfigAggregateWindow,
|
||||
QueryView,
|
||||
QueryViewProperties,
|
||||
ExtractWorkingView,
|
||||
} from 'src/types/dashboards'
|
||||
DashboardDraftQuery,
|
||||
BuilderConfig,
|
||||
BuilderConfigAggregateWindow,
|
||||
RemoteDataState,
|
||||
TimeMachineID,
|
||||
Color,
|
||||
} from 'src/types'
|
||||
import {Action} from 'src/timeMachine/actions'
|
||||
import {TimeMachineTab} from 'src/types/timeMachine'
|
||||
import {RemoteDataState, TimeMachineID} from 'src/types'
|
||||
import {Color} from 'src/types/colors'
|
||||
import {BuilderAggregateFunctionType} from 'src/client/generatedRoutes'
|
||||
|
||||
interface QueryBuilderState {
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ import {isFlagEnabled} from 'src/shared/utils/featureFlag'
|
|||
|
||||
// Types
|
||||
import {
|
||||
QueryView,
|
||||
BuilderAggregateFunctionType,
|
||||
BuilderTagsType,
|
||||
DashboardQuery,
|
||||
FluxTable,
|
||||
QueryView,
|
||||
AppState,
|
||||
DashboardDraftQuery,
|
||||
TimeRange,
|
||||
|
|
@ -193,77 +193,6 @@ export const getSymbolColumnsSelection = (state: AppState): string[] => {
|
|||
)
|
||||
}
|
||||
|
||||
export const getSaveableView = (state: AppState): QueryView & {id?: string} => {
|
||||
const {view, draftQueries} = getActiveTimeMachine(state)
|
||||
|
||||
let saveableView: QueryView & {id?: string} = {
|
||||
...view,
|
||||
properties: {
|
||||
...view.properties,
|
||||
queries: draftQueries,
|
||||
},
|
||||
}
|
||||
|
||||
if (saveableView.properties.type === 'histogram') {
|
||||
saveableView = {
|
||||
...saveableView,
|
||||
properties: {
|
||||
...saveableView.properties,
|
||||
xColumn: getXColumnSelection(state),
|
||||
fillColumns: getFillColumnsSelection(state),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (saveableView.properties.type === 'heatmap') {
|
||||
saveableView = {
|
||||
...saveableView,
|
||||
properties: {
|
||||
...saveableView.properties,
|
||||
xColumn: getXColumnSelection(state),
|
||||
yColumn: getYColumnSelection(state),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (saveableView.properties.type === 'scatter') {
|
||||
saveableView = {
|
||||
...saveableView,
|
||||
properties: {
|
||||
...saveableView.properties,
|
||||
xColumn: getXColumnSelection(state),
|
||||
yColumn: getYColumnSelection(state),
|
||||
fillColumns: getFillColumnsSelection(state),
|
||||
symbolColumns: getSymbolColumnsSelection(state),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (saveableView.properties.type === 'xy') {
|
||||
saveableView = {
|
||||
...saveableView,
|
||||
properties: {
|
||||
...saveableView.properties,
|
||||
xColumn: getXColumnSelection(state),
|
||||
yColumn: getYColumnSelection(state),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (saveableView.properties.type === 'line-plus-single-stat') {
|
||||
saveableView = {
|
||||
...saveableView,
|
||||
properties: {
|
||||
...saveableView.properties,
|
||||
xColumn: getXColumnSelection(state),
|
||||
yColumn: getYColumnSelection(state),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return saveableView
|
||||
}
|
||||
|
||||
export const getStartTime = (timeRange: TimeRange) => {
|
||||
if (!timeRange) {
|
||||
return Infinity
|
||||
|
|
@ -338,3 +267,74 @@ export const getActiveTagValues = (
|
|||
|
||||
return activeQueryBuilderTags[index].values
|
||||
}
|
||||
|
||||
export const getSaveableView = (state: AppState): QueryView & {id?: string} => {
|
||||
const {view, draftQueries} = getActiveTimeMachine(state)
|
||||
|
||||
let saveableView: QueryView & {id?: string} = {
|
||||
...view,
|
||||
properties: {
|
||||
...view.properties,
|
||||
queries: draftQueries,
|
||||
},
|
||||
}
|
||||
|
||||
if (saveableView.properties.type === 'histogram') {
|
||||
saveableView = {
|
||||
...saveableView,
|
||||
properties: {
|
||||
...saveableView.properties,
|
||||
xColumn: getXColumnSelection(state),
|
||||
fillColumns: getFillColumnsSelection(state),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (saveableView.properties.type === 'heatmap') {
|
||||
saveableView = {
|
||||
...saveableView,
|
||||
properties: {
|
||||
...saveableView.properties,
|
||||
xColumn: getXColumnSelection(state),
|
||||
yColumn: getYColumnSelection(state),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (saveableView.properties.type === 'scatter') {
|
||||
saveableView = {
|
||||
...saveableView,
|
||||
properties: {
|
||||
...saveableView.properties,
|
||||
xColumn: getXColumnSelection(state),
|
||||
yColumn: getYColumnSelection(state),
|
||||
fillColumns: getFillColumnsSelection(state),
|
||||
symbolColumns: getSymbolColumnsSelection(state),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (saveableView.properties.type === 'xy') {
|
||||
saveableView = {
|
||||
...saveableView,
|
||||
properties: {
|
||||
...saveableView.properties,
|
||||
xColumn: getXColumnSelection(state),
|
||||
yColumn: getYColumnSelection(state),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (saveableView.properties.type === 'line-plus-single-stat') {
|
||||
saveableView = {
|
||||
...saveableView,
|
||||
properties: {
|
||||
...saveableView.properties,
|
||||
xColumn: getXColumnSelection(state),
|
||||
yColumn: getYColumnSelection(state),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return saveableView
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import {
|
||||
View as GenView,
|
||||
Cell as GenCell,
|
||||
Dashboard as GenDashboard,
|
||||
Axis,
|
||||
ViewProperties,
|
||||
TableViewProperties,
|
||||
DashboardQuery,
|
||||
RenamableField,
|
||||
|
|
@ -24,13 +21,6 @@ export interface DashboardDraftQuery extends DashboardQuery {
|
|||
|
||||
export type BuilderConfigAggregateWindow = BuilderConfig['aggregateWindow']
|
||||
|
||||
export interface View<T extends ViewProperties = ViewProperties>
|
||||
extends GenView {
|
||||
properties: T
|
||||
cellID?: string
|
||||
dashboardID?: string
|
||||
}
|
||||
|
||||
export interface Cell extends GenCell {
|
||||
dashboardID: string
|
||||
status: RemoteDataState
|
||||
|
|
@ -44,34 +34,8 @@ export interface Dashboard extends Omit<GenDashboard, 'cells'> {
|
|||
status: RemoteDataState
|
||||
}
|
||||
|
||||
export type Base = Axis['base']
|
||||
|
||||
export type ViewType = ViewProperties['type']
|
||||
|
||||
export type ViewShape = ViewProperties['shape']
|
||||
|
||||
export type Omit<K, V> = Pick<K, Exclude<keyof K, V>>
|
||||
|
||||
export type NewView<T extends ViewProperties = ViewProperties> = Omit<
|
||||
View<T>,
|
||||
'id' | 'links'
|
||||
>
|
||||
|
||||
export type QueryViewProperties = Extract<
|
||||
ViewProperties,
|
||||
{queries: DashboardQuery[]}
|
||||
>
|
||||
|
||||
export type WorkingView<T extends ViewProperties> = View<T> | NewView<T>
|
||||
|
||||
export type QueryView = WorkingView<QueryViewProperties>
|
||||
|
||||
// Conditional type that narrows QueryView to those Views satisfying an
|
||||
// interface, e.g. the action payload's. It's useful when a payload has a
|
||||
// specific interface we know forces it to be a certain subset of
|
||||
// ViewProperties.
|
||||
export type ExtractWorkingView<T> = WorkingView<Extract<QueryViewProperties, T>>
|
||||
|
||||
export interface DashboardSwitcherLink {
|
||||
key: string
|
||||
text: string
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
export * from './alerting'
|
||||
export * from './arguments'
|
||||
export * from './ast'
|
||||
export * from './auth'
|
||||
export * from './autoRefresh'
|
||||
export * from './buckets'
|
||||
export * from './cloud'
|
||||
export * from './codemirror'
|
||||
export * from './colors'
|
||||
export * from './dashboards'
|
||||
|
|
@ -7,21 +12,26 @@ export * from './dataExplorer'
|
|||
export * from './dataLoaders'
|
||||
export * from './filterEditor'
|
||||
export * from './flux'
|
||||
export * from './layouts'
|
||||
export * from './histogram'
|
||||
export * from './hosts'
|
||||
export * from './influxAdmin'
|
||||
export * from './labels'
|
||||
export * from './layouts'
|
||||
export * from './layouts'
|
||||
export * from './links'
|
||||
export * from './localStorage'
|
||||
export * from './logEvent'
|
||||
export * from './members'
|
||||
export * from './monaco'
|
||||
export * from './notifications'
|
||||
export * from './orgs'
|
||||
export * from './overlay'
|
||||
export * from './promises'
|
||||
export * from './predicates'
|
||||
export * from './promises'
|
||||
export * from './queries'
|
||||
export * from './redux'
|
||||
export * from './resources'
|
||||
export * from './run'
|
||||
export * from './schemas'
|
||||
export * from './scrapers'
|
||||
export * from './services'
|
||||
|
|
@ -32,15 +42,6 @@ export * from './tasks'
|
|||
export * from './telegraf'
|
||||
export * from './templates'
|
||||
export * from './timeMachine'
|
||||
export * from './members'
|
||||
export * from './autoRefresh'
|
||||
export * from './arguments'
|
||||
export * from './timeZones'
|
||||
export * from './alerting'
|
||||
export * from './auth'
|
||||
export * from './cloud'
|
||||
export * from './resources'
|
||||
export * from './redux'
|
||||
export * from './run'
|
||||
export * from './variables'
|
||||
export * from './monaco'
|
||||
export * from './views'
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
RemoteDataState,
|
||||
Telegraf,
|
||||
Scraper,
|
||||
View,
|
||||
TasksState,
|
||||
VariablesState,
|
||||
} from 'src/types'
|
||||
|
|
@ -29,6 +30,7 @@ export enum ResourceType {
|
|||
Templates = 'templates',
|
||||
Telegrafs = 'telegrafs',
|
||||
Variables = 'variables',
|
||||
Views = 'views',
|
||||
}
|
||||
|
||||
export interface NormalizedState<R> {
|
||||
|
|
@ -62,4 +64,5 @@ export interface ResourceState {
|
|||
[ResourceType.Tasks]: TasksState
|
||||
[ResourceType.Telegrafs]: TelegrafsState
|
||||
[ResourceType.Variables]: VariablesState
|
||||
[ResourceType.Views]: NormalizedState<View>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
// Types
|
||||
import {
|
||||
Cell,
|
||||
Task,
|
||||
Dashboard,
|
||||
Variable,
|
||||
Telegraf,
|
||||
Member,
|
||||
Bucket,
|
||||
Scraper,
|
||||
Organization,
|
||||
Authorization,
|
||||
Bucket,
|
||||
Cell,
|
||||
Dashboard,
|
||||
Member,
|
||||
Organization,
|
||||
Scraper,
|
||||
Task,
|
||||
Telegraf,
|
||||
Variable,
|
||||
View,
|
||||
} from 'src/types'
|
||||
|
||||
// AuthEntities defines the result of normalizr's normalization
|
||||
|
|
@ -94,3 +95,9 @@ export interface VariableEntities {
|
|||
[uuid: string]: Variable
|
||||
}
|
||||
}
|
||||
|
||||
export interface ViewEntities {
|
||||
views: {
|
||||
[uuid: string]: View
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import {
|
|||
} from 'src/dataLoaders/reducers/telegrafEditor'
|
||||
import {TemplatesState} from 'src/templates/reducers'
|
||||
import {RangeState} from 'src/dashboards/reducers/ranges'
|
||||
import {ViewsState} from 'src/dashboards/reducers/views'
|
||||
import {UserSettingsState} from 'src/userSettings/reducers'
|
||||
import {OverlayState} from 'src/overlays/reducers/overlays'
|
||||
import {AutoRefreshState} from 'src/shared/reducers/autoRefresh'
|
||||
|
|
@ -60,7 +59,6 @@ export interface AppState {
|
|||
userSettings: UserSettingsState
|
||||
variableEditor: VariableEditorState
|
||||
VERSION: string
|
||||
views: ViewsState
|
||||
}
|
||||
|
||||
export type GetState = () => AppState
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import {View as GenView, Axis, ViewProperties, DashboardQuery} from 'src/client'
|
||||
import {RemoteDataState} from 'src/types'
|
||||
|
||||
export interface View<T extends ViewProperties = ViewProperties>
|
||||
extends GenView {
|
||||
properties: T
|
||||
cellID?: string
|
||||
dashboardID?: string
|
||||
status: RemoteDataState
|
||||
}
|
||||
export type Base = Axis['base']
|
||||
|
||||
export type ViewType = ViewProperties['type']
|
||||
|
||||
export type ViewShape = ViewProperties['shape']
|
||||
|
||||
export type NewView<T extends ViewProperties = ViewProperties> = Omit<
|
||||
View<T>,
|
||||
'id' | 'links'
|
||||
>
|
||||
|
||||
export type QueryViewProperties = Extract<
|
||||
ViewProperties,
|
||||
{queries: DashboardQuery[]}
|
||||
>
|
||||
|
||||
export type WorkingView<T extends ViewProperties> = View<T> | NewView<T>
|
||||
|
||||
export type QueryView = WorkingView<QueryViewProperties>
|
||||
|
||||
// Conditional type that narrows QueryView to those Views satisfying an
|
||||
// interface, e.g. the action payload's. It's useful when a payload has a
|
||||
// specific interface we know forces it to be a certain subset of
|
||||
// ViewProperties.
|
||||
export type ExtractWorkingView<T> = WorkingView<Extract<QueryViewProperties, T>>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// Types
|
||||
import {RemoteDataState, ViewEntities} from 'src/types'
|
||||
import {NormalizedSchema} from 'normalizr'
|
||||
|
||||
// Actions
|
||||
import {setDashboard} from 'src/dashboards/actions/creators'
|
||||
|
||||
export type Action =
|
||||
| ReturnType<typeof resetViews>
|
||||
| ReturnType<typeof setView>
|
||||
| ReturnType<typeof setViews>
|
||||
| ReturnType<typeof setDashboard>
|
||||
|
||||
export const RESET_VIEWS = 'RESET_VIEWS'
|
||||
export const SET_VIEW = 'SET_VIEW'
|
||||
export const SET_VIEWS = 'SET_VIEWS'
|
||||
|
||||
type ViewSchema<R extends string | string[]> = NormalizedSchema<ViewEntities, R>
|
||||
|
||||
export const resetViews = () =>
|
||||
({
|
||||
type: RESET_VIEWS,
|
||||
} as const)
|
||||
|
||||
export const setViews = (
|
||||
status: RemoteDataState,
|
||||
schema?: ViewSchema<string[]>
|
||||
) =>
|
||||
({
|
||||
type: SET_VIEWS,
|
||||
status,
|
||||
schema,
|
||||
} as const)
|
||||
|
||||
export const setView = (
|
||||
id: string,
|
||||
status: RemoteDataState,
|
||||
schema?: ViewSchema<string>
|
||||
) =>
|
||||
({
|
||||
type: SET_VIEW,
|
||||
id,
|
||||
status,
|
||||
schema,
|
||||
} as const)
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
// Libraries
|
||||
import {normalize} from 'normalizr'
|
||||
|
||||
// APIs
|
||||
import {
|
||||
getView as getViewAJAX,
|
||||
updateView as updateViewAJAX,
|
||||
} from 'src/dashboards/apis'
|
||||
|
||||
// Constants
|
||||
import * as copy from 'src/shared/copy/notifications'
|
||||
import * as schemas from 'src/schemas'
|
||||
|
||||
// Actions
|
||||
import {notify} from 'src/shared/actions/notifications'
|
||||
import {setActiveTimeMachine} from 'src/timeMachine/actions'
|
||||
import {executeQueries} from 'src/timeMachine/actions/queries'
|
||||
import {setView, Action} from 'src/views/actions/creators'
|
||||
|
||||
// Selectors
|
||||
import {getViewsForDashboard} from 'src/views/selectors'
|
||||
import {getTimeRangeByDashboardID} from 'src/dashboards/selectors/index'
|
||||
import {getByID} from 'src/resources/selectors'
|
||||
|
||||
import {refreshDashboardVariableValues} from 'src/dashboards/actions/thunks'
|
||||
|
||||
// Types
|
||||
import {
|
||||
RemoteDataState,
|
||||
QueryView,
|
||||
GetState,
|
||||
View,
|
||||
ViewEntities,
|
||||
TimeMachineID,
|
||||
ResourceType,
|
||||
} from 'src/types'
|
||||
import {Dispatch} from 'redux'
|
||||
|
||||
export const getView = (dashboardID: string, cellID: string) => async (
|
||||
dispatch: Dispatch<Action>
|
||||
): Promise<void> => {
|
||||
dispatch(setView(cellID, RemoteDataState.Loading))
|
||||
try {
|
||||
const view = await getViewAJAX(dashboardID, cellID)
|
||||
|
||||
const normView = normalize<View, ViewEntities, string>(view, schemas.view)
|
||||
|
||||
dispatch(setView(cellID, RemoteDataState.Done, normView))
|
||||
} catch {
|
||||
dispatch(setView(cellID, RemoteDataState.Error))
|
||||
}
|
||||
}
|
||||
|
||||
export const updateView = (dashboardID: string, view: View) => async (
|
||||
dispatch: Dispatch<Action>
|
||||
): Promise<View> => {
|
||||
const viewID = view.cellID
|
||||
|
||||
dispatch(setView(viewID, RemoteDataState.Loading))
|
||||
|
||||
try {
|
||||
const newView = await updateViewAJAX(dashboardID, viewID, view)
|
||||
|
||||
const normView = normalize<View, ViewEntities, string>(
|
||||
newView,
|
||||
schemas.view
|
||||
)
|
||||
|
||||
dispatch(setView(viewID, RemoteDataState.Done, normView))
|
||||
|
||||
return newView
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
dispatch(setView(viewID, RemoteDataState.Error))
|
||||
}
|
||||
}
|
||||
|
||||
export const updateViewAndVariables = (
|
||||
dashboardID: string,
|
||||
view: View
|
||||
) => async (dispatch, getState: GetState) => {
|
||||
const cellID = view.cellID
|
||||
|
||||
try {
|
||||
const newView = await updateViewAJAX(dashboardID, cellID, view)
|
||||
|
||||
const views = getViewsForDashboard(getState(), dashboardID)
|
||||
|
||||
views.splice(views.findIndex(v => v.id === newView.id), 1, newView)
|
||||
|
||||
await dispatch(refreshDashboardVariableValues(dashboardID, views))
|
||||
|
||||
const normView = normalize<View, ViewEntities, string>(
|
||||
newView,
|
||||
schemas.view
|
||||
)
|
||||
|
||||
dispatch(setView(cellID, RemoteDataState.Done, normView))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
dispatch(notify(copy.cellUpdateFailed()))
|
||||
dispatch(setView(cellID, RemoteDataState.Error))
|
||||
}
|
||||
}
|
||||
|
||||
export const getViewForTimeMachine = (
|
||||
dashboardID: string,
|
||||
cellID: string,
|
||||
timeMachineID: TimeMachineID
|
||||
) => async (dispatch, getState: GetState): Promise<void> => {
|
||||
try {
|
||||
const state = getState()
|
||||
let view = getByID<View>(state, ResourceType.Views, cellID) as QueryView
|
||||
|
||||
const timeRange = getTimeRangeByDashboardID(state, dashboardID)
|
||||
|
||||
if (!view) {
|
||||
dispatch(setView(cellID, RemoteDataState.Loading))
|
||||
view = (await getViewAJAX(dashboardID, cellID)) as QueryView
|
||||
}
|
||||
|
||||
dispatch(setActiveTimeMachine(timeMachineID, {view, timeRange}))
|
||||
dispatch(executeQueries(dashboardID))
|
||||
} catch (error) {
|
||||
dispatch(notify(copy.getViewFailed(error.message)))
|
||||
dispatch(setView(cellID, RemoteDataState.Error))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Constants
|
||||
import {INFERNO, NINETEEN_EIGHTY_FOUR} from '@influxdata/giraffe'
|
||||
import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes'
|
||||
import {DEFAULT_CELL_NAME} from 'src/dashboards/constants/index'
|
||||
import {DEFAULT_CELL_NAME} from 'src/dashboards/constants'
|
||||
import {
|
||||
DEFAULT_GAUGE_COLORS,
|
||||
DEFAULT_THRESHOLDS_LIST_COLORS,
|
||||
|
|
@ -11,30 +11,32 @@ import {DEFAULT_CHECK_EVERY} from 'src/alerting/constants'
|
|||
|
||||
// Types
|
||||
import {
|
||||
ViewType,
|
||||
Base,
|
||||
XYViewProperties,
|
||||
HistogramViewProperties,
|
||||
HeatmapViewProperties,
|
||||
ScatterViewProperties,
|
||||
LinePlusSingleStatProperties,
|
||||
SingleStatViewProperties,
|
||||
MarkdownViewProperties,
|
||||
TableViewProperties,
|
||||
GaugeViewProperties,
|
||||
NewView,
|
||||
ViewProperties,
|
||||
DashboardQuery,
|
||||
BuilderConfig,
|
||||
Axis,
|
||||
Color,
|
||||
CheckViewProperties,
|
||||
Base,
|
||||
BuilderConfig,
|
||||
CheckType,
|
||||
CheckViewProperties,
|
||||
Color,
|
||||
DashboardQuery,
|
||||
GaugeViewProperties,
|
||||
HeatmapViewProperties,
|
||||
HistogramViewProperties,
|
||||
LinePlusSingleStatProperties,
|
||||
MarkdownViewProperties,
|
||||
NewView,
|
||||
RemoteDataState,
|
||||
ScatterViewProperties,
|
||||
SingleStatViewProperties,
|
||||
TableViewProperties,
|
||||
ViewProperties,
|
||||
ViewType,
|
||||
XYViewProperties,
|
||||
} from 'src/types'
|
||||
|
||||
function defaultView() {
|
||||
export const defaultView = (name: string = DEFAULT_CELL_NAME) => {
|
||||
return {
|
||||
name: DEFAULT_CELL_NAME,
|
||||
name,
|
||||
status: RemoteDataState.Done,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +58,7 @@ export function defaultBuilderConfig(): BuilderConfig {
|
|||
}
|
||||
}
|
||||
|
||||
function defaultLineViewProperties() {
|
||||
export function defaultLineViewProperties() {
|
||||
return {
|
||||
queries: [defaultViewQuery()],
|
||||
colors: DEFAULT_LINE_COLORS as Color[],
|
||||
|
|
@ -255,7 +257,7 @@ const NEW_VIEW_CREATORS = {
|
|||
},
|
||||
}),
|
||||
threshold: (): NewView<CheckViewProperties> => ({
|
||||
name: 'check',
|
||||
...defaultView('check'),
|
||||
properties: {
|
||||
type: 'check',
|
||||
shape: 'chronograf-v2',
|
||||
|
|
@ -277,7 +279,7 @@ const NEW_VIEW_CREATORS = {
|
|||
},
|
||||
}),
|
||||
deadman: (): NewView<CheckViewProperties> => ({
|
||||
name: 'check',
|
||||
...defaultView('check'),
|
||||
properties: {
|
||||
type: 'check',
|
||||
shape: 'chronograf-v2',
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// Libraries
|
||||
import {produce} from 'immer'
|
||||
|
||||
// Types
|
||||
import {
|
||||
SET_VIEW,
|
||||
SET_VIEWS,
|
||||
RESET_VIEWS,
|
||||
Action,
|
||||
} from 'src/views/actions/creators'
|
||||
import {SET_DASHBOARD} from 'src/dashboards/actions/creators'
|
||||
import {View, RemoteDataState, ResourceState, ResourceType} from 'src/types'
|
||||
|
||||
// Helpers
|
||||
import {setResource, setResourceAtID} from 'src/resources/reducers/helpers'
|
||||
|
||||
export type ViewsState = ResourceState['views']
|
||||
|
||||
const initialState = (): ViewsState => ({
|
||||
status: RemoteDataState.NotStarted,
|
||||
byID: {},
|
||||
allIDs: [],
|
||||
})
|
||||
|
||||
const viewsReducer = (
|
||||
state: ViewsState = initialState(),
|
||||
action: Action
|
||||
): ViewsState =>
|
||||
produce(state, draftState => {
|
||||
switch (action.type) {
|
||||
case SET_DASHBOARD: {
|
||||
setResource<View>(draftState, action, ResourceType.Views)
|
||||
}
|
||||
|
||||
case SET_VIEWS: {
|
||||
setResource<View>(draftState, action, ResourceType.Views)
|
||||
|
||||
return
|
||||
}
|
||||
case SET_VIEW: {
|
||||
setResourceAtID<View>(draftState, action, ResourceType.Views)
|
||||
|
||||
return
|
||||
}
|
||||
case RESET_VIEWS: {
|
||||
return initialState()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default viewsReducer
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// Types
|
||||
import {AppState, View, ResourceType, Dashboard} from 'src/types'
|
||||
|
||||
// Selectors
|
||||
import {getByID} from 'src/resources/selectors'
|
||||
|
||||
export const getViewsForDashboard = (
|
||||
state: AppState,
|
||||
dashboardID: string
|
||||
): View[] => {
|
||||
const dashboard = getByID<Dashboard>(
|
||||
state,
|
||||
ResourceType.Dashboards,
|
||||
dashboardID
|
||||
)
|
||||
|
||||
const cellIDs = new Set(dashboard.cells.map(cellID => cellID))
|
||||
|
||||
const views = Object.values(state.resources.views.byID).filter(
|
||||
view => view && cellIDs.has(view.cellID)
|
||||
)
|
||||
|
||||
return views
|
||||
}
|
||||
Loading…
Reference in New Issue