diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts index 6cd55be21f984a..919dcdb354980b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts @@ -15,11 +15,23 @@ describe('buildSearchUIConfig', () => { it('builds a configuration object for Search UI', () => { const connector = {}; const schema = { - foo: SchemaType.Text, - bar: SchemaType.Number, + foo: { + type: SchemaType.Text, + capabilities: { + snippet: true, + facet: true, + }, + }, + bar: { + type: SchemaType.Number, + capabilities: { + snippet: false, + facet: false, + }, + }, }; const fields = { - filterFields: ['fieldA', 'fieldB'], + filterFields: ['foo', 'bar'], sortFields: [], }; @@ -32,13 +44,9 @@ describe('buildSearchUIConfig', () => { sortField: 'id', }, searchQuery: { - disjunctiveFacets: ['fieldA', 'fieldB'], + disjunctiveFacets: ['foo'], facets: { - fieldA: { - size: 30, - type: 'value', - }, - fieldB: { + foo: { size: 30, type: 'value', }, @@ -50,7 +58,6 @@ describe('buildSearchUIConfig', () => { foo: { raw: {}, snippet: { - fallback: true, size: 300, }, }, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts index 1edcc3cdfc27a6..97a8956dee75bf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts @@ -7,26 +7,34 @@ import type { APIConnector, SortDirection } from '@elastic/search-ui'; -import { Schema } from '../../../../shared/schema/types'; +import { SchemaType, AdvancedSchema } from '../../../../shared/schema/types'; import { Fields } from './types'; export const buildSearchUIConfig = ( apiConnector: APIConnector, - schema: Schema, + schema: AdvancedSchema, fields: Fields, initialState = { sortDirection: 'desc' as SortDirection, sortField: 'id' } ) => { - const facets = fields.filterFields.reduce((facetsConfig, fieldName) => { - // Geolocation fields do not support value facets https://www.elastic.co/guide/en/app-search/current/facets.html - if (schema[fieldName] === 'geolocation') { - return facetsConfig; - } - return { - ...facetsConfig, - [fieldName]: { type: 'value', size: 30 }, - }; - }, {}); + const facets = fields.filterFields + .filter((fieldName) => !!schema[fieldName] && schema[fieldName].type !== SchemaType.Geolocation) + .filter((fieldName) => !!schema[fieldName].capabilities.facet) + .reduce((facetsConfig, fieldName) => { + return { + ...facetsConfig, + [fieldName]: { type: 'value', size: 30 }, + }; + }, {}); + + const resultFields = Object.entries(schema) + .filter(([, schemaField]) => schemaField.type !== SchemaType.Nested) + .reduce((acc, [fieldName, schemaField]) => { + if (schemaField.capabilities.snippet) { + return { ...acc, [fieldName]: { raw: {}, snippet: { size: 300 } } }; + } + return { ...acc, [fieldName]: { raw: {} } }; + }, {}); return { alwaysSearchOnInitialLoad: true, @@ -34,25 +42,9 @@ export const buildSearchUIConfig = ( trackUrlState: false, initialState, searchQuery: { - disjunctiveFacets: fields.filterFields, + disjunctiveFacets: Object.keys(facets), facets, - result_fields: Object.keys(schema).reduce((acc: { [key: string]: object }, key: string) => { - if (schema[key] === 'text') { - // Only text fields support snippets - acc[key] = { - snippet: { - size: 300, - fallback: true, - }, - raw: {}, - }; - } else { - acc[key] = { - raw: {}, - }; - } - return acc; - }, {}), + result_fields: resultFields, }, }; }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.tsx index 534751465f3026..299a840cf8919b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.tsx @@ -52,14 +52,20 @@ export const CustomizationModal: React.FC = ({ sortFields.map(fieldNameToComboBoxOption) ); - const engineSchema = engine.schema || {}; + const schema = engine.advancedSchema || {}; const selectableFilterFields = useMemo( - () => Object.keys(engineSchema).map(fieldNameToComboBoxOption), - [engineSchema] + () => + Object.keys(schema) + .filter((fieldName) => schema[fieldName].capabilities.filter) + .map(fieldNameToComboBoxOption), + [schema] ); const selectableSortFields = useMemo( - () => Object.keys(engineSchema).map(fieldNameToComboBoxOption), - [engineSchema] + () => + Object.keys(schema) + .filter((fieldName) => schema[fieldName].capabilities.sort) + .map(fieldNameToComboBoxOption), + [schema] ); return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx index 397bcd2e14f83d..441841364489c5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx @@ -100,7 +100,7 @@ export const SearchExperience: React.FC = () => { const searchProviderConfig = buildSearchUIConfig( connector, - engine.schema || {}, + engine.advancedSchema || {}, fields, initialState ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts index acd53a89a7f1f4..d82b4f5d4b055d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts @@ -41,6 +41,19 @@ describe('EngineLogic', () => { isMeta: false, invalidBoosts: false, schema: { test: SchemaType.Text }, + advancedSchema: { + test: { + type: SchemaType.Text, + capabilities: { + fulltext: true, + filter: true, + facet: true, + sort: true, + snippet: true, + boost: true, + }, + }, + }, apiTokens: [], apiKey: 'some-key', adaptive_relevance_suggestions_active: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts index c9214e3c6b0b6d..1b4aa08980ef51 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts @@ -5,7 +5,12 @@ * 2.0. */ -import { Schema, SchemaConflicts, IIndexingStatus } from '../../../shared/schema/types'; +import { + Schema, + SchemaConflicts, + IIndexingStatus, + AdvancedSchema, +} from '../../../shared/schema/types'; import { ApiToken } from '../credentials/types'; export enum EngineTypes { @@ -47,6 +52,7 @@ export interface EngineDetails extends Engine { apiKey: string; elasticsearchIndexName?: string; schema: Schema; + advancedSchema: AdvancedSchema; schemaConflicts?: SchemaConflicts; unconfirmedFields?: string[]; activeReindexJob?: IIndexingStatus; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_token.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_token.tsx index 353d303da2b5a8..d2579d61419e59 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_token.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_token.tsx @@ -26,6 +26,7 @@ const fieldTypeToTokenMap = { [InternalSchemaType.Location]: 'tokenGeo', [SchemaType.Date]: 'tokenDate', [InternalSchemaType.Date]: 'tokenDate', + [InternalSchemaType.Nested]: 'tokenNested', }; export const ResultToken: React.FC = ({ fieldType }) => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/schema/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/schema/types.ts index 58ad584fd5b60c..6e206ddc719718 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/schema/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/schema/types.ts @@ -14,6 +14,7 @@ export enum SchemaType { Number = 'number', Geolocation = 'geolocation', Date = 'date', + Nested = 'nested', } // Certain API endpoints will use these internal type names, which map to the external names above export enum InternalSchemaType { @@ -21,6 +22,7 @@ export enum InternalSchemaType { Float = 'float', Location = 'location', Date = 'date', + Nested = 'nested', } export type Schema = Record; @@ -62,3 +64,20 @@ export interface FieldCoercionError { error: string; } export type FieldCoercionErrors = Record; + +export interface SchemaFieldCapabilities { + fulltext?: boolean; + filter?: boolean; + facet?: boolean; + sort?: boolean; + snippet?: boolean; + boost?: boolean; +} + +export interface AdvancedSchemaField { + type: SchemaType; + nestedPath?: string; + capabilities: SchemaFieldCapabilities; +} + +export type AdvancedSchema = Record; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx index cb9001faf11d21..485bf31ca77a83 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx @@ -54,6 +54,7 @@ export const TableRowActions: React.FunctionComponent<{ onClick={(event) => { onAddRemoveTagsClick((event.target as Element).closest('button')!); }} + disabled={!agent.active} key="addRemoveTags" > { - const { - page = 1, - perPage = 20, - sortField = 'enrolled_at', - sortOrder = 'desc', - kuery, - showInactive = false, - showUpgradeable, - searchAfter, - } = options; - const { pitId } = options; - const filters = []; - - if (kuery && kuery !== '') { - filters.push(kuery); - } - - if (showInactive === false) { - filters.push(ACTIVE_AGENT_CONDITION); - } - - const kueryNode = _joinFilters(filters); - const body = kueryNode ? { query: toElasticsearchQuery(kueryNode) } : {}; - - const queryAgents = async (from: number, size: number) => { - return esClient.search({ - from, - size, - track_total_hits: true, - rest_total_hits_as_int: true, - body: { - ...body, - sort: [{ [sortField]: { order: sortOrder } }], - }, - pit: { - id: pitId, - keep_alive: '1m', - }, - ...(searchAfter ? { search_after: searchAfter, from: 0 } : {}), - }); - }; - - const res = await queryAgents((page - 1) * perPage, perPage); - - let agents = res.hits.hits.map(searchHitToAgent); - let total = res.hits.total as number; - - // filtering for a range on the version string will not work, - // nor does filtering on a flattened field (local_metadata), so filter here - if (showUpgradeable) { - // query all agents, then filter upgradeable, and return the requested page and correct total - // if there are more than SO_SEARCH_LIMIT agents, the logic falls back to same as before - if (total < SO_SEARCH_LIMIT) { - const response = await queryAgents(0, SO_SEARCH_LIMIT); - agents = response.hits.hits - .map(searchHitToAgent) - .filter((agent) => isAgentUpgradeable(agent, appContextService.getKibanaVersion())); - total = agents.length; - const start = (page - 1) * perPage; - agents = agents.slice(start, start + perPage); - } else { - agents = agents.filter((agent) => - isAgentUpgradeable(agent, appContextService.getKibanaVersion()) - ); - } - } - - return { - agents, - total, - page, - perPage, - }; -} - -export async function openAgentsPointInTime(esClient: ElasticsearchClient): Promise { + index: string = AGENTS_INDEX +): Promise { const pitKeepAlive = '10m'; const pitRes = await esClient.openPointInTime({ - index: AGENTS_INDEX, + index, keep_alive: pitKeepAlive, }); return pitRes.id; } -export async function closeAgentsPointInTime(esClient: ElasticsearchClient, pitId: string) { +export async function closePointInTime(esClient: ElasticsearchClient, pitId: string) { try { await esClient.closePointInTime({ id: pitId }); } catch (error) { @@ -205,6 +120,8 @@ export async function getAgentsByKuery( showInactive: boolean; sortField?: string; sortOrder?: 'asc' | 'desc'; + pitId?: string; + searchAfter?: SortResults; } ): Promise<{ agents: Agent[]; @@ -220,6 +137,8 @@ export async function getAgentsByKuery( kuery, showInactive = false, showUpgradeable, + searchAfter, + pitId, } = options; const filters = []; @@ -240,16 +159,26 @@ export async function getAgentsByKuery( : []; const queryAgents = async (from: number, size: number) => esClient.search({ - index: AGENTS_INDEX, from, size, track_total_hits: true, rest_total_hits_as_int: true, - ignore_unavailable: true, body: { ...body, sort: [{ [sortField]: { order: sortOrder } }, ...secondarySort], }, + ...(pitId + ? { + pit: { + id: pitId, + keep_alive: '1m', + }, + } + : { + index: AGENTS_INDEX, + ignore_unavailable: true, + }), + ...(pitId && searchAfter ? { search_after: searchAfter, from: 0 } : {}), }); const res = await queryAgents((page - 1) * perPage, perPage); @@ -295,11 +224,11 @@ export async function processAgentsInBatches( includeSuccess: boolean ) => Promise<{ items: BulkActionResult[] }> ): Promise<{ items: BulkActionResult[] }> { - const pitId = await openAgentsPointInTime(esClient); + const pitId = await openPointInTime(esClient); const perPage = options.batchSize ?? SO_SEARCH_LIMIT; - const res = await getAgentsByKueryPit(esClient, { + const res = await getAgentsByKuery(esClient, { ...options, page: 1, perPage, @@ -315,7 +244,7 @@ export async function processAgentsInBatches( while (allAgentsProcessed < res.total) { const lastAgent = currentAgents[currentAgents.length - 1]; - const nextPage = await getAgentsByKueryPit(esClient, { + const nextPage = await getAgentsByKuery(esClient, { ...options, page: 1, perPage, @@ -328,7 +257,7 @@ export async function processAgentsInBatches( allAgentsProcessed += currentAgents.length; } - await closeAgentsPointInTime(esClient, pitId); + await closePointInTime(esClient, pitId); return results; } diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index c86e6e1df274fc..444108ec24ad5d 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -16,7 +16,15 @@ import type { AgentStatus } from '../../types'; import { AgentStatusKueryHelper } from '../../../common/services'; import { FleetUnauthorizedError } from '../../errors'; -import { getAgentById, getAgentsByKuery, removeSOAttributes } from './crud'; +import { appContextService } from '../app_context'; + +import { + closePointInTime, + getAgentById, + getAgentsByKuery, + openPointInTime, + removeSOAttributes, +} from './crud'; const DATA_STREAM_INDEX_PATTERN = 'logs-*-*,metrics-*-*,traces-*-*,synthetics-*-*'; const MAX_AGENT_DATA_PREVIEW_SIZE = 20; @@ -55,6 +63,18 @@ export async function getAgentStatusForAgentPolicy( agentPolicyId?: string, filterKuery?: string ) { + let pitId: string | undefined; + try { + pitId = await openPointInTime(esClient); + } catch (error) { + if (error.statusCode === 404) { + appContextService + .getLogger() + .debug('Index .fleet-agents does not exist yet, skipping point in time.'); + } else { + throw error; + } + } const [all, allActive, online, error, offline, updating] = await pMap( [ undefined, // All agents, including inactive @@ -69,11 +89,11 @@ export async function getAgentStatusForAgentPolicy( showInactive: index === 0, perPage: 0, page: 1, + pitId, kuery: joinKuerys( ...[ kuery, filterKuery, - `${AGENTS_PREFIX}.attributes.active:true`, agentPolicyId ? `${AGENTS_PREFIX}.policy_id:"${agentPolicyId}"` : undefined, ] ), @@ -83,7 +103,11 @@ export async function getAgentStatusForAgentPolicy( } ); - return { + if (pitId) { + await closePointInTime(esClient, pitId); + } + + const result = { total: allActive.total, inactive: all.total - allActive.total, online: online.total, @@ -94,6 +118,7 @@ export async function getAgentStatusForAgentPolicy( /* @deprecated Agent events do not exists anymore */ events: 0, }; + return result; } export async function getIncomingDataByAgentsId( esClient: ElasticsearchClient, diff --git a/x-pack/plugins/osquery/public/common/helpers.ts b/x-pack/plugins/osquery/public/common/helpers.ts index 5f83330fe9ee1b..a4683b60bcfa78 100644 --- a/x-pack/plugins/osquery/public/common/helpers.ts +++ b/x-pack/plugins/osquery/public/common/helpers.ts @@ -15,6 +15,7 @@ import { } from '../../common/search_strategy'; import { ESQuery } from '../../common/typed_json'; +import { ArrayItem } from '../shared_imports'; export const createFilter = (filterQuery: ESQuery | string | undefined) => isString(filterQuery) ? filterQuery : JSON.stringify(filterQuery); @@ -43,3 +44,12 @@ export const getInspectResponse = ( response: response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response, }); + +export const prepareEcsFieldsToValidate = (ecsMapping: ArrayItem[]): string[] => + ecsMapping + ?.map((_: unknown, index: number) => [ + `ecs_mapping[${index}].result.value`, + `ecs_mapping[${index}].key`, + ]) + .join(',') + .split(','); diff --git a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx index 55c3b545aedb5c..989e08f64d2748 100644 --- a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx +++ b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx @@ -40,6 +40,7 @@ import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; +import { prepareEcsFieldsToValidate } from '../../common/helpers'; import ECSSchema from '../../common/schemas/ecs/v8.2.0.json'; import osquerySchema from '../../common/schemas/osquery/v5.2.2.json'; @@ -57,6 +58,7 @@ import { UseArray, ArrayItem, FormArrayField, + useFormContext, } from '../../shared_imports'; import { OsqueryIcon } from '../../components/osquery_icon'; import { removeMultilines } from '../../../common/utils/build_query/remove_multilines'; @@ -768,9 +770,21 @@ export const ECSMappingEditorField = React.memo( ({ euiFieldProps }: ECSMappingEditorFieldProps) => { const lastItemPath = useRef(); const onAdd = useRef(); + const itemsList = useRef([]); const [osquerySchemaOptions, setOsquerySchemaOptions] = useState([]); const [{ query, ...formData }, formDataSerializer, isMounted] = useFormData(); + const { validateFields } = useFormContext(); + + useEffect(() => { + // Additional 'suspended' validation of osquery ecs fields. fieldsToValidateOnChange doesn't work because it happens before the osquerySchema gets updated. + const fieldsToValidate = prepareEcsFieldsToValidate(itemsList.current); + // it is always at least 2 - empty fields + if (fieldsToValidate.length > 2) { + setTimeout(() => validateFields(fieldsToValidate), 0); + } + }, [query, validateFields]); + useEffect(() => { if (!query?.length) { return; @@ -1074,6 +1088,7 @@ export const ECSMappingEditorField = React.memo( {({ items, addItem, removeItem }) => { lastItemPath.current = items[items.length - 1]?.path; onAdd.current = addItem; + itemsList.current = items; return ( <> diff --git a/x-pack/plugins/osquery/public/packs/queries/query_flyout.tsx b/x-pack/plugins/osquery/public/packs/queries/query_flyout.tsx index 2265420c07a334..5c6d2609c44dfd 100644 --- a/x-pack/plugins/osquery/public/packs/queries/query_flyout.tsx +++ b/x-pack/plugins/osquery/public/packs/queries/query_flyout.tsx @@ -72,7 +72,7 @@ const QueryFlyoutComponent: React.FC = ({ id: savedQuery.id, query: savedQuery.query, description: savedQuery.description, - platform: savedQuery.platform, + platform: savedQuery.platform ? savedQuery.platform : 'linux,windows,darwin', version: savedQuery.version, interval: savedQuery.interval, // @ts-expect-error update types diff --git a/x-pack/plugins/osquery/public/packs/queries/use_pack_query_form.tsx b/x-pack/plugins/osquery/public/packs/queries/use_pack_query_form.tsx index 2b044a443004d4..41a25bce0405f7 100644 --- a/x-pack/plugins/osquery/public/packs/queries/use_pack_query_form.tsx +++ b/x-pack/plugins/osquery/public/packs/queries/use_pack_query_form.tsx @@ -89,11 +89,6 @@ export const usePackQueryForm = ({ draft.platform.join(','); } - if (draft.platform?.split(',').length === 3) { - // if all platforms are checked then use undefined - delete draft.platform; - } - if (isArray(draft.version)) { if (!draft.version.length) { delete draft.version; diff --git a/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx index 1d0d9f28d097b6..a1350eceff89ce 100644 --- a/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx @@ -59,7 +59,7 @@ export const useSavedQueryForm = ({ defaultValue, handleSubmit }: UseSavedQueryF if (isArray(draft.version)) { if (!draft.version.length) { // @ts-expect-error update types - delete draft.version; + draft.version = ''; } else { draft.version = draft.version[0]; } diff --git a/x-pack/test/fleet_api_integration/apis/agents/status.ts b/x-pack/test/fleet_api_integration/apis/agents/status.ts index 9fc257f10c81df..0d22eeeae6228f 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/status.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/status.ts @@ -62,6 +62,24 @@ export default function ({ getService }: FtrProviderContext) { }, }, }); + // 1 agent inactive + await es.create({ + id: 'agent5', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + active: false, + access_api_key_id: 'api-key-4', + policy_id: 'policy1', + type: 'PERMANENT', + local_metadata: { host: { hostname: 'host5' } }, + user_provided_metadata: {}, + enrolled_at: '2022-06-21T12:17:25Z', + last_checkin: '2022-06-27T12:29:29Z', + }, + }, + }); }); after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/fleet/agents'); @@ -78,8 +96,8 @@ export default function ({ getService }: FtrProviderContext) { error: 0, offline: 1, updating: 1, - other: 1, - inactive: 0, + other: 2, + inactive: 1, }, }); });