Skip to content

Commit

Permalink
[wip] Move navLinks to core
Browse files Browse the repository at this point in the history
  • Loading branch information
joshdover committed Apr 9, 2019
1 parent 3123ea1 commit e55a705
Show file tree
Hide file tree
Showing 34 changed files with 542 additions and 284 deletions.
10 changes: 10 additions & 0 deletions src/core/public/chrome/chrome_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ const createSetupContractMock = () => {
setBreadcrumbs: jest.fn(),
getHelpExtension$: jest.fn(),
setHelpExtension: jest.fn(),
navLinks: {
getNavLinks$: jest.fn(),
add: jest.fn(),
clear: jest.fn(),
exists: jest.fn(),
get: jest.fn(),
getAll: jest.fn(),
showOnly: jest.fn(),
update: jest.fn(),
},
};
setupContract.getBrand$.mockReturnValue(new BehaviorSubject({} as ChromeBrand));
setupContract.getIsVisible$.mockReturnValue(new BehaviorSubject(false));
Expand Down
26 changes: 8 additions & 18 deletions src/core/public/chrome/chrome_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import * as Url from 'url';
import { i18n } from '@kbn/i18n';
import * as Rx from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { BasePathSetup } from '../base_path';
import { InjectedMetadataSetup } from '../injected_metadata';
import { NotificationsSetup } from '../notifications';
import { NavLinksService } from './nav_links/nav_links_service';

const IS_COLLAPSED_KEY = 'core.chrome.isCollapsed';

Expand Down Expand Up @@ -53,6 +55,7 @@ interface ConstructorParams {
}

