mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Update Project Selection in Serverless Top Navigation (#163076)
## Summary close https://github.com/elastic/kibana/issues/163014 - Changing `My Deployments -> Projects` - Removing hardcoded url and passing one from the config
This commit is contained in:
parent
9377ae708e
commit
1047eef005
18 changed files with 76 additions and 7 deletions
|
@ -225,6 +225,11 @@ export class ChromeService {
|
|||
projectNavigation.setProjectHome(homeHref);
|
||||
};
|
||||
|
||||
const setProjectsUrl = (projectsUrl: string) => {
|
||||
validateChromeStyle();
|
||||
projectNavigation.setProjectsUrl(projectsUrl);
|
||||
};
|
||||
|
||||
const isIE = () => {
|
||||
const ua = window.navigator.userAgent;
|
||||
const msie = ua.indexOf('MSIE '); // IE 10 or older
|
||||
|
@ -317,6 +322,7 @@ export class ChromeService {
|
|||
loadingCount$={http.getLoadingCount$()}
|
||||
headerBanner$={headerBanner$.pipe(takeUntil(this.stop$))}
|
||||
homeHref$={projectNavigation.getProjectHome$()}
|
||||
projectsUrl$={projectNavigation.getProjectsUrl$()}
|
||||
docLinks={docLinks}
|
||||
kibanaVersion={injectedMetadata.getKibanaVersion()}
|
||||
prependBasePath={http.basePath.prepend}
|
||||
|
@ -444,6 +450,7 @@ export class ChromeService {
|
|||
getChromeStyle$: () => chromeStyle$.pipe(takeUntil(this.stop$)),
|
||||
project: {
|
||||
setHome: setProjectHome,
|
||||
setProjectsUrl,
|
||||
setNavigation: setProjectNavigation,
|
||||
setSideNavComponent: setProjectSideNavComponent,
|
||||
setBreadcrumbs: setProjectBreadcrumbs,
|
||||
|
|
|
@ -35,6 +35,7 @@ export class ProjectNavigationService {
|
|||
current: SideNavComponent | null;
|
||||
}>({ current: null });
|
||||
private projectHome$ = new BehaviorSubject<string | undefined>(undefined);
|
||||
private projectsUrl$ = new BehaviorSubject<string | undefined>(undefined);
|
||||
private projectNavigation$ = new BehaviorSubject<ChromeProjectNavigation | undefined>(undefined);
|
||||
private activeNodes$ = new BehaviorSubject<ChromeProjectNavigationNode[][]>([]);
|
||||
private projectNavigationNavTreeFlattened: Record<string, ChromeProjectNavigationNode> = {};
|
||||
|
@ -66,6 +67,12 @@ export class ProjectNavigationService {
|
|||
getProjectHome$: () => {
|
||||
return this.projectHome$.asObservable();
|
||||
},
|
||||
setProjectsUrl: (projectsUrl: string) => {
|
||||
this.projectsUrl$.next(projectsUrl);
|
||||
},
|
||||
getProjectsUrl$: () => {
|
||||
return this.projectsUrl$.asObservable();
|
||||
},
|
||||
setProjectNavigation: (projectNavigation: ChromeProjectNavigation) => {
|
||||
this.projectNavigation$.next(projectNavigation);
|
||||
this.projectNavigationNavTreeFlattened = flattenNav(projectNavigation.navigationTree);
|
||||
|
|
|
@ -44,6 +44,12 @@ export interface InternalChromeStart extends ChromeStart {
|
|||
*/
|
||||
setHome(homeHref: string): void;
|
||||
|
||||
/**
|
||||
* Sets the cloud's projects page.
|
||||
* @param projectsUrl
|
||||
*/
|
||||
setProjectsUrl(projectsUrl: string): void;
|
||||
|
||||
/**
|
||||
* Sets the project navigation config to be used for rendering project navigation.
|
||||
* It is used for default project sidenav, project breadcrumbs, tracking active deep link.
|
||||
|
|
|
@ -28,6 +28,7 @@ describe('Header', () => {
|
|||
helpSupportUrl$: Rx.of('app/help'),
|
||||
helpMenuLinks$: Rx.of([]),
|
||||
homeHref$: Rx.of('app/home'),
|
||||
projectsUrl$: Rx.of('/projects/'),
|
||||
kibanaVersion: '8.9',
|
||||
loadingCount$: Rx.of(0),
|
||||
navControlsLeft$: Rx.of([]),
|
||||
|
@ -71,4 +72,15 @@ describe('Header', () => {
|
|||
await toggleNav();
|
||||
await toggleNav();
|
||||
});
|
||||
|
||||
it('displays the link to projects', async () => {
|
||||
render(
|
||||
<ProjectHeader {...mockProps}>
|
||||
<EuiHeader>Hello, world!</EuiHeader>
|
||||
</ProjectHeader>
|
||||
);
|
||||
|
||||
const projectsLink = await screen.getByTestId('projectsLink');
|
||||
expect(projectsLink).toHaveAttribute('href', '/projects/');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -78,8 +78,8 @@ const headerStrings = {
|
|||
}),
|
||||
},
|
||||
cloud: {
|
||||
linkToDeployments: i18n.translate('core.ui.primaryNav.cloud.linkToDeployments', {
|
||||
defaultMessage: 'My deployments',
|
||||
linkToProjects: i18n.translate('core.ui.primaryNav.cloud.linkToProjects', {
|
||||
defaultMessage: 'Projects',
|
||||
}),
|
||||
},
|
||||
nav: {
|
||||
|
@ -100,6 +100,7 @@ export interface Props {
|
|||
helpSupportUrl$: Observable<string>;
|
||||
helpMenuLinks$: Observable<ChromeHelpMenuLink[]>;
|
||||
homeHref$: Observable<string | undefined>;
|
||||
projectsUrl$: Observable<string | undefined>;
|
||||
kibanaVersion: string;
|
||||
application: InternalApplicationStart;
|
||||
loadingCount$: ReturnType<HttpStart['getLoadingCount$']>;
|
||||
|
@ -175,6 +176,7 @@ export const ProjectHeader = ({
|
|||
const [isOpen, setIsOpen] = useLocalStorage(LOCAL_STORAGE_IS_OPEN_KEY, true);
|
||||
const toggleCollapsibleNavRef = createRef<HTMLButtonElement & { euiAnimate: () => void }>();
|
||||
const headerActionMenuMounter = useHeaderActionMenuMounter(observables.actionMenu$);
|
||||
const projectsUrl = useObservable(observables.projectsUrl$);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -233,8 +235,8 @@ export const ProjectHeader = ({
|
|||
</EuiHeaderSectionItem>
|
||||
|
||||
<EuiHeaderSectionItem>
|
||||
<EuiHeaderLink href="https://cloud.elastic.co/deployments">
|
||||
{headerStrings.cloud.linkToDeployments}
|
||||
<EuiHeaderLink href={projectsUrl} data-test-subj={'projectsLink'}>
|
||||
{headerStrings.cloud.linkToProjects}
|
||||
</EuiHeaderLink>
|
||||
</EuiHeaderSectionItem>
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ const createStartContractMock = () => {
|
|||
setChromeStyle: jest.fn(),
|
||||
project: {
|
||||
setHome: jest.fn(),
|
||||
setProjectsUrl: jest.fn(),
|
||||
setNavigation: jest.fn(),
|
||||
setSideNavComponent: jest.fn(),
|
||||
setBreadcrumbs: jest.fn(),
|
||||
|
|
|
@ -223,6 +223,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
'xpack.cloud.profile_url (string)',
|
||||
'xpack.cloud.performance_url (string)',
|
||||
'xpack.cloud.users_and_roles_url (string)',
|
||||
'xpack.cloud.projects_url (any)', // It's a string (any because schema.conditional)
|
||||
// can't be used to infer urls or customer id from the outside
|
||||
'xpack.cloud.serverless.project_id (string)',
|
||||
'xpack.discoverEnhanced.actions.exploreDataInChart.enabled (boolean)',
|
||||
|
|
|
@ -15,6 +15,7 @@ const baseConfig = {
|
|||
deployment_url: '/abc123',
|
||||
profile_url: '/user/settings/',
|
||||
organization_url: '/account/',
|
||||
projects_url: '/projects/',
|
||||
};
|
||||
|
||||
describe('Cloud Plugin', () => {
|
||||
|
@ -60,6 +61,11 @@ describe('Cloud Plugin', () => {
|
|||
expect(setup.deploymentUrl).toBe('https://cloud.elastic.co/abc123');
|
||||
});
|
||||
|
||||
it('exposes projectsUrl', () => {
|
||||
const { setup } = setupPlugin();
|
||||
expect(setup.projectsUrl).toBe('https://cloud.elastic.co/projects/');
|
||||
});
|
||||
|
||||
it('exposes snapshotsUrl', () => {
|
||||
const { setup } = setupPlugin();
|
||||
expect(setup.snapshotsUrl).toBe('https://cloud.elastic.co/abc123/elasticsearch/snapshots/');
|
||||
|
|
|
@ -22,6 +22,7 @@ export interface CloudConfigType {
|
|||
base_url?: string;
|
||||
profile_url?: string;
|
||||
deployment_url?: string;
|
||||
projects_url?: string;
|
||||
billing_url?: string;
|
||||
organization_url?: string;
|
||||
users_and_roles_url?: string;
|
||||
|
@ -41,6 +42,7 @@ interface CloudUrls {
|
|||
snapshotsUrl?: string;
|
||||
performanceUrl?: string;
|
||||
usersAndRolesUrl?: string;
|
||||
projectsUrl?: string;
|
||||
}
|
||||
|
||||
export class CloudPlugin implements Plugin<CloudSetup> {
|
||||
|
@ -121,6 +123,7 @@ export class CloudPlugin implements Plugin<CloudSetup> {
|
|||
organizationUrl,
|
||||
performanceUrl,
|
||||
usersAndRolesUrl,
|
||||
projectsUrl,
|
||||
} = this.getCloudUrls();
|
||||
|
||||
let decodedId: DecodedCloudId | undefined;
|
||||
|
@ -136,6 +139,7 @@ export class CloudPlugin implements Plugin<CloudSetup> {
|
|||
deploymentUrl,
|
||||
profileUrl,
|
||||
organizationUrl,
|
||||
projectsUrl,
|
||||
elasticsearchUrl: decodedId?.elasticsearchUrl,
|
||||
kibanaUrl: decodedId?.kibanaUrl,
|
||||
isServerlessEnabled: this.isServerlessEnabled,
|
||||
|
@ -158,6 +162,7 @@ export class CloudPlugin implements Plugin<CloudSetup> {
|
|||
base_url: baseUrl,
|
||||
performance_url: performanceUrl,
|
||||
users_and_roles_url: usersAndRolesUrl,
|
||||
projects_url: projectsUrl,
|
||||
} = this.config;
|
||||
|
||||
const fullCloudDeploymentUrl = getFullCloudUrl(baseUrl, deploymentUrl);
|
||||
|
@ -166,6 +171,7 @@ export class CloudPlugin implements Plugin<CloudSetup> {
|
|||
const fullCloudOrganizationUrl = getFullCloudUrl(baseUrl, organizationUrl);
|
||||
const fullCloudPerformanceUrl = getFullCloudUrl(baseUrl, performanceUrl);
|
||||
const fullCloudUsersAndRolesUrl = getFullCloudUrl(baseUrl, usersAndRolesUrl);
|
||||
const fullCloudProjectsUrl = getFullCloudUrl(baseUrl, projectsUrl);
|
||||
const fullCloudSnapshotsUrl = `${fullCloudDeploymentUrl}/${CLOUD_SNAPSHOTS_PATH}`;
|
||||
|
||||
return {
|
||||
|
@ -176,6 +182,7 @@ export class CloudPlugin implements Plugin<CloudSetup> {
|
|||
snapshotsUrl: fullCloudSnapshotsUrl,
|
||||
performanceUrl: fullCloudPerformanceUrl,
|
||||
usersAndRolesUrl: fullCloudUsersAndRolesUrl,
|
||||
projectsUrl: fullCloudProjectsUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,10 @@ export interface CloudStart {
|
|||
* The full URL to the users and roles page on Elastic Cloud. Undefined if not running on Cloud.
|
||||
*/
|
||||
usersAndRolesUrl?: string;
|
||||
/**
|
||||
* The full URL to the serverless projects page on Elastic Cloud. Undefined if not running in Serverless.
|
||||
*/
|
||||
projectsUrl?: string;
|
||||
/**
|
||||
* The full URL to the elasticsearch cluster.
|
||||
*/
|
||||
|
@ -90,6 +94,10 @@ export interface CloudSetup {
|
|||
* The full URL to the deployment management page on Elastic Cloud. Undefined if not running on Cloud.
|
||||
*/
|
||||
deploymentUrl?: string;
|
||||
/**
|
||||
* The full URL to the serverless projects page on Elastic Cloud. Undefined if not running in Serverless.
|
||||
*/
|
||||
projectsUrl?: string;
|
||||
/**
|
||||
* The full URL to the user profile page on Elastic Cloud. Undefined if not running on Cloud.
|
||||
*/
|
||||
|
|
|
@ -29,6 +29,12 @@ const configSchema = schema.object({
|
|||
users_and_roles_url: schema.maybe(schema.string()),
|
||||
organization_url: schema.maybe(schema.string()),
|
||||
profile_url: schema.maybe(schema.string()),
|
||||
projects_url: schema.conditional(
|
||||
schema.contextRef('serverless'),
|
||||
true,
|
||||
schema.string({ defaultValue: '/projects/' }),
|
||||
schema.never()
|
||||
),
|
||||
trial_end_date: schema.maybe(schema.string()),
|
||||
is_elastic_staff_owned: schema.maybe(schema.boolean()),
|
||||
serverless: schema.maybe(
|
||||
|
@ -51,6 +57,7 @@ export const config: PluginConfigDescriptor<CloudConfigType> = {
|
|||
performance_url: true,
|
||||
organization_url: true,
|
||||
profile_url: true,
|
||||
projects_url: true,
|
||||
trial_end_date: true,
|
||||
is_elastic_staff_owned: true,
|
||||
serverless: {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"requiredPlugins": [
|
||||
"kibanaReact",
|
||||
"management",
|
||||
"cloud"
|
||||
],
|
||||
"optionalPlugins": [],
|
||||
"requiredBundles": []
|
||||
|
|
|
@ -65,6 +65,9 @@ export class ServerlessPlugin
|
|||
|
||||
// Casting the "chrome.projects" service to an "internal" type: this is intentional to obscure the property from Typescript.
|
||||
const { project } = core.chrome as InternalChromeStart;
|
||||
if (dependencies.cloud.projectsUrl) {
|
||||
project.setProjectsUrl(dependencies.cloud.projectsUrl);
|
||||
}
|
||||
|
||||
return {
|
||||
setSideNavComponent: (sideNavigationComponent) =>
|
||||
|
|
|
@ -13,6 +13,7 @@ import type {
|
|||
ChromeProjectNavigationNode,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public';
|
||||
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
|
@ -31,8 +32,10 @@ export interface ServerlessPluginStart {
|
|||
|
||||
export interface ServerlessPluginSetupDependencies {
|
||||
management: ManagementSetup;
|
||||
cloud: CloudSetup;
|
||||
}
|
||||
|
||||
export interface ServerlessPluginStartDependencies {
|
||||
management: ManagementStart;
|
||||
cloud: CloudStart;
|
||||
}
|
||||
|
|
|
@ -24,5 +24,6 @@
|
|||
"@kbn/core-chrome-browser",
|
||||
"@kbn/core-chrome-browser-internal",
|
||||
"@kbn/i18n-react",
|
||||
"@kbn/cloud-plugin",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1033,7 +1033,6 @@
|
|||
"core.ui.overlays.banner.attentionTitle": "Attention",
|
||||
"core.ui.overlays.banner.closeButtonLabel": "Fermer",
|
||||
"core.ui.primaryNav.addData": "Ajouter des intégrations",
|
||||
"core.ui.primaryNav.cloud.linkToDeployments": "Mes déploiements",
|
||||
"core.ui.primaryNav.goToHome.ariaLabel": "Accéder à la page d’accueil",
|
||||
"core.ui.primaryNav.pinnedLinksAriaLabel": "Liens épinglés",
|
||||
"core.ui.primaryNav.screenReaderLabel": "Principale",
|
||||
|
|
|
@ -1047,7 +1047,6 @@
|
|||
"core.ui.overlays.banner.attentionTitle": "注意",
|
||||
"core.ui.overlays.banner.closeButtonLabel": "閉じる",
|
||||
"core.ui.primaryNav.addData": "統合の追加",
|
||||
"core.ui.primaryNav.cloud.linkToDeployments": "マイデプロイ",
|
||||
"core.ui.primaryNav.goToHome.ariaLabel": "ホームページに移動",
|
||||
"core.ui.primaryNav.pinnedLinksAriaLabel": "ピン留めされたリンク",
|
||||
"core.ui.primaryNav.screenReaderLabel": "プライマリ",
|
||||
|
|
|
@ -1047,7 +1047,6 @@
|
|||
"core.ui.overlays.banner.attentionTitle": "注意",
|
||||
"core.ui.overlays.banner.closeButtonLabel": "关闭",
|
||||
"core.ui.primaryNav.addData": "添加集成",
|
||||
"core.ui.primaryNav.cloud.linkToDeployments": "我的部署",
|
||||
"core.ui.primaryNav.goToHome.ariaLabel": "前往主页",
|
||||
"core.ui.primaryNav.pinnedLinksAriaLabel": "置顶链接",
|
||||
"core.ui.primaryNav.screenReaderLabel": "主分片",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue