Skip to content

Commit

Permalink
Merge branch 'main' into alerting/rule-execution-duration-nicer
Browse files Browse the repository at this point in the history
  • Loading branch information
kibanamachine authored Nov 2, 2021
2 parents 573688a + f5e9a07 commit d8b51b4
Show file tree
Hide file tree
Showing 44 changed files with 1,681 additions and 202 deletions.
4 changes: 2 additions & 2 deletions .bazelrc.common
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ test --experimental_guard_against_concurrent_changes
query --experimental_guard_against_concurrent_changes

## Cache action outputs on disk so they persist across output_base and bazel shutdown (eg. changing branches)
common --disk_cache=~/.bazel-cache/disk-cache
build --disk_cache=~/.bazel-cache/disk-cache

## Bazel repo cache settings
common --repository_cache=~/.bazel-cache/repository-cache
build --repository_cache=~/.bazel-cache/repository-cache

# Bazel will create symlinks from the workspace directory to output artifacts.
# Build results will be placed in a directory called "bazel-bin"
Expand Down
12 changes: 1 addition & 11 deletions api_docs/fleet.json
Original file line number Diff line number Diff line change
Expand Up @@ -19696,16 +19696,6 @@
"path": "x-pack/plugins/fleet/common/constants/routes.ts",
"deprecated": false
},
{
"parentPluginId": "fleet",
"id": "def-common.AGENT_API_ROUTES.ENROLL_PATTERN",
"type": "string",
"tags": [],
"label": "ENROLL_PATTERN",
"description": [],
"path": "x-pack/plugins/fleet/common/constants/routes.ts",
"deprecated": false
},
{
"parentPluginId": "fleet",
"id": "def-common.AGENT_API_ROUTES.UNENROLL_PATTERN",
Expand Down Expand Up @@ -21889,4 +21879,4 @@
}
]
}
}
}
11 changes: 3 additions & 8 deletions src/plugins/custom_integrations/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,11 @@ export const INTEGRATION_CATEGORY_DISPLAY = {
web: 'Web',

// Kibana added
communication: 'Communication',
customer_support: 'Customer Support',
document_storage: 'Document Storage',
enterprise_management: 'Enterprise Management',
knowledge_platform: 'Knowledge Platform',
communications: 'Communications',
file_storage: 'File storage',
language_client: 'Language client',
project_management: 'Project Management',
software_development: 'Software Development',
upload_file: 'Upload a file',
website_search: 'Website Search',
website_search: 'Website search',
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ export default function QueryBarTopRow(props: QueryBarTopRowProps) {
isDisabled={isDateRangeInvalid}
isLoading={props.isLoading}
onClick={onClickSubmitButton}
fill={false}
data-test-subj="querySubmitButton"
/>
);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function TopNavMenuItem(props: TopNavMenuData) {
};

const btn = props.emphasize ? (
<EuiButton size="s" {...commonButtonProps}>
<EuiButton size="s" {...commonButtonProps} fill>
{upperFirst(props.label || props.id!)}
</EuiButton>
) : (
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/actions/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,8 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
plugins.taskManager,
core,
this.kibanaIndex,
this.preconfiguredActions
this.preconfiguredActions,
this.eventLogService
);
}

Expand Down
100 changes: 99 additions & 1 deletion x-pack/plugins/actions/server/usage/actions_telemetry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { elasticsearchClientMock } from '../../../../../src/core/server/elasticsearch/client/mocks';
import { getInUseTotalCount, getTotalCount } from './actions_telemetry';
import { getExecutionsPerDayCount, getInUseTotalCount, getTotalCount } from './actions_telemetry';

