refactor(dashboard): normalization (#16535)
* refactor: change client api impoorts * refactor(dashAPI): remove unneccessary helpers * refactor: remove unneccessary helpers * chore: move action creators to separate file * refactor(dashboard): action creators use const attribute * refactor(dashoards): normalization * chore: cleanup names of thunks * chore: sort action creator types * fix: saving to a dashboard cell * chore: move dashboard thunks to thunks file * fix: dash index table imports * fix: declare class properties * chore: skip monaco testpull/16553/head
parent
f4c529650a
commit
6c7a61e838
|
@ -360,7 +360,8 @@ describe('DataExplorer', () => {
|
|||
cy.getByTestID('switch-to-script-editor').click()
|
||||
})
|
||||
|
||||
it('enables the submit button when a query is typed', () => {
|
||||
// TODO: fix flakeyness of this test
|
||||
it.skip('enables the submit button when a query is typed', () => {
|
||||
cy.getByTestID('time-machine-submit-button').should('be.disabled')
|
||||
|
||||
cy.getByTestID('flux-editor').within(() => {
|
||||
|
|
|
@ -260,21 +260,22 @@ export const getAssetLimits = () => async (dispatch, getState: GetState) => {
|
|||
|
||||
export const checkDashboardLimits = () => (dispatch, getState: GetState) => {
|
||||
try {
|
||||
const state = getState()
|
||||
const {
|
||||
dashboards: {list},
|
||||
cloud: {limits},
|
||||
} = getState()
|
||||
resources,
|
||||
} = state
|
||||
|
||||
const dashboardsMax = extractDashboardMax(limits)
|
||||
const dashboardsCount = list.length
|
||||
const dashboardsCount = resources.dashboards.allIDs.length
|
||||
|
||||
if (dashboardsCount >= dashboardsMax) {
|
||||
dispatch(setDashboardLimitStatus(LimitStatus.EXCEEDED))
|
||||
} else {
|
||||
dispatch(setDashboardLimitStatus(LimitStatus.OK))
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -367,9 +368,9 @@ export const checkEndpointsLimits = () => (dispatch, getState: GetState) => {
|
|||
} = getState()
|
||||
|
||||
const endpointsMax = extractEndpointsMax(limits)
|
||||
const endpoinstCount = endpointsList.length
|
||||
const endpointsCount = endpointsList.length
|
||||
|
||||
if (endpoinstCount >= endpointsMax) {
|
||||
if (endpointsCount >= endpointsMax) {
|
||||
dispatch(setEndpointsLimitStatus(LimitStatus.EXCEEDED))
|
||||
} else {
|
||||
dispatch(setEndpointsLimitStatus(LimitStatus.OK))
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
// Types
|
||||
import {Dashboard, Label, RemoteDataState, DashboardEntities} from 'src/types'
|
||||
import {NormalizedSchema} from 'normalizr'
|
||||
|
||||
export const ADD_DASHBOARD_LABEL = 'ADD_DASHBOARD_LABEL'
|
||||
export const DELETE_DASHBOARD_FAILED = 'DELETE_DASHBOARD_FAILED'
|
||||
export const EDIT_DASHBOARD = 'EDIT_DASHBOARD'
|
||||
export const REMOVE_CELL = 'REMOVE_CELL'
|
||||
export const REMOVE_DASHBOARD = 'REMOVE_DASHBOARD'
|
||||
export const REMOVE_DASHBOARD_LABEL = 'REMOVE_DASHBOARD_LABEL'
|
||||
export const SET_DASHBOARD = 'SET_DASHBOARD'
|
||||
export const SET_DASHBOARDS = 'SET_DASHBOARDS'
|
||||
|
||||
export type Action =
|
||||
| ReturnType<typeof addDashboardLabel>
|
||||
| ReturnType<typeof deleteDashboardFailed>
|
||||
| ReturnType<typeof editDashboard>
|
||||
| ReturnType<typeof removeCell>
|
||||
| ReturnType<typeof removeDashboard>
|
||||
| ReturnType<typeof removeDashboardLabel>
|
||||
| ReturnType<typeof setDashboard>
|
||||
| ReturnType<typeof setDashboards>
|
||||
|
||||
// R is the type of the value of the "result" key in normalizr's normalization
|
||||
type DashboardSchema<R extends string | string[]> = NormalizedSchema<
|
||||
DashboardEntities,
|
||||
R
|
||||
>
|
||||
|
||||
// Action Creators
|
||||
export const editDashboard = (schema: DashboardSchema<string>) =>
|
||||
({
|
||||
type: EDIT_DASHBOARD,
|
||||
schema,
|
||||
} as const)
|
||||
|
||||
export const setDashboards = (
|
||||
status: RemoteDataState,
|
||||
schema?: DashboardSchema<string[]>
|
||||
) =>
|
||||
({
|
||||
type: SET_DASHBOARDS,
|
||||
status,
|
||||
schema,
|
||||
} as const)
|
||||
|
||||
export const setDashboard = (schema: DashboardSchema<string>) =>
|
||||
({
|
||||
type: SET_DASHBOARD,
|
||||
schema,
|
||||
} as const)
|
||||
|
||||
export const removeDashboard = (id: string) =>
|
||||
({
|
||||
type: REMOVE_DASHBOARD,
|
||||
id,
|
||||
} as const)
|
||||
|
||||
export const deleteDashboardFailed = (dashboard: Dashboard) =>
|
||||
({
|
||||
type: DELETE_DASHBOARD_FAILED,
|
||||
payload: {dashboard},
|
||||
} as const)
|
||||
|
||||
export const removeCell = (dashboardID: string, cellID: string) =>
|
||||
({
|
||||
type: REMOVE_CELL,
|
||||
dashboardID,
|
||||
cellID,
|
||||
} as const)
|
||||
|
||||
export const addDashboardLabel = (dashboardID: string, label: Label) =>
|
||||
({
|
||||
type: ADD_DASHBOARD_LABEL,
|
||||
dashboardID,
|
||||
label,
|
||||
} as const)
|
||||
|
||||
export const removeDashboardLabel = (dashboardID: string, labelID: string) =>
|
||||
({
|
||||
type: REMOVE_DASHBOARD_LABEL,
|
||||
dashboardID,
|
||||
labelID,
|
||||
} as const)
|
|
@ -2,7 +2,7 @@
|
|||
import {get, isUndefined} from 'lodash'
|
||||
|
||||
// Actions
|
||||
import {createCellWithView} from 'src/dashboards/actions'
|
||||
import {createCellWithView} from 'src/dashboards/actions/thunks'
|
||||
import {updateView} from 'src/dashboards/actions/views'
|
||||
|
||||
// Utils
|
||||
|
@ -10,9 +10,16 @@ import {createView} from 'src/shared/utils/view'
|
|||
import {getView} from 'src/dashboards/selectors'
|
||||
|
||||
// Types
|
||||
import {GetState, MarkdownViewProperties, NoteEditorMode} from 'src/types'
|
||||
import {
|
||||
GetState,
|
||||
MarkdownViewProperties,
|
||||
NoteEditorMode,
|
||||
ResourceType,
|
||||
Dashboard,
|
||||
} from 'src/types'
|
||||
import {NoteEditorState} from 'src/dashboards/reducers/notes'
|
||||
import {Dispatch} from 'react'
|
||||
import {getByID} from 'src/resources/selectors'
|
||||
|
||||
export type Action =
|
||||
| CloseNoteEditorAction
|
||||
|
@ -64,7 +71,11 @@ export const createNoteCell = (dashboardID: string) => (
|
|||
dispatch: Dispatch<Action | ReturnType<typeof createCellWithView>>,
|
||||
getState: GetState
|
||||
) => {
|
||||
const dashboard = getState().dashboards.list.find(d => d.id === dashboardID)
|
||||
const dashboard = getByID<Dashboard>(
|
||||
getState(),
|
||||
ResourceType.Dashboards,
|
||||
dashboardID
|
||||
)
|
||||
|
||||
if (!dashboard) {
|
||||
throw new Error(`could not find dashboard with id "${dashboardID}"`)
|
||||
|
|
|
@ -1,31 +1,15 @@
|
|||
// Libraries
|
||||
import {Dispatch} from 'redux'
|
||||
import {normalize} from 'normalizr'
|
||||
import {Dispatch} from 'react'
|
||||
import {push} from 'react-router-redux'
|
||||
|
||||
// APIs
|
||||
import {
|
||||
createDashboard as createDashboardAJAX,
|
||||
getDashboard as getDashboardAJAX,
|
||||
getDashboards as getDashboardsAJAX,
|
||||
deleteDashboard as deleteDashboardAJAX,
|
||||
updateDashboard as updateDashboardAJAX,
|
||||
updateCells as updateCellsAJAX,
|
||||
addCell as addCellAJAX,
|
||||
deleteCell as deleteCellAJAX,
|
||||
getView as getViewAJAX,
|
||||
updateView as updateViewAJAX,
|
||||
} from 'src/dashboards/apis'
|
||||
import {
|
||||
getVariables as apiGetVariables,
|
||||
getDashboard as apiGetDashboard,
|
||||
postDashboard as apiPostDashboard,
|
||||
postDashboardsCell as apiPostDashboardsCell,
|
||||
postDashboardsLabel as apiPostDashboardsLabel,
|
||||
deleteDashboardsLabel as apiDeleteDashboardsLabel,
|
||||
patchDashboardsCellsView as apiPatchDashboardsCellsView,
|
||||
getDashboardsCellsView as apiGetDashboardsCellsView,
|
||||
} from 'src/client'
|
||||
import {createDashboardFromTemplate as createDashboardFromTemplateAJAX} from 'src/templates/api'
|
||||
import * as dashAPI from 'src/dashboards/apis'
|
||||
import * as api from 'src/client'
|
||||
import * as tempAPI from 'src/templates/api'
|
||||
|
||||
// Schemas
|
||||
import * as schemas from 'src/schemas'
|
||||
|
||||
// Actions
|
||||
import {
|
||||
|
@ -35,13 +19,13 @@ import {
|
|||
import {
|
||||
deleteTimeRange,
|
||||
updateTimeRangeFromQueryParams,
|
||||
DeleteTimeRangeAction,
|
||||
} from 'src/dashboards/actions/ranges'
|
||||
import {setView, SetViewAction, setViews} from 'src/dashboards/actions/views'
|
||||
import {setView, setViews} from 'src/dashboards/actions/views'
|
||||
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 * as creators from 'src/dashboards/actions/creators'
|
||||
|
||||
// Utils
|
||||
import {addVariableDefaults} from 'src/variables/actions/thunks'
|
||||
|
@ -62,6 +46,7 @@ import {incrementCloneName} from 'src/utils/naming'
|
|||
import {isLimitError} from 'src/cloud/utils/limits'
|
||||
import {getOrg} from 'src/organizations/selectors'
|
||||
import {addLabelDefaults} from 'src/labels/utils'
|
||||
import {getAll, getByID} from 'src/resources/selectors'
|
||||
|
||||
// Constants
|
||||
import * as copy from 'src/shared/copy/notifications'
|
||||
|
@ -78,196 +63,13 @@ import {
|
|||
Label,
|
||||
RemoteDataState,
|
||||
NewCell,
|
||||
DashboardEntities,
|
||||
ResourceType,
|
||||
} from 'src/types'
|
||||
import {
|
||||
Dashboard as IDashboard,
|
||||
Cell as ICell,
|
||||
DashboardWithViewProperties,
|
||||
} from 'src/client'
|
||||
|
||||
export const addDashboardIDToCells = (
|
||||
cells: ICell[],
|
||||
dashboardID: string
|
||||
): Cell[] => {
|
||||
return cells.map(c => {
|
||||
return {...c, dashboardID}
|
||||
})
|
||||
}
|
||||
|
||||
export const addDashboardDefaults = (
|
||||
dashboard: IDashboard | DashboardWithViewProperties
|
||||
): Dashboard => {
|
||||
return {
|
||||
...dashboard,
|
||||
cells: addDashboardIDToCells(dashboard.cells, dashboard.id) || [],
|
||||
id: dashboard.id || '',
|
||||
labels: (dashboard.labels || []).map(addLabelDefaults),
|
||||
name: dashboard.name || '',
|
||||
orgID: dashboard.orgID || '',
|
||||
}
|
||||
}
|
||||
|
||||
export enum ActionTypes {
|
||||
SetDashboards = 'SET_DASHBOARDS',
|
||||
SetDashboard = 'SET_DASHBOARD',
|
||||
RemoveDashboard = 'REMOVE_DASHBOARD',
|
||||
DeleteDashboardFailed = 'DELETE_DASHBOARD_FAILED',
|
||||
EditDashboard = 'EDIT_DASHBOARD',
|
||||
RemoveCell = 'REMOVE_CELL',
|
||||
AddDashboardLabel = 'ADD_DASHBOARD_LABEL',
|
||||
RemoveDashboardLabel = 'REMOVE_DASHBOARD_LABEL',
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| SetDashboardsAction
|
||||
| RemoveDashboardAction
|
||||
| SetDashboardAction
|
||||
| EditDashboardAction
|
||||
| RemoveCellAction
|
||||
| SetViewAction
|
||||
| DeleteTimeRangeAction
|
||||
| DeleteDashboardFailedAction
|
||||
| AddDashboardLabelAction
|
||||
| RemoveDashboardLabelAction
|
||||
|
||||
interface RemoveCellAction {
|
||||
type: ActionTypes.RemoveCell
|
||||
payload: {
|
||||
dashboard: Dashboard
|
||||
cell: Cell
|
||||
}
|
||||
}
|
||||
|
||||
interface EditDashboardAction {
|
||||
type: ActionTypes.EditDashboard
|
||||
payload: {
|
||||
dashboard: Dashboard
|
||||
}
|
||||
}
|
||||
|
||||
interface SetDashboardsAction {
|
||||
type: ActionTypes.SetDashboards
|
||||
payload: {
|
||||
status: RemoteDataState
|
||||
list: Dashboard[]
|
||||
}
|
||||
}
|
||||
|
||||
interface RemoveDashboardAction {
|
||||
type: ActionTypes.RemoveDashboard
|
||||
payload: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
interface DeleteDashboardFailedAction {
|
||||
type: ActionTypes.DeleteDashboardFailed
|
||||
payload: {
|
||||
dashboard: Dashboard
|
||||
}
|
||||
}
|
||||
|
||||
interface SetDashboardAction {
|
||||
type: ActionTypes.SetDashboard
|
||||
payload: {
|
||||
dashboard: Dashboard
|
||||
}
|
||||
}
|
||||
|
||||
interface AddDashboardLabelAction {
|
||||
type: ActionTypes.AddDashboardLabel
|
||||
payload: {
|
||||
dashboardID: string
|
||||
label: Label
|
||||
}
|
||||
}
|
||||
|
||||
interface RemoveDashboardLabelAction {
|
||||
type: ActionTypes.RemoveDashboardLabel
|
||||
payload: {
|
||||
dashboardID: string
|
||||
label: Label
|
||||
}
|
||||
}
|
||||
|
||||
// Action Creators
|
||||
|
||||
export const editDashboard = (dashboard: Dashboard): EditDashboardAction => ({
|
||||
type: ActionTypes.EditDashboard,
|
||||
payload: {dashboard},
|
||||
})
|
||||
|
||||
export const setDashboards = (
|
||||
status: RemoteDataState,
|
||||
list?: Dashboard[]
|
||||
): SetDashboardsAction => {
|
||||
if (list) {
|
||||
list = list.map(obj => {
|
||||
if (obj.name === undefined) {
|
||||
obj.name = ''
|
||||
}
|
||||
if (obj.meta === undefined) {
|
||||
obj.meta = {}
|
||||
}
|
||||
if (obj.meta.updatedAt === undefined) {
|
||||
obj.meta.updatedAt = new Date().toDateString()
|
||||
}
|
||||
return obj
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
type: ActionTypes.SetDashboards,
|
||||
payload: {
|
||||
status,
|
||||
list,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const setDashboard = (dashboard: Dashboard): SetDashboardAction => ({
|
||||
type: ActionTypes.SetDashboard,
|
||||
payload: {dashboard},
|
||||
})
|
||||
|
||||
export const removeDashboard = (id: string): RemoveDashboardAction => ({
|
||||
type: ActionTypes.RemoveDashboard,
|
||||
payload: {id},
|
||||
})
|
||||
|
||||
export const deleteDashboardFailed = (
|
||||
dashboard: Dashboard
|
||||
): DeleteDashboardFailedAction => ({
|
||||
type: ActionTypes.DeleteDashboardFailed,
|
||||
payload: {dashboard},
|
||||
})
|
||||
|
||||
export const removeCell = (
|
||||
dashboard: Dashboard,
|
||||
cell: Cell
|
||||
): RemoveCellAction => ({
|
||||
type: ActionTypes.RemoveCell,
|
||||
payload: {dashboard, cell},
|
||||
})
|
||||
|
||||
export const addDashboardLabel = (
|
||||
dashboardID: string,
|
||||
label: Label
|
||||
): AddDashboardLabelAction => ({
|
||||
type: ActionTypes.AddDashboardLabel,
|
||||
payload: {dashboardID, label},
|
||||
})
|
||||
|
||||
export const removeDashboardLabel = (
|
||||
dashboardID: string,
|
||||
label: Label
|
||||
): RemoveDashboardLabelAction => ({
|
||||
type: ActionTypes.RemoveDashboardLabel,
|
||||
payload: {dashboardID, label},
|
||||
})
|
||||
type Action = creators.Action
|
||||
|
||||
// Thunks
|
||||
|
||||
export const createDashboard = () => async (
|
||||
dispatch,
|
||||
getState: GetState
|
||||
|
@ -281,8 +83,13 @@ export const createDashboard = () => async (
|
|||
orgID: org.id,
|
||||
}
|
||||
|
||||
const data = await createDashboardAJAX(newDashboard)
|
||||
dispatch(push(`/orgs/${org.id}/dashboards/${data.id}`))
|
||||
const resp = await api.postDashboard({data: newDashboard})
|
||||
|
||||
if (resp.status !== 201) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
dispatch(push(`/orgs/${org.id}/dashboards/${resp.data.id}`))
|
||||
dispatch(checkDashboardLimits())
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
@ -295,67 +102,32 @@ export const createDashboard = () => async (
|
|||
}
|
||||
}
|
||||
|
||||
export const cloneUtilFunc = async (dash: Dashboard, id: string) => {
|
||||
const cells = dash.cells
|
||||
const pendingViews = cells.map(cell =>
|
||||
apiGetDashboardsCellsView({
|
||||
dashboardID: dash.id,
|
||||
cellID: cell.id,
|
||||
}).then(res => {
|
||||
return {
|
||||
...res,
|
||||
cellID: cell.id,
|
||||
}
|
||||
})
|
||||
)
|
||||
const views = await Promise.all(pendingViews)
|
||||
|
||||
if (views.length > 0 && views.some(v => v.status !== 200)) {
|
||||
throw new Error('An error occurred cloning the dashboard')
|
||||
}
|
||||
|
||||
return views.map(async v => {
|
||||
const view = v.data as View
|
||||
const cell = cells.find(c => c.id === view.id)
|
||||
|
||||
if (cell && id) {
|
||||
const newCell = await apiPostDashboardsCell({
|
||||
dashboardID: id,
|
||||
data: cell,
|
||||
})
|
||||
if (newCell.status !== 201) {
|
||||
throw new Error('An error occurred cloning the dashboard')
|
||||
}
|
||||
return apiPatchDashboardsCellsView({
|
||||
dashboardID: id,
|
||||
cellID: newCell.data.id,
|
||||
data: view,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const cloneDashboard = (dashboard: Dashboard) => async (
|
||||
dispatch,
|
||||
getState: GetState
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const {dashboards} = getState()
|
||||
|
||||
const org = getOrg(getState())
|
||||
const allDashboardNames = dashboards.list.map(d => d.name)
|
||||
const state = getState()
|
||||
|
||||
const org = getOrg(state)
|
||||
const dashboards = getAll<Dashboard>(state, ResourceType.Dashboards)
|
||||
const allDashboardNames = dashboards.map(d => d.name)
|
||||
const clonedName = incrementCloneName(allDashboardNames, dashboard.name)
|
||||
|
||||
const getResp = await apiGetDashboard({dashboardID: dashboard.id})
|
||||
const getResp = await api.getDashboard({dashboardID: dashboard.id})
|
||||
|
||||
if (getResp.status !== 200) {
|
||||
throw new Error(getResp.data.message)
|
||||
}
|
||||
|
||||
const dash = addDashboardDefaults(getResp.data)
|
||||
const {entities, result} = normalize<Dashboard, DashboardEntities, string>(
|
||||
getResp.data,
|
||||
schemas.dashboard
|
||||
)
|
||||
|
||||
const postResp = await apiPostDashboard({
|
||||
const dash: Dashboard = entities[result]
|
||||
|
||||
const postResp = await api.postDashboard({
|
||||
data: {
|
||||
orgID: org.id,
|
||||
name: clonedName,
|
||||
|
@ -368,7 +140,7 @@ export const cloneDashboard = (dashboard: Dashboard) => async (
|
|||
}
|
||||
|
||||
const pendingLabels = dash.labels.map(l =>
|
||||
apiPostDashboardsLabel({
|
||||
api.postDashboardsLabel({
|
||||
dashboardID: postResp.data.id,
|
||||
data: {labelID: l.id},
|
||||
})
|
||||
|
@ -380,7 +152,7 @@ export const cloneDashboard = (dashboard: Dashboard) => async (
|
|||
throw new Error('An error occurred cloning the labels for this dashboard')
|
||||
}
|
||||
|
||||
const clonedViews = await cloneUtilFunc(dash, postResp.data.id)
|
||||
const clonedViews = await dashAPI.cloneUtilFunc(dash, postResp.data.id)
|
||||
|
||||
const newViews = await Promise.all(clonedViews)
|
||||
|
||||
|
@ -400,20 +172,29 @@ export const cloneDashboard = (dashboard: Dashboard) => async (
|
|||
}
|
||||
}
|
||||
|
||||
export const getDashboardsAsync = () => async (
|
||||
export const getDashboards = () => async (
|
||||
dispatch: Dispatch<Action>,
|
||||
getState: GetState
|
||||
): Promise<Dashboard[]> => {
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const org = getOrg(getState())
|
||||
const {setDashboards} = creators
|
||||
|
||||
dispatch(setDashboards(RemoteDataState.Loading))
|
||||
const dashboards = await getDashboardsAJAX(org.id)
|
||||
dispatch(setDashboards(RemoteDataState.Done, dashboards))
|
||||
const resp = await api.getDashboards({query: {orgID: org.id}})
|
||||
|
||||
return dashboards
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
const dashboards = normalize<Dashboard, DashboardEntities, string[]>(
|
||||
resp.data.dashboards,
|
||||
schemas.arrayOfDashboards
|
||||
)
|
||||
|
||||
dispatch(setDashboards(RemoteDataState.Done, dashboards))
|
||||
} catch (error) {
|
||||
dispatch(setDashboards(RemoteDataState.Error))
|
||||
dispatch(creators.setDashboards(RemoteDataState.Error))
|
||||
console.error(error)
|
||||
throw error
|
||||
}
|
||||
|
@ -425,11 +206,20 @@ export const createDashboardFromTemplate = (
|
|||
try {
|
||||
const org = getOrg(getState())
|
||||
|
||||
await createDashboardFromTemplateAJAX(template, org.id)
|
||||
await tempAPI.createDashboardFromTemplate(template, org.id)
|
||||
|
||||
const dashboards = await getDashboardsAJAX(org.id)
|
||||
const resp = await api.getDashboards({query: {orgID: org.id}})
|
||||
|
||||
dispatch(setDashboards(RemoteDataState.Done, dashboards))
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
const dashboards = normalize<Dashboard, DashboardEntities, string[]>(
|
||||
resp.data.dashboards,
|
||||
schemas.arrayOfDashboards
|
||||
)
|
||||
|
||||
dispatch(creators.setDashboards(RemoteDataState.Done, dashboards))
|
||||
dispatch(notify(copy.importDashboardSucceeded()))
|
||||
dispatch(checkDashboardLimits())
|
||||
} catch (error) {
|
||||
|
@ -441,14 +231,19 @@ export const createDashboardFromTemplate = (
|
|||
}
|
||||
}
|
||||
|
||||
export const deleteDashboardAsync = (dashboard: Dashboard) => async (
|
||||
export const deleteDashboard = (dashboard: Dashboard) => async (
|
||||
dispatch
|
||||
): Promise<void> => {
|
||||
dispatch(removeDashboard(dashboard.id))
|
||||
dispatch(creators.removeDashboard(dashboard.id))
|
||||
dispatch(deleteTimeRange(dashboard.id))
|
||||
|
||||
try {
|
||||
await deleteDashboardAJAX(dashboard)
|
||||
const resp = await api.deleteDashboard({dashboardID: dashboard.id})
|
||||
|
||||
if (resp.data !== 204) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
dispatch(notify(copy.dashboardDeleted(dashboard.name)))
|
||||
dispatch(checkDashboardLimits())
|
||||
} catch (error) {
|
||||
|
@ -456,7 +251,7 @@ export const deleteDashboardAsync = (dashboard: Dashboard) => async (
|
|||
notify(copy.dashboardDeleteFailed(dashboard.name, error.data.message))
|
||||
)
|
||||
|
||||
dispatch(deleteDashboardFailed(dashboard))
|
||||
dispatch(creators.deleteDashboardFailed(dashboard))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -470,30 +265,40 @@ export const refreshDashboardVariableValues = (
|
|||
return dispatch(refreshVariableValues(dashboardID, variablesInUse))
|
||||
}
|
||||
|
||||
export const getDashboardAsync = (dashboardID: string) => async (
|
||||
export const getDashboard = (dashboardID: string) => async (
|
||||
dispatch,
|
||||
getState: GetState
|
||||
): Promise<void> => {
|
||||
try {
|
||||
// Fetch the dashboard and all variables a user has access to
|
||||
const [dashboard] = await Promise.all([
|
||||
getDashboardAJAX(dashboardID),
|
||||
const [resp] = await Promise.all([
|
||||
api.getDashboard({dashboardID}),
|
||||
dispatch(getVariables()),
|
||||
])
|
||||
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
const normDash = normalize<Dashboard, DashboardEntities, string>(
|
||||
resp.data,
|
||||
schemas.dashboard
|
||||
)
|
||||
const {cells, id}: Dashboard = normDash.entities.dashboards[normDash.result]
|
||||
|
||||
// Fetch all the views in use on the dashboard
|
||||
const views = await Promise.all(
|
||||
dashboard.cells.map(cell => getViewAJAX(dashboard.id, cell.id))
|
||||
cells.map(cell => dashAPI.getView(id, cell.id))
|
||||
)
|
||||
|
||||
dispatch(setViews(RemoteDataState.Done, views))
|
||||
|
||||
// Ensure the values for the variables in use on the dashboard are populated
|
||||
await dispatch(refreshDashboardVariableValues(dashboard.id, views))
|
||||
await dispatch(refreshDashboardVariableValues(id, views))
|
||||
|
||||
// Now that all the necessary state has been loaded, set the dashboard
|
||||
dispatch(setDashboard(dashboard))
|
||||
dispatch(updateTimeRangeFromQueryParams(dashboardID))
|
||||
dispatch(creators.setDashboard(normDash))
|
||||
dispatch(updateTimeRangeFromQueryParams(id))
|
||||
} catch (error) {
|
||||
const org = getOrg(getState())
|
||||
dispatch(push(`/orgs/${org.id}/dashboards`))
|
||||
|
@ -502,12 +307,25 @@ export const getDashboardAsync = (dashboardID: string) => async (
|
|||
}
|
||||
}
|
||||
|
||||
export const updateDashboardAsync = (dashboard: Dashboard) => async (
|
||||
dispatch: Dispatch<Action | PublishNotificationAction>
|
||||
export const updateDashboard = (dashboard: Dashboard) => async (
|
||||
dispatch: Dispatch<creators.Action | PublishNotificationAction>
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const updatedDashboard = await updateDashboardAJAX(dashboard)
|
||||
dispatch(editDashboard(updatedDashboard))
|
||||
const resp = await api.patchDashboard({
|
||||
dashboardID: dashboard.id,
|
||||
data: dashboard,
|
||||
})
|
||||
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
const updatedDashboard = normalize<Dashboard, DashboardEntities, string>(
|
||||
resp.data,
|
||||
schemas.dashboard
|
||||
)
|
||||
|
||||
dispatch(creators.editDashboard(updatedDashboard))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
dispatch(notify(copy.dashboardUpdateFailed()))
|
||||
|
@ -521,26 +339,60 @@ export const createCellWithView = (
|
|||
) => async (dispatch, getState: GetState): Promise<void> => {
|
||||
try {
|
||||
const state = getState()
|
||||
let dashboard = state.dashboards.list.find(d => d.id === dashboardID)
|
||||
let dashboard = getByID<Dashboard>(
|
||||
state,
|
||||
ResourceType.Dashboards,
|
||||
dashboardID
|
||||
)
|
||||
|
||||
if (!dashboard) {
|
||||
dashboard = await getDashboardAJAX(dashboardID)
|
||||
const resp = await api.getDashboard({dashboardID})
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
const {entities, result} = normalize<
|
||||
Dashboard,
|
||||
DashboardEntities,
|
||||
string
|
||||
>(resp.data, schemas.dashboard)
|
||||
|
||||
dashboard = entities.dashboards[result]
|
||||
}
|
||||
|
||||
const cell: NewCell = getNewDashboardCell(dashboard, clonedCell)
|
||||
|
||||
// Create the cell
|
||||
const createdCell = await addCellAJAX(dashboardID, cell)
|
||||
const cellResp = await api.postDashboardsCell({dashboardID, data: cell})
|
||||
|
||||
if (cellResp.status !== 201) {
|
||||
throw new Error(cellResp.data.message)
|
||||
}
|
||||
|
||||
const createdCell = cellResp.data
|
||||
|
||||
// Create the view and associate it with the cell
|
||||
const newView = await updateViewAJAX(dashboardID, createdCell.id, view)
|
||||
const newView = await dashAPI.updateView(dashboardID, createdCell.id, view)
|
||||
|
||||
// Update the dashboard with the new cell
|
||||
let updatedDashboard: Dashboard = {
|
||||
...dashboard,
|
||||
cells: [...dashboard.cells, createdCell],
|
||||
cells: [...dashboard.cells, {...cellResp.data, dashboardID}],
|
||||
}
|
||||
|
||||
updatedDashboard = await updateDashboardAJAX(dashboard)
|
||||
const resp = await api.patchDashboard({dashboardID, data: updatedDashboard})
|
||||
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
const normDash = normalize<Dashboard, DashboardEntities, string>(
|
||||
resp.data,
|
||||
schemas.dashboard
|
||||
)
|
||||
const {entities, result} = normDash
|
||||
|
||||
updatedDashboard = entities[result]
|
||||
|
||||
// Refresh variables in use on dashboard
|
||||
const views = [...getViewsForDashboard(state, dashboard.id), newView]
|
||||
|
@ -548,7 +400,7 @@ export const createCellWithView = (
|
|||
await dispatch(refreshDashboardVariableValues(dashboard.id, views))
|
||||
|
||||
dispatch(setView(createdCell.id, newView, RemoteDataState.Done))
|
||||
dispatch(editDashboard(updatedDashboard))
|
||||
dispatch(creators.editDashboard(normDash))
|
||||
} catch {
|
||||
notify(copy.cellAddFailed())
|
||||
}
|
||||
|
@ -561,7 +413,7 @@ export const updateView = (dashboardID: string, view: View) => async (
|
|||
const cellID = view.cellID
|
||||
|
||||
try {
|
||||
const newView = await updateViewAJAX(dashboardID, cellID, view)
|
||||
const newView = await dashAPI.updateView(dashboardID, cellID, view)
|
||||
|
||||
const views = getViewsForDashboard(getState(), dashboardID)
|
||||
|
||||
|
@ -577,23 +429,36 @@ export const updateView = (dashboardID: string, view: View) => async (
|
|||
}
|
||||
}
|
||||
|
||||
export const updateCellsAsync = (dashboard: Dashboard, cells: Cell[]) => async (
|
||||
export const updateCells = (dashboard: Dashboard, cells: Cell[]) => async (
|
||||
dispatch: Dispatch<Action>
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const updatedCells = await updateCellsAJAX(dashboard.id, cells)
|
||||
const updatedDashboard = {
|
||||
...dashboard,
|
||||
cells: updatedCells,
|
||||
const resp = await api.putDashboardsCells({
|
||||
dashboardID: dashboard.id,
|
||||
data: cells,
|
||||
})
|
||||
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
dispatch(setDashboard(updatedDashboard))
|
||||
const updatedDashboard = {
|
||||
...dashboard,
|
||||
cells: resp.data.cells,
|
||||
}
|
||||
|
||||
const normDash = normalize<Dashboard, DashboardEntities, string>(
|
||||
updatedDashboard,
|
||||
schemas.dashboard
|
||||
)
|
||||
|
||||
dispatch(creators.setDashboard(normDash))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteCellAsync = (dashboard: Dashboard, cell: Cell) => async (
|
||||
export const deleteCell = (dashboard: Dashboard, cell: Cell) => async (
|
||||
dispatch,
|
||||
getState: GetState
|
||||
): Promise<void> => {
|
||||
|
@ -603,18 +468,18 @@ export const deleteCellAsync = (dashboard: Dashboard, cell: Cell) => async (
|
|||
)
|
||||
|
||||
await Promise.all([
|
||||
deleteCellAJAX(dashboard.id, cell),
|
||||
api.deleteDashboardsCell({dashboardID: dashboard.id, cellID: cell.id}),
|
||||
dispatch(refreshDashboardVariableValues(dashboard.id, views)),
|
||||
])
|
||||
|
||||
dispatch(removeCell(dashboard, cell))
|
||||
dispatch(creators.removeCell(dashboard.id, cell.id))
|
||||
dispatch(notify(copy.cellDeleted()))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const copyDashboardCellAsync = (dashboard: Dashboard, cell: Cell) => (
|
||||
export const copyDashboardCell = (dashboard: Dashboard, cell: Cell) => (
|
||||
dispatch: Dispatch<Action | PublishNotificationAction>
|
||||
) => {
|
||||
try {
|
||||
|
@ -624,19 +489,23 @@ export const copyDashboardCellAsync = (dashboard: Dashboard, cell: Cell) => (
|
|||
cells: [...dashboard.cells, clonedCell],
|
||||
}
|
||||
|
||||
dispatch(setDashboard(updatedDashboard))
|
||||
const normDash = normalize<Dashboard, DashboardEntities, string>(
|
||||
updatedDashboard,
|
||||
schemas.dashboard
|
||||
)
|
||||
|
||||
dispatch(creators.setDashboard(normDash))
|
||||
dispatch(notify(copy.cellAdded()))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const addDashboardLabelAsync = (
|
||||
dashboardID: string,
|
||||
label: Label
|
||||
) => async (dispatch: Dispatch<Action | PublishNotificationAction>) => {
|
||||
export const addDashboardLabel = (dashboardID: string, label: Label) => async (
|
||||
dispatch: Dispatch<Action | PublishNotificationAction>
|
||||
) => {
|
||||
try {
|
||||
const resp = await apiPostDashboardsLabel({
|
||||
const resp = await api.postDashboardsLabel({
|
||||
dashboardID,
|
||||
data: {labelID: label.id},
|
||||
})
|
||||
|
@ -647,19 +516,19 @@ export const addDashboardLabelAsync = (
|
|||
|
||||
const lab = addLabelDefaults(resp.data.label)
|
||||
|
||||
dispatch(addDashboardLabel(dashboardID, lab))
|
||||
dispatch(creators.addDashboardLabel(dashboardID, lab))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
dispatch(notify(copy.addDashboardLabelFailed()))
|
||||
}
|
||||
}
|
||||
|
||||
export const removeDashboardLabelAsync = (
|
||||
export const removeDashboardLabel = (
|
||||
dashboardID: string,
|
||||
label: Label
|
||||
) => async (dispatch: Dispatch<Action | PublishNotificationAction>) => {
|
||||
try {
|
||||
const resp = await apiDeleteDashboardsLabel({
|
||||
const resp = await api.deleteDashboardsLabel({
|
||||
dashboardID,
|
||||
labelID: label.id,
|
||||
})
|
||||
|
@ -668,7 +537,7 @@ export const removeDashboardLabelAsync = (
|
|||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
dispatch(removeDashboardLabel(dashboardID, label))
|
||||
dispatch(creators.removeDashboardLabel(dashboardID, label.id))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
dispatch(notify(copy.removedDashboardLabelFailed()))
|
||||
|
@ -680,8 +549,13 @@ export const selectVariableValue = (
|
|||
variableID: string,
|
||||
value: string
|
||||
) => async (dispatch, getState: GetState): Promise<void> => {
|
||||
const variables = getHydratedVariables(getState(), dashboardID)
|
||||
const dashboard = getState().dashboards.list.find(d => d.id === dashboardID)
|
||||
const state = getState()
|
||||
const variables = getHydratedVariables(state, dashboardID)
|
||||
const dashboard = getByID<Dashboard>(
|
||||
state,
|
||||
ResourceType.Dashboards,
|
||||
dashboardID
|
||||
)
|
||||
|
||||
dispatch(selectValue(dashboardID, variableID, value))
|
||||
|
||||
|
@ -695,12 +569,25 @@ export const convertToTemplate = (dashboardID: string) => async (
|
|||
try {
|
||||
dispatch(setExportTemplate(RemoteDataState.Loading))
|
||||
const org = getOrg(getState())
|
||||
const dashboard = await getDashboardAJAX(dashboardID)
|
||||
|
||||
const dashResp = await api.getDashboard({dashboardID})
|
||||
|
||||
if (dashResp.status !== 200) {
|
||||
throw new Error(dashResp.data.message)
|
||||
}
|
||||
|
||||
const {entities, result} = normalize<Dashboard, DashboardEntities, string>(
|
||||
dashResp.data,
|
||||
schemas.dashboard
|
||||
)
|
||||
|
||||
const dashboard: Dashboard = entities[result]
|
||||
|
||||
const pendingViews = dashboard.cells.map(c =>
|
||||
getViewAJAX(dashboardID, c.id)
|
||||
dashAPI.getView(dashboardID, c.id)
|
||||
)
|
||||
const views = await Promise.all(pendingViews)
|
||||
const resp = await apiGetVariables({query: {orgID: org.id}})
|
||||
const resp = await api.getVariables({query: {orgID: org.id}})
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
|
@ -1,132 +1,14 @@
|
|||
// Libraries
|
||||
import _ from 'lodash'
|
||||
|
||||
// APIs
|
||||
import {
|
||||
getDashboards as apiGetDashboards,
|
||||
getDashboard as apiGetDashboard,
|
||||
postDashboard as apiPostDashboard,
|
||||
deleteDashboard as apiDeleteDashboard,
|
||||
patchDashboard as apiPatchDashboard,
|
||||
postDashboardsCell as apiPostDashboardsCell,
|
||||
putDashboardsCells as apiPutDashboardsCells,
|
||||
deleteDashboardsCell as apiDeleteDashboardsCell,
|
||||
getDashboardsCellsView as apiGetDashboardsCellsView,
|
||||
patchDashboardsCellsView as apiPatchDashboardsCellsView,
|
||||
} from 'src/client'
|
||||
import * as api from 'src/client'
|
||||
|
||||
// Types
|
||||
import {Cell, NewCell, Dashboard, View, CreateDashboardRequest} from 'src/types'
|
||||
|
||||
// Utils
|
||||
import {
|
||||
addDashboardDefaults,
|
||||
addDashboardIDToCells,
|
||||
} from 'src/dashboards/actions'
|
||||
|
||||
export const getDashboards = async (orgID: string): Promise<Dashboard[]> => {
|
||||
const resp = await apiGetDashboards({query: {orgID}})
|
||||
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
return resp.data.dashboards.map(d => addDashboardDefaults(d))
|
||||
}
|
||||
|
||||
export const getDashboard = async (id: string): Promise<Dashboard> => {
|
||||
const resp = await apiGetDashboard({dashboardID: id})
|
||||
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
return addDashboardDefaults(resp.data)
|
||||
}
|
||||
|
||||
export const createDashboard = async (
|
||||
props: CreateDashboardRequest
|
||||
): Promise<Dashboard> => {
|
||||
const resp = await apiPostDashboard({data: props})
|
||||
|
||||
if (resp.status !== 201) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
return addDashboardDefaults(resp.data)
|
||||
}
|
||||
|
||||
export const deleteDashboard = async (dashboard: Dashboard): Promise<void> => {
|
||||
const resp = await apiDeleteDashboard({dashboardID: dashboard.id})
|
||||
|
||||
if (resp.status !== 204) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
}
|
||||
|
||||
export const updateDashboard = async (
|
||||
dashboard: Dashboard
|
||||
): Promise<Dashboard> => {
|
||||
const resp = await apiPatchDashboard({
|
||||
dashboardID: dashboard.id,
|
||||
data: dashboard,
|
||||
})
|
||||
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
return addDashboardDefaults(resp.data)
|
||||
}
|
||||
|
||||
export const addCell = async (
|
||||
dashboardID: string,
|
||||
cell: NewCell
|
||||
): Promise<Cell> => {
|
||||
const resp = await apiPostDashboardsCell({dashboardID, data: cell})
|
||||
|
||||
if (resp.status !== 201) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
const result = resp.data
|
||||
|
||||
const cellWithID = {...result, dashboardID}
|
||||
|
||||
return cellWithID
|
||||
}
|
||||
|
||||
export const updateCells = async (
|
||||
id: string,
|
||||
cells: Cell[]
|
||||
): Promise<Cell[]> => {
|
||||
const resp = await apiPutDashboardsCells({dashboardID: id, data: cells})
|
||||
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
const result = resp.data.cells
|
||||
|
||||
return addDashboardIDToCells(result, id)
|
||||
}
|
||||
|
||||
export const deleteCell = async (
|
||||
dashboardID: string,
|
||||
cell: Cell
|
||||
): Promise<void> => {
|
||||
const resp = await apiDeleteDashboardsCell({dashboardID, cellID: cell.id})
|
||||
|
||||
if (resp.status !== 204) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
}
|
||||
import {View, NewView, Dashboard} from 'src/types'
|
||||
|
||||
export const getView = async (
|
||||
dashboardID: string,
|
||||
cellID: string
|
||||
): Promise<View> => {
|
||||
const resp = await apiGetDashboardsCellsView({dashboardID, cellID})
|
||||
const resp = await api.getDashboardsCellsView({dashboardID, cellID})
|
||||
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.data.message)
|
||||
|
@ -138,9 +20,9 @@ export const getView = async (
|
|||
export const updateView = async (
|
||||
dashboardID: string,
|
||||
cellID: string,
|
||||
view: Partial<View>
|
||||
view: NewView
|
||||
): Promise<View> => {
|
||||
const resp = await apiPatchDashboardsCellsView({
|
||||
const resp = await api.patchDashboardsCellsView({
|
||||
dashboardID,
|
||||
cellID,
|
||||
data: view as View,
|
||||
|
@ -154,3 +36,47 @@ export const updateView = async (
|
|||
|
||||
return viewWithIDs
|
||||
}
|
||||
|
||||
export const cloneUtilFunc = async (dash: Dashboard, id: string) => {
|
||||
const cells = dash.cells
|
||||
const pendingViews = cells.map(cell =>
|
||||
api
|
||||
.getDashboardsCellsView({
|
||||
dashboardID: dash.id,
|
||||
cellID: cell.id,
|
||||
})
|
||||
.then(res => {
|
||||
return {
|
||||
...res,
|
||||
cellID: cell.id,
|
||||
}
|
||||
})
|
||||
)
|
||||
const views = await Promise.all(pendingViews)
|
||||
|
||||
if (views.length > 0 && views.some(v => v.status !== 200)) {
|
||||
throw new Error('An error occurred cloning the dashboard')
|
||||
}
|
||||
|
||||
return views.map(async v => {
|
||||
const view = v.data as View
|
||||
const cell = cells.find(c => c.id === view.id)
|
||||
|
||||
if (cell && id) {
|
||||
const newCell = await api.postDashboardsCell({
|
||||
dashboardID: id,
|
||||
data: cell,
|
||||
})
|
||||
|
||||
if (newCell.status !== 201) {
|
||||
throw new Error('An error occurred cloning the dashboard')
|
||||
}
|
||||
|
||||
return api.patchDashboardsCellsView({
|
||||
dashboardID: id,
|
||||
cellID: newCell.data.id,
|
||||
data: view,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import {withRouter, WithRouterProps} from 'react-router'
|
|||
import ExportOverlay from 'src/shared/components/ExportOverlay'
|
||||
|
||||
// Actions
|
||||
import {convertToTemplate as convertToTemplateAction} from 'src/dashboards/actions/index'
|
||||
import {convertToTemplate as convertToTemplateAction} from 'src/dashboards/actions/thunks'
|
||||
import {clearExportTemplate as clearExportTemplateAction} from 'src/templates/actions'
|
||||
|
||||
// Types
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import {withRouter, WithRouterProps} from 'react-router'
|
||||
import _ from 'lodash'
|
||||
import {isEmpty} from 'lodash'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
// Components
|
||||
|
@ -12,9 +12,9 @@ import {invalidJSON} from 'src/shared/copy/notifications'
|
|||
|
||||
// Actions
|
||||
import {
|
||||
getDashboardsAsync,
|
||||
getDashboards,
|
||||
createDashboardFromTemplate as createDashboardFromTemplateAction,
|
||||
} from 'src/dashboards/actions'
|
||||
} from 'src/dashboards/actions/thunks'
|
||||
import {notify as notifyAction} from 'src/shared/actions/notifications'
|
||||
|
||||
// Types
|
||||
|
@ -30,7 +30,7 @@ interface State {
|
|||
interface DispatchProps {
|
||||
createDashboardFromTemplate: typeof createDashboardFromTemplateAction
|
||||
notify: typeof notifyAction
|
||||
populateDashboards: typeof getDashboardsAsync
|
||||
populateDashboards: typeof getDashboards
|
||||
}
|
||||
|
||||
interface OwnProps extends WithRouterProps {
|
||||
|
@ -73,7 +73,7 @@ class DashboardImportOverlay extends PureComponent<Props> {
|
|||
return
|
||||
}
|
||||
|
||||
if (_.isEmpty(template)) {
|
||||
if (isEmpty(template)) {
|
||||
this.onDismiss()
|
||||
}
|
||||
|
||||
|
@ -89,9 +89,9 @@ class DashboardImportOverlay extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
createDashboardFromTemplate: createDashboardFromTemplateAction,
|
||||
notify: notifyAction,
|
||||
populateDashboards: getDashboardsAsync,
|
||||
populateDashboards: getDashboards,
|
||||
createDashboardFromTemplate: createDashboardFromTemplateAction,
|
||||
}
|
||||
|
||||
export default connect<{}, DispatchProps, OwnProps>(
|
||||
|
|
|
@ -16,7 +16,7 @@ import LimitChecker from 'src/cloud/components/LimitChecker'
|
|||
import RateLimitAlert from 'src/cloud/components/RateLimitAlert'
|
||||
|
||||
// Actions
|
||||
import * as dashboardActions from 'src/dashboards/actions'
|
||||
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 {
|
||||
|
@ -39,6 +39,7 @@ import {AUTOREFRESH_DEFAULT} from 'src/shared/constants'
|
|||
// Selectors
|
||||
import {getTimeRangeByDashboardID} from 'src/dashboards/selectors'
|
||||
import {getOrg} from 'src/organizations/selectors'
|
||||
import {getByID} from 'src/resources/selectors'
|
||||
|
||||
// Types
|
||||
import {
|
||||
|
@ -52,6 +53,7 @@ import {
|
|||
AutoRefreshStatus,
|
||||
Organization,
|
||||
RemoteDataState,
|
||||
ResourceType,
|
||||
} from 'src/types'
|
||||
import {WithRouterProps} from 'react-router'
|
||||
import {ManualRefreshProps} from 'src/shared/components/ManualRefresh'
|
||||
|
@ -73,11 +75,11 @@ interface StateProps {
|
|||
}
|
||||
|
||||
interface DispatchProps {
|
||||
deleteCell: typeof dashboardActions.deleteCellAsync
|
||||
copyCell: typeof dashboardActions.copyDashboardCellAsync
|
||||
getDashboard: typeof dashboardActions.getDashboardAsync
|
||||
updateDashboard: typeof dashboardActions.updateDashboardAsync
|
||||
updateCells: typeof dashboardActions.updateCellsAsync
|
||||
deleteCell: typeof dashboardActions.deleteCell
|
||||
copyCell: typeof dashboardActions.copyDashboardCell
|
||||
getDashboard: typeof dashboardActions.getDashboard
|
||||
updateDashboard: typeof dashboardActions.updateDashboard
|
||||
updateCells: typeof dashboardActions.updateCells
|
||||
updateQueryParams: typeof rangesActions.updateQueryParams
|
||||
setDashboardTimeRange: typeof rangesActions.setDashboardTimeRange
|
||||
handleChooseAutoRefresh: typeof setAutoRefreshInterval
|
||||
|
@ -299,7 +301,6 @@ class DashboardPage extends Component<Props> {
|
|||
const mstp = (state: AppState, {params: {dashboardID}}): StateProps => {
|
||||
const {
|
||||
links,
|
||||
dashboards,
|
||||
views: {views},
|
||||
userSettings: {showVariablesControls},
|
||||
cloud: {limits},
|
||||
|
@ -311,7 +312,11 @@ const mstp = (state: AppState, {params: {dashboardID}}): StateProps => {
|
|||
|
||||
const autoRefresh = state.autoRefresh[dashboardID] || AUTOREFRESH_DEFAULT
|
||||
|
||||
const dashboard = dashboards.list.find(d => d.id === dashboardID)
|
||||
const dashboard = getByID<Dashboard>(
|
||||
state,
|
||||
ResourceType.Dashboards,
|
||||
dashboardID
|
||||
)
|
||||
|
||||
const limitedResources = extractRateLimitResources(limits)
|
||||
const limitStatus = extractRateLimitStatus(limits)
|
||||
|
@ -330,11 +335,11 @@ const mstp = (state: AppState, {params: {dashboardID}}): StateProps => {
|
|||
}
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
getDashboard: dashboardActions.getDashboardAsync,
|
||||
updateDashboard: dashboardActions.updateDashboardAsync,
|
||||
copyCell: dashboardActions.copyDashboardCellAsync,
|
||||
deleteCell: dashboardActions.deleteCellAsync,
|
||||
updateCells: dashboardActions.updateCellsAsync,
|
||||
getDashboard: dashboardActions.getDashboard,
|
||||
updateDashboard: dashboardActions.updateDashboard,
|
||||
copyCell: dashboardActions.copyDashboardCell,
|
||||
deleteCell: dashboardActions.deleteCell,
|
||||
updateCells: dashboardActions.updateCells,
|
||||
handleChooseAutoRefresh: setAutoRefreshInterval,
|
||||
onSetAutoRefreshStatus: setAutoRefreshStatus,
|
||||
handleClickPresentationButton: appActions.delayEnablePresentationMode,
|
||||
|
|
|
@ -11,7 +11,7 @@ import VEOHeader from 'src/dashboards/components/VEOHeader'
|
|||
|
||||
// Actions
|
||||
import {setName} from 'src/timeMachine/actions'
|
||||
import {saveVEOView} from 'src/dashboards/actions'
|
||||
import {saveVEOView} from 'src/dashboards/actions/thunks'
|
||||
import {getViewForTimeMachine} from 'src/dashboards/actions/views'
|
||||
|
||||
// Utils
|
||||
|
|
|
@ -12,7 +12,7 @@ import VEOHeader from 'src/dashboards/components/VEOHeader'
|
|||
// Actions
|
||||
import {loadNewVEO} from 'src/timeMachine/actions'
|
||||
import {setName} from 'src/timeMachine/actions'
|
||||
import {saveVEOView} from 'src/dashboards/actions'
|
||||
import {saveVEOView} from 'src/dashboards/actions/thunks'
|
||||
|
||||
// Utils
|
||||
import {getActiveTimeMachine} from 'src/timeMachine/selectors'
|
||||
|
|
|
@ -11,11 +11,11 @@ import InlineLabels from 'src/shared/components/inlineLabels/InlineLabels'
|
|||
// Actions
|
||||
import {
|
||||
cloneDashboard,
|
||||
deleteDashboardAsync,
|
||||
updateDashboardAsync,
|
||||
addDashboardLabelAsync,
|
||||
removeDashboardLabelAsync,
|
||||
} from 'src/dashboards/actions'
|
||||
deleteDashboard,
|
||||
updateDashboard,
|
||||
addDashboardLabel,
|
||||
removeDashboardLabel,
|
||||
} from 'src/dashboards/actions/thunks'
|
||||
import {createLabel as createLabelAsync} from 'src/labels/actions'
|
||||
|
||||
// Selectors
|
||||
|
@ -44,8 +44,8 @@ interface DispatchProps {
|
|||
onDeleteDashboard: (dashboard: Dashboard) => void
|
||||
onUpdateDashboard: (dashboard: Dashboard) => void
|
||||
onCloneDashboard: (dashboard: Dashboard) => void
|
||||
onAddDashboardLabel: typeof addDashboardLabelAsync
|
||||
onRemoveDashboardLabel: typeof removeDashboardLabelAsync
|
||||
onAddDashboardLabel: typeof addDashboardLabel
|
||||
onRemoveDashboardLabel: typeof removeDashboardLabel
|
||||
onCreateLabel: typeof createLabelAsync
|
||||
onResetViews: typeof resetViews
|
||||
}
|
||||
|
@ -199,12 +199,12 @@ const mstp = ({labels}: AppState): StateProps => {
|
|||
|
||||
const mdtp: DispatchProps = {
|
||||
onCreateLabel: createLabelAsync,
|
||||
onAddDashboardLabel: addDashboardLabelAsync,
|
||||
onRemoveDashboardLabel: removeDashboardLabelAsync,
|
||||
onAddDashboardLabel: addDashboardLabel,
|
||||
onRemoveDashboardLabel: removeDashboardLabel,
|
||||
onResetViews: resetViews,
|
||||
onCloneDashboard: cloneDashboard,
|
||||
onDeleteDashboard: deleteDashboardAsync,
|
||||
onUpdateDashboard: updateDashboardAsync,
|
||||
onDeleteDashboard: deleteDashboard,
|
||||
onUpdateDashboard: updateDashboard,
|
||||
}
|
||||
|
||||
export default connect<StateProps, DispatchProps, PassedProps>(
|
||||
|
|
|
@ -22,6 +22,11 @@ interface Props {
|
|||
}
|
||||
|
||||
export default class DashboardCards extends PureComponent<Props> {
|
||||
private _frame
|
||||
private _window
|
||||
private _observer
|
||||
private _spinner
|
||||
|
||||
private memGetSortedResources = memoizeOne<typeof getSortedResources>(
|
||||
getSortedResources
|
||||
)
|
||||
|
|
|
@ -20,7 +20,7 @@ import {pageTitleSuffixer} from 'src/shared/utils/pageTitles'
|
|||
import {extractDashboardLimits} from 'src/cloud/utils/limits'
|
||||
|
||||
// Actions
|
||||
import {createDashboard as createDashboardAction} from 'src/dashboards/actions'
|
||||
import {createDashboard as createDashboardAction} from 'src/dashboards/actions/thunks'
|
||||
|
||||
// Types
|
||||
import {AppState} from 'src/types'
|
||||
|
|
|
@ -15,7 +15,8 @@ import {checkDashboardLimits as checkDashboardLimitsAction} from 'src/cloud/acti
|
|||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
// Types
|
||||
import {Dashboard, AppState, RemoteDataState} from 'src/types'
|
||||
import {Dashboard, AppState, RemoteDataState, ResourceType} from 'src/types'
|
||||
import {getAll} from 'src/resources/selectors'
|
||||
|
||||
interface OwnProps {
|
||||
onFilterChange: (searchTerm: string) => void
|
||||
|
@ -70,14 +71,13 @@ class DashboardsIndexContents extends Component<Props> {
|
|||
|
||||
const mstp = (state: AppState): StateProps => {
|
||||
const {
|
||||
dashboards: {list: dashboards},
|
||||
cloud: {
|
||||
limits: {status},
|
||||
},
|
||||
} = state
|
||||
|
||||
return {
|
||||
dashboards,
|
||||
dashboards: getAll<Dashboard>(state, ResourceType.Dashboards),
|
||||
limitStatus: status,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import _ from 'lodash'
|
|||
import {EmptyState, ResourceList} from '@influxdata/clockface'
|
||||
import AddResourceDropdown from 'src/shared/components/AddResourceDropdown'
|
||||
import DashboardCards from 'src/dashboards/components/dashboard_index/DashboardCards'
|
||||
import {createDashboard, getDashboardsAsync} from 'src/dashboards/actions'
|
||||
import {createDashboard, getDashboards} from 'src/dashboards/actions/thunks'
|
||||
import {getLabels} from 'src/labels/actions'
|
||||
|
||||
// Types
|
||||
|
@ -34,7 +34,7 @@ interface StateProps {
|
|||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getDashboards: typeof getDashboardsAsync
|
||||
getDashboards: typeof getDashboards
|
||||
onCreateDashboard: typeof createDashboard
|
||||
getLabels: typeof getLabels
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ class DashboardsTable extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
const mstp = (state: AppState): StateProps => {
|
||||
const status = state.dashboards.status
|
||||
const status = state.resources.dashboards.status
|
||||
|
||||
return {
|
||||
status,
|
||||
|
@ -167,7 +167,7 @@ const mstp = (state: AppState): StateProps => {
|
|||
}
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
getDashboards: getDashboardsAsync,
|
||||
getDashboards: getDashboards,
|
||||
onCreateDashboard: createDashboard,
|
||||
getLabels: getLabels,
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
import {
|
||||
|
@ -11,7 +10,7 @@ import {
|
|||
} from '@influxdata/clockface'
|
||||
|
||||
// Actions
|
||||
import {selectVariableValue} from 'src/dashboards/actions/index'
|
||||
import {selectVariableValue} from 'src/dashboards/actions/thunks'
|
||||
|
||||
// Utils
|
||||
import {getVariableValuesForDropdown} from 'src/dashboards/selectors'
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
// Libraries
|
||||
import {normalize} from 'normalizr'
|
||||
|
||||
// Schema
|
||||
import * as schemas from 'src/schemas'
|
||||
|
||||
// Reducer
|
||||
import {dashboardsReducer as reducer} from 'src/dashboards/reducers/dashboards'
|
||||
|
||||
|
@ -10,86 +16,110 @@ import {
|
|||
removeCell,
|
||||
addDashboardLabel,
|
||||
removeDashboardLabel,
|
||||
} from 'src/dashboards/actions/'
|
||||
} from 'src/dashboards/actions/creators'
|
||||
|
||||
// Resources
|
||||
import {dashboard} from 'src/dashboards/resources'
|
||||
import {labels} from 'mocks/dummyData'
|
||||
import {RemoteDataState} from '@influxdata/clockface'
|
||||
|
||||
// Types
|
||||
import {RemoteDataState, DashboardEntities, Dashboard} from 'src/types'
|
||||
|
||||
const status = RemoteDataState.Done
|
||||
|
||||
const initialState = () => ({
|
||||
status,
|
||||
byID: {
|
||||
[dashboard.id]: dashboard,
|
||||
['2']: {...dashboard, id: '2'},
|
||||
},
|
||||
allIDs: [dashboard.id, '2'],
|
||||
})
|
||||
|
||||
describe('dashboards reducer', () => {
|
||||
it('can set the dashboards', () => {
|
||||
const list = [dashboard]
|
||||
const schema = normalize<Dashboard, DashboardEntities, string[]>(
|
||||
[dashboard],
|
||||
schemas.arrayOfDashboards
|
||||
)
|
||||
|
||||
const expected = {status, list}
|
||||
const actual = reducer(undefined, setDashboards(status, list))
|
||||
const byID = schema.entities.dashboards
|
||||
const allIDs = schema.result
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
const actual = reducer(undefined, setDashboards(status, schema))
|
||||
|
||||
expect(actual.byID).toEqual(byID)
|
||||
expect(actual.allIDs).toEqual(allIDs)
|
||||
})
|
||||
|
||||
it('can remove a dashboard', () => {
|
||||
const d2 = {...dashboard, id: '2'}
|
||||
const list = [dashboard, d2]
|
||||
const expected = {list: [dashboard], status}
|
||||
const actual = reducer({list, status}, removeDashboard(d2.id))
|
||||
const allIDs = [dashboard.id]
|
||||
const byID = {[dashboard.id]: dashboard}
|
||||
|
||||
const state = initialState()
|
||||
const expected = {status, byID, allIDs}
|
||||
const actual = reducer(state, removeDashboard(state.allIDs[1]))
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
|
||||
it('can set a dashboard', () => {
|
||||
const loadedDashboard = {...dashboard, name: 'updated'}
|
||||
const d2 = {...dashboard, id: '2'}
|
||||
const state = {status, list: [dashboard, d2]}
|
||||
const name = 'updated name'
|
||||
const loadedDashboard = {...dashboard, name: 'updated name'}
|
||||
const schema = normalize<Dashboard, DashboardEntities, string>(
|
||||
loadedDashboard,
|
||||
schemas.dashboard
|
||||
)
|
||||
|
||||
const expected = {status, list: [loadedDashboard, d2]}
|
||||
const actual = reducer(state, setDashboard(loadedDashboard))
|
||||
const state = initialState()
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
const actual = reducer(state, setDashboard(schema))
|
||||
|
||||
expect(actual.byID[dashboard.id].name).toEqual(name)
|
||||
})
|
||||
|
||||
it('can edit a dashboard', () => {
|
||||
const updates = {...dashboard, name: 'updated dash'}
|
||||
const expected = {status, list: [updates]}
|
||||
const actual = reducer({status, list: [dashboard]}, editDashboard(updates))
|
||||
const name = 'updated name'
|
||||
const updates = {...dashboard, name}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
const schema = normalize<Dashboard, DashboardEntities, string>(
|
||||
updates,
|
||||
schemas.dashboard
|
||||
)
|
||||
|
||||
const state = initialState()
|
||||
const actual = reducer(state, editDashboard(schema))
|
||||
|
||||
expect(actual.byID[dashboard.id].name).toEqual(name)
|
||||
})
|
||||
|
||||
it('can remove a cell from a dashboard', () => {
|
||||
const expected = {status, list: [{...dashboard, cells: []}]}
|
||||
const actual = reducer(
|
||||
{status, list: [dashboard]},
|
||||
removeCell(dashboard, dashboard.cells[0])
|
||||
)
|
||||
const state = initialState()
|
||||
const {id} = dashboard
|
||||
const cellID = dashboard.cells[0].id
|
||||
const actual = reducer(state, removeCell(id, cellID))
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
expect(actual.byID[id].cells).toEqual([])
|
||||
})
|
||||
|
||||
it('can add labels to a dashboard', () => {
|
||||
const dashboardWithoutLabels = {...dashboard, labels: []}
|
||||
const expected = {status, list: [{...dashboard, labels: [labels[0]]}]}
|
||||
const actual = reducer(
|
||||
{status, list: [dashboardWithoutLabels]},
|
||||
addDashboardLabel(dashboardWithoutLabels.id, labels[0])
|
||||
)
|
||||
const {id} = dashboard
|
||||
const state = initialState()
|
||||
const label = labels[0]
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
const actual = reducer(state, addDashboardLabel(id, label))
|
||||
|
||||
expect(actual.byID[id].labels).toEqual([label])
|
||||
})
|
||||
|
||||
it('can remove labels from a dashboard', () => {
|
||||
const leftOverLabel = {...labels[0], name: 'wowowowo', id: '3'}
|
||||
const dashboardWithLabels = {
|
||||
...dashboard,
|
||||
labels: [labels[0], leftOverLabel],
|
||||
}
|
||||
const expected = {status, list: [{...dashboard, labels: [leftOverLabel]}]}
|
||||
const actual = reducer(
|
||||
{status, list: [dashboardWithLabels]},
|
||||
removeDashboardLabel(dashboardWithLabels.id, labels[0])
|
||||
)
|
||||
const {id} = dashboard
|
||||
const label = labels[0]
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
const state = initialState()
|
||||
const withLabel = reducer(state, addDashboardLabel(id, label))
|
||||
const actual = reducer(withLabel, removeDashboardLabel(id, labels[0].id))
|
||||
|
||||
expect(actual.byID[id].labels).toEqual([])
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,18 +1,38 @@
|
|||
// Libraries
|
||||
import {produce} from 'immer'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Types
|
||||
import {Action, ActionTypes} from 'src/dashboards/actions'
|
||||
import {Dashboard, RemoteDataState} from 'src/types'
|
||||
import {
|
||||
RemoteDataState,
|
||||
ResourceState,
|
||||
Dashboard,
|
||||
ResourceType,
|
||||
} from 'src/types'
|
||||
|
||||
export interface DashboardsState {
|
||||
list: Dashboard[]
|
||||
status: RemoteDataState
|
||||
}
|
||||
// Actions
|
||||
import {
|
||||
Action,
|
||||
SET_DASHBOARD,
|
||||
REMOVE_DASHBOARD,
|
||||
SET_DASHBOARDS,
|
||||
REMOVE_CELL,
|
||||
REMOVE_DASHBOARD_LABEL,
|
||||
ADD_DASHBOARD_LABEL,
|
||||
EDIT_DASHBOARD,
|
||||
} from 'src/dashboards/actions/creators'
|
||||
|
||||
// Utils
|
||||
import {
|
||||
setResource,
|
||||
removeResource,
|
||||
editResource,
|
||||
} from 'src/resources/reducers/helpers'
|
||||
|
||||
type DashboardsState = ResourceState['dashboards']
|
||||
|
||||
const initialState = () => ({
|
||||
list: [],
|
||||
byID: {},
|
||||
allIDs: [],
|
||||
status: RemoteDataState.NotStarted,
|
||||
})
|
||||
|
||||
|
@ -22,83 +42,66 @@ export const dashboardsReducer = (
|
|||
): DashboardsState => {
|
||||
return produce(state, draftState => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.SetDashboards: {
|
||||
const {list, status} = action.payload
|
||||
case SET_DASHBOARDS: {
|
||||
setResource<Dashboard>(draftState, action, ResourceType.Dashboards)
|
||||
|
||||
draftState.status = status
|
||||
if (list) {
|
||||
draftState.list = list
|
||||
return
|
||||
}
|
||||
|
||||
case REMOVE_DASHBOARD: {
|
||||
removeResource<Dashboard>(draftState, action)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case SET_DASHBOARD: {
|
||||
const {schema} = action
|
||||
const {entities, result} = schema
|
||||
|
||||
draftState.byID[result] = entities.dashboards[result]
|
||||
const exists = draftState.allIDs.find(id => id === result)
|
||||
|
||||
if (!exists) {
|
||||
draftState.allIDs.push(result)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case ActionTypes.RemoveDashboard: {
|
||||
const {id} = action.payload
|
||||
draftState.list = draftState.list.filter(l => l.id !== id)
|
||||
case EDIT_DASHBOARD: {
|
||||
editResource<Dashboard>(draftState, action, ResourceType.Dashboards)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case ActionTypes.SetDashboard: {
|
||||
const {dashboard} = action.payload
|
||||
draftState.list = _.unionBy([dashboard], state.list, 'id')
|
||||
case REMOVE_CELL: {
|
||||
const {dashboardID, cellID} = action
|
||||
|
||||
const {cells} = draftState.byID[dashboardID]
|
||||
|
||||
draftState.byID[dashboardID].cells = cells.filter(
|
||||
cell => cell.id !== cellID
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case ActionTypes.EditDashboard: {
|
||||
const {dashboard} = action.payload
|
||||
case ADD_DASHBOARD_LABEL: {
|
||||
const {dashboardID, label} = action
|
||||
|
||||
draftState.list = draftState.list.map(d => {
|
||||
if (d.id === dashboard.id) {
|
||||
return dashboard
|
||||
}
|
||||
return d
|
||||
})
|
||||
draftState.byID[dashboardID].labels.push(label)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case ActionTypes.RemoveCell: {
|
||||
const {dashboard, cell} = action.payload
|
||||
draftState.list = draftState.list.map(d => {
|
||||
if (d.id === dashboard.id) {
|
||||
const cells = d.cells.filter(c => c.id !== cell.id)
|
||||
d.cells = cells
|
||||
}
|
||||
case REMOVE_DASHBOARD_LABEL: {
|
||||
const {dashboardID, labelID} = action
|
||||
|
||||
return d
|
||||
})
|
||||
const {labels} = draftState.byID[dashboardID]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case ActionTypes.AddDashboardLabel: {
|
||||
const {dashboardID, label} = action.payload
|
||||
|
||||
draftState.list = draftState.list.map(d => {
|
||||
if (d.id === dashboardID) {
|
||||
d.labels = [...d.labels, label]
|
||||
}
|
||||
|
||||
return d
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case ActionTypes.RemoveDashboardLabel: {
|
||||
const {dashboardID, label} = action.payload
|
||||
draftState.list = draftState.list.map(d => {
|
||||
if (d.id === dashboardID) {
|
||||
const updatedLabels = d.labels.filter(el => !(label.id === el.id))
|
||||
|
||||
d.labels = updatedLabels
|
||||
}
|
||||
|
||||
return d
|
||||
})
|
||||
draftState.byID[dashboardID].labels = labels.filter(
|
||||
label => label.id !== labelID
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
ViewType,
|
||||
RemoteDataState,
|
||||
TimeRange,
|
||||
ResourceType,
|
||||
Dashboard,
|
||||
} from 'src/types'
|
||||
|
||||
import {
|
||||
|
@ -17,6 +19,7 @@ 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`)
|
||||
|
@ -47,8 +50,10 @@ export const getViewsForDashboard = (
|
|||
state: AppState,
|
||||
dashboardID: string
|
||||
): View[] => {
|
||||
const dashboard = state.dashboards.list.find(
|
||||
dashboard => dashboard.id === dashboardID
|
||||
const dashboard = getByID<Dashboard>(
|
||||
state,
|
||||
ResourceType.Dashboards,
|
||||
dashboardID
|
||||
)
|
||||
|
||||
const cellIDs = new Set(dashboard.cells.map(cell => cell.id))
|
||||
|
|
|
@ -6,6 +6,7 @@ import {get, isEmpty} from 'lodash'
|
|||
// Selectors
|
||||
import {getSaveableView} from 'src/timeMachine/selectors'
|
||||
import {getOrg} from 'src/organizations/selectors'
|
||||
import {getAll} from 'src/resources/selectors'
|
||||
|
||||
// Components
|
||||
import {Form, Input, Button, Grid} from '@influxdata/clockface'
|
||||
|
@ -21,12 +22,12 @@ import {
|
|||
} from 'src/dashboards/constants'
|
||||
|
||||
// Actions
|
||||
import {getDashboardsAsync, createCellWithView} from 'src/dashboards/actions'
|
||||
import {createDashboard} from 'src/dashboards/apis'
|
||||
import {getDashboards, createCellWithView} from 'src/dashboards/actions/thunks'
|
||||
import {postDashboard} from 'src/client'
|
||||
import {notify} from 'src/shared/actions/notifications'
|
||||
|
||||
// Types
|
||||
import {AppState, Dashboard, View} from 'src/types'
|
||||
import {AppState, Dashboard, View, ResourceType} from 'src/types'
|
||||
import {
|
||||
Columns,
|
||||
InputType,
|
||||
|
@ -49,7 +50,7 @@ interface StateProps {
|
|||
}
|
||||
|
||||
interface DispatchProps {
|
||||
handleGetDashboards: typeof getDashboardsAsync
|
||||
onGetDashboards: typeof getDashboards
|
||||
onCreateCellWithView: typeof createCellWithView
|
||||
notify: typeof notify
|
||||
}
|
||||
|
@ -70,8 +71,8 @@ class SaveAsCellForm extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const {handleGetDashboards} = this.props
|
||||
handleGetDashboards()
|
||||
const {onGetDashboards} = this.props
|
||||
onGetDashboards()
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
@ -199,8 +200,14 @@ class SaveAsCellForm extends PureComponent<Props, State> {
|
|||
name: dashboardName || DEFAULT_DASHBOARD_NAME,
|
||||
cells: [],
|
||||
}
|
||||
const dashboard = await createDashboard(newDashboard)
|
||||
onCreateCellWithView(dashboard.id, view)
|
||||
|
||||
const resp = await postDashboard({data: newDashboard})
|
||||
|
||||
if (resp.status !== 201) {
|
||||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
onCreateCellWithView(resp.data.id, view)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
@ -237,18 +244,15 @@ class SaveAsCellForm extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
const mstp = (state: AppState): StateProps => {
|
||||
const {
|
||||
dashboards: {list: dashboards},
|
||||
} = state
|
||||
|
||||
const view = getSaveableView(state)
|
||||
const org = getOrg(state)
|
||||
const dashboards = getAll<Dashboard>(state, ResourceType.Dashboards)
|
||||
|
||||
return {dashboards, view, orgID: get(org, 'id', '')}
|
||||
}
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
handleGetDashboards: getDashboardsAsync,
|
||||
onGetDashboards: getDashboards,
|
||||
onCreateCellWithView: createCellWithView,
|
||||
notify,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
@ -8,9 +7,8 @@ import LineProtocol from 'src/dataLoaders/components/lineProtocolWizard/configur
|
|||
import LineProtocolVerifyStep from 'src/dataLoaders/components/lineProtocolWizard/verify/LineProtocolVerifyStep'
|
||||
|
||||
// Types
|
||||
import {LineProtocolStep} from 'src/types'
|
||||
import {LineProtocolStepProps} from 'src/dataLoaders/components/lineProtocolWizard/LineProtocolWizard'
|
||||
import {Bucket} from 'src/types'
|
||||
import {Bucket, LineProtocolStep} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
stepProps: LineProtocolStepProps
|
||||
|
|
|
@ -7,11 +7,13 @@ import {connect} from 'react-redux'
|
|||
import {EmptyState} from '@influxdata/clockface'
|
||||
|
||||
// Types
|
||||
import {Dashboard, Organization, AppState} from 'src/types'
|
||||
import {Dashboard, Organization, AppState, ResourceType} from 'src/types'
|
||||
import {ComponentSize} from '@influxdata/clockface'
|
||||
|
||||
// Selectors
|
||||
import {getOrg} from 'src/organizations/selectors'
|
||||
import {getAll} from 'src/resources/selectors'
|
||||
|
||||
interface StateProps {
|
||||
dashboards: Dashboard[]
|
||||
org: Organization
|
||||
|
@ -50,7 +52,7 @@ class DashboardList extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
const mstp = (state: AppState): StateProps => ({
|
||||
dashboards: state.dashboards.list,
|
||||
dashboards: getAll<Dashboard>(state, ResourceType.Dashboards),
|
||||
org: getOrg(state),
|
||||
})
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import {getDashboards as apiGetDashboards} from 'src/client'
|
|||
import {Dashboard, Organization} from 'src/types'
|
||||
|
||||
// Utils
|
||||
import {addDashboardDefaults} from 'src/dashboards/actions'
|
||||
import {addDashboardDefaults} from 'src/schemas'
|
||||
|
||||
// CRUD APIs for Organizations and Organization resources
|
||||
// i.e. Organization Members, Buckets, Dashboards etc
|
||||
|
|
|
@ -4,11 +4,17 @@ import {get} from 'lodash'
|
|||
// Types
|
||||
import {AppState, ResourceType, RemoteDataState} from 'src/types'
|
||||
|
||||
export const getStatus = ({resources}: AppState, resource): RemoteDataState => {
|
||||
export const getStatus = (
|
||||
{resources}: AppState,
|
||||
resource: ResourceType
|
||||
): RemoteDataState => {
|
||||
return resources[resource].status
|
||||
}
|
||||
|
||||
export const getAll = <R>({resources}: AppState, resource): R[] => {
|
||||
export const getAll = <R>(
|
||||
{resources}: AppState,
|
||||
resource: ResourceType
|
||||
): R[] => {
|
||||
const allIDs: string[] = resources[resource].allIDs
|
||||
const byID: {[uuid: string]: R} = resources[resource].byID
|
||||
return allIDs.map(id => byID[id])
|
||||
|
@ -27,9 +33,5 @@ export const getByID = <R>(
|
|||
|
||||
const resource = get(byID, `${id}`)
|
||||
|
||||
if (!resource) {
|
||||
throw new Error(`Could not find resource of type "${type}" with id "${id}"`)
|
||||
}
|
||||
|
||||
return resource
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
Label,
|
||||
RemoteDataState,
|
||||
Variable,
|
||||
Dashboard,
|
||||
} from 'src/types'
|
||||
|
||||
// Utils
|
||||
|
@ -26,6 +27,56 @@ export const arrayOfAuths = [auth]
|
|||
export const bucket = new schema.Entity(ResourceType.Buckets)
|
||||
export const arrayOfBuckets = [bucket]
|
||||
|
||||
/* Cells */
|
||||
|
||||
// Defines the schema for the "cells" resource
|
||||
// export const cell = new schema.Entity(
|
||||
// ResourceType.Cells,
|
||||
// {},
|
||||
// {
|
||||
// processStrategy: (cell: Cell, parent: Dashboard) => ({
|
||||
// ...cell,
|
||||
// dashboardID: parent.id,
|
||||
// }),
|
||||
// }
|
||||
// )
|
||||
// export const arrayOfCells = [cell]
|
||||
|
||||
/* Dashboards */
|
||||
|
||||
// Defines the schema for the "dashboards" resource
|
||||
export const dashboard = new schema.Entity(
|
||||
ResourceType.Dashboards,
|
||||
{},
|
||||
{
|
||||
processStrategy: (dashboard: Dashboard) => addDashboardDefaults(dashboard),
|
||||
}
|
||||
)
|
||||
export const arrayOfDashboards = [dashboard]
|
||||
|
||||
export const addDashboardDefaults = (dashboard: Dashboard): Dashboard => {
|
||||
return {
|
||||
...dashboard,
|
||||
id: dashboard.id || '',
|
||||
labels: (dashboard.labels || []).map(addLabelDefaults),
|
||||
name: dashboard.name || '',
|
||||
orgID: dashboard.orgID || '',
|
||||
meta: addDashboardMetaDefaults(dashboard.meta),
|
||||
}
|
||||
}
|
||||
|
||||
const addDashboardMetaDefaults = (meta: Dashboard['meta']) => {
|
||||
if (!meta) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (!meta.updatedAt) {
|
||||
return {...meta, updatedAt: new Date().toDateString()}
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
/* Members */
|
||||
|
||||
// Defines the schema for the "members" resource
|
||||
|
@ -39,6 +90,7 @@ export const org = new schema.Entity(ResourceType.Orgs)
|
|||
export const arrayOfOrgs = [org]
|
||||
|
||||
/* Tasks */
|
||||
|
||||
// Defines the schema for the tasks resource
|
||||
export const task = new schema.Entity(
|
||||
ResourceType.Tasks,
|
||||
|
|
|
@ -3,20 +3,20 @@ import React, {PureComponent} from 'react'
|
|||
import {connect} from 'react-redux'
|
||||
|
||||
// Actions
|
||||
import {getLabels} from 'src/labels/actions'
|
||||
import {getBuckets} from 'src/buckets/actions/thunks'
|
||||
import {getTelegrafs} from 'src/telegrafs/actions/thunks'
|
||||
import {getPlugins} from 'src/dataLoaders/actions/telegrafEditor'
|
||||
import {getVariables} from 'src/variables/actions/thunks'
|
||||
import {getScrapers} from 'src/scrapers/actions/thunks'
|
||||
import {getDashboardsAsync} from 'src/dashboards/actions'
|
||||
import {getTasks} from 'src/tasks/actions/thunks'
|
||||
import {getAuthorizations} from 'src/authorizations/actions/thunks'
|
||||
import {getTemplates} from 'src/templates/actions'
|
||||
import {getMembers} from 'src/members/actions/thunks'
|
||||
import {getBuckets} from 'src/buckets/actions/thunks'
|
||||
import {getChecks} from 'src/alerting/actions/checks'
|
||||
import {getNotificationRules} from 'src/alerting/actions/notifications/rules'
|
||||
import {getDashboards} from 'src/dashboards/actions/thunks'
|
||||
import {getEndpoints} from 'src/alerting/actions/notifications/endpoints'
|
||||
import {getLabels} from 'src/labels/actions'
|
||||
import {getMembers} from 'src/members/actions/thunks'
|
||||
import {getNotificationRules} from 'src/alerting/actions/notifications/rules'
|
||||
import {getPlugins} from 'src/dataLoaders/actions/telegrafEditor'
|
||||
import {getScrapers} from 'src/scrapers/actions/thunks'
|
||||
import {getTasks} from 'src/tasks/actions/thunks'
|
||||
import {getTelegrafs} from 'src/telegrafs/actions/thunks'
|
||||
import {getTemplates} from 'src/templates/actions'
|
||||
import {getVariables} from 'src/variables/actions/thunks'
|
||||
|
||||
// Types
|
||||
import {AppState, RemoteDataState, ResourceType} from 'src/types'
|
||||
|
@ -40,7 +40,7 @@ interface DispatchProps {
|
|||
getVariables: typeof getVariables
|
||||
getScrapers: typeof getScrapers
|
||||
getAuthorizations: typeof getAuthorizations
|
||||
getDashboards: typeof getDashboardsAsync
|
||||
getDashboards: typeof getDashboards
|
||||
getTasks: typeof getTasks
|
||||
getTemplates: typeof getTemplates
|
||||
getMembers: typeof getMembers
|
||||
|
@ -160,7 +160,7 @@ const mdtp = {
|
|||
getVariables: getVariables,
|
||||
getScrapers: getScrapers,
|
||||
getAuthorizations: getAuthorizations,
|
||||
getDashboards: getDashboardsAsync,
|
||||
getDashboards: getDashboards,
|
||||
getTasks: getTasks,
|
||||
getTemplates: getTemplates,
|
||||
getMembers: getMembers,
|
||||
|
|
|
@ -14,6 +14,7 @@ export const getResourcesStatus = (
|
|||
case ResourceType.Tasks:
|
||||
case ResourceType.Scrapers:
|
||||
case ResourceType.Variables:
|
||||
case ResourceType.Dashboards:
|
||||
case ResourceType.Authorizations: {
|
||||
return state.resources[resource].status
|
||||
}
|
||||
|
|
|
@ -55,7 +55,6 @@ export const rootReducer = combineReducers<ReducerState>({
|
|||
alertBuilder: alertBuilderReducer,
|
||||
checks: checksReducer,
|
||||
cloud: combineReducers<{limits: LimitsState}>({limits: limitsReducer}),
|
||||
dashboards: dashboardsReducer,
|
||||
dataLoading: dataLoadingReducer,
|
||||
endpoints: endpointsReducer,
|
||||
labels: labelsReducer,
|
||||
|
@ -75,6 +74,7 @@ export const rootReducer = combineReducers<ReducerState>({
|
|||
telegrafs: telegrafsReducer,
|
||||
tokens: authsReducer,
|
||||
variables: variablesReducer,
|
||||
dashboards: dashboardsReducer,
|
||||
}),
|
||||
routing: routerReducer,
|
||||
rules: rulesReducer,
|
||||
|
|
|
@ -28,7 +28,7 @@ import * as copy from 'src/shared/copy/notifications'
|
|||
|
||||
// API
|
||||
import {client} from 'src/utils/api'
|
||||
import {createDashboardFromTemplate} from 'src/dashboards/actions'
|
||||
import {createDashboardFromTemplate} from 'src/dashboards/actions/thunks'
|
||||
import {createVariableFromTemplate} from 'src/variables/actions/thunks'
|
||||
import {createTaskFromTemplate} from 'src/tasks/actions/thunks'
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ import {
|
|||
postDashboardsCell as apiPostDashboardsCell,
|
||||
patchDashboardsCellsView as apiPatchDashboardsCellsView,
|
||||
} from 'src/client'
|
||||
import {addDashboardDefaults} from 'src/dashboards/actions'
|
||||
import {addDashboardDefaults} from 'src/schemas'
|
||||
// Create Dashboard Templates
|
||||
|
||||
// Types
|
||||
|
@ -80,7 +80,7 @@ export const createDashboardFromTemplate = async (
|
|||
throw new Error(resp.data.message)
|
||||
}
|
||||
|
||||
const createdDashboard = addDashboardDefaults(resp.data)
|
||||
const createdDashboard = addDashboardDefaults(resp.data as Dashboard)
|
||||
|
||||
// associate imported label id with new label
|
||||
const labelMap = await createLabelsFromTemplate(template, orgID)
|
||||
|
@ -98,7 +98,7 @@ export const createDashboardFromTemplate = async (
|
|||
throw new Error(getResp.data.message)
|
||||
}
|
||||
|
||||
const dashboard = addDashboardDefaults(getResp.data)
|
||||
const dashboard = addDashboardDefaults(getResp.data as Dashboard)
|
||||
return dashboard
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import {withRouter, WithRouterProps} from 'react-router'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
import {sortBy} from 'lodash'
|
||||
|
||||
// Components
|
||||
import {
|
||||
|
@ -16,7 +16,7 @@ import TemplateBrowserEmpty from 'src/templates/components/createFromTemplateOve
|
|||
import GetResources from 'src/shared/components/GetResources'
|
||||
|
||||
// Actions
|
||||
import {createDashboardFromTemplate as createDashboardFromTemplateAction} from 'src/dashboards/actions'
|
||||
import {createDashboardFromTemplate as createDashboardFromTemplateAction} from 'src/dashboards/actions/thunks'
|
||||
import {getTemplateByID} from 'src/templates/actions'
|
||||
|
||||
// Constants
|
||||
|
@ -191,7 +191,7 @@ const mstp = ({templates: {items, status}}: AppState): StateProps => {
|
|||
t => !t.meta.type || t.meta.type === TemplateType.Dashboard
|
||||
)
|
||||
|
||||
const templates = _.sortBy(filteredTemplates, item =>
|
||||
const templates = sortBy(filteredTemplates, item =>
|
||||
item.meta.name.toLocaleLowerCase()
|
||||
)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
Bucket,
|
||||
Dashboard,
|
||||
Authorization,
|
||||
Organization,
|
||||
Member,
|
||||
|
@ -13,6 +14,7 @@ import {
|
|||
export enum ResourceType {
|
||||
Authorizations = 'tokens',
|
||||
Buckets = 'buckets',
|
||||
Cells = 'cells',
|
||||
Checks = 'checks',
|
||||
Dashboards = 'dashboards',
|
||||
Labels = 'labels',
|
||||
|
@ -54,4 +56,5 @@ export interface ResourceState {
|
|||
[ResourceType.Scrapers]: NormalizedState<Scraper>
|
||||
[ResourceType.Tasks]: TasksState
|
||||
[ResourceType.Variables]: VariablesState
|
||||
[ResourceType.Dashboards]: NormalizedState<Dashboard>
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Types
|
||||
import {
|
||||
Task,
|
||||
Dashboard,
|
||||
Variable,
|
||||
Telegraf,
|
||||
Member,
|
||||
|
@ -26,6 +27,14 @@ export interface BucketEntities {
|
|||
}
|
||||
}
|
||||
|
||||
// DashboardEntities defines the result of normalizr's normalization
|
||||
// of the "dashboards" resource
|
||||
export interface DashboardEntities {
|
||||
dashboards: {
|
||||
[uuid: string]: Dashboard
|
||||
}
|
||||
}
|
||||
|
||||
// MemberEntities defines the result of normalizr's normalization
|
||||
// of the "members" resource
|
||||
export interface MemberEntities {
|
||||
|
|
|
@ -20,7 +20,6 @@ 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 {DashboardsState} from 'src/dashboards/reducers/dashboards'
|
||||
import {OverlayState} from 'src/overlays/reducers/overlays'
|
||||
import {AutoRefreshState} from 'src/shared/reducers/autoRefresh'
|
||||
import {LimitsState} from 'src/cloud/reducers/limits'
|
||||
|
@ -37,7 +36,6 @@ export interface AppState {
|
|||
autoRefresh: AutoRefreshState
|
||||
checks: ChecksState
|
||||
cloud: {limits: LimitsState}
|
||||
dashboards: DashboardsState
|
||||
dataLoading: DataLoadingState
|
||||
endpoints: NotificationEndpointsState
|
||||
labels: LabelsState
|
||||
|
|
Loading…
Reference in New Issue