diff --git a/public/components/custom_panels/custom_panel_table.tsx b/public/components/custom_panels/custom_panel_table.tsx index 8b2fe9d5b..00340d975 100644 --- a/public/components/custom_panels/custom_panel_table.tsx +++ b/public/components/custom_panels/custom_panel_table.tsx @@ -46,7 +46,13 @@ import { CustomPanelListType } from '../../../common/types/custom_panels'; import { getSampleDataModal } from '../common/helpers/add_sample_modal'; import { pageStyles } from '../../../common/constants/shared'; import { DeleteModal } from '../common/helpers/delete_modal'; -import { createPanel, fetchPanels, renameCustomPanel, selectPanelList } from './redux/panel_slice'; +import { + createPanel, + fetchPanels, + newPanelTemplate, + renameCustomPanel, + selectPanelList, +} from './redux/panel_slice'; /* * "CustomPanelTable" module, used to view all the saved panels @@ -65,7 +71,6 @@ import { createPanel, fetchPanels, renameCustomPanel, selectPanelList } from './ interface Props { loading: boolean; - createCustomPanel: (newCustomPanelName: string) => void; setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; parentBreadcrumbs: EuiBreadcrumb[]; cloneCustomPanel: (newCustomPanelName: string, customPanelId: string) => void; @@ -75,7 +80,6 @@ interface Props { export const CustomPanelTable = ({ loading, - createCustomPanel, setBreadcrumbs, parentBreadcrumbs, cloneCustomPanel, @@ -118,7 +122,8 @@ export const CustomPanelTable = ({ }; const onCreate = async (newCustomPanelName: string) => { - createCustomPanel(newCustomPanelName); + const newPanel = newPanelTemplate(newCustomPanelName); + dispatch(createPanel(newPanel)); closeModal(); }; diff --git a/public/components/custom_panels/custom_panel_view.tsx b/public/components/custom_panels/custom_panel_view.tsx index 22a0d33d1..2bf984adf 100644 --- a/public/components/custom_panels/custom_panel_view.tsx +++ b/public/components/custom_panels/custom_panel_view.tsx @@ -213,19 +213,10 @@ export const CustomPanelView = (props: CustomPanelViewProps) => { }; const onDatePickerChange = (timeProps: OnTimeChangeProps) => { - onTimeChange( - timeProps.start, - timeProps.end, - recentlyUsedRanges, - setRecentlyUsedRanges, - setStartTime, - setEndTime - ); + const { updatedRanges } = onTimeChange(timeProps.start, timeProps.end, recentlyUsedRanges); setStartTime(timeProps.start); setEndTime(timeProps.end); - dispatch(updatePanel({ ...panel, timeRange: { from: timeProps.start, to: timeProps.end } })); - - setRecentlyUsedRanges(updatedRanges.slice(0, 9)); + setRecentlyUsedRanges(updatedRanges); onRefreshFilters(timeProps.start, timeProps.end); }; diff --git a/public/components/custom_panels/custom_panel_view_so.tsx b/public/components/custom_panels/custom_panel_view_so.tsx index 3f6c61f30..dcaf61fc1 100644 --- a/public/components/custom_panels/custom_panel_view_so.tsx +++ b/public/components/custom_panels/custom_panel_view_so.tsx @@ -42,7 +42,7 @@ import { CUSTOM_PANELS_API_PREFIX, CUSTOM_PANELS_SAVED_OBJECT_TYPE, } from '../../../common/constants/custom_panels'; -import { CustomPanelType } from '../../../common/types/custom_panels'; +import { CustomPanelType, PanelType } from '../../../common/types/custom_panels'; import { PanelGridSO } from './panel_modules/panel_grid/panel_grid_so'; import { getCustomModal } from './helpers/modal_containers'; @@ -69,7 +69,10 @@ import { DeleteModal } from '../common/helpers/delete_modal'; import { VisaulizationFlyoutSO } from './panel_modules/visualization_flyout/visualization_flyout_so'; import { addVisualizationPanel } from './helpers/add_visualization_helper'; import { + clonePanel, + createPanel, fetchPanel, + newPanelTemplate, selectPanel, setPanel, setPanelEt, @@ -147,6 +150,7 @@ export const CustomPanelViewSO = (props: CustomPanelViewProps) => { const dispatch = useDispatch(); const panel = useSelector(selectPanel); + const [loading, setLoading] = useState(true); const [pplFilterValue, setPPLFilterValue] = useState(''); const [baseQuery, setBaseQuery] = useState(''); @@ -226,34 +230,6 @@ export const CustomPanelViewSO = (props: CustomPanelViewProps) => { showModal(); }; - const renameCustomPanel = (editedCustomPanelName: string) => { - if (!isNameValid(editedCustomPanelName)) { - setToast('Invalid Custom Panel name', 'danger'); - return Promise.reject(); - } - - const updatedPanel = { ...panel, name: editedCustomPanelName }; - - return coreRefs.savedObjectsClient - .update(CUSTOM_PANELS_SAVED_OBJECT_TYPE, panel.id, panel) - .then((res) => { - setOpenPanelName(editedCustomPanelName); - setToast(`Operational Panel successfully renamed into "${editedCustomPanelName}"`); - }) - .catch((err) => { - setToast( - 'Error renaming Operational Panel, please make sure you have the correct permission.', - 'danger' - ); - console.error(err.body.message); - }); - }; - const onRename = async (newCustomPanelName: string) => { - const newPanel = { ...panel, title: newCustomPanelName }; - dispatch(updatePanel(newPanel)); - closeModal(); - }; - const renamePanel = () => { setModalLayout( getCustomModal( @@ -271,13 +247,11 @@ export const CustomPanelViewSO = (props: CustomPanelViewProps) => { }; const onClone = async (newCustomPanelName: string) => { - cloneCustomPanel(newCustomPanelName, panelId).then((id: string) => { - window.location.assign(`${last(parentBreadcrumbs)!.href}${id}`); - }); + dispatch(clonePanel(panel, newCustomPanelName)); closeModal(); }; - const clonePanel = () => { + const clonePanelModal = () => { setModalLayout( getCustomModal( onClone, @@ -551,7 +525,7 @@ export const CustomPanelViewSO = (props: CustomPanelViewProps) => { 'data-test-subj': 'duplicatePanelContextMenuItem', onClick: () => { setPanelsMenuPopover(false); - clonePanel(); + clonePanelModal(); }, }, { @@ -567,20 +541,27 @@ export const CustomPanelViewSO = (props: CustomPanelViewProps) => { ]; // Fetch the custom panel on Initial Mount useEffect(() => { + setLoading(true); dispatch(fetchPanel(panelId)); }, []); // Toggle input type (disabled or not disabled) // Disabled when there no visualizations in panels or when the panel is in edit mode useEffect(() => { - checkDisabledInputs(); - }, [isEditing]); + !loading && checkDisabledInputs(); + }, [isEditing, loading]); // Build base query with all of the indices included in the current visualizations useEffect(() => { + if (loading) { + if (panel.id === props.panelId) setLoading(false); + else return; + } + checkDisabledInputs(); buildBaseQuery(); - }, [panel]); + setLoading(false); + }, [panel, loading]); // Edit the breadcrumb when panel name changes useEffect(() => { @@ -600,7 +581,9 @@ export const CustomPanelViewSO = (props: CustomPanelViewProps) => { chrome.setBreadcrumbs([...parentBreadcrumbs, ...newBreadcrumb]); }, [panelId, panel]); - return ( + return loading ? ( + <> + ) : (
diff --git a/public/components/custom_panels/helpers/utils.tsx b/public/components/custom_panels/helpers/utils.tsx index 16aa3e6bc..266939d09 100644 --- a/public/components/custom_panels/helpers/utils.tsx +++ b/public/components/custom_panels/helpers/utils.tsx @@ -515,3 +515,16 @@ export const displayVisualization = (metaData: any, data: any, type: string) => /> ); }; + +export const onTimeChange = ( + start: ShortDate, + end: ShortDate, + recentlyUsedRanges: DurationRange[] +) => { + const updatedRanges = recentlyUsedRanges.filter((recentlyUsedRange) => { + const isDuplicate = recentlyUsedRange.start === start && recentlyUsedRange.end === end; + return !isDuplicate; + }); + updatedRanges.unshift({ start, end }); + return { start, end, updatedRanges }; +}; diff --git a/public/components/custom_panels/home.tsx b/public/components/custom_panels/home.tsx index 0714b5630..6a88d5ced 100644 --- a/public/components/custom_panels/home.tsx +++ b/public/components/custom_panels/home.tsx @@ -30,9 +30,10 @@ import DSLService from '../../services/requests/dsl'; import PPLService from '../../services/requests/ppl'; import { CustomPanelTable } from './custom_panel_table'; import { CustomPanelView } from './custom_panel_view'; -import { CustomPanelViewSO } from './custom_panel_view_so'; import { isNameValid } from './helpers/utils'; -import { fetchPanels, uuidRx } from './redux/panel_slice'; +import { CustomPanelViewSO } from './custom_panel_view_so'; +import { coreRefs } from '../../framework/core_refs'; +import { deletePanel, fetchPanels, uuidRx } from './redux/panel_slice'; // import { ObjectFetcher } from '../common/objectFetcher'; @@ -95,62 +96,6 @@ export const Home = ({ window.location.assign(`${observabilityLogsID}#/explorer/${savedVisualizationId}`); }; - // Creates a new CustomPanel - const createCustomPanel = async (newCustomPanelName: string) => { - if (!isNameValid(newCustomPanelName)) { - setToast('Invalid Operational Panel name', 'danger'); - return; - } - - const newPanel: ObservabilityPanelAttrs = { - title: newCustomPanelName, - description: '', - dateCreated: new Date().getTime(), - dateModified: new Date().getTime(), - timeRange: { - to: 'now', - from: 'now-1d', - }, - queryFilter: { - query: '', - language: 'ppl', - }, - visualizations: [], - applicationId: '', - }; - - return coreSavedObjects.client - .create('observability-panel', newPanel, {}) - .then(async (res) => { - setToast(`Operational Panel "${newCustomPanelName}" successfully created!`); - window.location.assign(`${_.last(parentBreadcrumbs)!.href}${res.id}`); - }) - .catch((err) => { - setToast( - 'Please ask your administrator to enable Operational Panels for you.', - 'danger', - - Documentation - - ); - console.error('create error', err); - }); - }; - - const fetchSavedObjectPanel = async (id: string) => { - const soPanel = await coreRefs.savedObjectsClient?.get(CUSTOM_PANELS_SAVED_OBJECT_TYPE, id); - return savedObjectToCustomPanel(soPanel); - }; - - // Fetch Panel by id - const fetchLegacyPanel = async (id: string) => { - return http.get(`${CUSTOM_PANELS_API_PREFIX}/panels/${id}`); - // .then((res) => res.operationalPanel) - // .catch((err) => { - // console.error('Issue in fetching the operational panel to duplicate', err); - // }); - }; - const deletePanelSO = (customPanelIdList: string[]) => { const soPanelIds = customPanelIdList.filter((id) => id.match(uuidRx)); return Promise.all( @@ -280,7 +225,6 @@ export const Home = ({ return ( ({ + title: newName, + dateCreated: new Date().getTime(), + dateModified: new Date().getTime(), + visualizations: [], + queryFilter: { language: '', query: '' }, + timeRange: { from: 'now', to: 'now-1d' }, +}); + const initialState: InitialState = { id: '', - panel: { - id: '', - title: '', - visualizations: [], - dateCreated: 0, - dateModified: 0, - queryFilter: { language: '', query: '' }, - timeRange: { from: 'now', to: 'now-1d' }, - }, + panel: newPanelTemplate(''), panelList: [], + loadingFlag: false, }; export const panelSlice = createSlice({ @@ -59,7 +61,15 @@ export const { setPanel, setPanelList } = panelSlice.actions; export const panelReducer = panelSlice.reducer; -export const selectPanel = (rootState): CustomPanelType => rootState.customPanel.panel; +export const selectPanel = createSelector( + (rootState) => rootState.customPanel.panel, + (panel) => normalizedPanel(panel) +); + +const normalizedPanel = (panel): PanelType => ({ + ...newPanelTemplate(''), + ...panel, +}); export const selectPanelList = (rootState): CustomPanelType[] => rootState.customPanel.panelList; @@ -76,7 +86,6 @@ const fetchSavedObjectPanels$ = () => from(savedObjectPanelsClient.find()).pipe( mergeMap((res) => res.savedObjects), map(savedObjectToCustomPanel) - // tap((res) => console.log('panel', res)) ); const fetchObservabilityPanels$ = () => @@ -84,7 +93,6 @@ const fetchObservabilityPanels$ = () => mergeMap((res) => res), mergeMap((res) => res.panels as ObservabilityPanelAttrs[]), map((p: ObservabilityPanelAttrs) => ({ ...p, title: p.name, savedObject: false })) - // tap((res) => console.log('observability panels', res)) ); // Fetches all saved Custom Panels @@ -94,7 +102,6 @@ const fetchCustomPanels = async () => { fetchObservabilityPanels$() ).pipe( map((res) => { - console.log('fetchCustomPanels', res); return res as CustomPanelListType; }) ); @@ -104,14 +111,12 @@ const fetchCustomPanels = async () => { export const fetchPanels = () => async (dispatch, getState) => { const panels = await fetchCustomPanels(); - console.log('fetchPanels', { panels }); dispatch(setPanelList(panels)); }; export const fetchPanel = (id) => async (dispatch, getState) => { const soPanel = await savedObjectPanelsClient.get(id); const panel = savedObjectToCustomPanel(soPanel); - console.log('fetchPanel', panel); dispatch(setPanel(panel)); }; @@ -184,6 +189,26 @@ export const createPanel = (panel) => async (dispatch, getState) => { const newPanel = savedObjectToCustomPanel(newSOPanel); const panelList = getState().customPanel.panelList; dispatch(setPanelList([...panelList, newPanel])); + + window.location.replace(`#/${newPanel.id}`); +}; + +export const clonePanel = (panel, newPanelName) => async (dispatch, getState) => { + const { id, ...panelCopy } = { + ...panel, + title: newPanelName, + dateCreated: new Date().getTime(), + dateModified: new Date().getTime(), + } as PanelType; + + const newSOPanel = await savedObjectPanelsClient.create(panelCopy); + + const newPanel = savedObjectToCustomPanel(newSOPanel); + const panelList = getState().customPanel.panelList; + dispatch(setPanelList([...panelList, newPanel])); + dispatch(setPanel(newPanel)); + + window.location.replace(`#/${newPanel.id}`); }; const saveRenamedPanel = async (id, name) => {