Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## ChromeNavLink.euiIconType property

A EUI iconType that will be used for the app's icon. This icon takes precendence over the `icon` property.
A EUI iconType that will be used for the app's icon. This icon takes precedence over the `icon` property.

<b>Signature:</b>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ export interface ChromeNavLink
| [category](./kibana-plugin-core-public.chromenavlink.category.md) | <code>AppCategory</code> | The category the app lives in |
| [disabled](./kibana-plugin-core-public.chromenavlink.disabled.md) | <code>boolean</code> | Disables a link from being clickable. |
| [disableSubUrlTracking](./kibana-plugin-core-public.chromenavlink.disablesuburltracking.md) | <code>boolean</code> | A flag that tells legacy chrome to ignore the link when tracking sub-urls |
| [euiIconType](./kibana-plugin-core-public.chromenavlink.euiicontype.md) | <code>string</code> | A EUI iconType that will be used for the app's icon. This icon takes precendence over the <code>icon</code> property. |
| [euiIconType](./kibana-plugin-core-public.chromenavlink.euiicontype.md) | <code>string</code> | A EUI iconType that will be used for the app's icon. This icon takes precedence over the <code>icon</code> property. |
| [hidden](./kibana-plugin-core-public.chromenavlink.hidden.md) | <code>boolean</code> | Hides a link from the navigation. |
| [href](./kibana-plugin-core-public.chromenavlink.href.md) | <code>string</code> | Settled state between <code>url</code>, <code>baseUrl</code>, and <code>active</code> |
| [icon](./kibana-plugin-core-public.chromenavlink.icon.md) | <code>string</code> | A URL to an image file used as an icon. Used as a fallback if <code>euiIconType</code> is not provided. |
| [id](./kibana-plugin-core-public.chromenavlink.id.md) | <code>string</code> | A unique identifier for looking up links. |
| [linkToLastSubUrl](./kibana-plugin-core-public.chromenavlink.linktolastsuburl.md) | <code>boolean</code> | Whether or not the subUrl feature should be enabled. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
<b>Signature:</b>

```typescript
export declare type ChromeNavLinkUpdateableFields = Partial<Pick<ChromeNavLink, 'active' | 'disabled' | 'hidden' | 'url' | 'subUrlBase'>>;
export declare type ChromeNavLinkUpdateableFields = Partial<Pick<ChromeNavLink, 'active' | 'disabled' | 'hidden' | 'url' | 'subUrlBase' | 'href'>>;
```
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
<b>Signature:</b>

```typescript
QueryStringInput: React.FC<Pick<Props, "query" | "placeholder" | "onChange" | "onSubmit" | "prepend" | "indexPatterns" | "screenTitle" | "dataTestSubj" | "disableAutoFocus" | "persistedLog" | "bubbleSubmitEvent" | "languageSwitcherPopoverAnchorPosition">>
QueryStringInput: React.FC<Pick<Props, "query" | "placeholder" | "onChange" | "onSubmit" | "prepend" | "indexPatterns" | "dataTestSubj" | "screenTitle" | "disableAutoFocus" | "persistedLog" | "bubbleSubmitEvent" | "languageSwitcherPopoverAnchorPosition">>
```
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<b>Signature:</b>

```typescript
SearchBar: React.ComponentClass<Pick<Pick<SearchBarProps, "query" | "isLoading" | "filters" | "intl" | "indexPatterns" | "refreshInterval" | "customSubmitButton" | "screenTitle" | "dataTestSubj" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange">, "query" | "isLoading" | "filters" | "indexPatterns" | "refreshInterval" | "customSubmitButton" | "screenTitle" | "dataTestSubj" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange">, any> & {
WrappedComponent: React.ComponentType<Pick<SearchBarProps, "query" | "isLoading" | "filters" | "intl" | "indexPatterns" | "refreshInterval" | "customSubmitButton" | "screenTitle" | "dataTestSubj" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange"> & ReactIntl.InjectedIntlProps>;
SearchBar: React.ComponentClass<Pick<Pick<SearchBarProps, "query" | "isLoading" | "filters" | "intl" | "indexPatterns" | "dataTestSubj" | "refreshInterval" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange">, "query" | "isLoading" | "filters" | "indexPatterns" | "dataTestSubj" | "refreshInterval" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange">, any> & {
WrappedComponent: React.ComponentType<Pick<SearchBarProps, "query" | "isLoading" | "filters" | "intl" | "indexPatterns" | "dataTestSubj" | "refreshInterval" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange"> & ReactIntl.InjectedIntlProps>;
}
```
1 change: 1 addition & 0 deletions src/core/public/application/application_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const createSetupContractMock = (): jest.Mocked<ApplicationSetup> => ({
register: jest.fn(),
registerAppUpdater: jest.fn(),
registerMountContext: jest.fn(),
getComponent: jest.fn().mockImplementation(() => null),
});

