-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create a new dashboards page that makes use of embeddable dashboards …
…by reference (#6817) * First iteration of poc * first iteration of poc * Working on how to load dashboards * Logic to check if the dashboard exists and if not, create it * borrar metodo existsOrCreateDashboard en deshuso al reformular logica * logic for creating dashboards * show dashboard in vulnerabilities * dashboard component for vuls * Adding the use of the dashboard component in the vulnerabilities module * fixes in the two use cases * change in the visualization * update dashboard and visualization creation logic * update the method name to create index pattern * maintain visualization creation logic * update request * Using the endpoint to import and consume files from the backend * organizing code * complete logic in operation * complete logic in operation * delete folders and organize code * rename file * correct import * clean code * delete file * Code restructure * Code restructure * Code restructure * Code restructure --------- Co-authored-by: Federico Rodriguez <[email protected]>
- Loading branch information
1 parent
0cf78ba
commit f466e22
Showing
8 changed files
with
493 additions
and
2 deletions.
There are no files selected for viewing
182 changes: 182 additions & 0 deletions
182
plugins/main/public/components/common/hocs/validate-states-index-pattern-and-dashboards.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
import React from 'react'; | ||
import { compose } from 'redux'; | ||
import { connect } from 'react-redux'; | ||
import { withGuardAsync } from '.'; | ||
import { getSavedObjects } from '../../../kibana-services'; | ||
import { SavedObject } from '../../../react-services'; | ||
import { NOT_TIME_FIELD_NAME_INDEX_PATTERN } from '../../../../common/constants'; | ||
import { EuiButton, EuiEmptyPrompt, EuiLink } from '@elastic/eui'; | ||
import { webDocumentationLink } from '../../../../common/services/web_documentation'; | ||
import { vulnerabilityDetection } from '../../../utils/applications'; | ||
import { LoadingSpinnerDataSource } from '../loading/loading-spinner-data-source'; | ||
import NavigationService from '../../../react-services/navigation-service'; | ||
|
||
const INDEX_PATTERN_CREATION_NO_INDEX = 'INDEX_PATTERN_CREATION_NO_INDEX'; | ||
|
||
async function checkExistenceIndexPattern(indexPatternID) { | ||
return await getSavedObjects().client.get('index-pattern', indexPatternID); | ||
} | ||
|
||
async function checkExistenceIndices(indexPatternId) { | ||
try { | ||
const fields = await SavedObject.getIndicesFields(indexPatternId); | ||
return { exist: true, fields }; | ||
} catch (error) { | ||
return { exist: false }; | ||
} | ||
} | ||
|
||
async function createIndexPattern(indexPattern, fields) { | ||
try { | ||
await SavedObject.createSavedObject( | ||
'index-pattern', | ||
indexPattern, | ||
{ | ||
attributes: { | ||
title: indexPattern, | ||
timeFieldName: NOT_TIME_FIELD_NAME_INDEX_PATTERN, | ||
}, | ||
}, | ||
fields, | ||
); | ||
await SavedObject.validateIndexPatternSavedObjectCanBeFound([indexPattern]); | ||
} catch (error) { | ||
return { error: error.message }; | ||
} | ||
} | ||
|
||
export async function createDashboard() { | ||
try { | ||
// Create the dashboard | ||
const result = await SavedObject.createSavedObjectDashboard(); | ||
|
||
let targetDashboard = result?.data?.successResults?.find( | ||
dashboard => dashboard.id === '94febc80-55a2-11ef-a580-5b5ba88681be', | ||
); | ||
|
||
if (result) { | ||
return targetDashboard; | ||
} else { | ||
console.error('Failed to create dashboard.'); | ||
return null; | ||
} | ||
} catch (error) { | ||
console.error('Error creating dashboard:', error); | ||
return null; | ||
} | ||
} | ||
|
||
export async function validateVulnerabilitiesStateDataSources({ | ||
vulnerabilitiesStatesindexPatternID: indexPatternID, | ||
}) { | ||
try { | ||
// Check the existence of related index pattern | ||
const existIndexPattern = await checkExistenceIndexPattern(indexPatternID); | ||
let indexPattern = existIndexPattern; | ||
|
||
// If the index pattern does not exist, then check the existence of index | ||
if (existIndexPattern?.error?.statusCode === 404) { | ||
// Check the existence of indices | ||
const { exist, fields } = await checkExistenceIndices(indexPatternID); | ||
|
||
if (!exist) { | ||
return { | ||
ok: true, | ||
data: { | ||
error: { | ||
title: | ||
'Vulnerability detection seems to be disabled or has a problem', | ||
type: INDEX_PATTERN_CREATION_NO_INDEX, | ||
}, | ||
}, | ||
}; | ||
} | ||
// If some index matches the index pattern, then create the index pattern | ||
const resultCreateIndexPattern = await createIndexPattern( | ||
indexPatternID, | ||
fields, | ||
); | ||
if (resultCreateIndexPattern?.error) { | ||
return { | ||
ok: true, | ||
data: { | ||
error: { | ||
title: 'There was a problem creating the index pattern', | ||
message: resultCreateIndexPattern?.error, | ||
}, | ||
}, | ||
}; | ||
} | ||
/* WORKAROUND: Redirect to the root of Vulnerabilities Detection application that should | ||
redirects to the Dashboard tab. We want to redirect to this view, because we need the | ||
component is visible (visualizations) to ensure the process that defines the filters for the | ||
Events tab is run when the Dashboard component is unmounted. This workaround solves a | ||
problem in the Events tabs related there are no implicit filters when accessing if the HOC | ||
that protect the view is passed. | ||
*/ | ||
NavigationService.getInstance().navigateToApp(vulnerabilityDetection.id); | ||
} | ||
return { | ||
ok: false, | ||
data: { indexPattern }, | ||
}; | ||
} catch (error) { | ||
return { | ||
ok: true, | ||
data: { | ||
error: { title: 'There was a problem', message: error.message }, | ||
}, | ||
}; | ||
} | ||
} | ||
|
||
const errorPromptBody = { | ||
INDEX_PATTERN_CREATION_NO_INDEX: ( | ||
<p> | ||
Please check the cluster status. Also, you can check the{' '} | ||
<EuiLink | ||
href={webDocumentationLink( | ||
'user-manual/capabilities/vulnerability-detection/index.html', | ||
)} | ||
target='_blank' | ||
rel='noopener noreferrer' | ||
external | ||
> | ||
vulnerability detection documentation. | ||
</EuiLink> | ||
</p> | ||
), | ||
}; | ||
|
||
export const PromptCheckIndex = props => { | ||
const { refresh } = props; | ||
const { title, message } = props?.error; | ||
const body = errorPromptBody?.[props?.error?.type] || <p>{message}</p>; | ||
|
||
return ( | ||
<EuiEmptyPrompt | ||
iconType='alert' | ||
title={<h2>{title}</h2>} | ||
body={body} | ||
actions={ | ||
<EuiButton color='primary' fill onClick={refresh}> | ||
Refresh | ||
</EuiButton> | ||
} | ||
/> | ||
); | ||
}; | ||
|
||
const mapStateToProps = state => ({ | ||
vulnerabilitiesStatesindexPatternID: | ||
state.appConfig.data['vulnerabilities.pattern'], | ||
}); | ||
|
||
export const withVulnerabilitiesStateDataSource = compose( | ||
connect(mapStateToProps), | ||
withGuardAsync( | ||
validateVulnerabilitiesStateDataSources, | ||
({ error, check }) => <PromptCheckIndex error={error} refresh={check} />, | ||
() => <LoadingSpinnerDataSource />, | ||
), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
161 changes: 161 additions & 0 deletions
161
plugins/main/public/components/overview/poc/dashboards/overview/dashboard.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import React, { useState, useEffect } from 'react'; | ||
import { getPlugins } from '../../../../../kibana-services'; | ||
import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; | ||
import { withErrorBoundary } from '../../../../common/hocs'; | ||
import { compose } from 'redux'; | ||
import { | ||
withVulnerabilitiesStateDataSource, | ||
createDashboard, | ||
} from '../../../../common/hocs/validate-states-index-pattern-and-dashboards'; | ||
import { SavedObject } from '../../../../../react-services'; | ||
import { EuiButton } from '@elastic/eui'; | ||
|
||
const DashboardByRenderer = | ||
getPlugins().dashboard.DashboardContainerByValueRenderer; | ||
|
||
const transformPanelsJSON = ({ panelsJSON, references }) => | ||
Object.fromEntries( | ||
JSON.parse(panelsJSON).map(({ gridData, panelIndex, panelRefName }) => [ | ||
panelIndex, | ||
{ | ||
gridData, | ||
type: 'visualization', | ||
explicitInput: { | ||
id: panelIndex, | ||
savedObjectId: references.find(({ name }) => name === panelRefName) | ||
.id, | ||
}, | ||
}, | ||
]), | ||
); | ||
|
||
const transform = spec => { | ||
const options = JSON.parse(spec.attributes.optionsJSON); | ||
return { | ||
title: spec.attributes.title, | ||
panels: transformPanelsJSON({ | ||
panelsJSON: spec.attributes.panelsJSON, | ||
references: spec.references, | ||
}), | ||
useMargins: options.useMargins, | ||
hidePanelTitles: options.hidePanelTitles, | ||
description: spec.attributes.description, | ||
id: spec.id, | ||
}; | ||
}; | ||
|
||
export const DashboardSavedObject = ({ savedObjectId }) => { | ||
const [dashboardSpecForComponent, setDashboardSpecForComponent] = | ||
useState(null); | ||
|
||
useEffect(() => { | ||
(async () => { | ||
try { | ||
const { data } = await SavedObject.getDashboardById(savedObjectId); | ||
const dashboardSpecRenderer = transform(data); | ||
setDashboardSpecForComponent(dashboardSpecRenderer); | ||
} catch (error) { | ||
console.error('Error fetching dashboard:', error); | ||
} | ||
})(); | ||
}, [savedObjectId]); | ||
|
||
return dashboardSpecForComponent ? ( | ||
<DashboardByRenderer | ||
input={{ | ||
...dashboardSpecForComponent, | ||
viewMode: ViewMode.VIEW, | ||
isFullScreenMode: false, | ||
filters: [], | ||
query: '', | ||
refreshConfig: { | ||
pause: false, | ||
value: 15, | ||
}, | ||
}} | ||
/> | ||
) : ( | ||
<p>Loading dashboard...</p> | ||
); | ||
}; | ||
|
||
const DashboardComponent = () => { | ||
const [idDashboard, setIdDashboard] = useState(null); | ||
const [isLoading, setIsLoading] = useState(true); | ||
const [previousDashboardState, setPreviousDashboardState] = useState(null); | ||
|
||
useEffect(() => { | ||
(async () => { | ||
try { | ||
const dashboards = await SavedObject.getAllDashboards(); | ||
let targetDashboard = dashboards.data.saved_objects.find( | ||
dashboard => dashboard.id === '94febc80-55a2-11ef-a580-5b5ba88681be', | ||
); | ||
|
||
if (!targetDashboard) { | ||
const newDashboardId = await createDashboard(); | ||
if (newDashboardId) { | ||
targetDashboard = { id: newDashboardId.id }; | ||
setIdDashboard(targetDashboard.id); | ||
} | ||
} else { | ||
setIdDashboard(targetDashboard.id); | ||
setPreviousDashboardState(targetDashboard); | ||
} | ||
} catch (error) { | ||
console.error('Error processing dashboards:', error); | ||
} finally { | ||
setIsLoading(false); | ||
} | ||
})(); | ||
}, [idDashboard]); | ||
|
||
const handleRestart = async () => { | ||
try { | ||
const savedObjectId = '94febc80-55a2-11ef-a580-5b5ba88681be'; | ||
const dashboardChanged = await SavedObject.getDashboardById( | ||
savedObjectId, | ||
); | ||
|
||
const changed = | ||
JSON.stringify(dashboardChanged) !== | ||
JSON.stringify(previousDashboardState); | ||
|
||
if (changed) { | ||
if ( | ||
window.confirm( | ||
'The dashboard has changed. Do you want to revert to the previous version?', | ||
) | ||
) { | ||
await createDashboard(); // Restore the dashboard to its previous state | ||
setIdDashboard(null); // Force re-rendering | ||
setTimeout(() => setIdDashboard(savedObjectId), 0); // Reassign ID to render the restored dashboard | ||
} | ||
} else { | ||
alert('No changes detected in the dashboard.'); | ||
} | ||
} catch (error) { | ||
console.error('Error processing dashboard changes:', error); | ||
} | ||
}; | ||
|
||
return ( | ||
<> | ||
{idDashboard ? ( | ||
<> | ||
<EuiButton onClick={handleRestart}>Restart</EuiButton> | ||
<DashboardSavedObject key={idDashboard} savedObjectId={idDashboard} /> | ||
</> | ||
) : isLoading ? ( | ||
<p>Loading dashboard...</p> | ||
) : ( | ||
<p>No matching dashboard found.</p> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
export const DashboardPOCByReference = compose( | ||
withErrorBoundary, | ||
withVulnerabilitiesStateDataSource, | ||
)(DashboardComponent); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.