Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 34 additions & 5 deletions dev_docs/tutorials/versioning_http_apis.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -212,17 +212,45 @@ The changes are:

### 4. Adhere to the HTTP versioning specification

#### Choosing the right version

##### Public endpoints
We categorize our endpoints based on their intended audience: `public` or `internal`. Different versioning practices apply to each.

#### Public endpoints
Public endpoints include any endpoint that is intended for users to directly integrate with via HTTP.

<DocCallOut title="Version all public endpoints">
All Kibana's public endpoints must be versioned using the format described below.
</DocCallOut>

##### Version lifecycle

Introducing a new version or moving a current version into deprecation to eventually be deleted must
follow [this process](https://github.com/elastic/dev/issues/new?assignees=&labels=breaking-change-proposal&projects=&template=breaking-change.md).

##### Version format

Choose a date string in the format `YYYY-MM-DD`. This date should be the date that a (group) of APIs was made available.

##### Internal endpoints
Internal endpoints are all non-public endpoints (see definition above).
--------

#### Internal endpoints
Internal endpoints are all non-public endpoints (see definition above). Note: these endpoints do not need to be versioned,
but versioning can be leveraged to maintain BWC with existing clients.

If you need to maintain backwards-compatibility for an internal endpoint use a single, larger-than-zero number. Ex. `1`.
##### Version lifecycle

Introducing/removing a version is up to the team who owns the HTTP API. Consider how introduction or removal might
affect client code when being rolled out.

<DocCallOut title="Keep internal versions to a minimum">
To keep maintenance light it is **highly** recommended to reduce the number of versions you have for internal endpoints. In your code it is possible to
centrally define and share internal versions through code that is `common` to your browser- and server-side plugin code.
</DocCallOut>


##### Version format

If you need to version an internal endpoint use a single, larger-than-zero major version. Ex. `1`.


#### Use the versioned router
Expand Down Expand Up @@ -335,4 +363,5 @@ export class MyPlugin implements Plugin {
```

#### Additional reading

For more details on the versioning specification see [this document](https://docs.google.com/document/d/1YpF6hXIHZaHvwNaQAxWFzexUF1nbqACTtH2IfDu0ldA/edit?usp=sharing).
35 changes: 30 additions & 5 deletions packages/core/chrome/core-chrome-browser/src/project_navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import type { ComponentType } from 'react';
import type { ChromeNavLink } from './nav_links';

/** @internal */
type AppId = string;
Expand All @@ -17,20 +18,44 @@ type DeepLinkId = string;
/** @internal */
export type AppDeepLinkId = `${AppId}:${DeepLinkId}`;

/** @public */
/**
* @public
*
* App id or deeplink id
*/
export type ChromeProjectNavigationLink = AppId | AppDeepLinkId;

/** @public */
export interface ChromeProjectNavigationNode {
id?: string;
link?: ChromeProjectNavigationLink;
children?: ChromeProjectNavigationNode[];
title?: string;
/** Optional id, if not passed a "link" must be provided. */
id: string;
/** Optional title. If not provided and a "link" is provided the title will be the Deep link title */
title: string;
/** Path in the tree of the node */
path: string[];
/** App id or deeplink id */
deepLink?: ChromeNavLink;
/** Optional icon for the navigation node. Note: not all navigation depth will render the icon */
icon?: string;
/** Optional children of the navigation node */
children?: ChromeProjectNavigationNode[];
/**
* Temporarilly we allow href to be passed.
* Once all the deeplinks will be exposed in packages we will not allow href anymore
* and force deeplink id to be passed
*/
href?: string;
}

/** @public */
export interface ChromeProjectNavigation {
/**
* The URL href for the home link
*/
homeRef: string;
/**
* The navigation tree representation of the side bar navigation.
*/
navigationTree: ChromeProjectNavigationNode[];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ describe('validateTypeMigrations', () => {
});

expect(() => validate({ type, kibanaVersion: '3.2.3' })).toThrowErrorMatchingInlineSnapshot(
`"Type foo: Uusing modelVersions requires to specify switchToModelVersionAt"`
`"Type foo: Using modelVersions requires to specify switchToModelVersionAt"`
);
});

Expand Down Expand Up @@ -234,6 +234,15 @@ describe('validateTypeMigrations', () => {
`"Type foo: gaps between model versions aren't allowed (missing versions: 2,4,5)"`
);
});

it('does not throw passing an empty model version map', () => {
const type = createType({
name: 'foo',
modelVersions: {},
});

expect(() => validate({ type, kibanaVersion: '3.2.3' })).not.toThrow();
});
});

describe('modelVersions mapping additions', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,45 +73,47 @@ export function validateTypeMigrations({
const modelVersionMap =
typeof type.modelVersions === 'function' ? type.modelVersions() : type.modelVersions ?? {};

if (Object.keys(modelVersionMap).length > 0 && !type.switchToModelVersionAt) {
throw new Error(
`Type ${type.name}: Uusing modelVersions requires to specify switchToModelVersionAt`
);
}
if (Object.keys(modelVersionMap).length > 0) {
if (!type.switchToModelVersionAt) {
throw new Error(
`Type ${type.name}: Using modelVersions requires to specify switchToModelVersionAt`
);
}

Object.entries(modelVersionMap).forEach(([version, definition]) => {
assertValidModelVersion(version);
});
Object.entries(modelVersionMap).forEach(([version, definition]) => {
assertValidModelVersion(version);
});

const { min: minVersion, max: maxVersion } = Object.keys(modelVersionMap).reduce(
(minMax, rawVersion) => {
const version = Number.parseInt(rawVersion, 10);
minMax.min = Math.min(minMax.min, version);
minMax.max = Math.max(minMax.max, version);
return minMax;
},
{ min: Infinity, max: -Infinity }
);
const { min: minVersion, max: maxVersion } = Object.keys(modelVersionMap).reduce(
(minMax, rawVersion) => {
const version = Number.parseInt(rawVersion, 10);
minMax.min = Math.min(minMax.min, version);
minMax.max = Math.max(minMax.max, version);
return minMax;
},
{ min: Infinity, max: -Infinity }
);

if (minVersion > 1) {
throw new Error(`Type ${type.name}: model versioning must start with version 1`);
}
if (minVersion > 1) {
throw new Error(`Type ${type.name}: model versioning must start with version 1`);
}

validateAddedMappings(type.name, type.mappings, modelVersionMap);
validateAddedMappings(type.name, type.mappings, modelVersionMap);

const missingVersions = getMissingVersions(
minVersion,
maxVersion,
Object.keys(modelVersionMap).map((v) => Number.parseInt(v, 10))
);
if (missingVersions.length) {
throw new Error(
`Type ${
type.name
}: gaps between model versions aren't allowed (missing versions: ${missingVersions.join(
','
)})`
const missingVersions = getMissingVersions(
minVersion,
maxVersion,
Object.keys(modelVersionMap).map((v) => Number.parseInt(v, 10))
);
if (missingVersions.length) {
throw new Error(
`Type ${
type.name
}: gaps between model versions aren't allowed (missing versions: ${missingVersions.join(
','
)})`
);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pageLoadAssetSize:
banners: 17946
bfetch: 22837
canvas: 1066647
cases: 175000
cases: 180000
charts: 55000
cloud: 21076
cloudChat: 19894
Expand Down
15 changes: 14 additions & 1 deletion packages/shared-ux/chrome/navigation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,20 @@
*/

export { NavigationKibanaProvider, NavigationProvider } from './src/services';
export { Navigation } from './src/ui/navigation';

export { DefaultNavigation, Navigation, getPresets } from './src/ui';

export type {
NavigationTreeDefinition,
ProjectNavigationDefinition,
NodeDefinition,
NavigationGroupPreset,
GroupDefinition,
RecentlyAccessedDefinition,
CloudLinkDefinition,
RootNavigationItemDefinition,
} from './src/ui';

export type {
ChromeNavigation,
ChromeNavigationViewModel,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiCollapsibleNavGroup, EuiLink } from '@elastic/eui';
import React, { FC } from 'react';
import { getI18nStrings } from '../i18n_strings';

const i18nTexts = getI18nStrings();

const presets = {
projects: {
href: 'https://cloud.elastic.co/projects',
icon: 'spaces',
title: i18nTexts.linkToCloudProjects,
dataTestSubj: 'nav-header-link-to-projects',
},
deployments: {
href: 'https://cloud.elastic.co/deployments',
icon: 'spaces',
title: i18nTexts.linkToCloudDeployments,
dataTestSubj: 'nav-header-link-to-deployments',
},
};

export interface Props {
/** Use one of the cloud link presets */
preset?: 'projects' | 'deployments' | null;
/** Optional. If "preset" is not provided it is required */
href?: string;
/** Optional. If "preset" is not provided it is required */
icon?: string;
/** Optional. If "preset" is not provided it is required */
title?: string;
}

export const CloudLink: FC<Props> = ({ preset, href: _href, icon: _icon, title: _title }) => {
if (preset === null) {
return null;
}

if (!preset && (!_href || !_icon || !_title)) {
throw new Error(`Navigation.CloudLink requires href, icon, and title`);
}

const { href, icon, title, dataTestSubj } =
preset && presets[preset]
? presets[preset]!
: {
href: _href,
icon: _icon,
title: _title,
dataTestSubj: 'nav-header-link-to-cloud',
};

return (
<EuiLink href={href} color="text" data-test-subj={dataTestSubj}>
<EuiCollapsibleNavGroup iconType={icon} title={title} />
</EuiLink>
);
};
11 changes: 11 additions & 0 deletions packages/shared-ux/chrome/navigation/src/ui/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export type { Props as CloudLinkProps } from './cloud_link';
export { Navigation } from './navigation';
export type { Props as RecentlyAccessedProps } from './recently_accessed';
Loading