const createInternalSetupContractMock = (): jest.Mocked<InternalApplicationSetup> => ({
Expand Down
50 changes: 24 additions & 26 deletions src/core/public/chrome/chrome_service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { ChromeDocTitle, DocTitleService } from './doc_title';
import { ChromeNavControls, NavControlsService } from './nav_controls';
import { ChromeNavLinks, NavLinksService } from './nav_links';
import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_accessed';
import { Header, LoadingIndicator } from './ui';
import { Header } from './ui';
import { NavType } from './ui/header';
import { ChromeHelpExtensionMenuLink } from './ui/header/header_help_menu';
export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle };
Expand Down Expand Up @@ -180,31 +180,29 @@ export class ChromeService {
docTitle,

getHeaderComponent: () => (
<React.Fragment>
<LoadingIndicator loadingCount$={http.getLoadingCount$()} />
<Header
application={application}
appTitle$={appTitle$.pipe(takeUntil(this.stop$))}
badge$={badge$.pipe(takeUntil(this.stop$))}
basePath={http.basePath}
breadcrumbs$={breadcrumbs$.pipe(takeUntil(this.stop$))}
kibanaDocLink={docLinks.links.kibana}
forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()}
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))}
homeHref={http.basePath.prepend('/app/home')}
isVisible$={this.isVisible$}
kibanaVersion={injectedMetadata.getKibanaVersion()}
legacyMode={injectedMetadata.getLegacyMode()}
navLinks$={navLinks.getNavLinks$()}
recentlyAccessed$={recentlyAccessed.get$()}
navControlsLeft$={navControls.getLeft$()}
navControlsRight$={navControls.getRight$()}
onIsLockedUpdate={setIsNavDrawerLocked}
isLocked$={getIsNavDrawerLocked$}
navType$={getNavType$}
/>
</React.Fragment>
<Header
loadingCount$={http.getLoadingCount$()}
application={application}
appTitle$={appTitle$.pipe(takeUntil(this.stop$))}
badge$={badge$.pipe(takeUntil(this.stop$))}
basePath={http.basePath}
breadcrumbs$={breadcrumbs$.pipe(takeUntil(this.stop$))}
kibanaDocLink={docLinks.links.kibana}
forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()}
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))}
homeHref={http.basePath.prepend('/app/home')}
isVisible$={this.isVisible$}
kibanaVersion={injectedMetadata.getKibanaVersion()}
legacyMode={injectedMetadata.getLegacyMode()}
navLinks$={navLinks.getNavLinks$()}
recentlyAccessed$={recentlyAccessed.get$()}
navControlsLeft$={navControls.getLeft$()}
navControlsRight$={navControls.getRight$()}
onIsLockedUpdate={setIsNavDrawerLocked}
isLocked$={getIsNavDrawerLocked$}
navType$={getNavType$}
/>
),

