Skip to content

Commit

Permalink
Merge branch 'kbn-grid-layout_switch-to-props_2024-11-19' of github.c…
Browse files Browse the repository at this point in the history
…om:heenawter/kibana into kbn-grid-layout_switch-to-props_2024-11-19
  • Loading branch information
Heenawter committed Nov 21, 2024
2 parents c3664e6 + 62cd9e7 commit 9f81efe
Show file tree
Hide file tree
Showing 64 changed files with 1,460 additions and 184 deletions.
12 changes: 8 additions & 4 deletions dev_docs/key_concepts/feature_privileges.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,10 @@ public setup(core: CoreSetup, deps: FeatureControlExampleDeps) {
{
path: '/internal/my_plugin/sensitive_action',
validate: false,
options: {
tags: ['access:my_closed_example_api'],
security: {
authz: {
requiredPrivileges: ['my_closed_example_api']
}
},
},
async (context, request, response) => {
Expand All @@ -193,8 +195,11 @@ public setup(core: CoreSetup, deps: FeatureControlExampleDeps) {
);
}
```
<DocCallOut>
For more information on the `security.authz` object and API authorization, please refer to our guide on <DocLink id="kibDevDocsSecurityAPIAuthorization" text="Configuring authorization on routes"/>
</DocCallOut>

Notice, we've added an `options.tags` property for the API route that returns sensitive information. This tag is then used in the privileges object as follow
Notice, we've added a `security.authz.requiredPrivileges` property for the API route that returns sensitive information. This added configuration is then used in the privileges object as follow

```ts
{
Expand Down Expand Up @@ -347,7 +352,6 @@ A deep dive into every option for the Kibana Feature configuration and what they
}
```


### FeatureKibanaPrivileges Interface

#### excludeFromBasePrivileges (optional)
Expand Down
8 changes: 3 additions & 5 deletions docs/user/reporting/automating-report-generation.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,17 @@ To create the POST URL for PDF reports:

. Open the dashboard, visualization, or **Canvas** workpad you want to view as a report.

. From the toolbar, click *Share > PDF Reports*, then choose an option:
* If you are using *Dashboard* or *Visualize Library*, from the toolbar, click *Share > Export*, select the PDF option then click *Copy POST URL*.

* If you are using *Dashboard* or *Visulize Library*, click *Copy POST URL*.

* If you are using *Canvas*, click *Advanced options > Copy POST URL*.
* If you are using *Canvas*, from the toolbar, click *Share > PDF Reports*, then click *Advanced options > Copy POST URL*.

To create the POST URL for CSV reports:

. Go to *Discover*.

. Open the saved search you want to share.

. In the toolbar, click *Share > CSV Reports > Copy POST URL*.
. In the toolbar, click *Share > Export > Copy POST URL*.

[float]
[[use-watcher]]
Expand Down
7 changes: 6 additions & 1 deletion packages/kbn-cli-dev-mode/src/watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ const packageMatcher = makeMatcher([
/**
* Any code that is outside of a package must match this in order to trigger a restart
*/
const nonPackageMatcher = makeMatcher(['config/**/*.yml']);
const nonPackageMatcher = makeMatcher(['config/**/*.yml', 'plugins/**/server/**/*']);
const staticFileMatcher = makeMatcher(['plugins/**/kibana.json']);

export interface Options {
enabled: boolean;
Expand Down Expand Up @@ -87,6 +88,10 @@ export class Watcher {
if (result.type === 'non-package') {
return nonPackageMatcher(result.repoRel) && fire(result.repoRel);
}

if (result.type === 'static') {
return staticFileMatcher(result.repoRel) && fire(result.repoRel);
}
}
},
{
Expand Down
8 changes: 4 additions & 4 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,11 @@
"groupName": "@elastic/appex-qa dependencies",
"matchDepNames": [
"cheerio",
"@types/enzyme",
"@types/faker",
"@types/pixelmatch",
"@types/pngjs",
"@types/supertest",
"@wojtekmaj/enzyme-adapter-react-17",
"babel-plugin-istanbul",
"enzyme",
"enzyme-to-json",
"faker",
"nyc",
"oboe",
Expand Down Expand Up @@ -175,10 +171,13 @@
"matchDepNames": [
"@elastic/filesaver",
"@elastic/numeral",
"@wojtekmaj/enzyme-adapter-react-17",
"base64-js",
"blurhash",
"classnames",
"deep-freeze-strict",
"enzyme",
"enzyme-to-json",
"fflate",
"history",
"lz-string",
Expand All @@ -188,6 +187,7 @@
"@types/base64-js",
"@types/classnames",
"@types/deep-freeze-strict",
"@types/enzyme",
"@types/history",
"@types/lz-string",
"@types/styled-components"
Expand Down
2 changes: 1 addition & 1 deletion src/dev/build/tasks/os_packages/docker_generator/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export async function runDockerGenerator(
*/
if (flags.baseImage === 'wolfi')
baseImageName =
'docker.elastic.co/wolfi/chainguard-base:latest@sha256:26caa6beaee2bbf739a82e91a35173892dfe888d0a744b9e46cdc19a90d8656f';
'docker.elastic.co/wolfi/chainguard-base:latest@sha256:32099b99697d9da842c1ccacdbef1beee05a68cddb817e858d7656df45ed4c93';

let imageFlavor = '';
if (flags.baseImage === 'ubi') imageFlavor += `-ubi`;
Expand Down
9 changes: 9 additions & 0 deletions src/plugins/data_views/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,12 @@ export const EXISTING_INDICES_PATH = '/internal/data_views/_existing_indices';
export const DATA_VIEWS_FIELDS_EXCLUDED_TIERS = 'data_views:fields_excluded_data_tiers';

export const DEFAULT_DATA_VIEW_ID = 'defaultIndex';

/**
* Valid `failureReason` attribute values for `has_es_data` API error responses
*/
export enum HasEsDataFailureReason {
localDataTimeout = 'local_data_timeout',
remoteDataTimeout = 'remote_data_timeout',
unknown = 'unknown',
}
1 change: 1 addition & 0 deletions src/plugins/data_views/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export {
META_FIELDS,
DATA_VIEW_SAVED_OBJECT_TYPE,
MAX_DATA_VIEW_FIELD_DESCRIPTION_LENGTH,
HasEsDataFailureReason,
} from './constants';

export { LATEST_VERSION } from './content_management/v1/constants';
Expand Down
1 change: 1 addition & 0 deletions src/plugins/data_views/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,4 +571,5 @@ export interface ClientConfigType {
scriptedFieldsEnabled?: boolean;
dataTiersExcludedForFields?: string;
fieldListCachingEnabled?: boolean;
hasEsDataTimeout: number;
}
73 changes: 73 additions & 0 deletions src/plugins/data_views/public/services/has_data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { coreMock } from '@kbn/core/public/mocks';

import { HasData } from './has_data';
import { HttpFetchError } from '@kbn/core-http-browser-internal/src/http_fetch_error';

describe('when calling hasData service', () => {
describe('hasDataView', () => {
Expand Down Expand Up @@ -170,6 +171,78 @@ describe('when calling hasData service', () => {

expect(await response).toBe(false);
});

it('should return true and show an error toast when checking for remote cluster data times out', async () => {
const coreStart = coreMock.createStart();
const http = coreStart.http;

// Mock getIndices
const spy = jest.spyOn(http, 'get').mockImplementation(() =>
Promise.reject(
new HttpFetchError(
'Timeout while checking for Elasticsearch data',
'TimeoutError',
new Request(''),
undefined,
{
statusCode: 504,
message: 'Timeout while checking for Elasticsearch data',
attributes: {
failureReason: 'remote_data_timeout',
},
}
)
)
);
const hasData = new HasData();
const hasDataService = hasData.start(coreStart, true);
const response = hasDataService.hasESData();

expect(spy).toHaveBeenCalledTimes(1);
expect(await response).toBe(true);
expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledTimes(1);
expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledWith({
title: 'Remote cluster timeout',
text: 'Checking for data on remote clusters timed out. One or more remote clusters may be unavailable.',
});
});

it('should return true and not show an error toast when checking for remote cluster data times out, but onRemoteDataTimeout is overridden', async () => {
const coreStart = coreMock.createStart();
const http = coreStart.http;

// Mock getIndices
const responseBody = {
statusCode: 504,
message: 'Timeout while checking for Elasticsearch data',
attributes: {
failureReason: 'remote_data_timeout',
},
};
const spy = jest
.spyOn(http, 'get')
.mockImplementation(() =>
Promise.reject(
new HttpFetchError(
'Timeout while checking for Elasticsearch data',
'TimeoutError',
new Request(''),
undefined,
responseBody
)
)
);
const hasData = new HasData();
const hasDataService = hasData.start(coreStart, true);
const onRemoteDataTimeout = jest.fn();
const response = hasDataService.hasESData({ onRemoteDataTimeout });

expect(spy).toHaveBeenCalledTimes(1);
expect(await response).toBe(true);
expect(coreStart.notifications.toasts.addDanger).not.toHaveBeenCalled();
expect(onRemoteDataTimeout).toHaveBeenCalledTimes(1);
expect(onRemoteDataTimeout).toHaveBeenCalledWith(responseBody);
});
});

describe('resolve/cluster not available', () => {
Expand Down
56 changes: 49 additions & 7 deletions src/plugins/data_views/public/services/has_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,22 @@
*/

import { CoreStart, HttpStart } from '@kbn/core/public';
import { DEFAULT_ASSETS_TO_IGNORE } from '../../common';
import { IHttpFetchError, ResponseErrorBody, isHttpFetchError } from '@kbn/core-http-browser';
import { isObject } from 'lodash';
import { i18n } from '@kbn/i18n';
import { DEFAULT_ASSETS_TO_IGNORE, HasEsDataFailureReason } from '../../common';
import { HasDataViewsResponse, IndicesViaSearchResponse } from '..';
import { IndicesResponse, IndicesResponseModified } from '../types';

export interface HasEsDataParams {
/**
* Callback to handle the case where checking for remote data times out.
* If not provided, the default behavior is to show a toast notification.
* @param body The error response body
*/
onRemoteDataTimeout?: (body: ResponseErrorBody) => void;
}

export class HasData {
private removeAliases = (source: IndicesResponseModified): boolean => !source.item.indices;

Expand All @@ -38,28 +50,55 @@ export class HasData {
return hasLocalESData;
};

const hasESDataViaResolveCluster = async () => {
const hasESDataViaResolveCluster = async (
onRemoteDataTimeout: (body: ResponseErrorBody) => void
) => {
try {
const { hasEsData } = await http.get<{ hasEsData: boolean }>(
'/internal/data_views/has_es_data',
{
version: '1',
}
{ version: '1' }
);

return hasEsData;
} catch (e) {
if (
this.isResponseError(e) &&
e.body?.statusCode === 504 &&
e.body?.attributes?.failureReason === HasEsDataFailureReason.remoteDataTimeout
) {
onRemoteDataTimeout(e.body);

// In the case of a remote cluster timeout,
// we can't be sure if there is data or not,
// so just assume there is
return true;
}

// fallback to previous implementation
return hasESDataViaResolveIndex();
}
};

const showRemoteDataTimeoutToast = () =>
core.notifications.toasts.addDanger({
title: i18n.translate('dataViews.hasData.remoteDataTimeoutTitle', {
defaultMessage: 'Remote cluster timeout',
}),
text: i18n.translate('dataViews.hasData.remoteDataTimeoutText', {
defaultMessage:
'Checking for data on remote clusters timed out. One or more remote clusters may be unavailable.',
}),
});

return {
/**
* Check to see if ES data exists
*/
hasESData: async (): Promise<boolean> => {
hasESData: async ({
onRemoteDataTimeout = showRemoteDataTimeoutToast,
}: HasEsDataParams = {}): Promise<boolean> => {
if (callResolveCluster) {
return hasESDataViaResolveCluster();
return hasESDataViaResolveCluster(onRemoteDataTimeout);
}
return hasESDataViaResolveIndex();
},
Expand All @@ -82,6 +121,9 @@ export class HasData {

// ES Data

private isResponseError = (e: Error): e is IHttpFetchError<ResponseErrorBody> =>
isHttpFetchError(e) && isObject(e.body) && 'message' in e.body && 'statusCode' in e.body;

private responseToItemArray = (response: IndicesResponse): IndicesResponseModified[] => {
const { indices = [], aliases = [] } = response;
const source: IndicesResponseModified[] = [];
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/data_views/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ const configSchema = schema.object({
schema.boolean({ defaultValue: false }),
schema.never()
),

dataTiersExcludedForFields: schema.conditional(
schema.contextRef('serverless'),
true,
Expand All @@ -60,6 +59,7 @@ const configSchema = schema.object({
schema.boolean({ defaultValue: false }),
schema.boolean({ defaultValue: true })
),
hasEsDataTimeout: schema.number({ defaultValue: 5000 }),
});

type ConfigType = TypeOf<typeof configSchema>;
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/data_views/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@ export class DataViewsServerPlugin

registerRoutes({
http: core.http,
logger: this.logger,
getStartServices: core.getStartServices,
isRollupsEnabled: () => this.rollupsEnabled,
dataViewRestCounter,
hasEsDataTimeout: config.hasEsDataTimeout,
});

expressions.registerFunction(getIndexPatternLoad({ getStartServices: core.getStartServices }));
Expand Down
Loading

0 comments on commit 9f81efe

Please sign in to comment.