describe('actions telemetry', () => {
test('getTotalCount should replace first symbol . to __ for action types names', async () => {
Expand Down Expand Up @@ -604,4 +604,102 @@ Object {
}
`);
});

test('getExecutionsTotalCount', async () => {
const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser;
mockEsClient.search.mockReturnValueOnce(
// @ts-expect-error not full search response
elasticsearchClientMock.createSuccessTransportRequestPromise({
aggregations: {
totalExecutions: {
byConnectorTypeId: {
value: {
connectorTypes: {
'.slack': 100,
'.server-log': 20,
},
total: 120,
},
},
},
failedExecutions: {
refs: {
byConnectorTypeId: {
value: {
connectorTypes: {
'.slack': 7,
},
total: 7,
},
},
},
},
avgDuration: { value: 10 },
avgDurationByType: {
doc_count: 216,
actionSavedObjects: {
doc_count: 108,
byTypeId: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [
{
key: '.server-log',
doc_count: 99,
refs: {
doc_count: 99,
avgDuration: {
value: 919191.9191919192,
},
},
},
{
key: '.email',
doc_count: 9,
refs: {
doc_count: 9,
avgDuration: {
value: 4.196666666666667e8,
},
},
},
],
},
},
},
},
})
);

// for .slack connectors
mockEsClient.search.mockReturnValueOnce(
// @ts-expect-error not full search response
elasticsearchClientMock.createSuccessTransportRequestPromise({
aggregations: {
avgDuration: { value: 10 },
},
})
);
const telemetry = await getExecutionsPerDayCount(mockEsClient, 'test');

expect(mockEsClient.search).toHaveBeenCalledTimes(1);
expect(telemetry).toStrictEqual({
avgExecutionTime: 0,
avgExecutionTimeByType: {
'__server-log': 919191.9191919192,
__email: 419666666.6666667,
},

countByType: {
__slack: 100,

'__server-log': 20,
},
countFailed: 7,
countFailedByType: {
__slack: 7,
},
countTotal: 120,
});
});
});
182 changes: 181 additions & 1 deletion x-pack/plugins/actions/server/usage/actions_telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,4 +379,184 @@ function replaceFirstAndLastDotSymbols(strToReplace: string) {
return hasLastSymbolDot ? `${appliedString.slice(0, -1)}__` : appliedString;
}

// TODO: Implement executions count telemetry with eventLog, when it will write to index
export async function getExecutionsPerDayCount(
esClient: ElasticsearchClient,
eventLogIndex: string
): Promise<{
countTotal: number;
countByType: Record<string, number>;
countFailed: number;
countFailedByType: Record<string, number>;
avgExecutionTime: number;
avgExecutionTimeByType: Record<string, number>;
}> {
const scriptedMetric = {
scripted_metric: {
init_script: 'state.connectorTypes = [:]; state.total = 0;',
map_script: `
if (doc['kibana.saved_objects.type'].value == 'action') {
String connectorType = doc['kibana.saved_objects.type_id'].value;
state.connectorTypes.put(connectorType, state.connectorTypes.containsKey(connectorType) ? state.connectorTypes.get(connectorType) + 1 : 1);
state.total++;
}
`,
// Combine script is executed per cluster, but we already have a key-value pair per cluster.
// Despite docs that say this is optional, this script can't be blank.
combine_script: 'return state',
// Reduce script is executed across all clusters, so we need to add up all the total from each cluster
// This also needs to account for having no data
reduce_script: `
Map connectorTypes = [:];
long total = 0;
for (state in states) {
if (state !== null) {
total += state.total;
for (String k : state.connectorTypes.keySet()) {
connectorTypes.put(k, connectorTypes.containsKey(k) ? connectorTypes.get(k) + state.connectorTypes.get(k) : state.connectorTypes.get(k));
}
}
}
Map result = new HashMap();
result.total = total;
result.connectorTypes = connectorTypes;
return result;
`,
},
};

const { body: actionResults } = await esClient.search({
index: eventLogIndex,
size: 0,
body: {
query: {
bool: {
filter: {
bool: {
must: [
{
term: { 'event.action': 'execute' },
},
{
term: { 'event.provider': 'actions' },
},
{
range: {
'@timestamp': {
gte: 'now-1d',
},
},
},
],
},
},
},
},
aggs: {
totalExecutions: {
nested: {
path: 'kibana.saved_objects',
},
aggs: {
byConnectorTypeId: scriptedMetric,
},
},
failedExecutions: {
filter: {
bool: {
filter: [
{
term: {
'event.outcome': 'failure',
},
},
],
},
},
aggs: {
refs: {
nested: {
path: 'kibana.saved_objects',
},
aggs: {
byConnectorTypeId: scriptedMetric,
},
},
},
},
avgDuration: { avg: { field: 'event.duration' } },
avgDurationByType: {
nested: {
path: 'kibana.saved_objects',
},
aggs: {
actionSavedObjects: {
filter: { term: { 'kibana.saved_objects.type': 'action' } },
aggs: {
byTypeId: {
terms: {
field: 'kibana.saved_objects.type_id',
},
aggs: {
refs: {
reverse_nested: {},
aggs: {
avgDuration: { avg: { field: 'event.duration' } },
},
},
},
},
},
},
},
},
},
},
});

// @ts-expect-error aggegation type is not specified
const aggsExecutions = actionResults.aggregations.totalExecutions?.byConnectorTypeId.value;
// convert nanoseconds to milliseconds
const aggsAvgExecutionTime = Math.round(
// @ts-expect-error aggegation type is not specified
actionResults.aggregations.avgDuration.value / (1000 * 1000)
);
const aggsFailedExecutions =
// @ts-expect-error aggegation type is not specified
actionResults.aggregations.failedExecutions?.refs?.byConnectorTypeId.value;

const avgDurationByType =
// @ts-expect-error aggegation type is not specified
actionResults.aggregations.avgDurationByType?.actionSavedObjects?.byTypeId?.buckets;

const avgExecutionTimeByType: Record<string, number> = avgDurationByType.reduce(
// @ts-expect-error aggegation type is not specified
(res: Record<string, number>, bucket) => {
res[replaceFirstAndLastDotSymbols(bucket.key)] = bucket?.refs.avgDuration.value;
return res;
},
{}
);

return {
countTotal: aggsExecutions.total,
countByType: Object.entries(aggsExecutions.connectorTypes).reduce(
(res: Record<string, number>, [key, value]) => {
// @ts-expect-error aggegation type is not specified
res[replaceFirstAndLastDotSymbols(key)] = value;
return res;
},
{}
),
countFailed: aggsFailedExecutions.total,
countFailedByType: Object.entries(aggsFailedExecutions.connectorTypes).reduce(
(res: Record<string, number>, [key, value]) => {
// @ts-expect-error aggegation type is not specified
res[replaceFirstAndLastDotSymbols(key)] = value;
return res;
},
{}
),
avgExecutionTime: aggsAvgExecutionTime,
avgExecutionTimeByType,
};
}
Loading

0 comments on commit d8b51b4

Please sign in to comment.