[SecuritySolution] Reveal data quality entry on the side-nav in serverless (#180816)

## Summary

Test env 
serverless:
https://p.elstc.co/paste/4JjGBmXv#Yogkkphe2hFyBRWIhsAZ3ivsvAve4TJkS2BzOqPQp3w
~ESS:
https://p.elstc.co/paste/EsCQBV7V#P18h4HGLZkrPm66+KB2WVH6srVwDwXqVsciQDtI8vbc~


<img width="2559" alt="Screenshot 2024-04-17 at 13 48 08"
src="558075d9-f58a-458e-8006-7506ab432f5a">
<img width="2558" alt="Screenshot 2024-04-17 at 13 48 26"
src="7010e3c0-e0bb-4981-8a09-f478ed0fe018">


5776c9c2-2957-480e-8a2c-fdd687cdebc5




We can merge this PR once all the dependencies are merged.

dependencies:
1. https://github.com/elastic/kibana/pull/179666
2. https://github.com/elastic/elasticsearch-serverless/pull/1482
3. ~https://github.com/elastic/elasticsearch-serverless/pull/1705~
4. https://github.com/elastic/elasticsearch-serverless/pull/1753

Steps to verify:

0. Enable Docker
1. Start serverless ES with the commit where the stats api was merged:
```
yarn es serverless --projectType security --tag git-2eb724699bc0
```

start Kibana
```
yarn serverless-security --no-base-path
```

2. ~Endable the settings~ This step in no longer needed.
```
curl -u {YOUR_ELASTIC_USERNAME}:{ELASTIC_PASSWORD} -X PUT "localhost:9200/_cluster/settings?pretty" -H 'Content-Type: application/json' -d'
{
  "persistent" : {
    "metering.index-info-task.enabled" : true
  }
}
```
3. In Kibana dev tool, add some testing data:
```
PUT my-test-index

PUT my-test-index/_mapping
{
"properties": {
  "@timestamp": {
    "type": "date"
  },
  "agent.type": {
    "type": "constant_keyword"
  },
  "event.category": {
    "type": "constant_keyword"
  }
}
}

POST my-test-index/_doc
{
"@timestamp": "2024-04-22T09:41:49.668Z",
"host": {
  "name": "foo"
},
"event": {
  "category": "an_invalid_category"
},
"some.field": "this",
"source": {
  "port": 90210,
  "ip": "10.1.2.3"
}
}

POST my-test-index/_doc
{
"@timestamp": "2024-04-22T09:42:22.123Z",
"host": {
  "name": "bar"
},
"event": {
  "category": "an_invalid_category"
},
"some.field": "space",
"source": {
  "port": 867,
  "ip": "10.9.8.7"
}
}
```

5. Go to
/app/management/kibana/dataViews/dataView/security-solution-default ,
and add `my-test-*` to default index pattern
6. Go to `/app/security/data_quality` and verify that there's no doc
size in the dashboard. Dashboard and treemap render correctly.

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Sergi Massaneda <sergi.massaneda@elastic.co>
This commit is contained in:
Angela Chuang 2024-04-22 18:39:31 +01:00 committed by GitHub
parent 8aa7606a21
commit 98c596a5e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 50 additions and 43 deletions

View file

@ -270,7 +270,7 @@ const IndexPropertiesComponent: React.FC<Props> = ({
};
updatePatternRollup(updatedRollup);
if (indexId && requestTime != null && requestTime > 0 && partitionedFieldMetadata) {
if (indexName && requestTime != null && requestTime > 0 && partitionedFieldMetadata) {
const report = {
batchId: uuidv4(),
ecsVersion: EcsVersion,

View file

@ -22,6 +22,7 @@ import {
getShowPagination,
getSummaryTableColumns,
getSummaryTableILMPhaseColumn,
getSummaryTableSizeInBytesColumn,
getToggleButtonId,
IndexSummaryTableItem,
} from './helpers';
@ -468,6 +469,25 @@ describe('helpers', () => {
});
});
describe('getSummaryTableSizeInBytesColumn', () => {
test('it returns the expected column configuration when `isILMAvailable` is true', () => {
const column = getSummaryTableSizeInBytesColumn({
isILMAvailable: true,
formatBytes: jest.fn(),
});
expect(column.length).toEqual(1);
expect(column[0].name).toEqual('Size');
});
test('it returns an emptry array when `isILMAvailable` is false', () => {
const column = getSummaryTableSizeInBytesColumn({
isILMAvailable: false,
formatBytes: jest.fn(),
});
expect(column.length).toEqual(0);
});
});
describe('ilmPhase column render()', () => {
test('it renders the expected ilmPhase badge content', () => {
const columns = getSummaryTableColumns({

View file

@ -121,23 +121,29 @@ export const getSummaryTableILMPhaseColumn = (
: [];
export const getSummaryTableSizeInBytesColumn = ({
isILMAvailable,
formatBytes,
}: {
isILMAvailable: boolean;
formatBytes: (value: number | undefined) => string;
}): Array<EuiBasicTableColumn<IndexSummaryTableItem>> => [
{
field: 'sizeInBytes',
name: i18n.SIZE,
render: (_, { sizeInBytes }) =>
Number.isInteger(sizeInBytes) ? (
<EuiToolTip content={INDEX_SIZE_TOOLTIP}>
<span data-test-subj="sizeInBytes">{formatBytes(sizeInBytes)}</span>
</EuiToolTip>
) : null,
sortable: true,
truncateText: false,
},
];
}): Array<EuiBasicTableColumn<IndexSummaryTableItem>> =>
isILMAvailable
? [
{
field: 'sizeInBytes',
name: i18n.SIZE,
render: (_, { sizeInBytes }) =>
Number.isInteger(sizeInBytes) ? (
<EuiToolTip content={INDEX_SIZE_TOOLTIP}>
<span data-test-subj="sizeInBytes">{formatBytes(sizeInBytes)}</span>
</EuiToolTip>
) : null,
sortable: true,
truncateText: false,
},
]
: [];
export const getSummaryTableColumns = ({
formatBytes,
formatNumber,
@ -247,18 +253,7 @@ export const getSummaryTableColumns = ({
truncateText: false,
},
...getSummaryTableILMPhaseColumn(isILMAvailable),
{
field: 'sizeInBytes',
name: i18n.SIZE,
render: (_, { sizeInBytes }) =>
Number.isInteger(sizeInBytes) ? (
<EuiToolTip content={INDEX_SIZE_TOOLTIP}>
<span data-test-subj="sizeInBytes">{formatBytes(sizeInBytes)}</span>
</EuiToolTip>
) : null,
sortable: true,
truncateText: false,
},
...getSummaryTableSizeInBytesColumn({ isILMAvailable, formatBytes }),
{
field: 'checkedAt',
name: i18n.LAST_CHECK,

View file

@ -199,7 +199,7 @@ export interface MeteringIndicesStatsResponse {
export type DataQualityIndexCheckedParams = DataQualityCheckAllCompletedParams & {
errorCount?: number;
ilmPhase?: string;
indexId?: string;
indexId?: string | null;
indexName: string;
sameFamilyFields?: string[];
unallowedMappingFields?: string[];

View file

@ -185,10 +185,8 @@ export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRoll
const indexId = getIndexId({ indexName, stats });
if (
indexId != null &&
stats &&
results &&
ilmExplain &&
requestTime != null &&
requestTime > 0 &&
partitionedFieldMetadata
@ -197,7 +195,7 @@ export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRoll
batchId,
ecsVersion: EcsVersion,
errorCount: error ? 1 : 0,
ilmPhase: getIlmPhase(ilmExplain[indexName], isILMAvailable),
ilmPhase: getIlmPhase(ilmExplain?.[indexName], isILMAvailable),
indexId,
indexName,
isCheckAll: true,

View file

@ -24,9 +24,9 @@ export const resultsFieldMap: FieldMap = {
unallowedMappingFields: { type: 'keyword', required: true, array: true },
unallowedValueFields: { type: 'keyword', required: true, array: true },
sizeInBytes: { type: 'long', required: true },
ilmPhase: { type: 'keyword', required: true },
ilmPhase: { type: 'keyword', required: false },
markdownComments: { type: 'text', required: true, array: true },
ecsVersion: { type: 'keyword', required: true },
indexId: { type: 'keyword', required: true },
indexId: { type: 'keyword', required: false },
error: { type: 'text', required: false },
};

View file

@ -22,16 +22,16 @@ const ResultDocumentInterface = t.interface({
unallowedMappingFields: t.array(t.string),
unallowedValueFields: t.array(t.string),
sizeInBytes: t.number,
ilmPhase: t.string,
markdownComments: t.array(t.string),
ecsVersion: t.string,
indexId: t.string,
error: t.union([t.string, t.null]),
});
const ResultDocumentOptional = t.partial({
indexPattern: t.string,
checkedBy: t.string,
indexId: t.string,
ilmPhase: t.string,
});
export const ResultDocument = t.intersection([ResultDocumentInterface, ResultDocumentOptional]);

View file

@ -6,7 +6,7 @@
*/
import { SecurityPageName } from '@kbn/security-solution-navigation';
import { cloneDeep, find, remove } from 'lodash';
import { cloneDeep, remove } from 'lodash';
import type { AppLinkItems, LinkItem } from '../../../common/links/types';
import { createInvestigationsLinkFromTimeline } from './sections/investigations_links';
import { mlAppLink } from './sections/ml_links';
@ -26,12 +26,6 @@ export const solutionAppLinksSwitcher = (appLinks: AppLinkItems): AppLinkItems =
solutionAppLinks.push(createInvestigationsLinkFromTimeline(timelineLinkItem));
}
// Remove data quality dashboard link
const dashboardLinkItem = find(solutionAppLinks, { id: SecurityPageName.dashboards });
if (dashboardLinkItem && dashboardLinkItem.links) {
remove(dashboardLinkItem.links, { id: SecurityPageName.dataQuality });
}
// Remove manage link
const [manageLinkItem] = remove(solutionAppLinks, { id: SecurityPageName.administration });

View file

@ -11,7 +11,7 @@ import type { TelemetryEventTypes } from '../../constants';
export type ReportDataQualityIndexCheckedParams = ReportDataQualityCheckAllCompletedParams & {
errorCount?: number;
ilmPhase?: string;
indexId?: string;
indexId?: string | null;
indexName: string;
sameFamilyFields?: string[];
unallowedMappingFields?: string[];