interface SetupDeps {
basePath: BasePathSetup;
injectedMetadata: InjectedMetadataSetup;
notifications: NotificationsSetup;
}
Expand All @@ -61,12 +64,13 @@ interface SetupDeps {
export class ChromeService {
private readonly stop$ = new Rx.ReplaySubject(1);
private readonly browserSupportsCsp: boolean;
private readonly navLinks = new NavLinksService();

public constructor({ browserSupportsCsp }: ConstructorParams) {
this.browserSupportsCsp = browserSupportsCsp;
}

public setup({ injectedMetadata, notifications }: SetupDeps) {
public setup({ basePath, injectedMetadata, notifications }: SetupDeps) {
const FORCE_HIDDEN = isEmbedParamInHash();

const brand$ = new Rx.BehaviorSubject<ChromeBrand>({});
Expand Down Expand Up @@ -100,14 +104,8 @@ export class ChromeService {
*
*/
setBrand: (brand: ChromeBrand) => {
brand$.next(
Object.freeze({
logo: brand.logo,
smallLogo: brand.smallLogo,
})
);
brand$.next(Object.freeze({ logo: brand.logo, smallLogo: brand.smallLogo }));
},

/**
* Get an observable of the current brand information.
*/
Expand All @@ -121,7 +119,6 @@ export class ChromeService {
setIsVisible: (visibility: boolean) => {
isVisible$.next(visibility);
},

/**
* Get an observable of the current visibility state of the chrome.
*/
Expand All @@ -130,7 +127,6 @@ export class ChromeService {
map(visibility => (FORCE_HIDDEN ? false : visibility)),
takeUntil(this.stop$)
),

/**
* Set the collapsed state of the chrome navigation.
*/
Expand All @@ -142,12 +138,10 @@ export class ChromeService {
localStorage.removeItem(IS_COLLAPSED_KEY);
}
},

/**
* Get an observable of the current collapsed state of the chrome.
*/
getIsCollapsed$: () => isCollapsed$.pipe(takeUntil(this.stop$)),

/**
* Add a className that should be set on the application container.
*/
Expand All @@ -156,7 +150,6 @@ export class ChromeService {
update.add(className);
applicationClasses$.next(update);
},

/**
* Remove a className added with `addApplicationClass()`. If className is unknown it is ignored.
*/
Expand All @@ -165,7 +158,6 @@ export class ChromeService {
update.delete(className);
applicationClasses$.next(update);
},

/**
* Get the current set of classNames that will be set on the application container.
*/
Expand All @@ -174,34 +166,32 @@ export class ChromeService {
map(set => [...set]),
takeUntil(this.stop$)
),

/**
* Get an observable of the current list of breadcrumbs
*/
getBreadcrumbs$: () => breadcrumbs$.pipe(takeUntil(this.stop$)),

/**
* Override the current set of breadcrumbs
*/
setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => {
breadcrumbs$.next(newBreadcrumbs);
},

/**
* Get an observable of the current custom help conttent
*/
getHelpExtension$: () => helpExtension$.pipe(takeUntil(this.stop$)),

/**
* Override the current set of breadcrumbs
*/
setHelpExtension: (helpExtension?: ChromeHelpExtension) => {
helpExtension$.next(helpExtension);
},
navLinks: this.navLinks.setup(basePath),
};
}

public stop() {
this.navLinks.stop();
this.stop$.next();
}
}
Expand Down
1 change: 1 addition & 0 deletions src/core/public/chrome/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ export {
ChromeBrand,
ChromeHelpExtension,
} from './chrome_service';
export { ChromeNavLinkProperties } from './nav_links';
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,5 @@
* under the License.
*/

import chrome from 'ui/chrome';

const timelionUiEnabled = chrome.getInjected('timelionUiEnabled');
if (timelionUiEnabled === false && chrome.navLinkExists('timelion')) {
chrome.getNavLinkById('timelion').hidden = true;
}
export { ChromeNavLinkProperties } from './nav_link';
export { NavLinksService } from './nav_links_service';
113 changes: 113 additions & 0 deletions src/core/public/chrome/nav_links/nav_link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* 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 { BasePathSetup } from '../../base_path';

export interface ChromeNavLinkProperties {
/**
* A unique identifier for looking up links.
*/
id: string;

/**
* Indicates whether or not this app is currently on the screen.
*
* NOTE: remove this when ApplicationService is implemented and managing apps.
*/
active: boolean;

/**
* Disables a link from being clickable.
*
* NOTE: this is only used by the ML and Graph plugins currently. They use this field
* to disable the nav link when the license is expired.
*/
disabled: boolean;

/**
* Hides a link from the navigation.
*
* NOTE: remove this when ApplicationService is implemented. Instead, plugins should only
* register an Application if needed.
*/
hidden: boolean;

/**
* An ordinal used to sort nav links relative to one another for display.
*/
order: number;

/**
* The title of the application.
*/
title: string;

/**
* A tooltip shown when hovering over an app link.
*/
tooltip: string;

/**
* The base URL used to open the root of an application.
*/
appUrl: string;

/**
* A url that legacy apps can set to deep link into their applications.
*
* NOTE: Currently used by the "lastSubUrl" feature legacy/ui/chrome. This should
* be removed once the ApplicationService is implemented and mounting apps. At that
* time, each app can handle opening to the previous location when they are mounted.
*/
url?: string;

/**
* A EUI iconType that will be used for the app's icon. This icon
* takes precendence over the `icon` property.
*/
euiIconType?: string;

/**
* A URL to an image file used as an icon. Used as a fallback
* if `euiIconType` is not provided.
*/
icon?: string;
}

export type NavLinkUpdateableFields = Partial<
Pick<ChromeNavLinkProperties, 'active' | 'disabled' | 'hidden' | 'url'>
>;

export class NavLink {
public readonly id: string;
public readonly properties: Readonly<ChromeNavLinkProperties>;

constructor(properties: ChromeNavLinkProperties, private readonly basePath: BasePathSetup) {
if (!properties || !properties.id) {
throw new Error('`id` is required.');
}

this.id = properties.id;
this.properties = Object.freeze(properties);
}

public update(newProps: NavLinkUpdateableFields) {
return new NavLink({ ...this.properties, ...newProps }, this.basePath);
}
}
88 changes: 88 additions & 0 deletions src/core/public/chrome/nav_links/nav_links_service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* 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 { sortBy } from 'lodash';
import { BehaviorSubject, ReplaySubject } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { BasePathSetup } from '../../base_path';
import { ChromeNavLinkProperties, NavLink, NavLinkUpdateableFields } from './nav_link';

export class NavLinksService {
private readonly stop$ = new ReplaySubject(1);

public setup(basePath: BasePathSetup) {
const navLinks$ = new BehaviorSubject<ReadonlyArray<NavLink>>([]);

return {
getNavLinks$: () => {
return navLinks$.pipe(
map(sortNavLinks),
takeUntil(this.stop$)
);
},

add(navLink: ChromeNavLinkProperties) {
navLinks$.next([...navLinks$.value, new NavLink(navLink, basePath)]);
},

clear() {
navLinks$.next([]);
},

get(id: string) {
const link = navLinks$.value.find(l => l.id === id);
return link ? link.properties : undefined;
},

getAll() {
return sortNavLinks(navLinks$.value);
},

exists(id: string) {
return this.get(id) !== undefined;
},

showOnly(id: string) {
navLinks$.next(navLinks$.value.filter(link => link.id === id));
},

update(id: string, values: NavLinkUpdateableFields) {
if (!this.exists(id)) {
return;
}

navLinks$.next(
navLinks$.value.map(link => {
return link.id === id ? link.update(values) : link;
})
);

return this.get(id);
},
};
}

public stop() {
this.stop$.next();
}
}

function sortNavLinks(navLinks: ReadonlyArray<NavLink>) {
return sortBy(navLinks.map(link => link.properties), 'order');
}
1 change: 1 addition & 0 deletions src/core/public/core_system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export class CoreSystem {
const chrome = this.chrome.setup({
injectedMetadata,
notifications,
basePath,
});

const core: CoreSetup = {
Expand Down
9 changes: 8 additions & 1 deletion src/core/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@
*/

import { BasePathSetup } from './base_path';
import { ChromeBrand, ChromeBreadcrumb, ChromeHelpExtension, ChromeSetup } from './chrome';
import {
ChromeBrand,
ChromeBreadcrumb,
ChromeHelpExtension,
ChromeNavLinkProperties,
ChromeSetup,
} from './chrome';
import { FatalErrorsSetup } from './fatal_errors';
import { HttpSetup } from './http';
import { I18nSetup } from './i18n';
Expand Down Expand Up @@ -56,6 +62,7 @@ export {
ChromeBreadcrumb,
ChromeBrand,
ChromeHelpExtension,
ChromeNavLinkProperties,
InjectedMetadataSetup,
InjectedMetadataParams,
Plugin,
Expand Down
Loading

0 comments on commit e55a705

Please sign in to comment.