setAppTitle: (appTitle: string) => appTitle$.next(appTitle),
Expand Down
14 changes: 11 additions & 3 deletions src/core/public/chrome/nav_links/nav_link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export interface ChromeNavLink {

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

Expand All @@ -72,6 +72,14 @@ export interface ChromeNavLink {
*/
readonly icon?: string;

/**
* Settled state between `url`, `baseUrl`, and `active`
*
* @internalRemarks
* This should be required once legacy apps are gone.
*/
readonly href?: string;

/** LEGACY FIELDS */

/**
Expand Down Expand Up @@ -144,7 +152,7 @@ export interface ChromeNavLink {

/** @public */
export type ChromeNavLinkUpdateableFields = Partial<
Pick<ChromeNavLink, 'active' | 'disabled' | 'hidden' | 'url' | 'subUrlBase'>
Pick<ChromeNavLink, 'active' | 'disabled' | 'hidden' | 'url' | 'subUrlBase' | 'href'>
>;

export class NavLinkWrapper {
Expand All @@ -162,7 +170,7 @@ export class NavLinkWrapper {

public update(newProps: ChromeNavLinkUpdateableFields) {
// Enforce limited properties at runtime for JS code
newProps = pick(newProps, ['active', 'disabled', 'hidden', 'url', 'subUrlBase']);
newProps = pick(newProps, ['active', 'disabled', 'hidden', 'url', 'subUrlBase', 'href']);
return new NavLinkWrapper({ ...this.properties, ...newProps });
}
}
27 changes: 21 additions & 6 deletions src/core/public/chrome/nav_links/to_nav_link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,40 @@ import { appendAppPath } from '../../application/utils';

export function toNavLink(app: App | LegacyApp, basePath: IBasePath): NavLinkWrapper {
const useAppStatus = app.navLinkStatus === AppNavLinkStatus.default;
const baseUrl = isLegacyApp(app) ? basePath.prepend(app.appUrl) : basePath.prepend(app.appRoute!);
const relativeBaseUrl = isLegacyApp(app)
? basePath.prepend(app.appUrl)
: basePath.prepend(app.appRoute!);
const url = relativeToAbsolute(appendAppPath(relativeBaseUrl, app.defaultPath));
const baseUrl = relativeToAbsolute(relativeBaseUrl);

return new NavLinkWrapper({
...app,
hidden: useAppStatus
? app.status === AppStatus.inaccessible
: app.navLinkStatus === AppNavLinkStatus.hidden,
disabled: useAppStatus ? false : app.navLinkStatus === AppNavLinkStatus.disabled,
legacy: isLegacyApp(app),
baseUrl: relativeToAbsolute(baseUrl),
baseUrl,
...(isLegacyApp(app)
? {}
? {
href: url && !url.startsWith(app.subUrlBase!) ? url : baseUrl,
}
: {
url: relativeToAbsolute(appendAppPath(baseUrl, app.defaultPath)),
href: url,
url,
}),
Comment on lines -37 to 48
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving the url logic to this function instead of the component makes sense, but I'm not sure to understand why href was introduced instead of just using the existing url and baseUrl here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly to align with EUI prop names otherwise it's one more prop I have to alias at the component level.

Also has a couple side benefits:

  • Makes sure that nothing breaks if something else was low key relying on url being a certain thing at a certain time so makes the refactor a little safer (can refactor one thing at a time)
  • Also makes clear what this will be used for (URLs can be constructed in a lot of different ways so knowing exactly what you're getting can be a bit of a toss up. With href you know exactly what the ultimate purpose of the prop is.)

});
}

function relativeToAbsolute(url: string) {
// convert all link urls to absolute urls
/**
* @param {string} url - a relative or root relative url. If a relative path is given then the
* absolute url returned will depend on the current page where this function is called from. For example
* if you are on page "http://www.mysite.com/shopping/kids" and you pass this function "adults", you would get
* back "http://www.mysite.com/shopping/adults". If you passed this function a root relative path, or one that
* starts with a "/", for example "/account/cart", you would get back "http://www.mysite.com/account/cart".
* @return {string} the relative url transformed into an absolute url
*/
export function relativeToAbsolute(url: string) {
const a = document.createElement('a');
a.setAttribute('href', url);
return a.href;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export interface ChromeRecentlyAccessed {
*
* @param link a relative URL to the resource (not including the {@link HttpStart.basePath | `http.basePath`})
* @param label the label to display in the UI
* @param id a unique string used to de-duplicate the recently accessed llist.
* @param id a unique string used to de-duplicate the recently accessed list.
*/
add(link: string, label: string, id: string): void;

Expand Down
Loading