[7.x] Remove browser basePath service, move functionality into browser http service (#36611) (#37387)

* Remove browser basePath service, move functionality into browser http service (#36611)

* Remove browser basePath service, move functionality into browser http service

* Update generated documentation for removal of browser basePath

* Fix type interface for removal of basePath

* Split IHttpService into separate setup and start interfaces

* Rename appendToBasePath to prependBasePath, rename removeFromBasePath to removeBasePath

* Fix import of http test setup
This commit is contained in:
Eli Perelman 2019-05-31 12:54:49 -05:00 committed by GitHub
parent c0b857598a
commit c019dc5bd0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 789 additions and 830 deletions

View file

@ -1,24 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [BasePathSetup](./kibana-plugin-public.basepathsetup.md) &gt; [addToPath](./kibana-plugin-public.basepathsetup.addtopath.md)
## BasePathSetup.addToPath() method
Add the current basePath to a path string.
<b>Signature:</b>
```typescript
addToPath(path: string): string;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| path | <code>string</code> | A relative url including the leading <code>/</code>, otherwise it will be returned without modification |
<b>Returns:</b>
`string`

View file

@ -1,19 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [BasePathSetup](./kibana-plugin-public.basepathsetup.md) &gt; [get](./kibana-plugin-public.basepathsetup.get.md)
## BasePathSetup.get() method
Get the basePath as defined by the server
<b>Signature:</b>
```typescript
get(): string;
```
<b>Returns:</b>
`string`
The basePath as defined by the server

View file

@ -1,22 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [BasePathSetup](./kibana-plugin-public.basepathsetup.md)
## BasePathSetup interface
Provides access to the 'server.basePath' configuration option in kibana.yml
<b>Signature:</b>
```typescript
export interface BasePathSetup
```
## Methods
| Method | Description |
| --- | --- |
| [addToPath(path)](./kibana-plugin-public.basepathsetup.addtopath.md) | Add the current basePath to a path string. |
| [get()](./kibana-plugin-public.basepathsetup.get.md) | Get the basePath as defined by the server |
| [removeFromPath(path)](./kibana-plugin-public.basepathsetup.removefrompath.md) | Removes basePath from the given path if the path starts with it |

View file

@ -1,24 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [BasePathSetup](./kibana-plugin-public.basepathsetup.md) &gt; [removeFromPath](./kibana-plugin-public.basepathsetup.removefrompath.md)
## BasePathSetup.removeFromPath() method
Removes basePath from the given path if the path starts with it
<b>Signature:</b>
```typescript
removeFromPath(path: string): string;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| path | <code>string</code> | A relative url that starts with the basePath, which will be stripped |
<b>Returns:</b>
`string`

View file

@ -1,13 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [BasePathStart](./kibana-plugin-public.basepathstart.md)
## BasePathStart type
Provides access to the 'server.basePath' configuration option in kibana.yml
<b>Signature:</b>
```typescript
export declare type BasePathStart = BasePathSetup;
```

View file

@ -1,13 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [CoreSetup](./kibana-plugin-public.coresetup.md) &gt; [basePath](./kibana-plugin-public.coresetup.basepath.md)
## CoreSetup.basePath property
[BasePathSetup](./kibana-plugin-public.basepathsetup.md)
<b>Signature:</b>
```typescript
basePath: BasePathSetup;
```

View file

@ -16,7 +16,6 @@ export interface CoreSetup
| Property | Type | Description |
| --- | --- | --- |
| [basePath](./kibana-plugin-public.coresetup.basepath.md) | <code>BasePathSetup</code> | [BasePathSetup](./kibana-plugin-public.basepathsetup.md) |
| [chrome](./kibana-plugin-public.coresetup.chrome.md) | <code>ChromeSetup</code> | [ChromeSetup](./kibana-plugin-public.chromesetup.md) |
| [fatalErrors](./kibana-plugin-public.coresetup.fatalerrors.md) | <code>FatalErrorsSetup</code> | [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) |
| [http](./kibana-plugin-public.coresetup.http.md) | <code>HttpSetup</code> | [HttpSetup](./kibana-plugin-public.httpsetup.md) |

View file

@ -1,13 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [CoreStart](./kibana-plugin-public.corestart.md) &gt; [basePath](./kibana-plugin-public.corestart.basepath.md)
## CoreStart.basePath property
[BasePathStart](./kibana-plugin-public.basepathstart.md)
<b>Signature:</b>
```typescript
basePath: BasePathStart;
```

View file

@ -17,7 +17,6 @@ export interface CoreStart
| Property | Type | Description |
| --- | --- | --- |
| [application](./kibana-plugin-public.corestart.application.md) | <code>Pick&lt;ApplicationStart, 'capabilities'&gt;</code> | [ApplicationStart](./kibana-plugin-public.applicationstart.md) |
| [basePath](./kibana-plugin-public.corestart.basepath.md) | <code>BasePathStart</code> | [BasePathStart](./kibana-plugin-public.basepathstart.md) |
| [chrome](./kibana-plugin-public.corestart.chrome.md) | <code>ChromeStart</code> | [ChromeStart](./kibana-plugin-public.chromestart.md) |
| [http](./kibana-plugin-public.corestart.http.md) | <code>HttpStart</code> | [HttpStart](./kibana-plugin-public.httpstart.md) |
| [i18n](./kibana-plugin-public.corestart.i18n.md) | <code>I18nStart</code> | [I18nStart](./kibana-plugin-public.i18nstart.md) |

View file

@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [addLoadingCount](./kibana-plugin-public.httpservicebase.addloadingcount.md)
## HttpServiceBase.addLoadingCount() method
<b>Signature:</b>
```typescript
addLoadingCount(count$: Observable<number>): void;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| count$ | <code>Observable&lt;number&gt;</code> | |
<b>Returns:</b>
`void`

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [delete](./kibana-plugin-public.httpservicebase.delete.md)
## HttpServiceBase.delete property
<b>Signature:</b>
```typescript
delete: HttpHandler;
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [fetch](./kibana-plugin-public.httpservicebase.fetch.md)
## HttpServiceBase.fetch property
<b>Signature:</b>
```typescript
fetch: HttpHandler;
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [get](./kibana-plugin-public.httpservicebase.get.md)
## HttpServiceBase.get property
<b>Signature:</b>
```typescript
get: HttpHandler;
```

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [getBasePath](./kibana-plugin-public.httpservicebase.getbasepath.md)
## HttpServiceBase.getBasePath() method
<b>Signature:</b>
```typescript
getBasePath(): string;
```
<b>Returns:</b>
`string`

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [getLoadingCount$](./kibana-plugin-public.httpservicebase.getloadingcount$.md)
## HttpServiceBase.getLoadingCount$() method
<b>Signature:</b>
```typescript
getLoadingCount$(): Observable<number>;
```
<b>Returns:</b>
`Observable<number>`

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [head](./kibana-plugin-public.httpservicebase.head.md)
## HttpServiceBase.head property
<b>Signature:</b>
```typescript
head: HttpHandler;
```

View file

@ -0,0 +1,37 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md)
## HttpServiceBase interface
<b>Signature:</b>
```typescript
export interface HttpServiceBase
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [delete](./kibana-plugin-public.httpservicebase.delete.md) | <code>HttpHandler</code> | |
| [fetch](./kibana-plugin-public.httpservicebase.fetch.md) | <code>HttpHandler</code> | |
| [get](./kibana-plugin-public.httpservicebase.get.md) | <code>HttpHandler</code> | |
| [head](./kibana-plugin-public.httpservicebase.head.md) | <code>HttpHandler</code> | |
| [options](./kibana-plugin-public.httpservicebase.options.md) | <code>HttpHandler</code> | |
| [patch](./kibana-plugin-public.httpservicebase.patch.md) | <code>HttpHandler</code> | |
| [post](./kibana-plugin-public.httpservicebase.post.md) | <code>HttpHandler</code> | |
| [put](./kibana-plugin-public.httpservicebase.put.md) | <code>HttpHandler</code> | |
## Methods
| Method | Description |
| --- | --- |
| [addLoadingCount(count$)](./kibana-plugin-public.httpservicebase.addloadingcount.md) | |
| [getBasePath()](./kibana-plugin-public.httpservicebase.getbasepath.md) | |
| [getLoadingCount$()](./kibana-plugin-public.httpservicebase.getloadingcount$.md) | |
| [prependBasePath(path)](./kibana-plugin-public.httpservicebase.prependbasepath.md) | |
| [removeBasePath(path)](./kibana-plugin-public.httpservicebase.removebasepath.md) | |
| [stop()](./kibana-plugin-public.httpservicebase.stop.md) | |

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [options](./kibana-plugin-public.httpservicebase.options.md)
## HttpServiceBase.options property
<b>Signature:</b>
```typescript
options: HttpHandler;
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [patch](./kibana-plugin-public.httpservicebase.patch.md)
## HttpServiceBase.patch property
<b>Signature:</b>
```typescript
patch: HttpHandler;
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [post](./kibana-plugin-public.httpservicebase.post.md)
## HttpServiceBase.post property
<b>Signature:</b>
```typescript
post: HttpHandler;
```

View file

@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [prependBasePath](./kibana-plugin-public.httpservicebase.prependbasepath.md)
## HttpServiceBase.prependBasePath() method
<b>Signature:</b>
```typescript
prependBasePath(path: string): string;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| path | <code>string</code> | |
<b>Returns:</b>
`string`

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [put](./kibana-plugin-public.httpservicebase.put.md)
## HttpServiceBase.put property
<b>Signature:</b>
```typescript
put: HttpHandler;
```

View file

@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [removeBasePath](./kibana-plugin-public.httpservicebase.removebasepath.md)
## HttpServiceBase.removeBasePath() method
<b>Signature:</b>
```typescript
removeBasePath(path: string): string;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| path | <code>string</code> | |
<b>Returns:</b>
`string`

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [stop](./kibana-plugin-public.httpservicebase.stop.md)
## HttpServiceBase.stop() method
<b>Signature:</b>
```typescript
stop(): void;
```
<b>Returns:</b>
`void`

View file

@ -8,5 +8,5 @@
<b>Signature:</b>
```typescript
export declare type HttpSetup = ReturnType<HttpService['setup']>;
export declare type HttpSetup = HttpServiceBase;
```

View file

@ -8,5 +8,5 @@
<b>Signature:</b>
```typescript
export declare type HttpStart = ReturnType<HttpService['start']>;
export declare type HttpStart = HttpServiceBase;
```

View file

@ -23,7 +23,6 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| --- | --- |
| [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | |
| [ApplicationStart](./kibana-plugin-public.applicationstart.md) | |
| [BasePathSetup](./kibana-plugin-public.basepathsetup.md) | Provides access to the 'server.basePath' configuration option in kibana.yml |
| [Capabilities](./kibana-plugin-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. |
| [ChromeBadge](./kibana-plugin-public.chromebadge.md) | |
| [ChromeBrand](./kibana-plugin-public.chromebrand.md) | |
@ -33,6 +32,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [CoreStart](./kibana-plugin-public.corestart.md) | Core services exposed to the <code>Plugin</code> start lifecycle |
| [FatalErrorInfo](./kibana-plugin-public.fatalerrorinfo.md) | Represents the <code>message</code> and <code>stack</code> of a fatal Error |
| [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. |
| [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) | |
| [I18nSetup](./kibana-plugin-public.i18nsetup.md) | I18nSetup.Context is required by any localizable React component from @<!-- -->kbn/i18n and @<!-- -->elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. |
| [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) | |
| [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | |
@ -46,7 +46,6 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| Type Alias | Description |
| --- | --- |
| [BasePathStart](./kibana-plugin-public.basepathstart.md) | Provides access to the 'server.basePath' configuration option in kibana.yml |
| [ChromeHelpExtension](./kibana-plugin-public.chromehelpextension.md) | |
| [ChromeSetup](./kibana-plugin-public.chromesetup.md) | |
| [ChromeStart](./kibana-plugin-public.chromestart.md) | |

View file

@ -17,7 +17,6 @@
* under the License.
*/
import { basePathServiceMock } from '../base_path/base_path_service.mock';
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
import { MockCapabilitiesService } from './application_service.test.mocks';
import { ApplicationService } from './application_service';
@ -29,9 +28,7 @@ describe('#start()', () => {
setup.registerApp({ id: 'app1' } as any);
setup.registerLegacyApp({ id: 'app2' } as any);
const injectedMetadata = injectedMetadataServiceMock.createStartContract();
const basePath = basePathServiceMock.createStartContract();
expect((await service.start({ basePath, injectedMetadata })).availableApps)
.toMatchInlineSnapshot(`
expect((await service.start({ injectedMetadata })).availableApps).toMatchInlineSnapshot(`
Array [
Object {
"id": "app1",
@ -48,11 +45,9 @@ Array [
const setup = service.setup();
setup.registerApp({ id: 'app1' } as any);
const injectedMetadata = injectedMetadataServiceMock.createStartContract();
const basePath = basePathServiceMock.createStartContract();
await service.start({ basePath, injectedMetadata });
await service.start({ injectedMetadata });
expect(MockCapabilitiesService.start).toHaveBeenCalledWith({
apps: [{ id: 'app1' }],
basePath,
injectedMetadata,
});
});
@ -62,11 +57,9 @@ Array [
const setup = service.setup();
setup.registerLegacyApp({ id: 'legacyApp1' } as any);
const injectedMetadata = injectedMetadataServiceMock.createStartContract();
const basePath = basePathServiceMock.createStartContract();
await service.start({ basePath, injectedMetadata });
await service.start({ injectedMetadata });
expect(MockCapabilitiesService.start).toHaveBeenCalledWith({
apps: [{ id: 'legacyApp1' }],
basePath,
injectedMetadata,
});
});

View file

@ -20,7 +20,6 @@
import { Observable, BehaviorSubject } from 'rxjs';
import { CapabilitiesStart, CapabilitiesService, Capabilities } from './capabilities';
import { InjectedMetadataStart } from '../injected_metadata';
import { BasePathStart } from '../base_path';
interface BaseApp {
id: string;
@ -106,7 +105,6 @@ export interface ApplicationStart {
}
interface StartDeps {
basePath: BasePathStart;
injectedMetadata: InjectedMetadataStart;
}
@ -130,14 +128,13 @@ export class ApplicationService {
};
}
public async start({ basePath, injectedMetadata }: StartDeps): Promise<ApplicationStart> {
public async start({ injectedMetadata }: StartDeps): Promise<ApplicationStart> {
this.apps$.complete();
this.legacyApps$.complete();
const apps = [...this.apps$.value, ...this.legacyApps$.value];
const { capabilities, availableApps } = await this.capabilities.start({
apps,
basePath,
injectedMetadata,
});

View file

@ -19,13 +19,11 @@
import { InjectedMetadataService } from '../../injected_metadata';
import { CapabilitiesService } from './capabilities_service';
import { basePathServiceMock } from '../../base_path/base_path_service.mock';
describe('#start', () => {
const basePath = basePathServiceMock.createStartContract();
basePath.addToPath.mockImplementation(str => str);
const injectedMetadata = new InjectedMetadataService({
injectedMetadata: {
version: 'kibanaVersion',
capabilities: {
catalogue: {},
management: {},
@ -42,7 +40,7 @@ describe('#start', () => {
it('filters available apps based on returned navLinks', async () => {
const service = new CapabilitiesService();
expect((await service.start({ apps, basePath, injectedMetadata })).availableApps).toEqual([
expect((await service.start({ apps, injectedMetadata })).availableApps).toEqual([
{ id: 'app1' },
]);
});
@ -51,7 +49,6 @@ describe('#start', () => {
const service = new CapabilitiesService();
const { capabilities } = await service.start({
apps,
basePath,
injectedMetadata,
});

View file

@ -20,11 +20,9 @@
import { deepFreeze, RecursiveReadonly } from '../../utils/deep_freeze';
import { MixedApp } from '../application_service';
import { InjectedMetadataStart } from '../../injected_metadata';
import { BasePathStart } from '../../base_path';
interface StartDeps {
apps: ReadonlyArray<MixedApp>;
basePath: BasePathStart;
injectedMetadata: InjectedMetadataStart;
}
@ -74,7 +72,7 @@ export interface CapabilitiesStart {
* Service that is responsible for UI Capabilities.
*/
export class CapabilitiesService {
public async start({ apps, basePath, injectedMetadata }: StartDeps): Promise<CapabilitiesStart> {
public async start({ apps, injectedMetadata }: StartDeps): Promise<CapabilitiesStart> {
const capabilities = deepFreeze(injectedMetadata.getCapabilities());
const availableApps = apps.filter(app => capabilities.navLinks[app.id]);

View file

@ -1,50 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { BasePathService, BasePathSetup } from './base_path_service';
const createSetupContractMock = () => {
const setupContract: jest.Mocked<BasePathSetup> = {
get: jest.fn(),
addToPath: jest.fn(),
removeFromPath: jest.fn(),
};
setupContract.get.mockReturnValue('get');
setupContract.addToPath.mockReturnValue('addToPath');
setupContract.removeFromPath.mockReturnValue('removeFromPath');
return setupContract;
};
const createStartContractMock = createSetupContractMock;
type BasePathServiceContract = PublicMethodsOf<BasePathService>;
const createMock = () => {
const mocked: jest.Mocked<BasePathServiceContract> = {
setup: jest.fn(),
start: jest.fn(),
};
mocked.setup.mockReturnValue(createSetupContractMock());
mocked.start.mockReturnValue(createStartContractMock());
return mocked;
};
export const basePathServiceMock = {
create: createMock,
createSetupContract: createSetupContractMock,
createStartContract: createStartContractMock,
};

View file

@ -1,104 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
import { BasePathService } from './base_path_service';
function setupService(options: any = {}) {
const injectedBasePath: string =
options.injectedBasePath === undefined ? '/foo/bar' : options.injectedBasePath;
const service = new BasePathService();
const injectedMetadata = injectedMetadataServiceMock.createSetupContract();
injectedMetadata.getBasePath.mockReturnValue(injectedBasePath);
const setupContract = service.setup({
injectedMetadata,
});
return {
service,
setupContract,
injectedBasePath,
};
}
describe('setup.get()', () => {
it('returns an empty string if no basePath is injected', () => {
const { setupContract } = setupService({ injectedBasePath: null });
expect(setupContract.get()).toBe('');
});
it('returns the injected basePath', () => {
const { setupContract } = setupService();
expect(setupContract.get()).toBe('/foo/bar');
});
});
describe('setup.addToPath()', () => {
it('adds the base path to the path if it is relative and starts with a slash', () => {
const { setupContract } = setupService();
expect(setupContract.addToPath('/a/b')).toBe('/foo/bar/a/b');
});
it('leaves the query string and hash of path unchanged', () => {
const { setupContract } = setupService();
expect(setupContract.addToPath('/a/b?x=y#c/d/e')).toBe('/foo/bar/a/b?x=y#c/d/e');
});
it('returns the path unchanged if it does not start with a slash', () => {
const { setupContract } = setupService();
expect(setupContract.addToPath('a/b')).toBe('a/b');
});
it('returns the path unchanged it it has a hostname', () => {
const { setupContract } = setupService();
expect(setupContract.addToPath('http://localhost:5601/a/b')).toBe('http://localhost:5601/a/b');
});
});
describe('setup.removeFromPath()', () => {
it('removes the basePath if relative path starts with it', () => {
const { setupContract } = setupService();
expect(setupContract.removeFromPath('/foo/bar/a/b')).toBe('/a/b');
});
it('leaves query string and hash intact', () => {
const { setupContract } = setupService();
expect(setupContract.removeFromPath('/foo/bar/a/b?c=y#1234')).toBe('/a/b?c=y#1234');
});
it('ignores urls with hostnames', () => {
const { setupContract } = setupService();
expect(setupContract.removeFromPath('http://localhost:5601/foo/bar/a/b')).toBe(
'http://localhost:5601/foo/bar/a/b'
);
});
it('returns slash if path is just basePath', () => {
const { setupContract } = setupService();
expect(setupContract.removeFromPath('/foo/bar')).toBe('/');
});
it('returns full path if basePath is not its own segment', () => {
const { setupContract } = setupService();
expect(setupContract.removeFromPath('/foo/barhop')).toBe('/foo/barhop');
});
});

View file

@ -1,104 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable max-classes-per-file */
import { InjectedMetadataSetup, InjectedMetadataStart } from '../injected_metadata';
import { modifyUrl } from '../utils';
/**
* Provides access to the 'server.basePath' configuration option in kibana.yml
*
* @public
*/
export interface BasePathSetup {
/**
* Get the basePath as defined by the server
*
* @returns The basePath as defined by the server
*/
get(): string;
/**
* Add the current basePath to a path string.
*
* @param path - A relative url including the leading `/`, otherwise it will be returned without modification
*/
addToPath(path: string): string;
/**
* Removes basePath from the given path if the path starts with it
*
* @param path - A relative url that starts with the basePath, which will be stripped
*/
removeFromPath(path: string): string;
}
/**
* Provides access to the 'server.basePath' configuration option in kibana.yml
*
* @public
*/
export type BasePathStart = BasePathSetup;
interface SetupDeps {
injectedMetadata: InjectedMetadataSetup;
}
interface StartDeps {
injectedMetadata: InjectedMetadataStart;
}
/** @internal */
export class BasePathService {
public setup({ injectedMetadata }: SetupDeps) {
const basePath = injectedMetadata.getBasePath() || '';
const basePathSetup: BasePathSetup = {
get: () => basePath,
addToPath: path => {
return modifyUrl(path, parts => {
if (!parts.hostname && parts.pathname && parts.pathname.startsWith('/')) {
parts.pathname = `${basePath}${parts.pathname}`;
}
});
},
removeFromPath(path: string): string {
if (!basePath) {
return path;
}
if (path === basePath) {
return '/';
}
if (path.startsWith(basePath + '/')) {
return path.slice(basePath.length);
}
return path;
},
};
return basePathSetup;
}
public start({ injectedMetadata }: StartDeps) {
return this.setup({ injectedMetadata });
}
}

View file

@ -1,20 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { BasePathService, BasePathSetup, BasePathStart } from './base_path_service';

View file

@ -27,7 +27,7 @@ import { InjectedMetadataSetup } from '../injected_metadata';
import { NotificationsSetup } from '../notifications';
import { NavLinksService } from './nav_links/nav_links_service';
import { ApplicationStart } from '../application';
import { BasePathStart } from '../base_path';
import { HttpStart } from '../http';
const IS_COLLAPSED_KEY = 'core.chrome.isCollapsed';
@ -70,7 +70,7 @@ interface SetupDeps {
interface StartDeps {
application: ApplicationStart;
basePath: BasePathStart;
http: HttpStart;
}
/** @internal */
@ -230,9 +230,9 @@ export class ChromeService {
};
}
public start({ application, basePath }: StartDeps) {
public start({ application, http }: StartDeps) {
return {
navLinks: this.navLinks.start({ application, basePath }),
navLinks: this.navLinks.start({ application, http }),
};
}

View file

@ -28,8 +28,8 @@ const mockAppService = {
],
} as any;
const mockBasePath = {
addToPath: (url: string) => `wow${url}`,
const mockHttp = {
prependBasePath: (url: string) => `wow${url}`,
} as any;
describe('NavLinksService', () => {
@ -38,7 +38,7 @@ describe('NavLinksService', () => {
beforeEach(() => {
service = new NavLinksService();
start = service.start({ application: mockAppService, basePath: mockBasePath });
start = service.start({ application: mockAppService, http: mockHttp });
});
describe('#getNavLinks$()', () => {

View file

@ -22,17 +22,17 @@ import { BehaviorSubject, ReplaySubject } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { NavLinkWrapper, NavLinkUpdateableFields } from './nav_link';
import { ApplicationStart } from '../../application';
import { BasePathStart } from '../../base_path';
import { HttpStart } from '../../http';
interface StartDeps {
application: ApplicationStart;
basePath: BasePathStart;
http: HttpStart;
}
export class NavLinksService {
private readonly stop$ = new ReplaySubject(1);
public start({ application, basePath }: StartDeps) {
public start({ application, http }: StartDeps) {
const navLinks$ = new BehaviorSubject<ReadonlyMap<string, NavLinkWrapper>>(
new Map(
application.availableApps.map(
@ -42,7 +42,7 @@ export class NavLinksService {
new NavLinkWrapper({
...app,
// Either rootRoute or appUrl must be defined.
baseUrl: relativeToAbsolute(basePath.addToPath((app.rootRoute || app.appUrl)!)),
baseUrl: relativeToAbsolute(http.prependBasePath((app.rootRoute || app.appUrl)!)),
}),
] as [string, NavLinkWrapper]
)

View file

@ -17,7 +17,6 @@
* under the License.
*/
import { basePathServiceMock } from './base_path/base_path_service.mock';
import { applicationServiceMock } from './application/application_service.mock';
import { chromeServiceMock } from './chrome/chrome_service.mock';
import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock';
@ -74,12 +73,6 @@ jest.doMock('./http', () => ({
HttpService: HttpServiceConstructor,
}));
export const MockBasePathService = basePathServiceMock.create();
export const BasePathServiceConstructor = jest.fn().mockImplementation(() => MockBasePathService);
jest.doMock('./base_path', () => ({
BasePathService: BasePathServiceConstructor,
}));
export const MockUiSettingsService = uiSettingsServiceMock.create();
export const UiSettingsServiceConstructor = jest
.fn()

View file

@ -18,14 +18,12 @@
*/
import {
BasePathServiceConstructor,
ChromeServiceConstructor,
FatalErrorsServiceConstructor,
HttpServiceConstructor,
I18nServiceConstructor,
InjectedMetadataServiceConstructor,
LegacyPlatformServiceConstructor,
MockBasePathService,
MockChromeService,
MockFatalErrorsService,
MockHttpService,
@ -78,7 +76,6 @@ describe('constructor', () => {
expect(FatalErrorsServiceConstructor).toHaveBeenCalledTimes(1);
expect(NotificationServiceConstructor).toHaveBeenCalledTimes(1);
expect(HttpServiceConstructor).toHaveBeenCalledTimes(1);
expect(BasePathServiceConstructor).toHaveBeenCalledTimes(1);
expect(UiSettingsServiceConstructor).toHaveBeenCalledTimes(1);
expect(ChromeServiceConstructor).toHaveBeenCalledTimes(1);
expect(OverlayServiceConstructor).toHaveBeenCalledTimes(1);
@ -169,11 +166,6 @@ describe('#setup()', () => {
expect(MockHttpService.setup).toHaveBeenCalledTimes(1);
});
it('calls basePath#setup()', async () => {
await setupCore();
expect(MockBasePathService.setup).toHaveBeenCalledTimes(1);
});
it('calls uiSettings#setup()', async () => {
await setupCore();
expect(MockUiSettingsService.setup).toHaveBeenCalledTimes(1);

View file

@ -20,7 +20,6 @@
import './core.css';
import { InternalCoreSetup, InternalCoreStart } from '.';
import { BasePathService } from './base_path';
import { ChromeService } from './chrome';
import { FatalErrorsService, FatalErrorsSetup } from './fatal_errors';
import { HttpService } from './http';
@ -61,7 +60,6 @@ export class CoreSystem {
private readonly notifications: NotificationsService;
private readonly http: HttpService;
private readonly uiSettings: UiSettingsService;
private readonly basePath: BasePathService;
private readonly chrome: ChromeService;
private readonly i18n: I18nService;
private readonly overlay: OverlayService;
@ -95,7 +93,6 @@ export class CoreSystem {
this.notifications = new NotificationsService();
this.http = new HttpService();
this.basePath = new BasePathService();
this.uiSettings = new UiSettingsService();
this.overlay = new OverlayService();
this.application = new ApplicationService();
@ -117,27 +114,14 @@ export class CoreSystem {
const i18n = this.i18n.setup();
const injectedMetadata = this.injectedMetadata.setup();
this.fatalErrorsSetup = this.fatalErrors.setup({ injectedMetadata, i18n });
const basePath = this.basePath.setup({ injectedMetadata });
const http = this.http.setup({
basePath,
injectedMetadata,
fatalErrors: this.fatalErrorsSetup,
});
const uiSettings = this.uiSettings.setup({
http,
injectedMetadata,
basePath,
});
const http = this.http.setup({ injectedMetadata, fatalErrors: this.fatalErrorsSetup });
const uiSettings = this.uiSettings.setup({ http, injectedMetadata });
const notifications = this.notifications.setup({ uiSettings });
const application = this.application.setup();
const chrome = this.chrome.setup({
injectedMetadata,
notifications,
});
const chrome = this.chrome.setup({ injectedMetadata, notifications });
const core: InternalCoreSetup = {
application,
basePath,
chrome,
fatalErrors: this.fatalErrorsSetup,
http,
@ -166,11 +150,10 @@ export class CoreSystem {
public async start() {
try {
const injectedMetadata = await this.injectedMetadata.start();
const basePath = await this.basePath.start({ injectedMetadata });
const http = await this.http.start();
const http = await this.http.start({ injectedMetadata, fatalErrors: this.fatalErrorsSetup });
const i18n = await this.i18n.start();
const application = await this.application.start({ basePath, injectedMetadata });
const chrome = await this.chrome.start({ application, basePath });
const application = await this.application.start({ injectedMetadata });
const chrome = await this.chrome.start({ application, http });
const notificationsTargetDomElement = document.createElement('div');
const overlayTargetDomElement = document.createElement('div');
@ -191,7 +174,6 @@ export class CoreSystem {
const core: InternalCoreStart = {
application,
basePath,
chrome,
http,
i18n,

View file

@ -1,91 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { merge } from 'lodash';
import { format } from 'url';
import { HttpFetchOptions, HttpBody, Deps } from './types';
import { HttpFetchError } from './http_fetch_error';
const JSON_CONTENT = /^(application\/(json|x-javascript)|text\/(x-)?javascript|x-json)(;.*)?$/;
const NDJSON_CONTENT = /^(application\/ndjson)(;.*)?$/;
export const setup = ({ basePath, injectedMetadata }: Deps) => {
async function fetch(path: string, options: HttpFetchOptions = {}): Promise<HttpBody> {
const { query, prependBasePath, ...fetchOptions } = merge(
{
method: 'GET',
credentials: 'same-origin',
prependBasePath: true,
headers: {
'kbn-version': injectedMetadata.getKibanaVersion(),
'Content-Type': 'application/json',
},
},
options
);
const url = format({
pathname: prependBasePath ? basePath.addToPath(path) : path,
query,
});
if (
options.headers &&
'Content-Type' in options.headers &&
options.headers['Content-Type'] === undefined
) {
delete fetchOptions.headers['Content-Type'];
}
let response;
let body = null;
try {
response = await window.fetch(url, fetchOptions as RequestInit);
} catch (err) {
throw new HttpFetchError(err.message);
}
const contentType = response.headers.get('Content-Type') || '';
try {
if (NDJSON_CONTENT.test(contentType)) {
body = await response.blob();
} else if (JSON_CONTENT.test(contentType)) {
body = await response.json();
} else {
body = await response.text();
}
} catch (err) {
throw new HttpFetchError(err.message, response, body);
}
if (!response.ok) {
throw new HttpFetchError(response.statusText, response, body);
}
return body;
}
function shorthand(method: string) {
return (path: string, options: HttpFetchOptions = {}) => fetch(path, { ...options, method });
}
return { fetch, shorthand };
};

View file

@ -17,9 +17,10 @@
* under the License.
*/
import { HttpService, HttpSetup, HttpStart } from './http_service';
import { HttpService } from './http_service';
import { HttpSetup, HttpStart } from './types';
const createSetupContractMock = (): jest.Mocked<HttpSetup> => ({
const createServiceMock = () => ({
fetch: jest.fn(),
get: jest.fn(),
head: jest.fn(),
@ -28,10 +29,16 @@ const createSetupContractMock = (): jest.Mocked<HttpSetup> => ({
patch: jest.fn(),
delete: jest.fn(),
options: jest.fn(),
getBasePath: jest.fn(),
prependBasePath: jest.fn(),
removeBasePath: jest.fn(),
addLoadingCount: jest.fn(),
getLoadingCount$: jest.fn(),
stop: jest.fn(),
});
const createStartContractMock = (): jest.Mocked<HttpStart> => undefined;
const createSetupContractMock = (): jest.Mocked<HttpSetup> => createServiceMock();
const createStartContractMock = (): jest.Mocked<HttpStart> => createServiceMock();
const createMock = (): jest.Mocked<PublicMethodsOf<HttpService>> => ({
setup: jest.fn().mockReturnValue(createSetupContractMock()),
start: jest.fn().mockReturnValue(createStartContractMock()),

View file

@ -21,34 +21,97 @@ import * as Rx from 'rxjs';
import { toArray } from 'rxjs/operators';
// @ts-ignore
import fetchMock from 'fetch-mock/es5/client';
import { BasePathService } from '../base_path/base_path_service';
import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.mock';
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
import { HttpService } from './http_service';
import { readFileSync } from 'fs';
import { join } from 'path';
import { setup, SetupTap } from '../../../test_utils/public/http_test_setup';
function setupService() {
const httpService = new HttpService();
const fatalErrors = fatalErrorsServiceMock.createSetupContract();
const injectedMetadata = injectedMetadataServiceMock.createSetupContract();
const setupFakeBasePath: SetupTap = injectedMetadata => {
injectedMetadata.getBasePath.mockReturnValue('/foo/bar');
};
injectedMetadata.getBasePath.mockReturnValueOnce('http://localhost/myBase');
describe('getBasePath', () => {
it('returns an empty string if no basePath is injected', () => {
const { http } = setup(injectedMetadata => {
injectedMetadata.getBasePath.mockReturnValue('');
});
const basePath = new BasePathService().setup({ injectedMetadata });
const http = httpService.setup({ basePath, fatalErrors, injectedMetadata });
expect(http.getBasePath()).toBe('');
});
return { httpService, fatalErrors, http };
}
it('returns the injected basePath', () => {
const { http } = setup(setupFakeBasePath);
describe('http requests', async () => {
expect(http.getBasePath()).toBe('/foo/bar');
});
});
describe('prependBasePath', () => {
it('adds the base path to the path if it is relative and starts with a slash', () => {
const { http } = setup(setupFakeBasePath);
expect(http.prependBasePath('/a/b')).toBe('/foo/bar/a/b');
});
it('leaves the query string and hash of path unchanged', () => {
const { http } = setup(setupFakeBasePath);
expect(http.prependBasePath('/a/b?x=y#c/d/e')).toBe('/foo/bar/a/b?x=y#c/d/e');
});
it('returns the path unchanged if it does not start with a slash', () => {
const { http } = setup(setupFakeBasePath);
expect(http.prependBasePath('a/b')).toBe('a/b');
});
it('returns the path unchanged it it has a hostname', () => {
const { http } = setup(setupFakeBasePath);
expect(http.prependBasePath('http://localhost:5601/a/b')).toBe('http://localhost:5601/a/b');
});
});
describe('removeBasePath', () => {
it('removes the basePath if relative path starts with it', () => {
const { http } = setup(setupFakeBasePath);
expect(http.removeBasePath('/foo/bar/a/b')).toBe('/a/b');
});
it('leaves query string and hash intact', () => {
const { http } = setup(setupFakeBasePath);
expect(http.removeBasePath('/foo/bar/a/b?c=y#1234')).toBe('/a/b?c=y#1234');
});
it('ignores urls with hostnames', () => {
const { http } = setup(setupFakeBasePath);
expect(http.removeBasePath('http://localhost:5601/foo/bar/a/b')).toBe(
'http://localhost:5601/foo/bar/a/b'
);
});
it('returns slash if path is just basePath', () => {
const { http } = setup(setupFakeBasePath);
expect(http.removeBasePath('/foo/bar')).toBe('/');
});
it('returns full path if basePath is not its own segment', () => {
const { http } = setup(setupFakeBasePath);
expect(http.removeBasePath('/foo/barhop')).toBe('/foo/barhop');
});
});
describe('http requests', () => {
afterEach(() => {
fetchMock.restore();
});
it('should use supplied request method', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.post('*', {});
await http.fetch('/my/path', { method: 'POST' });
@ -57,7 +120,7 @@ describe('http requests', async () => {
});
it('should use supplied Content-Type', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.get('*', {});
await http.fetch('/my/path', { headers: { 'Content-Type': 'CustomContentType' } });
@ -68,7 +131,7 @@ describe('http requests', async () => {
});
it('should use supplied pathname and querystring', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.get('*', {});
await http.fetch('/my/path', { query: { a: 'b' } });
@ -77,7 +140,7 @@ describe('http requests', async () => {
});
it('should use supplied headers', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.get('*', {});
await http.fetch('/my/path', {
@ -92,7 +155,7 @@ describe('http requests', async () => {
});
it('should return response', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.get('*', { foo: 'bar' });
@ -102,7 +165,7 @@ describe('http requests', async () => {
});
it('should prepend url with basepath by default', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.get('*', {});
await http.fetch('/my/path');
@ -111,7 +174,7 @@ describe('http requests', async () => {
});
it('should not prepend url with basepath when disabled', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.get('*', {});
await http.fetch('my/path', { prependBasePath: false });
@ -120,7 +183,7 @@ describe('http requests', async () => {
});
it('should make request with defaults', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.get('*', {});
await http.fetch('/my/path');
@ -136,7 +199,7 @@ describe('http requests', async () => {
});
it('should reject on network error', async () => {
const { http } = setupService();
const { http } = setup();
expect.assertions(1);
fetchMock.get('*', { status: 500 });
@ -145,7 +208,7 @@ describe('http requests', async () => {
});
it('should contain error message when throwing response', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.get('*', { status: 404, body: { foo: 'bar' } });
@ -162,7 +225,7 @@ describe('http requests', async () => {
});
it('should support get() helper', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.get('*', {});
await http.get('/my/path', { method: 'POST' });
@ -171,7 +234,7 @@ describe('http requests', async () => {
});
it('should support head() helper', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.head('*', {});
await http.head('/my/path', { method: 'GET' });
@ -180,7 +243,7 @@ describe('http requests', async () => {
});
it('should support post() helper', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.post('*', {});
await http.post('/my/path', { method: 'GET', body: '{}' });
@ -189,7 +252,7 @@ describe('http requests', async () => {
});
it('should support put() helper', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.put('*', {});
await http.put('/my/path', { method: 'GET', body: '{}' });
@ -198,7 +261,7 @@ describe('http requests', async () => {
});
it('should support patch() helper', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.patch('*', {});
await http.patch('/my/path', { method: 'GET', body: '{}' });
@ -207,7 +270,7 @@ describe('http requests', async () => {
});
it('should support delete() helper', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.delete('*', {});
await http.delete('/my/path', { method: 'GET' });
@ -216,7 +279,7 @@ describe('http requests', async () => {
});
it('should support options() helper', async () => {
const { http } = setupService();
const { http } = setup();
fetchMock.mock('*', { method: 'OPTIONS' });
await http.options('/my/path', { method: 'GET' });
@ -225,7 +288,7 @@ describe('http requests', async () => {
});
it('should make requests for NDJSON content', async () => {
const { http } = setupService();
const { http } = setup();
const content = readFileSync(join(__dirname, '_import_objects.ndjson'), { encoding: 'utf-8' });
const body = new FormData();
@ -250,9 +313,9 @@ describe('http requests', async () => {
});
});
describe('addLoadingCount()', async () => {
describe('addLoadingCount()', () => {
it('subscribes to passed in sources, unsubscribes on stop', () => {
const { httpService, http } = setupService();
const { httpService, http } = setup();
const unsubA = jest.fn();
const subA = jest.fn().mockReturnValue(unsubA);
@ -275,23 +338,23 @@ describe('addLoadingCount()', async () => {
});
it('adds a fatal error if source observables emit an error', async () => {
const { http, fatalErrors } = setupService();
const { http, fatalErrors } = setup();
http.addLoadingCount(Rx.throwError(new Error('foo bar')));
expect(fatalErrors.add.mock.calls).toMatchSnapshot();
});
it('adds a fatal error if source observable emits a negative number', async () => {
const { http, fatalErrors } = setupService();
const { http, fatalErrors } = setup();
http.addLoadingCount(Rx.of(1, 2, 3, 4, -9));
expect(fatalErrors.add.mock.calls).toMatchSnapshot();
});
});
describe('getLoadingCount$()', async () => {
describe('getLoadingCount$()', () => {
it('emits 0 initially, the right count when sources emit their own count, and ends with zero', async () => {
const { httpService, http } = setupService();
const { httpService, http } = setup();
const countA$ = new Rx.Subject<number>();
const countB$ = new Rx.Subject<number>();
@ -318,7 +381,7 @@ describe('getLoadingCount$()', async () => {
});
it('only emits when loading count changes', async () => {
const { httpService, http } = setupService();
const { httpService, http } = setup();
const count$ = new Rx.Subject<number>();
const promise = http

View file

@ -17,84 +17,25 @@
* under the License.
*/
import * as Rx from 'rxjs';
import {
distinctUntilChanged,
endWith,
map,
pairwise,
startWith,
takeUntil,
tap,
} from 'rxjs/operators';
import { Deps } from './types';
import { setup } from './fetch';
import { HttpDeps, HttpSetup, HttpStart, HttpServiceBase } from './types';
import { setup } from './http_setup';
/** @internal */
export class HttpService {
private readonly loadingCount$ = new Rx.BehaviorSubject(0);
private readonly stop$ = new Rx.Subject();
private service!: HttpServiceBase;
public setup(deps: Deps) {
const { fetch, shorthand } = setup(deps);
return {
fetch,
delete: shorthand('DELETE'),
get: shorthand('GET'),
head: shorthand('HEAD'),
options: shorthand('OPTIONS'),
patch: shorthand('PATCH'),
post: shorthand('POST'),
put: shorthand('PUT'),
addLoadingCount: (count$: Rx.Observable<number>) => {
count$
.pipe(
distinctUntilChanged(),
tap(count => {
if (count < 0) {
throw new Error(
'Observables passed to loadingCount.add() must only emit positive numbers'
);
}
}),
// use takeUntil() so that we can finish each stream on stop() the same way we do when they complete,
// by removing the previous count from the total
takeUntil(this.stop$),
endWith(0),
startWith(0),
pairwise(),
map(([prev, next]) => next - prev)
)
.subscribe({
next: delta => {
this.loadingCount$.next(this.loadingCount$.getValue() + delta);
},
error: error => {
deps.fatalErrors.add(error);
},
});
},
getLoadingCount$: () => {
return this.loadingCount$.pipe(distinctUntilChanged());
},
};
public setup(deps: HttpDeps): HttpSetup {
this.service = setup(deps.injectedMetadata, deps.fatalErrors);
return this.service;
}
// eslint-disable-next-line no-unused-params
public start() {}
public start(deps: HttpDeps): HttpStart {
return this.service || this.setup(deps);
}
public stop() {
this.stop$.next();
this.loadingCount$.complete();
if (this.service) {
this.service.stop();
}
}
}
/** @public */
export type HttpSetup = ReturnType<HttpService['setup']>;
/** @public */
export type HttpStart = ReturnType<HttpService['start']>;

View file

@ -0,0 +1,203 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import {
distinctUntilChanged,
endWith,
map,
pairwise,
startWith,
takeUntil,
tap,
} from 'rxjs/operators';
import { merge } from 'lodash';
import { format } from 'url';
import { InjectedMetadataSetup } from '../injected_metadata';
import { FatalErrorsSetup } from '../fatal_errors';
import { modifyUrl } from '../utils';
import { HttpBody, HttpFetchOptions, HttpServiceBase } from './types';
import { HttpFetchError } from './http_fetch_error';
const JSON_CONTENT = /^(application\/(json|x-javascript)|text\/(x-)?javascript|x-json)(;.*)?$/;
const NDJSON_CONTENT = /^(application\/ndjson)(;.*)?$/;
export const setup = (
injectedMetadata: InjectedMetadataSetup,
fatalErrors: FatalErrorsSetup | null
): HttpServiceBase => {
const loadingCount$ = new BehaviorSubject(0);
const stop$ = new Subject();
const kibanaVersion = injectedMetadata.getKibanaVersion();
const basePath = injectedMetadata.getBasePath() || '';
function prependBasePath(path: string): string {
return modifyUrl(path, parts => {
if (!parts.hostname && parts.pathname && parts.pathname.startsWith('/')) {
parts.pathname = `${basePath}${parts.pathname}`;
}
});
}
async function fetch(path: string, options?: HttpFetchOptions): Promise<HttpBody> {
const { query, prependBasePath: shouldPrependBasePath, ...fetchOptions } = merge(
{
method: 'GET',
credentials: 'same-origin',
prependBasePath: true,
headers: {
'kbn-version': kibanaVersion,
'Content-Type': 'application/json',
},
},
options || {}
);
const url = format({
pathname: shouldPrependBasePath ? prependBasePath(path) : path,
query,
});
if (
options &&
options.headers &&
'Content-Type' in options.headers &&
options.headers['Content-Type'] === undefined
) {
delete fetchOptions.headers['Content-Type'];
}
let response;
let body = null;
try {
response = await window.fetch(url, fetchOptions as RequestInit);
} catch (err) {
throw new HttpFetchError(err.message);
}
const contentType = response.headers.get('Content-Type') || '';
try {
if (NDJSON_CONTENT.test(contentType)) {
body = await response.blob();
} else if (JSON_CONTENT.test(contentType)) {
body = await response.json();
} else {
const text = await response.text();
try {
body = JSON.parse(text);
} catch (err) {
body = text;
}
}
} catch (err) {
throw new HttpFetchError(err.message, response, body);
}
if (!response.ok) {
throw new HttpFetchError(response.statusText, response, body);
}
return body;
}
function shorthand(method: string) {
return (path: string, options: HttpFetchOptions = {}) => fetch(path, { ...options, method });
}
function stop() {
stop$.next();
loadingCount$.complete();
}
function getBasePath() {
return basePath;
}
function removeBasePath(path: string): string {
if (!basePath) {
return path;
}
if (path === basePath) {
return '/';
}
if (path.startsWith(`${basePath}/`)) {
return path.slice(basePath.length);
}
return path;
}
function addLoadingCount(count$: Observable<number>) {
count$
.pipe(
distinctUntilChanged(),
tap(count => {
if (count < 0) {
throw new Error(
'Observables passed to loadingCount.add() must only emit positive numbers'
);
}
}),
// use takeUntil() so that we can finish each stream on stop() the same way we do when they complete,
// by removing the previous count from the total
takeUntil(stop$),
endWith(0),
startWith(0),
pairwise(),
map(([prev, next]) => next - prev)
)
.subscribe({
next: delta => {
loadingCount$.next(loadingCount$.getValue() + delta);
},
error: error => {
if (fatalErrors) {
fatalErrors.add(error);
}
},
});
}
function getLoadingCount$() {
return loadingCount$.pipe(distinctUntilChanged());
}
return {
stop,
getBasePath,
prependBasePath,
removeBasePath,
fetch,
delete: shorthand('DELETE'),
get: shorthand('GET'),
head: shorthand('HEAD'),
options: shorthand('OPTIONS'),
patch: shorthand('PATCH'),
post: shorthand('POST'),
put: shorthand('PUT'),
addLoadingCount,
getLoadingCount$,
};
};

View file

@ -17,5 +17,6 @@
* under the License.
*/
export { HttpService, HttpSetup, HttpStart } from './http_service';
export { HttpService } from './http_service';
export { HttpFetchError } from './http_fetch_error';
export { HttpServiceBase, HttpSetup, HttpStart } from './types';

View file

@ -17,13 +17,36 @@
* under the License.
*/
import { BasePathSetup } from '../base_path';
import { Observable } from 'rxjs';
import { InjectedMetadataSetup } from '../injected_metadata';
import { FatalErrorsSetup } from '../fatal_errors';
/** @public */
export interface HttpServiceBase {
stop(): void;
getBasePath(): string;
prependBasePath(path: string): string;
removeBasePath(path: string): string;
fetch: HttpHandler;
delete: HttpHandler;
get: HttpHandler;
head: HttpHandler;
options: HttpHandler;
patch: HttpHandler;
post: HttpHandler;
put: HttpHandler;
addLoadingCount(count$: Observable<number>): void;
getLoadingCount$(): Observable<number>;
}
/** @public */
export type HttpSetup = HttpServiceBase;
/** @public */
export type HttpStart = HttpServiceBase;
/** @public */
export interface HttpHeadersInit {
[name: string]: any;
}
/** @public */
export interface HttpRequestInit {
body?: BodyInit | null;
cache?: RequestCache;
@ -39,17 +62,22 @@ export interface HttpRequestInit {
signal?: AbortSignal | null;
window?: any;
}
export interface Deps {
basePath: BasePathSetup;
/** @public */
export interface HttpDeps {
injectedMetadata: InjectedMetadataSetup;
fatalErrors: FatalErrorsSetup;
fatalErrors: FatalErrorsSetup | null;
}
/** @public */
export interface HttpFetchQuery {
[key: string]: string | number | boolean | undefined;
}
/** @public */
export interface HttpFetchOptions extends HttpRequestInit {
query?: HttpFetchQuery;
prependBasePath?: boolean;
headers?: HttpHeadersInit;
}
/** @public */
export type HttpHandler = (path: string, options?: HttpFetchOptions) => Promise<HttpBody>;
/** @public */
export type HttpBody = BodyInit | null;

View file

@ -35,7 +35,6 @@
* @packageDocumentation
*/
import { BasePathSetup, BasePathStart } from './base_path';
import {
ChromeBadge,
ChromeBrand,
@ -46,7 +45,7 @@ import {
ChromeStart,
} from './chrome';
import { FatalErrorsSetup, FatalErrorInfo } from './fatal_errors';
import { HttpSetup, HttpStart } from './http';
import { HttpServiceBase, HttpSetup, HttpStart } from './http';
import { I18nSetup, I18nStart } from './i18n';
import { InjectedMetadataSetup, InjectedMetadataStart, LegacyNavLink } from './injected_metadata';
import {
@ -74,8 +73,6 @@ export { CoreContext, CoreSystem } from './core_system';
* https://github.com/Microsoft/web-build-tools/issues/1237
*/
export interface CoreSetup {
/** {@link BasePathSetup} */
basePath: BasePathSetup;
/** {@link ChromeSetup} */
chrome: ChromeSetup;
/** {@link FatalErrorsSetup} */
@ -102,8 +99,6 @@ export interface CoreSetup {
export interface CoreStart {
/** {@link ApplicationStart} */
application: Pick<ApplicationStart, 'capabilities'>;
/** {@link BasePathStart} */
basePath: BasePathStart;
/** {@link ChromeStart} */
chrome: ChromeStart;
/** {@link HttpStart} */
@ -131,8 +126,7 @@ export interface InternalCoreStart extends CoreStart {
export {
ApplicationSetup,
ApplicationStart,
BasePathSetup,
BasePathStart,
HttpServiceBase,
HttpSetup,
HttpStart,
FatalErrorsSetup,

View file

@ -149,7 +149,6 @@ jest.mock('ui/chrome/services/global_nav_state', () => {
};
});
import { basePathServiceMock } from '../base_path/base_path_service.mock';
import { chromeServiceMock } from '../chrome/chrome_service.mock';
import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.mock';
import { httpServiceMock } from '../http/http_service.mock';
@ -162,7 +161,6 @@ import { LegacyPlatformService } from './legacy_service';
import { applicationServiceMock } from '../application/application_service.mock';
const applicationSetup = applicationServiceMock.createSetupContract();
const basePathSetup = basePathServiceMock.createSetupContract();
const chromeSetup = chromeServiceMock.createSetupContract();
const fatalErrorsSetup = fatalErrorsServiceMock.createSetupContract();
const httpSetup = httpServiceMock.createSetupContract();
@ -185,7 +183,6 @@ const defaultSetupDeps = {
injectedMetadata: injectedMetadataSetup,
notifications: notificationsSetup,
http: httpSetup,
basePath: basePathSetup,
uiSettings: uiSettingsSetup,
chrome: chromeSetup,
},
@ -193,10 +190,9 @@ const defaultSetupDeps = {
};
const applicationStart = applicationServiceMock.createStartContract();
const basePathStart = basePathServiceMock.createStartContract();
const httpStart = httpServiceMock.createStartContract();
const chromeStart = chromeServiceMock.createStartContract();
const i18nStart = i18nServiceMock.createStartContract();
const httpStart = httpServiceMock.createStartContract();
const injectedMetadataStart = injectedMetadataServiceMock.createStartContract();
const notificationsStart = notificationServiceMock.createStartContract();
const overlayStart = overlayServiceMock.createStartContract();
@ -204,10 +200,9 @@ const overlayStart = overlayServiceMock.createStartContract();
const defaultStartDeps = {
core: {
application: applicationStart,
basePath: basePathStart,
http: httpStart,
chrome: chromeStart,
i18n: i18nStart,
http: httpStart,
injectedMetadata: injectedMetadataStart,
notifications: notificationsStart,
overlays: overlayStart,
@ -290,7 +285,7 @@ describe('#setup()', () => {
legacyPlatform.setup(defaultSetupDeps);
expect(mockBasePathInit).toHaveBeenCalledTimes(1);
expect(mockBasePathInit).toHaveBeenCalledWith(basePathSetup);
expect(mockBasePathInit).toHaveBeenCalledWith(httpSetup);
});
it('passes basePath service to ui/chrome/api/ui_settings', () => {

View file

@ -62,7 +62,6 @@ export class LegacyPlatformService {
fatalErrors,
notifications,
http,
basePath,
uiSettings,
chrome,
} = core;
@ -75,7 +74,7 @@ export class LegacyPlatformService {
require('ui/kfetch').__newPlatformSetup__(http);
require('ui/notify/toasts').__newPlatformSetup__(notifications.toasts);
require('ui/chrome/api/loading_count').__newPlatformSetup__(http);
require('ui/chrome/api/base_path').__newPlatformSetup__(basePath);
require('ui/chrome/api/base_path').__newPlatformSetup__(http);
require('ui/chrome/api/ui_settings').__newPlatformSetup__(uiSettings);
require('ui/chrome/api/injected_vars').__newPlatformSetup__(injectedMetadata);
require('ui/chrome/api/controls').__newPlatformSetup__(chrome);

View file

@ -17,7 +17,6 @@
* under the License.
*/
export { basePathServiceMock } from './base_path/base_path_service.mock';
export { chromeServiceMock } from './chrome/chrome_service.mock';
export { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock';
export { httpServiceMock } from './http/http_service.mock';

View file

@ -63,7 +63,6 @@ export function createPluginSetupContext<TSetup, TStart, TPluginsSetup, TPlugins
): CoreSetup {
return {
http: deps.http,
basePath: deps.basePath,
chrome: deps.chrome,
fatalErrors: deps.fatalErrors,
i18n: deps.i18n,
@ -91,9 +90,8 @@ export function createPluginStartContext<TSetup, TStart, TPluginsSetup, TPlugins
application: {
capabilities: deps.application.capabilities,
},
chrome: deps.chrome,
basePath: deps.basePath,
http: deps.http,
chrome: deps.chrome,
i18n: deps.i18n,
notifications: deps.notifications,
overlays: deps.overlays,

View file

@ -39,10 +39,9 @@ import { overlayServiceMock } from '../overlays/overlay_service.mock';
import { chromeServiceMock } from '../chrome/chrome_service.mock';
import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.mock';
import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock';
import { basePathServiceMock } from '../base_path/base_path_service.mock';
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
import { UiSettingsClient } from '../ui_settings';
import { httpServiceMock } from '../http/http_service.mock';
import { UiSettingsClient } from '../ui_settings';
import { CoreSetup, CoreStart } from '..';
export let mockPluginInitializers: Map<PluginName, MockedPluginInitializer>;
@ -74,11 +73,6 @@ beforeEach(() => {
]);
return metadata;
})(),
basePath: (function() {
const basePath = basePathServiceMock.createSetupContract();
basePath.addToPath.mockImplementation(path => path);
return basePath;
})(),
chrome: chromeServiceMock.createSetupContract(),
fatalErrors: fatalErrorsServiceMock.createSetupContract(),
http: httpServiceMock.createSetupContract(),
@ -89,9 +83,8 @@ beforeEach(() => {
mockSetupContext = omit(mockSetupDeps, 'application', 'injectedMetadata');
mockStartDeps = {
application: applicationServiceMock.createStartContract(),
basePath: basePathServiceMock.createStartContract(),
chrome: chromeServiceMock.createStartContract(),
http: httpServiceMock.createStartContract(),
chrome: chromeServiceMock.createStartContract(),
i18n: i18nServiceMock.createStartContract(),
injectedMetadata: injectedMetadataServiceMock.createStartContract(),
notifications: notificationServiceMock.createStartContract(),
@ -171,14 +164,14 @@ test('`PluginsService.setup` fails if any plugin instance does not have a setup
);
});
test('`PluginsService.setup` calls loadPluginBundles with basePath and plugins', async () => {
test('`PluginsService.setup` calls loadPluginBundles with http and plugins', async () => {
const pluginsService = new PluginsService(mockCoreContext);
await pluginsService.setup(mockSetupDeps);
expect(mockLoadPluginBundle).toHaveBeenCalledTimes(3);
expect(mockLoadPluginBundle).toHaveBeenCalledWith(mockSetupDeps.basePath.addToPath, 'pluginA');
expect(mockLoadPluginBundle).toHaveBeenCalledWith(mockSetupDeps.basePath.addToPath, 'pluginB');
expect(mockLoadPluginBundle).toHaveBeenCalledWith(mockSetupDeps.basePath.addToPath, 'pluginC');
expect(mockLoadPluginBundle).toHaveBeenCalledWith(mockSetupDeps.http.prependBasePath, 'pluginA');
expect(mockLoadPluginBundle).toHaveBeenCalledWith(mockSetupDeps.http.prependBasePath, 'pluginB');
expect(mockLoadPluginBundle).toHaveBeenCalledWith(mockSetupDeps.http.prependBasePath, 'pluginC');
});
test('`PluginsService.setup` initalizes plugins with CoreContext', async () => {

View file

@ -70,7 +70,7 @@ export class PluginsService implements CoreService<PluginsServiceSetup, PluginsS
);
// Load plugin bundles
await this.loadPluginBundles(deps.basePath.addToPath);
await this.loadPluginBundles(deps.http.prependBasePath);
// Setup each plugin with required and optional plugin contracts
const contracts = new Map<string, unknown>();

View file

@ -36,16 +36,6 @@ export interface ApplicationStart {
mount: (mountHandler: Function) => void;
}
// @public
export interface BasePathSetup {
addToPath(path: string): string;
get(): string;
removeFromPath(path: string): string;
}
// @public
export type BasePathStart = BasePathSetup;
// @public
export interface Capabilities {
[key: string]: Record<string, boolean | Record<string, boolean>>;
@ -118,8 +108,6 @@ export interface CoreContext {
// @public
export interface CoreSetup {
// (undocumented)
basePath: BasePathSetup;
// (undocumented)
chrome: ChromeSetup;
// (undocumented)
@ -139,8 +127,6 @@ export interface CoreStart {
// (undocumented)
application: Pick<ApplicationStart, 'capabilities'>;
// (undocumented)
basePath: BasePathStart;
// (undocumented)
chrome: ChromeStart;
// (undocumented)
http: HttpStart;
@ -180,13 +166,45 @@ export interface FatalErrorsSetup {
get$: () => Rx.Observable<FatalErrorInfo>;
}
// Warning: (ae-forgotten-export) The symbol "HttpService" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export type HttpSetup = ReturnType<HttpService['setup']>;
export interface HttpServiceBase {
// (undocumented)
addLoadingCount(count$: Observable<number>): void;
// (undocumented)
delete: HttpHandler;
// Warning: (ae-forgotten-export) The symbol "HttpHandler" needs to be exported by the entry point index.d.ts
//
// (undocumented)
fetch: HttpHandler;
// (undocumented)
get: HttpHandler;
// (undocumented)
getBasePath(): string;
// (undocumented)
getLoadingCount$(): Observable<number>;
// (undocumented)
head: HttpHandler;
// (undocumented)
options: HttpHandler;
// (undocumented)
patch: HttpHandler;
// (undocumented)
post: HttpHandler;
// (undocumented)
prependBasePath(path: string): string;
// (undocumented)
put: HttpHandler;
// (undocumented)
removeBasePath(path: string): string;
// (undocumented)
stop(): void;
}
// @public (undocumented)
export type HttpStart = ReturnType<HttpService['start']>;
export type HttpSetup = HttpServiceBase;
// @public (undocumented)
export type HttpStart = HttpServiceBase;
// @public
export interface I18nSetup {

View file

@ -8,9 +8,9 @@ Array [
"body": "{\\"changes\\":{\\"foo\\":\\"bar\\"}}",
"credentials": "same-origin",
"headers": Object {
"Content-Type": "application/json",
"accept": "application/json",
"content-type": "application/json",
"kbn-version": "v9.9.9",
"kbn-version": "kibanaVersion",
},
"method": "POST",
},
@ -21,9 +21,9 @@ Array [
"body": "{\\"changes\\":{\\"bar\\":\\"box\\"}}",
"credentials": "same-origin",
"headers": Object {
"Content-Type": "application/json",
"accept": "application/json",
"content-type": "application/json",
"kbn-version": "v9.9.9",
"kbn-version": "kibanaVersion",
},
"method": "POST",
},
@ -39,9 +39,9 @@ Array [
"body": "{\\"changes\\":{\\"foo\\":\\"a\\"}}",
"credentials": "same-origin",
"headers": Object {
"Content-Type": "application/json",
"accept": "application/json",
"content-type": "application/json",
"kbn-version": "v9.9.9",
"kbn-version": "kibanaVersion",
},
"method": "POST",
},
@ -52,9 +52,9 @@ Array [
"body": "{\\"changes\\":{\\"foo\\":\\"d\\"}}",
"credentials": "same-origin",
"headers": Object {
"Content-Type": "application/json",
"accept": "application/json",
"content-type": "application/json",
"kbn-version": "v9.9.9",
"kbn-version": "kibanaVersion",
},
"method": "POST",
},
@ -70,9 +70,9 @@ Array [
"body": "{\\"changes\\":{\\"foo\\":\\"bar\\"}}",
"credentials": "same-origin",
"headers": Object {
"Content-Type": "application/json",
"accept": "application/json",
"content-type": "application/json",
"kbn-version": "v9.9.9",
"kbn-version": "kibanaVersion",
},
"method": "POST",
},
@ -83,9 +83,9 @@ Array [
"body": "{\\"changes\\":{\\"box\\":\\"bar\\"}}",
"credentials": "same-origin",
"headers": Object {
"Content-Type": "application/json",
"accept": "application/json",
"content-type": "application/json",
"kbn-version": "v9.9.9",
"kbn-version": "kibanaVersion",
},
"method": "POST",
},
@ -101,9 +101,9 @@ Array [
"body": "{\\"changes\\":{\\"foo\\":\\"bar\\"}}",
"credentials": "same-origin",
"headers": Object {
"Content-Type": "application/json",
"accept": "application/json",
"content-type": "application/json",
"kbn-version": "v9.9.9",
"kbn-version": "kibanaVersion",
},
"method": "POST",
},
@ -142,9 +142,9 @@ Array [
"body": "{\\"changes\\":{\\"foo\\":\\"bar\\"}}",
"credentials": "same-origin",
"headers": Object {
"Content-Type": "application/json",
"accept": "application/json",
"content-type": "application/json",
"kbn-version": "v9.9.9",
"kbn-version": "kibanaVersion",
},
"method": "POST",
},

View file

@ -5,11 +5,35 @@ exports[`#setup constructs UiSettingsClient and UiSettingsApi: UiSettingsApi arg
"calls": Array [
Array [
Object {
"addToPath": [MockFunction],
"addLoadingCount": [MockFunction] {
"calls": Array [
Array [
Object {
"loadingCountObservable": true,
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
},
"delete": [MockFunction],
"fetch": [MockFunction],
"get": [MockFunction],
"removeFromPath": [MockFunction],
"getBasePath": [MockFunction],
"getLoadingCount$": [MockFunction],
"head": [MockFunction],
"options": [MockFunction],
"patch": [MockFunction],
"post": [MockFunction],
"prependBasePath": [MockFunction],
"put": [MockFunction],
"removeBasePath": [MockFunction],
"stop": [MockFunction],
},
"kibanaVersion",
],
],
"results": Array [

View file

@ -22,17 +22,18 @@ import fetchMock from 'fetch-mock/es5/client';
import * as Rx from 'rxjs';
import { takeUntil, toArray } from 'rxjs/operators';
import { basePathServiceMock } from '../base_path/base_path_service.mock';
import { setup as httpSetup } from '../../../test_utils/public/http_test_setup';
import { UiSettingsApi } from './ui_settings_api';
function setup() {
const basePath = basePathServiceMock.createSetupContract();
basePath.addToPath.mockImplementation(path => `/foo/bar${path}`);
const { http } = httpSetup(injectedMetadata => {
injectedMetadata.getBasePath.mockReturnValue('/foo/bar');
});
const uiSettingsApi = new UiSettingsApi(basePath, 'v9.9.9');
const uiSettingsApi = new UiSettingsApi(http);
return {
basePath,
http,
uiSettingsApi,
};
}

View file

@ -19,7 +19,7 @@
import { BehaviorSubject } from 'rxjs';
import { BasePathSetup } from '../base_path';
import { HttpSetup } from '../http';
import { UiSettingsState } from './types';
export interface UiSettingsApiResponse {
@ -47,7 +47,7 @@ export class UiSettingsApi {
private readonly loadingCount$ = new BehaviorSubject(0);
constructor(private readonly basePath: BasePathSetup, private readonly kibanaVersion: string) {}
constructor(private readonly http: HttpSetup) {}
/**
* Adds a key+value that will be sent to the server ASAP. If a request is
@ -115,6 +115,7 @@ export class UiSettingsApi {
try {
this.sendInProgress = true;
changes.callback(
undefined,
await this.sendRequest('POST', '/api/kibana/settings', {
@ -131,28 +132,24 @@ export class UiSettingsApi {
/**
* Calls window.fetch() with the proper headers and error handling logic.
*
* TODO: migrate this to kfetch or whatever the new platform equivalent is once it exists
*/
private async sendRequest(method: string, path: string, body: any) {
private async sendRequest(method: string, path: string, body: any): Promise<any> {
try {
this.loadingCount$.next(this.loadingCount$.getValue() + 1);
const response = await fetch(this.basePath.addToPath(path), {
return await this.http.fetch(path, {
method,
body: JSON.stringify(body),
headers: {
accept: 'application/json',
'content-type': 'application/json',
'kbn-version': this.kibanaVersion,
},
credentials: 'same-origin',
});
if (response.status >= 300) {
throw new Error(`Request failed with status code: ${response.status}`);
} catch (err) {
if (err.response && err.response.status >= 300) {
throw new Error(`Request failed with status code: ${err.response.status}`);
}
return await response.json();
throw err;
} finally {
this.loadingCount$.next(this.loadingCount$.getValue() - 1);
}

View file

@ -19,7 +19,6 @@
import { MockUiSettingsApi, MockUiSettingsClient } from './ui_settings_service.test.mocks';
import { basePathServiceMock } from '../base_path/base_path_service.mock';
import { httpServiceMock } from '../http/http_service.mock';
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
import { UiSettingsService } from './ui_settings_service';
@ -29,7 +28,6 @@ const httpSetup = httpServiceMock.createSetupContract();
const defaultDeps = {
http: httpSetup,
injectedMetadata: injectedMetadataServiceMock.createSetupContract(),
basePath: basePathServiceMock.createSetupContract(),
};
afterEach(() => {

View file

@ -17,7 +17,6 @@
* under the License.
*/
import { BasePathSetup } from '../base_path';
import { HttpSetup } from '../http';
import { InjectedMetadataSetup } from '../injected_metadata';
@ -27,7 +26,6 @@ import { UiSettingsClient } from './ui_settings_client';
interface UiSettingsServiceDeps {
http: HttpSetup;
injectedMetadata: InjectedMetadataSetup;
basePath: BasePathSetup;
}
/** @internal */
@ -35,8 +33,8 @@ export class UiSettingsService {
private uiSettingsApi?: UiSettingsApi;
private uiSettingsClient?: UiSettingsClient;
public setup({ http, injectedMetadata, basePath }: UiSettingsServiceDeps): UiSettingsSetup {
this.uiSettingsApi = new UiSettingsApi(basePath, injectedMetadata.getKibanaVersion());
public setup({ http, injectedMetadata }: UiSettingsServiceDeps): UiSettingsSetup {
this.uiSettingsApi = new UiSettingsApi(http);
http.addLoadingCount(this.uiSettingsApi.getLoadingCount$());
// TODO: Migrate away from legacyMetadata https://github.com/elastic/kibana/issues/22779

View file

@ -18,7 +18,7 @@
*/
import { createKfetch } from 'ui/kfetch/kfetch';
import { setup } from 'test_utils/kfetch_test_setup';
import { setup } from 'test_utils/http_test_setup';
const mockIndexPattern = {
id: '1234',

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { basePathServiceMock } from '../../../../../core/public/mocks';
import { httpServiceMock } from '../../../../../core/public/mocks';
import { __newPlatformSetup__, initChromeBasePathApi } from './base_path';
function initChrome() {
@ -26,39 +26,43 @@ function initChrome() {
return chrome;
}
const newPlatformBasePath = basePathServiceMock.createSetupContract();
__newPlatformSetup__(newPlatformBasePath);
const newPlatformHttp = httpServiceMock.createSetupContract();
__newPlatformSetup__(newPlatformHttp);
newPlatformHttp.getBasePath.mockImplementation(() => 'gotBasePath');
newPlatformHttp.prependBasePath.mockImplementation(() => 'addedToPath');
newPlatformHttp.removeBasePath.mockImplementation(() => 'removedFromPath');
beforeEach(() => {
jest.clearAllMocks();
});
describe('#getBasePath()', () => {
it('proxies to newPlatformBasePath.get()', () => {
it('proxies to newPlatformHttp.getBasePath()', () => {
const chrome = initChrome();
expect(newPlatformBasePath.get).not.toHaveBeenCalled();
expect(chrome.getBasePath()).toBe('get');
expect(newPlatformBasePath.get).toHaveBeenCalledTimes(1);
expect(newPlatformBasePath.get).toHaveBeenCalledWith();
expect(newPlatformHttp.prependBasePath).not.toHaveBeenCalled();
expect(chrome.getBasePath()).toBe('gotBasePath');
expect(newPlatformHttp.getBasePath).toHaveBeenCalledTimes(1);
expect(newPlatformHttp.getBasePath).toHaveBeenCalledWith();
});
});
describe('#addBasePath()', () => {
it('proxies to newPlatformBasePath.addToPath(path)', () => {
it('proxies to newPlatformHttp.prependBasePath(path)', () => {
const chrome = initChrome();
expect(newPlatformBasePath.addToPath).not.toHaveBeenCalled();
expect(chrome.addBasePath('foo/bar')).toBe('addToPath');
expect(newPlatformBasePath.addToPath).toHaveBeenCalledTimes(1);
expect(newPlatformBasePath.addToPath).toHaveBeenCalledWith('foo/bar');
expect(newPlatformHttp.prependBasePath).not.toHaveBeenCalled();
expect(chrome.addBasePath('foo/bar')).toBe('addedToPath');
expect(newPlatformHttp.prependBasePath).toHaveBeenCalledTimes(1);
expect(newPlatformHttp.prependBasePath).toHaveBeenCalledWith('foo/bar');
});
});
describe('#removeBasePath', () => {
it('proxies to newPlatformBasePath.removeFromPath(path)', () => {
it('proxies to newPlatformBasePath.removeBasePath(path)', () => {
const chrome = initChrome();
expect(newPlatformBasePath.removeFromPath).not.toHaveBeenCalled();
expect(chrome.removeBasePath('foo/bar')).toBe('removeFromPath');
expect(newPlatformBasePath.removeFromPath).toHaveBeenCalledTimes(1);
expect(newPlatformBasePath.removeFromPath).toHaveBeenCalledWith('foo/bar');
expect(newPlatformHttp.removeBasePath).not.toHaveBeenCalled();
expect(chrome.removeBasePath('foo/bar')).toBe('removedFromPath');
expect(newPlatformHttp.removeBasePath).toHaveBeenCalledTimes(1);
expect(newPlatformHttp.removeBasePath).toHaveBeenCalledWith('foo/bar');
});
});

View file

@ -17,19 +17,20 @@
* under the License.
*/
import { BasePathSetup } from '../../../../../core/public';
let newPlatformBasePath: BasePathSetup;
import { HttpSetup } from '../../../../../core/public';
export function __newPlatformSetup__(instance: BasePathSetup) {
if (newPlatformBasePath) {
throw new Error('ui/chrome/api/base_path is already initialized');
let newPlatformHttp: HttpSetup;
export function __newPlatformSetup__(instance: HttpSetup) {
if (newPlatformHttp) {
throw new Error('ui/chrome/api/http is already initialized');
}
newPlatformBasePath = instance;
newPlatformHttp = instance;
}
export function initChromeBasePathApi(chrome: { [key: string]: any }) {
chrome.getBasePath = () => newPlatformBasePath.get();
chrome.addBasePath = (path: string) => newPlatformBasePath.addToPath(path);
chrome.removeBasePath = (path: string) => newPlatformBasePath.removeFromPath(path);
chrome.getBasePath = newPlatformHttp.getBasePath.bind(newPlatformHttp);
chrome.addBasePath = newPlatformHttp.prependBasePath.bind(newPlatformHttp);
chrome.removeBasePath = newPlatformHttp.removeBasePath.bind(newPlatformHttp);
}

View file

@ -20,7 +20,7 @@
// @ts-ignore
import fetchMock from 'fetch-mock/es5/client';
import { __newPlatformSetup__, kfetch } from '../kfetch';
import { setup } from '../../../../test_utils/public/kfetch_test_setup';
import { setup } from '../../../../test_utils/public/http_test_setup';
import { isAutoCreateIndexError } from './error_auto_create_index';

View file

@ -24,7 +24,7 @@ import { join } from 'path';
import { __newPlatformSetup__, addInterceptor, kfetch, KFetchOptions } from '.';
import { Interceptor, resetInterceptors, withDefaultOptions } from './kfetch';
import { KFetchError } from './kfetch_error';
import { setup } from '../../../../test_utils/public/kfetch_test_setup';
import { setup } from '../../../../test_utils/public/http_test_setup';
describe('kfetch', () => {
beforeAll(() => {

View file

@ -79,7 +79,7 @@ export const configureAppAngularModule = (angularModule: IModule) => {
const getEsUrl = (newPlatform: InternalCoreSetup) => {
const a = document.createElement('a');
a.href = newPlatform.basePath.addToPath('/elasticsearch');
a.href = newPlatform.http.prependBasePath('/elasticsearch');
const protocolPort = /https/.test(a.protocol) ? 443 : 80;
const port = a.port || protocolPort;
return {

View file

@ -19,20 +19,29 @@
/* eslint-disable @kbn/eslint/no-restricted-paths */
import { HttpService } from '../../core/public/http';
import { BasePathService } from '../../core/public/base_path';
import { fatalErrorsServiceMock } from '../../core/public/fatal_errors/fatal_errors_service.mock';
import { injectedMetadataServiceMock } from '../../core/public/injected_metadata/injected_metadata_service.mock';
/* eslint-enable @kbn/eslint/no-restricted-paths */
export function setup() {
const httpService = new HttpService();
const fatalErrors = fatalErrorsServiceMock.createSetupContract();
const injectedMetadata = injectedMetadataServiceMock.createSetupContract();
export type SetupTap = (
injectedMetadata: ReturnType<typeof injectedMetadataServiceMock.createSetupContract>,
fatalErrors: ReturnType<typeof fatalErrorsServiceMock.createSetupContract>
) => void;
const defaultTap: SetupTap = (
injectedMetadata: ReturnType<typeof injectedMetadataServiceMock.createSetupContract>
) => {
injectedMetadata.getBasePath.mockReturnValue('http://localhost/myBase');
};
const basePath = new BasePathService().setup({ injectedMetadata });
const http = httpService.setup({ basePath, fatalErrors, injectedMetadata });
export function setup(tap: SetupTap = defaultTap) {
const injectedMetadata = injectedMetadataServiceMock.createSetupContract();
const fatalErrors = fatalErrorsServiceMock.createSetupContract();
return { httpService, fatalErrors, http };
tap(injectedMetadata, fatalErrors);
const httpService = new HttpService();
const http = httpService.setup({ fatalErrors, injectedMetadata });
return { httpService, injectedMetadata, fatalErrors, http };
}