Skip to content

Commit

Permalink
Merge branch 'main' into issue-move-autocomplete-to-unified-search
Browse files Browse the repository at this point in the history
  • Loading branch information
kibanamachine authored Apr 21, 2022
2 parents 68ab513 + 6447923 commit 2c0b86e
Show file tree
Hide file tree
Showing 40 changed files with 829 additions and 248 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,10 @@ module.exports = {
},
],
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
'react-hooks/exhaustive-deps': ['error', { additionalHooks: '^useFetcher$' }],
'react-hooks/exhaustive-deps': [
'error',
{ additionalHooks: '^(useFetcher|useProgressiveFetcher)$' },
],
},
},
{
Expand Down
5 changes: 5 additions & 0 deletions src/core/types/elasticsearch/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,11 @@ export type AggregateOf<
reverse_nested: {
doc_count: number;
} & SubAggregateOf<TAggregationContainer, TDocument>;
random_sampler: {
seed: number;
probability: number;
doc_count: number;
} & SubAggregateOf<TAggregationContainer, TDocument>;
sampler: {
doc_count: number;
} & SubAggregateOf<TAggregationContainer, TDocument>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,36 @@ describe('sendRequestToES', () => {
expect(mockedSendRequestToES).toHaveBeenCalledTimes(1);
}
});
describe('successful response value', () => {
describe('with text', () => {
it('should return value with lines separated', async () => {
mockedSendRequestToES.mockResolvedValue('\ntest_index-1 [] \ntest_index-2 []\n');
const response = await sendRequestToES({
http: mockContextValue.services.http,
requests: [{ method: 'GET', url: 'test-1', data: [] }],
});

expect(response).toMatchInlineSnapshot(`
"
test_index-1 []
test_index-2 []
"
`);
expect(mockedSendRequestToES).toHaveBeenCalledTimes(1);
});
});

describe('with parsed json', () => {
it('should stringify value', async () => {
mockedSendRequestToES.mockResolvedValue(JSON.stringify({ test: 'some value' }));
const response = await sendRequestToES({
http: mockContextValue.services.http,
requests: [{ method: 'GET', url: 'test-2', data: [] }],
});

expect(typeof response).toBe('string');
expect(mockedSendRequestToES).toHaveBeenCalledTimes(1);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]>
if (body instanceof ArrayBuffer) {
value = body;
} else {
value = JSON.stringify(body, null, 2);
value = typeof body === 'string' ? body : JSON.stringify(body, null, 2);
}

const warnings = response.headers.get('warning');
Expand Down
78 changes: 74 additions & 4 deletions src/plugins/console/public/lib/autocomplete/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@ import { i18n } from '@kbn/i18n';

// TODO: All of these imports need to be moved to the core editor so that it can inject components from there.
import {
getTopLevelUrlCompleteComponents,
getEndpointBodyCompleteComponents,
getGlobalAutocompleteComponents,
getTopLevelUrlCompleteComponents,
getUnmatchedEndpointComponents,
// @ts-ignore
} from '../kb/kb';

import { createTokenIterator } from '../../application/factories';
import { Position, Token, Range, CoreEditor } from '../../types';
import type { CoreEditor, Position, Range, Token } from '../../types';
import type RowParser from '../row_parser';

import * as utils from '../utils';

// @ts-ignore
import { populateContext } from './engine';
import { AutoCompleteContext, ResultTerm } from './types';
import type { AutoCompleteContext, DataAutoCompleteRulesOneOf, ResultTerm } from './types';
// @ts-ignore
import { URL_PATH_END_MARKER } from './components';

Expand Down Expand Up @@ -349,14 +349,84 @@ export default function ({
});
}

/**
* Get a different set of templates based on the value configured in the request.
* For example, when creating a snapshot repository of different types (`fs`, `url` etc),
* different properties are inserted in the textarea based on the type.
* E.g. https://github.com/elastic/kibana/blob/main/src/plugins/console/server/lib/spec_definitions/json/overrides/snapshot.create_repository.json
*/
function getConditionalTemplate(
name: string,
autocompleteRules: Record<string, unknown> | null | undefined
) {
const obj = autocompleteRules && autocompleteRules[name];

if (obj) {
const currentLineNumber = editor.getCurrentPosition().lineNumber;

if (hasOneOfIn(obj)) {
// Get the line number of value that should provide different templates based on that
const startLine = getStartLineNumber(currentLineNumber, obj.__one_of);
// Join line values from start to current line
const lines = editor.getLines(startLine, currentLineNumber).join('\n');
// Get the correct template by comparing the autocomplete rules against the lines
const prop = getProperty(lines, obj.__one_of);
if (prop && prop.__template) {
return prop.__template;
}
}
}
}

/**
* Check if object has a property of '__one_of'
*/
function hasOneOfIn(value: unknown): value is { __one_of: DataAutoCompleteRulesOneOf[] } {
return typeof value === 'object' && value !== null && '__one_of' in value;
}

/**
* Get the start line of value that matches the autocomplete rules condition
*/
function getStartLineNumber(currentLine: number, rules: DataAutoCompleteRulesOneOf[]): number {
if (currentLine === 1) {
return currentLine;
}
const value = editor.getLineValue(currentLine);
const prop = getProperty(value, rules);
if (prop) {
return currentLine;
}
return getStartLineNumber(currentLine - 1, rules);
}

/**
* Get the matching property based on the given condition
*/
function getProperty(condition: string, rules: DataAutoCompleteRulesOneOf[]) {
return rules.find((rule) => {
if (rule.__condition && rule.__condition.lines_regex) {
return new RegExp(rule.__condition.lines_regex, 'm').test(condition);
}
return false;
});
}

function applyTerm(term: {
value?: string;
context?: AutoCompleteContext;
template?: { __raw: boolean; value: string };
template?: { __raw?: boolean; value?: string; [key: string]: unknown };
insertValue?: string;
}) {
const context = term.context!;

if (context?.endpoint && term.value) {
const { data_autocomplete_rules: autocompleteRules } = context.endpoint;
const template = getConditionalTemplate(term.value, autocompleteRules);
if (template) {
term.template = template;
}
}
// make sure we get up to date replacement info.
addReplacementInfoToContext(context, editor.getCurrentPosition(), term.insertValue);

Expand Down
9 changes: 9 additions & 0 deletions src/plugins/console/public/lib/autocomplete/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ export interface ResultTerm {
value?: string;
}

export interface DataAutoCompleteRulesOneOf {
__condition?: {
lines_regex: string;
};
__template: Record<string, unknown>;
[key: string]: unknown;
}

export interface AutoCompleteContext {
autoCompleteSet?: null | ResultTerm[];
endpoint?: null | {
Expand All @@ -24,6 +32,7 @@ export interface AutoCompleteContext {
bodyAutocompleteRootComponents: unknown;
id?: string;
documentation?: string;
data_autocomplete_rules?: Record<string, unknown> | null;
};
urlPath?: null | unknown;
urlParamsTokenPath?: Array<Record<string, string>> | null;
Expand Down
44 changes: 44 additions & 0 deletions test/functional/apps/console/_autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
*/

import expect from '@kbn/expect';
import { asyncForEach } from '@kbn/std';
import { FtrProviderContext } from '../../ftr_provider_context';

export default function ({ getService, getPageObjects }: FtrProviderContext) {
const log = getService('log');
const retry = getService('retry');
const PageObjects = getPageObjects(['common', 'console']);

describe('console autocomplete feature', function describeIndexTests() {
Expand Down Expand Up @@ -62,5 +64,47 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(lastChar).to.be.eql(',');
});
});

describe('with conditional templates', async () => {
const CONDITIONAL_TEMPLATES = [
{
type: 'fs',
template: `"location": "path"`,
},
{
type: 'url',
template: `"url": ""`,
},
{ type: 's3', template: `"bucket": ""` },
{
type: 'azure',
template: `"path": ""`,
},
];

beforeEach(async () => {
await PageObjects.console.clearTextArea();
await PageObjects.console.enterRequest('\n POST _snapshot/test_repo');
});

await asyncForEach(CONDITIONAL_TEMPLATES, async ({ type, template }) => {
it('should insert different templates depending on the value of type', async () => {
await PageObjects.console.enterText(`{\n\t"type": "${type}"`);
await PageObjects.console.pressEnter();
// Prompt autocomplete for 'settings'
await PageObjects.console.promptAutocomplete('s');

await retry.waitFor('autocomplete to be visible', () =>
PageObjects.console.isAutocompleteVisible()
);
await PageObjects.console.pressEnter();
await retry.try(async () => {
const request = await PageObjects.console.getRequest();
log.debug(request);
expect(request).to.contain(`${template}`);
});
});
});
});
});
}
5 changes: 3 additions & 2 deletions test/functional/page_objects/console_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,11 @@ export class ConsolePageObject extends FtrService {
}
}

public async promptAutocomplete() {
// Prompt autocomplete window and provide a initial letter of properties to narrow down the results. E.g. 'b' = 'bool'
public async promptAutocomplete(letter = 'b') {
const textArea = await this.testSubjects.find('console-textarea');
await textArea.clickMouseButton();
await textArea.type('b');
await textArea.type(letter);
await this.retry.waitFor('autocomplete to be visible', () => this.isAutocompleteVisible());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useTimeRange } from '../../../hooks/use_time_range';
import { SearchBar } from '../../shared/search_bar';
import { ServiceList } from './service_list';
import { MLCallout, shouldDisplayMlCallout } from '../../shared/ml_callout';
import { useProgressiveFetcher } from '../../../hooks/use_progressive_fetcher';
import { joinByKey } from '../../../../common/utils/join_by_key';
import { ServiceInventoryFieldName } from '../../../../common/service_inventory';
import { orderServiceItems } from './service_list/order_service_items';
Expand Down Expand Up @@ -62,7 +63,7 @@ function useServicesFetcher() {
[start, end, environment, kuery, serviceGroup]
);

const mainStatisticsFetch = useFetcher(
const mainStatisticsFetch = useProgressiveFetcher(
(callApmApi) => {
if (start && end) {
return callApmApi('GET /internal/apm/services', {
Expand All @@ -88,9 +89,14 @@ function useServicesFetcher() {

const { data: mainStatisticsData = initialData } = mainStatisticsFetch;

const comparisonFetch = useFetcher(
const comparisonFetch = useProgressiveFetcher(
(callApmApi) => {
if (start && end && mainStatisticsData.items.length) {
if (
start &&
end &&
mainStatisticsData.items.length &&
mainStatisticsFetch.status === FETCH_STATUS.SUCCESS
) {
return callApmApi('GET /internal/apm/services/detailed_statistics', {
params: {
query: {
Expand Down Expand Up @@ -141,14 +147,16 @@ export function ServiceInventory() {
!userHasDismissedCallout &&
shouldDisplayMlCallout(anomalyDetectionSetupState);

const useOptimizedSorting = useKibana().services.uiSettings?.get<boolean>(
apmServiceInventoryOptimizedSorting
);
const useOptimizedSorting =
useKibana().services.uiSettings?.get<boolean>(
apmServiceInventoryOptimizedSorting
) || false;

let isLoading: boolean;

if (useOptimizedSorting) {
isLoading =
// ensures table is usable when sorted and filtered services have loaded
sortedAndFilteredServicesFetch.status === FETCH_STATUS.LOADING ||
(sortedAndFilteredServicesFetch.status === FETCH_STATUS.SUCCESS &&
sortedAndFilteredServicesFetch.data?.services.length === 0 &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React from 'react';
import { useApmParams } from '../../../hooks/use_apm_params';
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
import { APIReturnType } from '../../../services/rest/create_call_apm_api';
import { SearchBar } from '../../shared/search_bar';
import { TraceList } from './trace_list';
import { useFallbackToTransactionsFetcher } from '../../../hooks/use_fallback_to_transactions_fetcher';
import { AggregatedTransactionsBadge } from '../../shared/aggregated_transactions_badge';
import { useTimeRange } from '../../../hooks/use_time_range';
import { useProgressiveFetcher } from '../../../hooks/use_progressive_fetcher';

type TracesAPIResponse = APIReturnType<'GET /internal/apm/traces'>;
const DEFAULT_RESPONSE: TracesAPIResponse = {
Expand All @@ -31,7 +32,7 @@ export function TraceOverview() {

const { start, end } = useTimeRange({ rangeFrom, rangeTo });

const { status, data = DEFAULT_RESPONSE } = useFetcher(
const { status, data = DEFAULT_RESPONSE } = useProgressiveFetcher(
(callApmApi) => {
if (start && end) {
return callApmApi('GET /internal/apm/traces', {
Expand Down
Loading

0 comments on commit 2c0b86e

Please sign in to comment.