diff --git a/CHANGELOG.md b/CHANGELOG.md index 545ee8110c8..ba0570bd4ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Console] Migrate `/lib/autocomplete/` module to TypeScript ([#4148](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4148)) - [Console] Migrate `/lib/!autocomplete/` module to TypeScript ([#4150](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4150)) - [Dashboard] Restructure the `Dashboard` plugin folder to be more cohesive with the project ([#4575](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4575)) +- Refactor logo usage to centralize and optimize assets and improve tests ([#4702](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4702)) ### 🔩 Tests diff --git a/packages/osd-dev-utils/src/serializers/flat_object_serializer.ts b/packages/osd-dev-utils/src/serializers/flat_object_serializer.ts new file mode 100644 index 00000000000..e3dec842ad9 --- /dev/null +++ b/packages/osd-dev-utils/src/serializers/flat_object_serializer.ts @@ -0,0 +1,93 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +const walk = (value: any, path: string[] = [], collector: string[] = []) => { + let objValue; + switch (Object.prototype.toString.call(value)) { + case '[object Map]': + case '[object WeakMap]': + // Turn into an Object so it can be iterated + objValue = Object.fromEntries(value); + break; + + case '[object Set]': + case '[object WeakSet]': + // Turn into an Array so it can be iterated + objValue = Array.from(value); + break; + + case '[object Object]': + case '[object Array]': + objValue = value; + break; + + case '[object RegExp]': + case '[object Function]': + case '[object Date]': + case '[object Boolean]': + case '[object Number]': + case '[object Symbol]': + case '[object Error]': + collector.push(`${path.join('.')} = ${value.toString()}`); + break; + + case '[object Null]': + collector.push(`${path.join('.')} = null`); + break; + + case '[object Undefined]': + collector.push(`${path.join('.')} = undefined`); + break; + + case '[object String]': + collector.push(`${path.join('.')} = ${JSON.stringify(value)}`); + break; + + case '[object BigInt]': + collector.push(`${path.join('.')} = ${value.toString()}n`); + break; + + default: + // if it is a TypedArray, turn it into an array + if (value instanceof Object.getPrototypeOf(Uint8Array)) { + objValue = Array.from(value); + } + } + + // If objValue is set, it is an Array or Object that can be iterated; else bail. + if (!objValue) return collector; + + if (Array.isArray(objValue)) { + objValue.forEach((v, i) => { + walk(v, [...path, i.toString()], collector); + }); + } else { + // eslint-disable-next-line guard-for-in + for (const key in objValue) { + walk(objValue[key], [...path, key], collector); + } + } + + return collector; +}; + +/** + * The serializer flattens objects into dotified key-value pairs, each on a line, and + * sorts them to aid in diff-ing. + * + * Example: + * { K: ["a", "b", { X: 1n }], Y: 1} + * + * Serialized: + * K.0 = "a" + * K.1 = "b" + * K.2.X = 1n + * Y = 1 + */ +export const flatObjectSerializer = { + test: (value: any) => + ['[object Object]', '[object Array]'].includes(Object.prototype.toString.call(value)), + serialize: (value: any) => walk(value).sort().join('\n'), +}; diff --git a/packages/osd-dev-utils/src/serializers/index.ts b/packages/osd-dev-utils/src/serializers/index.ts index 2755a5a7914..e31ef04898b 100644 --- a/packages/osd-dev-utils/src/serializers/index.ts +++ b/packages/osd-dev-utils/src/serializers/index.ts @@ -34,3 +34,4 @@ export * from './recursive_serializer'; export * from './any_instance_serizlizer'; export * from './replace_serializer'; export * from './strip_promises_serizlizer'; +export * from './flat_object_serializer'; diff --git a/packages/osd-stylelint-config/config/restricted_properties.json b/packages/osd-stylelint-config/config/restricted_properties.json index e63b8e114b0..b8499b02834 100644 --- a/packages/osd-stylelint-config/config/restricted_properties.json +++ b/packages/osd-stylelint-config/config/restricted_properties.json @@ -1,5 +1,6 @@ { "font-family": { + "explanation": "All \"font-family\" styles should be inherited from OUI themes and components. Remove the rule.", "approved": [ "src/plugins/discover_legacy/public/application/_discover.scss", "src/plugins/maps_legacy/public/map/_leaflet_overrides.scss", diff --git a/src/core/common/index.ts b/src/core/common/index.ts new file mode 100644 index 00000000000..64fb2c6f435 --- /dev/null +++ b/src/core/common/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { ImageType, ColorScheme, getLogos } from './logos'; +export type { Logos } from './types'; diff --git a/src/core/common/logos/__snapshots__/get_logos.test.ts.snap b/src/core/common/logos/__snapshots__/get_logos.test.ts.snap new file mode 100644 index 00000000000..6c7746f3533 --- /dev/null +++ b/src/core/common/logos/__snapshots__/get_logos.test.ts.snap @@ -0,0 +1,613 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getLogos when branding has both light and dark logos and only light spinners returns the correct logos 1`] = ` +AnimatedMark.dark.type = "custom" +AnimatedMark.dark.url = "/custom/branded/spinner.svg" +AnimatedMark.light.type = "custom" +AnimatedMark.light.url = "/custom/branded/spinner.svg" +AnimatedMark.type = "custom" +AnimatedMark.url = "/custom/branded/spinner.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo-darkmode.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark-darkmode.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark-darkmode.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo-darkmode.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo.svg" +colorScheme = "light" +`; + +exports[`getLogos when branding has both light and dark logos and only light spinners returns the correct logos when dark color scheme is requested 1`] = ` +AnimatedMark.dark.type = "custom" +AnimatedMark.dark.url = "/custom/branded/spinner.svg" +AnimatedMark.light.type = "custom" +AnimatedMark.light.url = "/custom/branded/spinner.svg" +AnimatedMark.type = "custom" +AnimatedMark.url = "/custom/branded/spinner.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo-darkmode.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo-darkmode.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark-darkmode.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark-darkmode.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark-darkmode.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark-darkmode.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo-darkmode.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo-darkmode.svg" +colorScheme = "dark" +`; + +exports[`getLogos when branding has both light and dark logos and only light spinners returns the correct logos when light color scheme is requested 1`] = ` +AnimatedMark.dark.type = "custom" +AnimatedMark.dark.url = "/custom/branded/spinner.svg" +AnimatedMark.light.type = "custom" +AnimatedMark.light.url = "/custom/branded/spinner.svg" +AnimatedMark.type = "custom" +AnimatedMark.url = "/custom/branded/spinner.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo-darkmode.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark-darkmode.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark-darkmode.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo-darkmode.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo.svg" +colorScheme = "light" +`; + +exports[`getLogos when branding has both light and dark logos and spinners returns the correct logos 1`] = ` +AnimatedMark.dark.type = "custom" +AnimatedMark.dark.url = "/custom/branded/spinner-darkmode.svg" +AnimatedMark.light.type = "custom" +AnimatedMark.light.url = "/custom/branded/spinner.svg" +AnimatedMark.type = "custom" +AnimatedMark.url = "/custom/branded/spinner.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo-darkmode.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark-darkmode.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark-darkmode.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo-darkmode.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo.svg" +colorScheme = "light" +`; + +exports[`getLogos when branding has both light and dark logos and spinners returns the correct logos when dark color scheme is requested 1`] = ` +AnimatedMark.dark.type = "custom" +AnimatedMark.dark.url = "/custom/branded/spinner-darkmode.svg" +AnimatedMark.light.type = "custom" +AnimatedMark.light.url = "/custom/branded/spinner.svg" +AnimatedMark.type = "custom" +AnimatedMark.url = "/custom/branded/spinner-darkmode.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo-darkmode.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo-darkmode.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark-darkmode.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark-darkmode.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark-darkmode.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark-darkmode.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo-darkmode.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo-darkmode.svg" +colorScheme = "dark" +`; + +exports[`getLogos when branding has both light and dark logos and spinners returns the correct logos when light color scheme is requested 1`] = ` +AnimatedMark.dark.type = "custom" +AnimatedMark.dark.url = "/custom/branded/spinner-darkmode.svg" +AnimatedMark.light.type = "custom" +AnimatedMark.light.url = "/custom/branded/spinner.svg" +AnimatedMark.type = "custom" +AnimatedMark.url = "/custom/branded/spinner.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo-darkmode.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark-darkmode.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark-darkmode.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo-darkmode.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo.svg" +colorScheme = "light" +`; + +exports[`getLogos when branding has both light and dark logos returns the correct logos 1`] = ` +AnimatedMark.dark.type = "alternative" +AnimatedMark.dark.url = "/custom/branded/mark-darkmode.svg" +AnimatedMark.light.type = "alternative" +AnimatedMark.light.url = "/custom/branded/mark.svg" +AnimatedMark.type = "alternative" +AnimatedMark.url = "/custom/branded/mark.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo-darkmode.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark-darkmode.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark-darkmode.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo-darkmode.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo.svg" +colorScheme = "light" +`; + +exports[`getLogos when branding has both light and dark logos returns the correct logos when dark color scheme is requested 1`] = ` +AnimatedMark.dark.type = "alternative" +AnimatedMark.dark.url = "/custom/branded/mark-darkmode.svg" +AnimatedMark.light.type = "alternative" +AnimatedMark.light.url = "/custom/branded/mark.svg" +AnimatedMark.type = "alternative" +AnimatedMark.url = "/custom/branded/mark-darkmode.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo-darkmode.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo-darkmode.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark-darkmode.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark-darkmode.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark-darkmode.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark-darkmode.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo-darkmode.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo-darkmode.svg" +colorScheme = "dark" +`; + +exports[`getLogos when branding has both light and dark logos returns the correct logos when light color scheme is requested 1`] = ` +AnimatedMark.dark.type = "alternative" +AnimatedMark.dark.url = "/custom/branded/mark-darkmode.svg" +AnimatedMark.light.type = "alternative" +AnimatedMark.light.url = "/custom/branded/mark.svg" +AnimatedMark.type = "alternative" +AnimatedMark.url = "/custom/branded/mark.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo-darkmode.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark-darkmode.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark-darkmode.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo-darkmode.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo.svg" +colorScheme = "light" +`; + +exports[`getLogos when branding has only light logos and spinner returns the correct logos 1`] = ` +AnimatedMark.dark.type = "custom" +AnimatedMark.dark.url = "/custom/branded/spinner.svg" +AnimatedMark.light.type = "custom" +AnimatedMark.light.url = "/custom/branded/spinner.svg" +AnimatedMark.type = "custom" +AnimatedMark.url = "/custom/branded/spinner.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo.svg" +colorScheme = "light" +`; + +exports[`getLogos when branding has only light logos and spinner returns the correct logos when dark color scheme is requested 1`] = ` +AnimatedMark.dark.type = "custom" +AnimatedMark.dark.url = "/custom/branded/spinner.svg" +AnimatedMark.light.type = "custom" +AnimatedMark.light.url = "/custom/branded/spinner.svg" +AnimatedMark.type = "custom" +AnimatedMark.url = "/custom/branded/spinner.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo.svg" +colorScheme = "dark" +`; + +exports[`getLogos when branding has only light logos and spinner returns the correct logos when light color scheme is requested 1`] = ` +AnimatedMark.dark.type = "custom" +AnimatedMark.dark.url = "/custom/branded/spinner.svg" +AnimatedMark.light.type = "custom" +AnimatedMark.light.url = "/custom/branded/spinner.svg" +AnimatedMark.type = "custom" +AnimatedMark.url = "/custom/branded/spinner.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo.svg" +colorScheme = "light" +`; + +exports[`getLogos when branding has only light logos returns the correct logos 1`] = ` +AnimatedMark.dark.type = "alternative" +AnimatedMark.dark.url = "/custom/branded/mark.svg" +AnimatedMark.light.type = "alternative" +AnimatedMark.light.url = "/custom/branded/mark.svg" +AnimatedMark.type = "alternative" +AnimatedMark.url = "/custom/branded/mark.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo.svg" +colorScheme = "light" +`; + +exports[`getLogos when branding has only light logos returns the correct logos when dark color scheme is requested 1`] = ` +AnimatedMark.dark.type = "alternative" +AnimatedMark.dark.url = "/custom/branded/mark.svg" +AnimatedMark.light.type = "alternative" +AnimatedMark.light.url = "/custom/branded/mark.svg" +AnimatedMark.type = "alternative" +AnimatedMark.url = "/custom/branded/mark.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo.svg" +colorScheme = "dark" +`; + +exports[`getLogos when branding has only light logos returns the correct logos when light color scheme is requested 1`] = ` +AnimatedMark.dark.type = "alternative" +AnimatedMark.dark.url = "/custom/branded/mark.svg" +AnimatedMark.light.type = "alternative" +AnimatedMark.light.url = "/custom/branded/mark.svg" +AnimatedMark.type = "alternative" +AnimatedMark.url = "/custom/branded/mark.svg" +Application.dark.type = "custom" +Application.dark.url = "/custom/branded/logo.svg" +Application.light.type = "custom" +Application.light.url = "/custom/branded/logo.svg" +Application.type = "custom" +Application.url = "/custom/branded/logo.svg" +CenterMark.dark.type = "custom" +CenterMark.dark.url = "/custom/branded/mark.svg" +CenterMark.light.type = "custom" +CenterMark.light.url = "/custom/branded/mark.svg" +CenterMark.type = "custom" +CenterMark.url = "/custom/branded/mark.svg" +Mark.dark.type = "custom" +Mark.dark.url = "/custom/branded/mark.svg" +Mark.light.type = "custom" +Mark.light.url = "/custom/branded/mark.svg" +Mark.type = "custom" +Mark.url = "/custom/branded/mark.svg" +OpenSearch.dark.type = "custom" +OpenSearch.dark.url = "/custom/branded/logo.svg" +OpenSearch.light.type = "custom" +OpenSearch.light.url = "/custom/branded/logo.svg" +OpenSearch.type = "custom" +OpenSearch.url = "/custom/branded/logo.svg" +colorScheme = "light" +`; + +exports[`getLogos when unbranded returns the correct logos 1`] = ` +AnimatedMark.dark.type = "default" +AnimatedMark.dark.url = "/mocked/base/path/ui/logos/opensearch_spinner_on_dark.svg" +AnimatedMark.light.type = "default" +AnimatedMark.light.url = "/mocked/base/path/ui/logos/opensearch_spinner_on_light.svg" +AnimatedMark.type = "default" +AnimatedMark.url = "/mocked/base/path/ui/logos/opensearch_spinner_on_light.svg" +Application.dark.type = "default" +Application.dark.url = "/mocked/base/path/ui/logos/opensearch_dashboards_on_dark.svg" +Application.light.type = "default" +Application.light.url = "/mocked/base/path/ui/logos/opensearch_dashboards_on_light.svg" +Application.type = "default" +Application.url = "/mocked/base/path/ui/logos/opensearch_dashboards_on_light.svg" +CenterMark.dark.type = "default" +CenterMark.dark.url = "/mocked/base/path/ui/logos/opensearch_center_mark_on_dark.svg" +CenterMark.light.type = "default" +CenterMark.light.url = "/mocked/base/path/ui/logos/opensearch_center_mark_on_light.svg" +CenterMark.type = "default" +CenterMark.url = "/mocked/base/path/ui/logos/opensearch_center_mark_on_light.svg" +Mark.dark.type = "default" +Mark.dark.url = "/mocked/base/path/ui/logos/opensearch_mark_on_dark.svg" +Mark.light.type = "default" +Mark.light.url = "/mocked/base/path/ui/logos/opensearch_mark_on_light.svg" +Mark.type = "default" +Mark.url = "/mocked/base/path/ui/logos/opensearch_mark_on_light.svg" +OpenSearch.dark.type = "default" +OpenSearch.dark.url = "/mocked/base/path/ui/logos/opensearch_on_dark.svg" +OpenSearch.light.type = "default" +OpenSearch.light.url = "/mocked/base/path/ui/logos/opensearch_on_light.svg" +OpenSearch.type = "default" +OpenSearch.url = "/mocked/base/path/ui/logos/opensearch_on_light.svg" +colorScheme = "light" +`; + +exports[`getLogos when unbranded returns the correct logos when dark color scheme is requested 1`] = ` +AnimatedMark.dark.type = "default" +AnimatedMark.dark.url = "/mocked/base/path/ui/logos/opensearch_spinner_on_dark.svg" +AnimatedMark.light.type = "default" +AnimatedMark.light.url = "/mocked/base/path/ui/logos/opensearch_spinner_on_light.svg" +AnimatedMark.type = "default" +AnimatedMark.url = "/mocked/base/path/ui/logos/opensearch_spinner_on_dark.svg" +Application.dark.type = "default" +Application.dark.url = "/mocked/base/path/ui/logos/opensearch_dashboards_on_dark.svg" +Application.light.type = "default" +Application.light.url = "/mocked/base/path/ui/logos/opensearch_dashboards_on_light.svg" +Application.type = "default" +Application.url = "/mocked/base/path/ui/logos/opensearch_dashboards_on_dark.svg" +CenterMark.dark.type = "default" +CenterMark.dark.url = "/mocked/base/path/ui/logos/opensearch_center_mark_on_dark.svg" +CenterMark.light.type = "default" +CenterMark.light.url = "/mocked/base/path/ui/logos/opensearch_center_mark_on_light.svg" +CenterMark.type = "default" +CenterMark.url = "/mocked/base/path/ui/logos/opensearch_center_mark_on_dark.svg" +Mark.dark.type = "default" +Mark.dark.url = "/mocked/base/path/ui/logos/opensearch_mark_on_dark.svg" +Mark.light.type = "default" +Mark.light.url = "/mocked/base/path/ui/logos/opensearch_mark_on_light.svg" +Mark.type = "default" +Mark.url = "/mocked/base/path/ui/logos/opensearch_mark_on_dark.svg" +OpenSearch.dark.type = "default" +OpenSearch.dark.url = "/mocked/base/path/ui/logos/opensearch_on_dark.svg" +OpenSearch.light.type = "default" +OpenSearch.light.url = "/mocked/base/path/ui/logos/opensearch_on_light.svg" +OpenSearch.type = "default" +OpenSearch.url = "/mocked/base/path/ui/logos/opensearch_on_dark.svg" +colorScheme = "dark" +`; + +exports[`getLogos when unbranded returns the correct logos when light color scheme is requested 1`] = ` +AnimatedMark.dark.type = "default" +AnimatedMark.dark.url = "/mocked/base/path/ui/logos/opensearch_spinner_on_dark.svg" +AnimatedMark.light.type = "default" +AnimatedMark.light.url = "/mocked/base/path/ui/logos/opensearch_spinner_on_light.svg" +AnimatedMark.type = "default" +AnimatedMark.url = "/mocked/base/path/ui/logos/opensearch_spinner_on_light.svg" +Application.dark.type = "default" +Application.dark.url = "/mocked/base/path/ui/logos/opensearch_dashboards_on_dark.svg" +Application.light.type = "default" +Application.light.url = "/mocked/base/path/ui/logos/opensearch_dashboards_on_light.svg" +Application.type = "default" +Application.url = "/mocked/base/path/ui/logos/opensearch_dashboards_on_light.svg" +CenterMark.dark.type = "default" +CenterMark.dark.url = "/mocked/base/path/ui/logos/opensearch_center_mark_on_dark.svg" +CenterMark.light.type = "default" +CenterMark.light.url = "/mocked/base/path/ui/logos/opensearch_center_mark_on_light.svg" +CenterMark.type = "default" +CenterMark.url = "/mocked/base/path/ui/logos/opensearch_center_mark_on_light.svg" +Mark.dark.type = "default" +Mark.dark.url = "/mocked/base/path/ui/logos/opensearch_mark_on_dark.svg" +Mark.light.type = "default" +Mark.light.url = "/mocked/base/path/ui/logos/opensearch_mark_on_light.svg" +Mark.type = "default" +Mark.url = "/mocked/base/path/ui/logos/opensearch_mark_on_light.svg" +OpenSearch.dark.type = "default" +OpenSearch.dark.url = "/mocked/base/path/ui/logos/opensearch_on_dark.svg" +OpenSearch.light.type = "default" +OpenSearch.light.url = "/mocked/base/path/ui/logos/opensearch_on_light.svg" +OpenSearch.type = "default" +OpenSearch.url = "/mocked/base/path/ui/logos/opensearch_on_light.svg" +colorScheme = "light" +`; diff --git a/src/core/common/logos/constants.ts b/src/core/common/logos/constants.ts new file mode 100644 index 00000000000..0972554f116 --- /dev/null +++ b/src/core/common/logos/constants.ts @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export enum ImageType { + DEFAULT = 'default', + CUSTOM = 'custom', + ALTERNATIVE = 'alternative', +} + +export enum ColorScheme { + LIGHT = 'light', + DARK = 'dark', +} diff --git a/src/core/common/logos/get_logos.mock.ts b/src/core/common/logos/get_logos.mock.ts new file mode 100644 index 00000000000..d36591c52b6 --- /dev/null +++ b/src/core/common/logos/get_logos.mock.ts @@ -0,0 +1,27 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { getLogos } from './get_logos'; +import { Logos } from './types'; + +export const getLogosMock: { + default: DeeplyMockedKeys; + branded: DeeplyMockedKeys; +} = { + default: getLogos({}, ''), + branded: getLogos( + { + logo: { + defaultUrl: '/custom/branded/logo.svg', + darkModeUrl: '/custom/branded/logo-darkmode.svg', + }, + mark: { + defaultUrl: '/custom/branded/mark.svg', + darkModeUrl: '/custom/branded/mark-darkmode.svg', + }, + }, + '' + ), +}; diff --git a/src/core/common/logos/get_logos.test.ts b/src/core/common/logos/get_logos.test.ts new file mode 100644 index 00000000000..d79564af431 --- /dev/null +++ b/src/core/common/logos/get_logos.test.ts @@ -0,0 +1,133 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { getLogos } from './get_logos'; +import { flatObjectSerializer } from '@osd/dev-utils'; + +const serverBasePathMocked = '/mocked/base/path'; + +expect.addSnapshotSerializer(flatObjectSerializer); + +describe('getLogos', () => { + describe('when unbranded', () => { + const branding = {}; + + it('returns the correct logos', () => { + expect(getLogos(branding, serverBasePathMocked)).toMatchSnapshot(); + }); + it('returns the correct logos when light color scheme is requested', () => { + expect(getLogos({ ...branding, darkMode: false }, serverBasePathMocked)).toMatchSnapshot(); + }); + it('returns the correct logos when dark color scheme is requested', () => { + expect(getLogos({ ...branding, darkMode: true }, serverBasePathMocked)).toMatchSnapshot(); + }); + }); + + describe('when branding has only light logos', () => { + const branding = { + logo: { defaultUrl: '/custom/branded/logo.svg' }, + mark: { defaultUrl: '/custom/branded/mark.svg' }, + }; + it('returns the correct logos', () => { + expect(getLogos(branding, serverBasePathMocked)).toMatchSnapshot(); + }); + it('returns the correct logos when light color scheme is requested', () => { + expect(getLogos({ ...branding, darkMode: false }, serverBasePathMocked)).toMatchSnapshot(); + }); + it('returns the correct logos when dark color scheme is requested', () => { + expect(getLogos({ ...branding, darkMode: true }, serverBasePathMocked)).toMatchSnapshot(); + }); + }); + + describe('when branding has only light logos and spinner', () => { + const branding = { + logo: { defaultUrl: '/custom/branded/logo.svg' }, + mark: { defaultUrl: '/custom/branded/mark.svg' }, + loadingLogo: { defaultUrl: '/custom/branded/spinner.svg' }, + }; + it('returns the correct logos', () => { + expect(getLogos(branding, serverBasePathMocked)).toMatchSnapshot(); + }); + it('returns the correct logos when light color scheme is requested', () => { + expect(getLogos({ ...branding, darkMode: false }, serverBasePathMocked)).toMatchSnapshot(); + }); + it('returns the correct logos when dark color scheme is requested', () => { + expect(getLogos({ ...branding, darkMode: true }, serverBasePathMocked)).toMatchSnapshot(); + }); + }); + + describe('when branding has both light and dark logos', () => { + const branding = { + logo: { + defaultUrl: '/custom/branded/logo.svg', + darkModeUrl: '/custom/branded/logo-darkmode.svg', + }, + mark: { + defaultUrl: '/custom/branded/mark.svg', + darkModeUrl: '/custom/branded/mark-darkmode.svg', + }, + }; + it('returns the correct logos', () => { + expect(getLogos(branding, serverBasePathMocked)).toMatchSnapshot(); + }); + it('returns the correct logos when light color scheme is requested', () => { + expect(getLogos({ ...branding, darkMode: false }, serverBasePathMocked)).toMatchSnapshot(); + }); + it('returns the correct logos when dark color scheme is requested', () => { + expect(getLogos({ ...branding, darkMode: true }, serverBasePathMocked)).toMatchSnapshot(); + }); + }); + + describe('when branding has both light and dark logos and spinners', () => { + const branding = { + logo: { + defaultUrl: '/custom/branded/logo.svg', + darkModeUrl: '/custom/branded/logo-darkmode.svg', + }, + mark: { + defaultUrl: '/custom/branded/mark.svg', + darkModeUrl: '/custom/branded/mark-darkmode.svg', + }, + loadingLogo: { + defaultUrl: '/custom/branded/spinner.svg', + darkModeUrl: '/custom/branded/spinner-darkmode.svg', + }, + }; + it('returns the correct logos', () => { + expect(getLogos(branding, serverBasePathMocked)).toMatchSnapshot(); + }); + it('returns the correct logos when light color scheme is requested', () => { + expect(getLogos({ ...branding, darkMode: false }, serverBasePathMocked)).toMatchSnapshot(); + }); + it('returns the correct logos when dark color scheme is requested', () => { + expect(getLogos({ ...branding, darkMode: true }, serverBasePathMocked)).toMatchSnapshot(); + }); + }); + + describe('when branding has both light and dark logos and only light spinners', () => { + const branding = { + logo: { + defaultUrl: '/custom/branded/logo.svg', + darkModeUrl: '/custom/branded/logo-darkmode.svg', + }, + mark: { + defaultUrl: '/custom/branded/mark.svg', + darkModeUrl: '/custom/branded/mark-darkmode.svg', + }, + loadingLogo: { + defaultUrl: '/custom/branded/spinner.svg', + }, + }; + it('returns the correct logos', () => { + expect(getLogos(branding, serverBasePathMocked)).toMatchSnapshot(); + }); + it('returns the correct logos when light color scheme is requested', () => { + expect(getLogos({ ...branding, darkMode: false }, serverBasePathMocked)).toMatchSnapshot(); + }); + it('returns the correct logos when dark color scheme is requested', () => { + expect(getLogos({ ...branding, darkMode: true }, serverBasePathMocked)).toMatchSnapshot(); + }); + }); +}); diff --git a/src/core/common/logos/get_logos.ts b/src/core/common/logos/get_logos.ts new file mode 100644 index 00000000000..c27a5306e9e --- /dev/null +++ b/src/core/common/logos/get_logos.ts @@ -0,0 +1,142 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { deepFreeze } from '@osd/std'; +import type { ImageItem, LogoItem, Logos } from './types'; +import { ColorScheme, ImageType } from './constants'; +import { Branding } from '../../types'; + +// The logos are stored at `src/core/server/core_app/assets/logos` to have a pretty URL +export const OPENSEARCH_DASHBOARDS_THEMED = 'ui/logos/opensearch_dashboards.svg'; +export const OPENSEARCH_DASHBOARDS_ON_LIGHT = 'ui/logos/opensearch_dashboards_on_light.svg'; +export const OPENSEARCH_DASHBOARDS_ON_DARK = 'ui/logos/opensearch_dashboards_on_dark.svg'; +export const OPENSEARCH_THEMED = 'ui/logos/opensearch.svg'; +export const OPENSEARCH_ON_LIGHT = 'ui/logos/opensearch_on_light.svg'; +export const OPENSEARCH_ON_DARK = 'ui/logos/opensearch_on_dark.svg'; +export const MARK_THEMED = 'ui/logos/opensearch_mark.svg'; +export const MARK_ON_LIGHT = 'ui/logos/opensearch_mark_on_light.svg'; +export const MARK_ON_DARK = 'ui/logos/opensearch_mark_on_dark.svg'; +export const CENTER_MARK_THEMED = 'ui/logos/opensearch_center_mark.svg'; +export const CENTER_MARK_ON_LIGHT = 'ui/logos/opensearch_center_mark_on_light.svg'; +export const CENTER_MARK_ON_DARK = 'ui/logos/opensearch_center_mark_on_dark.svg'; +export const ANIMATED_MARK_THEMED = 'ui/logos/opensearch_spinner.svg'; +export const ANIMATED_MARK_ON_LIGHT = 'ui/logos/opensearch_spinner_on_light.svg'; +export const ANIMATED_MARK_ON_DARK = 'ui/logos/opensearch_spinner_on_dark.svg'; + +interface AssetOption { + url?: string; + type: ImageType; + colorScheme: ColorScheme; +} + +/** + * Loops through the assets to find one that has a `url` set. If dark color-scheme asset is needed, + * light assets can be used for fallback but not vice vera. + * Place defaults at the end of assets to use them as final fallbacks. + * `assets` should have dark - light assets of each type, one after the other. + */ +const getFirstUsableAsset = ( + assets: AssetOption[], + requireDarkColorScheme: boolean = false +): ImageItem => { + for (const { url, type, colorScheme } of assets) { + if (url && (requireDarkColorScheme || colorScheme === 'light')) return { url, type }; + } + + // `assets` will contain the default assets so the code will never get here + throw new Error('No default asset found'); +}; + +const getLogo = (assets: AssetOption[], requireDarkColorScheme: boolean = false): LogoItem => { + const lightAsset = getFirstUsableAsset(assets, false); + const darkAsset = getFirstUsableAsset(assets, true); + const colorSchemeAsset = requireDarkColorScheme ? darkAsset : lightAsset; + + return { + light: lightAsset, + dark: darkAsset, + url: colorSchemeAsset.url, + type: colorSchemeAsset.type!, + }; +}; + +/** + * Generates all the combinations of logos based on the color-scheme and branding config + * + * Ideally, the default logos would point to color-scheme-aware (aka themed) imagery while the dark and light + * subtypes reference the dark and light variants. Sadly, Safari doesn't support color-schemes in SVGs yet. + * https://bugs.webkit.org/show_bug.cgi?id=199134 + */ +export const getLogos = (branding: Branding = {}, serverBasePath: string): Logos => { + const { + logo: { defaultUrl: customLogoUrl, darkModeUrl: customDarkLogoUrl } = {}, + mark: { defaultUrl: customMarkUrl, darkModeUrl: customDarkMarkUrl } = {}, + loadingLogo: { defaultUrl: customAnimatedUrl, darkModeUrl: customDarkAnimatedMarkUrl } = {}, + darkMode = false, + } = branding; + + // OSD logos + const defaultLightColorSchemeOpenSearchDashboards = `${serverBasePath}/${OPENSEARCH_DASHBOARDS_ON_LIGHT}`; + const defaultDarkColorSchemeOpenSearchDashboards = `${serverBasePath}/${OPENSEARCH_DASHBOARDS_ON_DARK}`; + // OS logos + const defaultLightColorSchemeOpenSearch = `${serverBasePath}/${OPENSEARCH_ON_LIGHT}`; + const defaultDarkColorSchemeOpenSearch = `${serverBasePath}/${OPENSEARCH_ON_DARK}`; + // OS marks + const defaultLightColorSchemeMark = `${serverBasePath}/${MARK_ON_LIGHT}`; + const defaultDarkColorSchemeMark = `${serverBasePath}/${MARK_ON_DARK}`; + // OS marks variant padded (but not centered) within the container + // ToDo: This naming is misleading; figure out if the distinction could be handled with CSS padding alone + // https://github.com/opensearch-project/OpenSearch-Dashboards/issues/4714 + const defaultLightColorSchemeCenterMark = `${serverBasePath}/${CENTER_MARK_ON_LIGHT}`; + const defaultDarkColorSchemeCenterMark = `${serverBasePath}/${CENTER_MARK_ON_DARK}`; + // OS animated marks + const defaultLightColorSchemeAnimatedMark = `${serverBasePath}/${ANIMATED_MARK_ON_LIGHT}`; + const defaultDarkColorSchemeAnimatedMark = `${serverBasePath}/${ANIMATED_MARK_ON_DARK}`; + + const colorScheme: ColorScheme = darkMode ? ColorScheme.DARK : ColorScheme.LIGHT; + + // It is easier to read the lines unwrapped, so + // prettier-ignore + return deepFreeze({ + OpenSearch: getLogo([ + { url: customDarkLogoUrl, type: ImageType.CUSTOM, colorScheme: ColorScheme.DARK }, + { url: customLogoUrl, type: ImageType.CUSTOM, colorScheme: ColorScheme.LIGHT }, + { url: defaultDarkColorSchemeOpenSearch, type: ImageType.DEFAULT, colorScheme: ColorScheme.DARK }, + { url: defaultLightColorSchemeOpenSearch, type: ImageType.DEFAULT, colorScheme: ColorScheme.LIGHT }, + ], darkMode), + + Application: getLogo([ + { url: customDarkLogoUrl, type: ImageType.CUSTOM, colorScheme: ColorScheme.DARK }, + { url: customLogoUrl, type: ImageType.CUSTOM, colorScheme: ColorScheme.LIGHT }, + { url: defaultDarkColorSchemeOpenSearchDashboards, type: ImageType.DEFAULT, colorScheme: ColorScheme.DARK }, + { url: defaultLightColorSchemeOpenSearchDashboards, type: ImageType.DEFAULT, colorScheme: ColorScheme.LIGHT }, + ], darkMode), + + Mark: getLogo([ + { url: customDarkMarkUrl, type: ImageType.CUSTOM, colorScheme: ColorScheme.DARK }, + { url: customMarkUrl, type: ImageType.CUSTOM, colorScheme: ColorScheme.LIGHT }, + { url: defaultDarkColorSchemeMark, type: ImageType.DEFAULT, colorScheme: ColorScheme.DARK }, + { url: defaultLightColorSchemeMark, type: ImageType.DEFAULT, colorScheme: ColorScheme.LIGHT }, + ], darkMode), + + CenterMark: getLogo([ + { url: customDarkMarkUrl, type: ImageType.CUSTOM, colorScheme: ColorScheme.DARK }, + { url: customMarkUrl, type: ImageType.CUSTOM, colorScheme: ColorScheme.LIGHT }, + { url: defaultDarkColorSchemeCenterMark, type: ImageType.DEFAULT, colorScheme: ColorScheme.DARK }, + { url: defaultLightColorSchemeCenterMark, type: ImageType.DEFAULT, colorScheme: ColorScheme.LIGHT }, + ], darkMode), + + AnimatedMark: getLogo([ + { url: customDarkAnimatedMarkUrl, type: ImageType.CUSTOM, colorScheme: ColorScheme.DARK }, + { url: customAnimatedUrl, type: ImageType.CUSTOM, colorScheme: ColorScheme.LIGHT }, + { url: customDarkMarkUrl, type: ImageType.ALTERNATIVE, colorScheme: ColorScheme.DARK }, + { url: customMarkUrl, type: ImageType.ALTERNATIVE, colorScheme: ColorScheme.LIGHT }, + { url: defaultDarkColorSchemeAnimatedMark, type: ImageType.DEFAULT, colorScheme: ColorScheme.DARK }, + { url: defaultLightColorSchemeAnimatedMark, type: ImageType.DEFAULT, colorScheme: ColorScheme.LIGHT }, + ], darkMode), + + colorScheme, + }); +}; diff --git a/src/core/common/logos/index.ts b/src/core/common/logos/index.ts new file mode 100644 index 00000000000..7525b1a3a31 --- /dev/null +++ b/src/core/common/logos/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +// ToDo: Extend this to handle everything related to white-labelling + +export { getLogos } from './get_logos'; +export { ImageType, ColorScheme } from './constants'; diff --git a/src/core/common/logos/types.ts b/src/core/common/logos/types.ts new file mode 100644 index 00000000000..1885b09017a --- /dev/null +++ b/src/core/common/logos/types.ts @@ -0,0 +1,34 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ColorScheme, ImageType } from './constants'; + +/** + * @public + */ +export interface Logos { + /** + * @deprecated Use {@link Logos.Application} instead. + */ + readonly OpenSearch: LogoItem; + readonly Application: LogoItem; + readonly Mark: LogoItem; + /** + * @deprecated Use {@link Logos.Mark} instead. + */ + readonly CenterMark: LogoItem; + readonly AnimatedMark: LogoItem; + readonly colorScheme: ColorScheme; +} + +export type LogoItem = ImageItem & Record; + +export interface ImageItem { + /** + * The URL of the image + */ + readonly url: string; + readonly type: ImageType; +} diff --git a/src/core/common/mocks.ts b/src/core/common/mocks.ts new file mode 100644 index 00000000000..b1863a96fa3 --- /dev/null +++ b/src/core/common/mocks.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { getLogosMock } from './logos/get_logos.mock'; diff --git a/src/core/common/types.ts b/src/core/common/types.ts new file mode 100644 index 00000000000..1f618a8c396 --- /dev/null +++ b/src/core/common/types.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export type { Logos } from './logos/types'; diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index 8e5205e6f9b..14b516ff95b 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -30,7 +30,8 @@ import { BehaviorSubject } from 'rxjs'; import type { PublicMethodsOf } from '@osd/utility-types'; -import { ChromeBadge, ChromeBrand, ChromeBreadcrumb, ChromeService, InternalChromeStart } from './'; +import { ChromeBadge, ChromeBreadcrumb, ChromeService, InternalChromeStart } from './'; +import { getLogosMock } from '../../common/mocks'; const createStartContractMock = () => { const startContract: DeeplyMockedKeys = { @@ -54,6 +55,7 @@ const createStartContractMock = () => { change: jest.fn(), reset: jest.fn(), }, + logos: getLogosMock.default, navControls: { registerLeft: jest.fn(), registerCenter: jest.fn(), @@ -63,8 +65,6 @@ const createStartContractMock = () => { getRight$: jest.fn(), }, setAppTitle: jest.fn(), - setBrand: jest.fn(), - getBrand$: jest.fn(), setIsVisible: jest.fn(), getIsVisible$: jest.fn(), addApplicationClass: jest.fn(), @@ -82,7 +82,6 @@ const createStartContractMock = () => { setCustomNavLink: jest.fn(), }; startContract.navLinks.getAll.mockReturnValue([]); - startContract.getBrand$.mockReturnValue(new BehaviorSubject({} as ChromeBrand)); startContract.getIsVisible$.mockReturnValue(new BehaviorSubject(false)); startContract.getApplicationClasses$.mockReturnValue(new BehaviorSubject(['class-name'])); startContract.getBadge$.mockReturnValue(new BehaviorSubject({} as ChromeBadge)); diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index b8635f5a070..f11b0f3965e 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -145,36 +145,6 @@ describe('start', () => { }); }); - describe('brand', () => { - it('updates/emits the brand as it changes', async () => { - const { chrome, service } = await start(); - const promise = chrome.getBrand$().pipe(toArray()).toPromise(); - - chrome.setBrand({ - logo: 'big logo', - smallLogo: 'not so big logo', - }); - chrome.setBrand({ - logo: 'big logo without small logo', - }); - service.stop(); - - await expect(promise).resolves.toMatchInlineSnapshot(` - Array [ - Object {}, - Object { - "logo": "big logo", - "smallLogo": "not so big logo", - }, - Object { - "logo": "big logo without small logo", - "smallLogo": undefined, - }, - ] - `); - }); - }); - describe('visibility', () => { it('emits false when no application is mounted', async () => { const { chrome, service } = await start(); @@ -478,7 +448,6 @@ describe('stop', () => { it('completes applicationClass$, getIsNavDrawerLocked, breadcrumbs$, isVisible$, and brand$ observables', async () => { const { chrome, service } = await start(); const promise = Rx.combineLatest( - chrome.getBrand$(), chrome.getApplicationClasses$(), chrome.getIsNavDrawerLocked$(), chrome.getBreadcrumbs$(), @@ -496,7 +465,6 @@ describe('stop', () => { await expect( Rx.combineLatest( - chrome.getBrand$(), chrome.getApplicationClasses$(), chrome.getIsNavDrawerLocked$(), chrome.getBreadcrumbs$(), diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 97746f465ab..d094d86360e 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -49,6 +49,9 @@ import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_acce import { Header } from './ui'; import { ChromeHelpExtensionMenuLink } from './ui/header/header_help_menu'; import { Branding } from '../'; +import { getLogos } from '../../common'; +import type { Logos } from '../../common/types'; + export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle }; const IS_LOCKED_KEY = 'core.chrome.isLocked'; @@ -60,12 +63,6 @@ export interface ChromeBadge { iconType?: IconType; } -/** @public */ -export interface ChromeBrand { - logo?: string; - smallLogo?: string; -} - /** @public */ export type ChromeBreadcrumb = EuiBreadcrumb; @@ -156,7 +153,6 @@ export class ChromeService { this.initVisibility(application); const appTitle$ = new BehaviorSubject('Overview'); - const brand$ = new BehaviorSubject({}); const applicationClasses$ = new BehaviorSubject>(new Set()); const helpExtension$ = new BehaviorSubject(undefined); const breadcrumbs$ = new BehaviorSubject([]); @@ -185,6 +181,8 @@ export class ChromeService { const getIsNavDrawerLocked$ = isNavDrawerLocked$.pipe(takeUntil(this.stop$)); + const logos = getLogos(injectedMetadata.getBranding(), http.basePath.serverBasePath); + const isIE = () => { const ua = window.navigator.userAgent; const msie = ua.indexOf('MSIE '); // IE 10 or older @@ -234,6 +232,7 @@ export class ChromeService { navLinks, recentlyAccessed, docTitle, + logos, getHeaderComponent: () => (
), setAppTitle: (appTitle: string) => appTitle$.next(appTitle), - getBrand$: () => brand$.pipe(takeUntil(this.stop$)), - - setBrand: (brand: ChromeBrand) => { - brand$.next( - Object.freeze({ - logo: brand.logo, - smallLogo: brand.smallLogo, - }) - ); - }, - getIsVisible$: () => this.isVisible$, setIsVisible: (isVisible: boolean) => this.isForceHidden$.next(!isVisible), @@ -371,6 +360,8 @@ export interface ChromeStart { recentlyAccessed: ChromeRecentlyAccessed; /** {@inheritdoc ChromeDocTitle} */ docTitle: ChromeDocTitle; + /** {@inheritdoc Logos} */ + readonly logos: Logos; /** * Sets the current app's title @@ -381,31 +372,6 @@ export interface ChromeStart { */ setAppTitle(appTitle: string): void; - /** - * Get an observable of the current brand information. - */ - getBrand$(): Observable; - - /** - * Set the brand configuration. - * - * @remarks - * Normally the `logo` property will be rendered as the - * CSS background for the home link in the chrome navigation, but when the page is - * rendered in a small window the `smallLogo` will be used and rendered at about - * 45px wide. - * - * @example - * ```js - * chrome.setBrand({ - * logo: 'url(/plugins/app/logo.png) center no-repeat' - * smallLogo: 'url(/plugins/app/logo-small.png) center no-repeat' - * }) - * ``` - * - */ - setBrand(brand: ChromeBrand): void; - /** * Get an observable of the current visibility state of the chrome. */ diff --git a/src/core/public/chrome/index.ts b/src/core/public/chrome/index.ts index 3b9ad62cfe1..4cd43362767 100644 --- a/src/core/public/chrome/index.ts +++ b/src/core/public/chrome/index.ts @@ -34,7 +34,6 @@ export { ChromeService, ChromeStart, InternalChromeStart, - ChromeBrand, ChromeHelpExtension, } from './chrome_service'; export { diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 9bdbec781c5..7b4e3ba472d 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -60,15 +60,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "serverBasePath": "/test", } } - branding={ - Object { - "darkMode": false, - "mark": Object { - "darkModeUrl": "/darkModeLogo", - "defaultUrl": "/defaultModeLogo", - }, - } - } + branding={Object {}} closeNav={[Function]} customNavLink$={ BehaviorSubject { @@ -131,6 +123,71 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` id="collapsibe-nav" isLocked={false} isNavOpen={true} + logos={ + Object { + "AnimatedMark": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_spinner_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_spinner_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_spinner_on_light.svg", + }, + "Application": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_dashboards_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_dashboards_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_dashboards_on_light.svg", + }, + "CenterMark": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_center_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_center_mark_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_center_mark_on_light.svg", + }, + "Mark": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_mark_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_mark_on_light.svg", + }, + "OpenSearch": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_on_light.svg", + }, + "colorScheme": "light", + } + } navLinks$={ BehaviorSubject { "_isScalar": false, @@ -757,9 +814,9 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiFlexItem eui-yScroll" > @@ -798,7 +855,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="/defaultModeLogo" + data-test-opensearch-logo="/test/ui/logos/opensearch_mark_on_light.svg" data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} @@ -809,7 +866,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` >
@@ -858,10 +915,10 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` > @@ -1954,15 +2011,7 @@ exports[`CollapsibleNav renders the default nav 1`] = ` "serverBasePath": "/test", } } - branding={ - Object { - "darkMode": false, - "mark": Object { - "darkModeUrl": "/darkModeLogo", - "defaultUrl": "/defaultModeLogo", - }, - } - } + branding={Object {}} closeNav={[Function]} customNavLink$={ BehaviorSubject { @@ -2017,6 +2066,71 @@ exports[`CollapsibleNav renders the default nav 1`] = ` id="collapsibe-nav" isLocked={false} isNavOpen={false} + logos={ + Object { + "AnimatedMark": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_spinner_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_spinner_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_spinner_on_light.svg", + }, + "Application": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_dashboards_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_dashboards_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_dashboards_on_light.svg", + }, + "CenterMark": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_center_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_center_mark_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_center_mark_on_light.svg", + }, + "Mark": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_mark_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_mark_on_light.svg", + }, + "OpenSearch": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_on_light.svg", + }, + "colorScheme": "light", + } + } navLinks$={ BehaviorSubject { "_isScalar": false, @@ -2199,15 +2313,7 @@ exports[`CollapsibleNav renders the default nav 2`] = ` "serverBasePath": "/test", } } - branding={ - Object { - "darkMode": false, - "mark": Object { - "darkModeUrl": "/darkModeLogo", - "defaultUrl": "/defaultModeLogo", - }, - } - } + branding={Object {}} closeNav={[Function]} customNavLink$={ BehaviorSubject { @@ -2263,6 +2369,71 @@ exports[`CollapsibleNav renders the default nav 2`] = ` isLocked={false} isNavOpen={false} isOpen={true} + logos={ + Object { + "AnimatedMark": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_spinner_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_spinner_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_spinner_on_light.svg", + }, + "Application": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_dashboards_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_dashboards_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_dashboards_on_light.svg", + }, + "CenterMark": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_center_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_center_mark_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_center_mark_on_light.svg", + }, + "Mark": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_mark_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_mark_on_light.svg", + }, + "OpenSearch": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_on_light.svg", + }, + "colorScheme": "light", + } + } navLinks$={ BehaviorSubject { "_isScalar": false, @@ -2445,15 +2616,7 @@ exports[`CollapsibleNav renders the default nav 3`] = ` "serverBasePath": "/test", } } - branding={ - Object { - "darkMode": false, - "mark": Object { - "darkModeUrl": "/darkModeLogo", - "defaultUrl": "/defaultModeLogo", - }, - } - } + branding={Object {}} closeNav={[Function]} customNavLink$={ BehaviorSubject { @@ -2509,6 +2672,71 @@ exports[`CollapsibleNav renders the default nav 3`] = ` isLocked={true} isNavOpen={false} isOpen={true} + logos={ + Object { + "AnimatedMark": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_spinner_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_spinner_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_spinner_on_light.svg", + }, + "Application": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_dashboards_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_dashboards_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_dashboards_on_light.svg", + }, + "CenterMark": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_center_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_center_mark_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_center_mark_on_light.svg", + }, + "Mark": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_mark_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_mark_on_light.svg", + }, + "OpenSearch": Object { + "dark": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/test/ui/logos/opensearch_on_light.svg", + }, + "type": "default", + "url": "/test/ui/logos/opensearch_on_light.svg", + }, + "colorScheme": "light", + } + } navLinks$={ BehaviorSubject { "_isScalar": false, @@ -2922,7 +3150,7 @@ exports[`CollapsibleNav renders the default nav 3`] = ` `; -exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 1`] = ` +exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1`] = ` @@ -3476,7 +3773,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 1`] = } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="/darkModeLogo" + data-test-opensearch-logo="/custom/branded/mark-darkmode.svg" data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} @@ -3487,7 +3784,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 1`] = >
@@ -3536,10 +3833,10 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 1`] = > @@ -3968,7 +4265,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 1`] = `; -exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] = ` +exports[`CollapsibleNav with custom branding renders the nav bar in default mode 1`] = ` @@ -4521,7 +4887,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] = } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="/defaultModeLogo" + data-test-opensearch-logo="/custom/branded/mark.svg" data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} @@ -4532,7 +4898,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] = >
@@ -4581,10 +4947,10 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] = > @@ -5013,7 +5379,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] = `; -exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] = ` +exports[`CollapsibleNav without custom branding renders the nav bar in dark mode 1`] = ` @@ -5564,7 +5994,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] = } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="undefined/opensearch_mark_default_mode.svg" + data-test-opensearch-logo="/test/ui/logos/opensearch_mark_on_dark.svg" data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} @@ -5575,7 +6005,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] = >
@@ -5624,10 +6054,10 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] = > @@ -6056,7 +6486,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] = `; -exports[`CollapsibleNav renders the nav bar with custom logo in default mode 1`] = ` +exports[`CollapsibleNav without custom branding renders the nav bar in default mode 1`] = ` - - -
-
- -
-
-
- - - -
-
-
-
-
-
- -
- -
-
- -
- - - - - - - -

- OpenSearch Dashboards -

-
-
- - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="/defaultModeLogo" - data-test-subj="collapsibleNavGroup-opensearchDashboards" - id="mockId" - initialIsOpen={true} - isLoading={false} - isLoadingMessage={false} - onToggle={[Function]} - paddingSize="none" - > -
-
- -
-
- -
-
-
- - - -
-
-
-
-
-
-
-
- - - - - - - -

- Observability -

-
-
- - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="logoObservability" - data-test-subj="collapsibleNavGroup-observability" - id="mockId" - initialIsOpen={true} - isLoading={false} - isLoadingMessage={false} - onToggle={[Function]} - paddingSize="none" - > -
-
- -
-
- -
-
-
- - - -
-
-
-
-
-
-
-
- - -
-
- -
    - - - - Dock navigation - - , - } - } - color="subdued" - data-test-subj="collapsible-nav-lock" - iconType="lockOpen" - label="Dock navigation" - onClick={[Function]} - size="xs" - > -
  • - -
  • -
    -
-
-
-
-
-
-
-
- - - -
-`; - -exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`] = ` - @@ -7653,7 +7097,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`] } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-opensearch-logo="undefined/opensearch_mark_default_mode.svg" + data-test-opensearch-logo="/test/ui/logos/opensearch_mark_on_light.svg" data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} @@ -7664,7 +7108,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`] >
@@ -7713,10 +7157,10 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`] > diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 9068e225c8b..b9da5ac37db 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -247,18 +247,7 @@ exports[`Header handles visibility and lock changes 1`] = ` "serverBasePath": "/test", } } - branding={ - Object { - "applicationTitle": "OpenSearch Dashboards", - "darkMode": false, - "logo": Object { - "defaultUrl": "/", - }, - "mark": Object { - "defaultUrl": "/", - }, - } - } + branding={Object {}} breadcrumbs$={ BehaviorSubject { "_isScalar": false, @@ -1437,6 +1426,71 @@ exports[`Header handles visibility and lock changes 1`] = ` "thrownError": null, } } + logos={ + Object { + "AnimatedMark": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_spinner_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_spinner_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_spinner_on_light.svg", + }, + "Application": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_dashboards_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_dashboards_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_dashboards_on_light.svg", + }, + "CenterMark": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_center_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_center_mark_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_center_mark_on_light.svg", + }, + "Mark": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_mark_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_mark_on_light.svg", + }, + "OpenSearch": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_on_light.svg", + }, + "colorScheme": "light", + } + } navControlsCenter$={ BehaviorSubject { "_isScalar": false, @@ -1888,27 +1942,8 @@ exports[`Header handles visibility and lock changes 1`] = ` "borders": "none", "items": Array [ , ], }, @@ -2335,27 +2434,8 @@ exports[`Header handles visibility and lock changes 1`] = ` className="euiHeaderSectionItem" > OpenSearch Dashboards logo @@ -2948,18 +3092,7 @@ exports[`Header handles visibility and lock changes 1`] = ` className="euiHeaderSectionItem euiHeaderSectionItem--borderRight" >
@@ -5625,18 +5877,6 @@ exports[`Header handles visibility and lock changes 1`] = ` "serverBasePath": "/test", } } - branding={ - Object { - "applicationTitle": "OpenSearch Dashboards", - "darkMode": false, - "logo": Object { - "defaultUrl": "/", - }, - "mark": Object { - "defaultUrl": "/", - }, - } - } closeNav={[Function]} customNavLink$={ BehaviorSubject { @@ -5696,6 +5936,71 @@ exports[`Header handles visibility and lock changes 1`] = ` id="mockId" isLocked={true} isNavOpen={false} + logos={ + Object { + "AnimatedMark": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_spinner_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_spinner_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_spinner_on_light.svg", + }, + "Application": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_dashboards_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_dashboards_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_dashboards_on_light.svg", + }, + "CenterMark": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_center_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_center_mark_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_center_mark_on_light.svg", + }, + "Mark": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_mark_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_mark_on_light.svg", + }, + "OpenSearch": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_on_light.svg", + }, + "colorScheme": "light", + } + } navLinks$={ BehaviorSubject { "_isScalar": false, @@ -6634,14 +6939,6 @@ exports[`Header renders condensed header 1`] = ` } branding={ Object { - "applicationTitle": "Foobar Dashboards", - "darkMode": false, - "logo": Object { - "defaultUrl": "/foo", - }, - "mark": Object { - "defaultUrl": "/foo", - }, "useExpandedHeader": false, } } @@ -7777,6 +8074,71 @@ exports[`Header renders condensed header 1`] = ` "thrownError": null, } } + logos={ + Object { + "AnimatedMark": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_spinner_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_spinner_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_spinner_on_light.svg", + }, + "Application": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_dashboards_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_dashboards_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_dashboards_on_light.svg", + }, + "CenterMark": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_center_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_center_mark_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_center_mark_on_light.svg", + }, + "Mark": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_mark_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_mark_on_light.svg", + }, + "OpenSearch": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_on_light.svg", + }, + "colorScheme": "light", + } + } navControlsCenter$={ BehaviorSubject { "_isScalar": false, @@ -8280,14 +8642,6 @@ exports[`Header renders condensed header 1`] = `
@@ -10872,19 +11348,6 @@ exports[`Header renders condensed header 1`] = ` "serverBasePath": "/test", } } - branding={ - Object { - "applicationTitle": "Foobar Dashboards", - "darkMode": false, - "logo": Object { - "defaultUrl": "/foo", - }, - "mark": Object { - "defaultUrl": "/foo", - }, - "useExpandedHeader": false, - } - } closeNav={[Function]} customNavLink$={ BehaviorSubject { @@ -10939,6 +11402,71 @@ exports[`Header renders condensed header 1`] = ` id="mockId" isLocked={false} isNavOpen={false} + logos={ + Object { + "AnimatedMark": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_spinner_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_spinner_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_spinner_on_light.svg", + }, + "Application": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_dashboards_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_dashboards_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_dashboards_on_light.svg", + }, + "CenterMark": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_center_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_center_mark_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_center_mark_on_light.svg", + }, + "Mark": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_mark_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_mark_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_mark_on_light.svg", + }, + "OpenSearch": Object { + "dark": Object { + "type": "default", + "url": "/ui/logos/opensearch_on_dark.svg", + }, + "light": Object { + "type": "default", + "url": "/ui/logos/opensearch_on_light.svg", + }, + "type": "default", + "url": "/ui/logos/opensearch_on_light.svg", + }, + "colorScheme": "light", + } + } navLinks$={ BehaviorSubject { "_isScalar": false, diff --git a/src/core/public/chrome/ui/header/__snapshots__/header_logo.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header_logo.test.tsx.snap index 7e44e456f32..ed8ee0c5f18 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header_logo.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header_logo.test.tsx.snap @@ -1,2176 +1,39 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Header logo when dark-themed uses custom dark-mode logo if branding dark-mode logo is provided 1`] = ` - - - custom title logo - - -`; - -exports[`Header logo when dark-themed uses dashboards' dark logo if branding containing a mark but not a logo is provided 1`] = ` - - - custom title logo - - -`; - -exports[`Header logo when dark-themed uses dashboards' dark logo if branding containing no logo is provided 1`] = ` - - - custom title logo - - -`; - -exports[`Header logo when dark-themed uses dashboards' dark logo if no branding is provided 1`] = ` - - - opensearch dashboards logo - - -`; - -exports[`Header logo when dark-themed uses default-themed custom logo if branding without dark-mode logo is provided 1`] = ` - - - custom title logo - - -`; - -exports[`Header logo when default-themed uses custom default-mode logo if branding logo is provided 1`] = ` - - - custom title logo - - -`; - -exports[`Header logo when default-themed uses dashboards logo if branding containing a mark but not a logo is provided 1`] = ` - - - custom title logo - - -`; - -exports[`Header logo when default-themed uses dashboards logo if branding containing no logo is provided 1`] = ` - - - custom title logo - - -`; - -exports[`Header logo when default-themed uses dashboards logo if no branding is provided 1`] = ` - - - opensearch dashboards logo - - +exports[`Header logo uses branded application title when provided 1`] = ` + + Page Title logo + +`; + +exports[`Header logo uses default application title when not branded 1`] = ` + + opensearch dashboards logo + `; diff --git a/src/core/public/chrome/ui/header/__snapshots__/home_icon.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/home_icon.test.tsx.snap index 63e83acd78e..c1a7c11b69f 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/home_icon.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/home_icon.test.tsx.snap @@ -1,1995 +1,45 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Home button icon in condensed dark mode uses custom mark dark mode URL 1`] = ` - - - - - -`; - -exports[`Home button icon in condensed dark mode uses custom mark default mode URL if no dark mode mark provided 1`] = ` - - - - - -`; - -exports[`Home button icon in condensed dark mode uses opensearch mark if custom logo provided without mark 1`] = ` - - - - - -`; - -exports[`Home button icon in condensed dark mode uses opensearch mark if no mark provided 1`] = ` - - - - - -`; - -exports[`Home button icon in condensed light mode uses custom mark default mode URL 1`] = ` - - - - - -`; - -exports[`Home button icon in condensed light mode uses opensearch mark if custom logo provided without mark 1`] = ` - - - - - -`; - -exports[`Home button icon in condensed light mode uses opensearch mark if no mark provided 1`] = ` - - - - - -`; - -exports[`Home button icon in dark mode uses custom mark dark mode URL 1`] = ` - - - - - -`; - -exports[`Home button icon in dark mode uses custom mark default mode URL if no dark mode mark provided 1`] = ` - - - - - -`; - -exports[`Home button icon in dark mode uses home icon if custom logo provided without mark 1`] = ` - - - - - -`; - -exports[`Home button icon in dark mode uses home icon if no mark provided 1`] = ` - - - - - -`; - -exports[`Home button icon in light mode uses custom mark default mode URL 1`] = ` - - - - - -`; - -exports[`Home button icon in light mode uses home icon if custom logo provided without mark 1`] = ` - - - - - -`; - -exports[`Home button icon in light mode uses home icon if no branding provided 1`] = ` - - - - - -`; - -exports[`Home button icon in light mode uses home icon if no mark provided 1`] = ` - - - - - +exports[`Home icon, custom branded, uses the custom logo when header is expanded 1`] = ` + +`; + +exports[`Home icon, custom branded, uses the custom logo when header is not expanded 1`] = ` + +`; + +exports[`Home icon, unbranded, uses the home icon when header is expanded 1`] = ` + +`; + +exports[`Home icon, unbranded, uses the mark logo when header is not expanded 1`] = ` + `; diff --git a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx index dc44fe5053f..955b7d7ca24 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx @@ -37,6 +37,7 @@ import { ChromeNavLink, DEFAULT_APP_CATEGORIES } from '../../..'; import { httpServiceMock } from '../../../http/http_service.mock'; import { ChromeRecentlyAccessedHistoryItem } from '../../recently_accessed'; import { CollapsibleNav } from './collapsible_nav'; +import { getLogos } from '../../../../common'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => 'mockId', @@ -64,10 +65,22 @@ function mockRecentNavLink({ label = 'recent' }: Partial Promise.resolve(), navigateToUrl: () => Promise.resolve(), customNavLink$: new BehaviorSubject(undefined), - branding: { - darkMode: false, - mark: { - defaultUrl: '/defaultModeLogo', - darkModeUrl: '/darkModeLogo', - }, - }, + branding, + logos: getLogos(branding, mockBasePath.serverBasePath), }; } @@ -212,77 +220,79 @@ describe('CollapsibleNav', () => { expectNavIsClosed(component); }); - it('renders the nav bar with custom logo in default mode', () => { - const navLinks = [ - mockLink({ category: opensearchDashboards }), - mockLink({ category: observability }), - ]; - const recentNavLinks = [mockRecentNavLink({})]; - const component = mount( - - ); - // check if nav bar renders default mode custom logo - expect(component).toMatchSnapshot(); + describe('with custom branding', () => { + it('renders the nav bar in default mode', () => { + const navLinks = [ + mockLink({ category: opensearchDashboards }), + mockLink({ category: observability }), + ]; + const recentNavLinks = [mockRecentNavLink({})]; + const component = mount( + + ); - // check if nav bar renders the original default mode opensearch mark - component.setProps({ - branding: { - darkMode: false, - mark: {}, - }, + expect(component).toMatchSnapshot(); }); - expect(component).toMatchSnapshot(); - }); - it('renders the nav bar with custom logo in dark mode', () => { - const navLinks = [ - mockLink({ category: opensearchDashboards }), - mockLink({ category: observability }), - ]; - const recentNavLinks = [mockRecentNavLink({})]; - const component = mount( - - ); - // check if nav bar renders dark mode custom logo - component.setProps({ - branding: { - darkMode: true, - mark: { - defaultUrl: '/defaultModeLogo', - darkModeUrl: '/darkModeLogo', - }, - }, + it('renders the nav bar in dark mode', () => { + const navLinks = [ + mockLink({ category: opensearchDashboards }), + mockLink({ category: observability }), + ]; + const recentNavLinks = [mockRecentNavLink({})]; + const component = mount( + + ); + + expect(component).toMatchSnapshot(); }); - expect(component).toMatchSnapshot(); + }); - // check if nav bar renders default mode custom logo - component.setProps({ - branding: { - darkMode: true, - mark: { - defaultUrl: '/defaultModeLogo', - }, - }, + describe('without custom branding', () => { + it('renders the nav bar in default mode', () => { + const navLinks = [ + mockLink({ category: opensearchDashboards }), + mockLink({ category: observability }), + ]; + const recentNavLinks = [mockRecentNavLink({})]; + const component = mount( + + ); + + expect(component).toMatchSnapshot(); }); - expect(component).toMatchSnapshot(); - // check if nav bar renders the original dark mode opensearch mark - component.setProps({ - branding: { - darkMode: false, - mark: {}, - }, + it('renders the nav bar in dark mode', () => { + const navLinks = [ + mockLink({ category: opensearchDashboards }), + mockLink({ category: observability }), + ]; + const recentNavLinks = [mockRecentNavLink({})]; + const component = mount( + + ); + + expect(component).toMatchSnapshot(); }); - expect(component).toMatchSnapshot(); }); }); diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index 51d43d96f7f..8b178200114 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -50,7 +50,7 @@ import { InternalApplicationStart } from '../../../application/types'; import { HttpStart } from '../../../http'; import { OnIsLockedUpdate } from './'; import { createEuiListItem, createRecentNavLink, isModifiedOrPrevented } from './nav_link'; -import { ChromeBranding } from '../../chrome_service'; +import type { Logos } from '../../../../common/types'; function getAllCategories(allCategorizedLinks: Record) { const allCategories = {} as Record; @@ -101,7 +101,7 @@ interface Props { navigateToApp: InternalApplicationStart['navigateToApp']; navigateToUrl: InternalApplicationStart['navigateToUrl']; customNavLink$: Rx.Observable; - branding: ChromeBranding; + logos: Logos; } export function CollapsibleNav({ @@ -115,7 +115,7 @@ export function CollapsibleNav({ closeNav, navigateToApp, navigateToUrl, - branding, + logos, ...observables }: Props) { const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); @@ -138,42 +138,6 @@ export function CollapsibleNav({ }); }; - const DEFAULT_OPENSEARCH_MARK = `${branding.assetFolderUrl}/opensearch_mark_default_mode.svg`; - const DARKMODE_OPENSEARCH_MARK = `${branding.assetFolderUrl}/opensearch_mark_dark_mode.svg`; - - const darkMode = branding.darkMode; - const markDefault = branding.mark?.defaultUrl; - const markDarkMode = branding.mark?.darkModeUrl; - - /** - * Use branding configurations to check which URL to use for rendering - * side menu opensearch logo in default mode - * - * @returns a valid custom URL or original default mode opensearch mark if no valid URL is provided - */ - const customSideMenuLogoDefaultMode = () => { - return markDefault ?? DEFAULT_OPENSEARCH_MARK; - }; - - /** - * Use branding configurations to check which URL to use for rendering - * side menu opensearch logo in dark mode - * - * @returns a valid custom URL or original dark mode opensearch mark if no valid URL is provided - */ - const customSideMenuLogoDarkMode = () => { - return markDarkMode ?? markDefault ?? DARKMODE_OPENSEARCH_MARK; - }; - - /** - * Render custom side menu logo for both default mode and dark mode - * - * @returns a valid logo URL - */ - const customSideMenuLogo = () => { - return darkMode ? customSideMenuLogoDarkMode() : customSideMenuLogoDefaultMode(); - }; - return ( { const category = categoryDictionary[categoryName]!; const opensearchLinkLogo = - category.id === 'opensearchDashboards' ? customSideMenuLogo() : category.euiIconType; + category.id === 'opensearchDashboards' ? logos.Mark.url : category.euiIconType; return ( {}, - branding: { - darkMode: false, - logo: { defaultUrl: '/' }, - mark: { defaultUrl: '/' }, - applicationTitle: 'OpenSearch Dashboards', - }, + branding: {}, survey: '/', + logos: chromeServiceMock.createStartContract().logos, }; } @@ -102,17 +98,17 @@ describe('Header', () => { const recentlyAccessed$ = new BehaviorSubject([ { link: '', label: 'dashboard', id: 'dashboard' }, ]); - const component = mountWithIntl( -
- ); + const props = { + ...mockProps(), + isVisible$, + breadcrumbs$, + navLinks$, + recentlyAccessed$, + isLocked$, + customNavLink$, + }; + + const component = mountWithIntl(
); expect(component.find('EuiHeader').exists()).toBeFalsy(); expect(component.find('EuiProgress').exists()).toBeTruthy(); @@ -120,7 +116,6 @@ describe('Header', () => { component.update(); expect(component.find('EuiHeader.primaryHeader').exists()).toBeTruthy(); expect(component.find('EuiHeader.expandedHeader').exists()).toBeTruthy(); - expect(component.find('HeaderLogo').exists()).toBeTruthy(); expect(component.find('HeaderNavControls')).toHaveLength(5); expect(component.find('[data-test-subj="toggleNavButton"]').exists()).toBeTruthy(); expect(component.find('HomeLoader').exists()).toBeTruthy(); @@ -131,6 +126,11 @@ describe('Header', () => { expect(component.find('EuiFlyout[aria-label="Primary"]').exists()).toBeFalsy(); + const headerLogo = component.find('HeaderLogo'); + expect(headerLogo.exists()).toBeTruthy(); + expect(headerLogo.prop('backgroundColorScheme')).toEqual('dark'); + expect(headerLogo.prop('logos')).toEqual(props.logos); + act(() => isLocked$.next(true)); component.update(); expect(component.find('EuiFlyout[aria-label="Primary"]').exists()).toBeTruthy(); @@ -139,16 +139,13 @@ describe('Header', () => { it('renders condensed header', () => { const branding = { - darkMode: false, - logo: { defaultUrl: '/foo' }, - mark: { defaultUrl: '/foo' }, - applicationTitle: 'Foobar Dashboards', useExpandedHeader: false, }; const props = { ...mockProps(), branding, }; + const component = mountWithIntl(
); expect(component.find('EuiHeader.primaryHeader').exists()).toBeTruthy(); diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 9496b76b998..acc7c686914 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -64,6 +64,7 @@ import { HomeLoader } from './home_loader'; import { HeaderNavControls } from './header_nav_controls'; import { HeaderActionMenu } from './header_action_menu'; import { HeaderLogo } from './header_logo'; +import type { Logos } from '../../../../common/types'; export interface HeaderProps { opensearchDashboardsVersion: string; @@ -90,6 +91,7 @@ export interface HeaderProps { loadingCount$: ReturnType; onIsLockedUpdate: OnIsLockedUpdate; branding: ChromeBranding; + logos: Logos; survey: string | undefined; } @@ -102,6 +104,7 @@ export function Header({ homeHref, branding, survey, + logos, ...observables }: HeaderProps) { const isVisible = useObservable(observables.isVisible$, false); @@ -117,7 +120,7 @@ export function Header({ const className = classnames('hide-for-sharing', 'headerGlobalNav'); const { useExpandedHeader = true } = branding; - const headerTheme: EuiHeaderProps['theme'] = 'dark'; + const expandedHeaderColorScheme: EuiHeaderProps['theme'] = 'dark'; return ( <> @@ -126,7 +129,7 @@ export function Header({ {useExpandedHeader && ( , ], borders: 'none', @@ -200,6 +203,7 @@ export function Header({ navLinks$={observables.navLinks$} navigateToApp={application.navigateToApp} branding={branding} + logos={logos} loadingCount$={observables.loadingCount$} /> @@ -259,7 +263,7 @@ export function Header({ } }} customNavLink$={observables.customNavLink$} - branding={branding} + logos={logos} />
diff --git a/src/core/public/chrome/ui/header/header_logo.test.tsx b/src/core/public/chrome/ui/header/header_logo.test.tsx index 6d31e71c1f0..c43073ec7f4 100644 --- a/src/core/public/chrome/ui/header/header_logo.test.tsx +++ b/src/core/public/chrome/ui/header/header_logo.test.tsx @@ -3,183 +3,102 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiHeaderProps } from '@elastic/eui'; import React from 'react'; import { BehaviorSubject } from 'rxjs'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { HeaderLogo, DEFAULT_DARK_LOGO, DEFAULT_LOGO } from './header_logo'; -import { BasePath } from '../../../http/base_path'; +import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { HeaderLogo } from './header_logo'; +import { getLogosMock } from '../../../../common/mocks'; -const basePath = new BasePath('/base'); +const mockTitle = 'Page Title'; const mockProps = () => ({ href: '/', - basePath, navLinks$: new BehaviorSubject([]), forceNavigation$: new BehaviorSubject(false), navigateToApp: jest.fn(), branding: {}, - theme: 'default' as EuiHeaderProps['theme'], + logos: getLogosMock.default, }); describe('Header logo', () => { - describe('when default-themed ', () => { - it('uses dashboards logo if no branding is provided', () => { - const branding = {}; - const props = { - ...mockProps(), - branding, - }; - const component = mountWithIntl(); - const img = component.find('.logoContainer img'); - expect(img.prop('src')).toEqual(`${basePath.serverBasePath}/${DEFAULT_LOGO}`); - expect(img.prop('alt')).toEqual(`opensearch dashboards logo`); - expect(component).toMatchSnapshot(); - }); + it("uses the light color-scheme's Application logo by default", () => { + const props = { + ...mockProps(), + }; + const component = shallowWithIntl(); + const img = component.find('.logoContainer img'); + expect(img.prop('src')).toEqual(props.logos.Application.light.url); + }); - it('uses dashboards logo if branding containing no logo is provided', () => { - const branding = { - logo: {}, - mark: {}, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; - const props = { - ...mockProps(), - branding, - }; - const component = mountWithIntl(); - const img = component.find('.logoContainer img'); - expect(img.prop('src')).toEqual(`${basePath.serverBasePath}/${DEFAULT_LOGO}`); - expect(img.prop('alt')).toEqual(`${branding.applicationTitle} logo`); - expect(component).toMatchSnapshot(); - }); + it("uses the light color-scheme's Application logo if the header's theme is not dark", () => { + const props = { + ...mockProps(), + backgroundColorScheme: 'light' as const, + }; + const component = shallowWithIntl(); + const img = component.find('.logoContainer img'); + expect(img.prop('src')).toEqual(props.logos.Application.light.url); + }); - it('uses dashboards logo if branding containing a mark but not a logo is provided', () => { - const branding = { - logo: {}, - mark: { defaultUrl: '/defaultModeMark' }, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; - const props = { - ...mockProps(), - branding, - }; - const component = mountWithIntl(); - const img = component.find('.logoContainer img'); - expect(img.prop('src')).toEqual(`${basePath.serverBasePath}/${DEFAULT_LOGO}`); - expect(img.prop('alt')).toEqual(`${branding.applicationTitle} logo`); - expect(component).toMatchSnapshot(); - }); + it("uses the normal color-scheme's Application logo if the header's theme is not dark", () => { + const props = { + ...mockProps(), + backgroundColorScheme: 'normal' as const, + }; + const component = shallowWithIntl(); + const img = component.find('.logoContainer img'); + expect(img.prop('src')).toEqual(props.logos.Application.light.url); + }); - it('uses custom default-mode logo if branding logo is provided', () => { - const branding = { - logo: { defaultUrl: '/defaultModeLogo' }, - mark: {}, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; - const props = { - ...mockProps(), - branding, - }; - const component = mountWithIntl(); - const img = component.find('.logoContainer img'); - expect(img.prop('src')).toEqual(branding.logo.defaultUrl); - expect(img.prop('alt')).toEqual(`${branding.applicationTitle} logo`); - expect(component).toMatchSnapshot(); - }); + it("uses the dark color-scheme's Application logo if the header's theme is dark", () => { + const props = { + ...mockProps(), + backgroundColorScheme: 'dark' as const, + }; + const component = shallowWithIntl(); + const img = component.find('.logoContainer img'); + expect(img.prop('src')).toEqual(props.logos.Application.dark.url); }); - describe('when dark-themed', () => { - it("uses dashboards' dark logo if no branding is provided", () => { - const branding = {}; - const props = { - ...mockProps(), - branding, - theme: 'dark' as EuiHeaderProps['theme'], - }; - const component = mountWithIntl(); - const img = component.find('.logoContainer img'); - expect(img.prop('src')).toEqual(`${basePath.serverBasePath}/${DEFAULT_DARK_LOGO}`); - expect(img.prop('alt')).toEqual(`opensearch dashboards logo`); - expect(component).toMatchSnapshot(); - }); + it('uses default application title when not branded', () => { + const props = { + ...mockProps(), + }; + const component = shallowWithIntl(); + const img = component.find('.logoContainer img'); + expect(img.prop('data-test-subj')).toEqual(`defaultLogo`); + expect(img.prop('alt')).toEqual(`opensearch dashboards logo`); + expect(component).toMatchSnapshot(); + }); - it("uses dashboards' dark logo if branding containing no logo is provided", () => { - const branding = { - logo: {}, - mark: {}, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; - const props = { - ...mockProps(), - branding, - theme: 'dark' as EuiHeaderProps['theme'], - }; - const component = mountWithIntl(); - const img = component.find('.logoContainer img'); - expect(img.prop('src')).toEqual(`${basePath.serverBasePath}/${DEFAULT_DARK_LOGO}`); - expect(img.prop('alt')).toEqual(`${branding.applicationTitle} logo`); - expect(component).toMatchSnapshot(); - }); + it('uses branded application title when provided', () => { + const props = { + ...mockProps(), + logos: getLogosMock.branded, + branding: { + applicationTitle: mockTitle, + }, + }; + const component = shallowWithIntl(); + const img = component.find('.logoContainer img'); + expect(img.prop('data-test-subj')).toEqual(`customLogo`); + expect(img.prop('alt')).toEqual(`${mockTitle} logo`); + expect(component).toMatchSnapshot(); + }); - it("uses dashboards' dark logo if branding containing a mark but not a logo is provided", () => { - const branding = { - logo: {}, - mark: { defaultUrl: '/defaultModeMark' }, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; + describe('onClick', () => { + it('uses default application title when not branded', () => { const props = { ...mockProps(), - branding, - theme: 'dark' as EuiHeaderProps['theme'], }; const component = mountWithIntl(); - const img = component.find('.logoContainer img'); - expect(img.prop('src')).toEqual(`${basePath.serverBasePath}/${DEFAULT_DARK_LOGO}`); - expect(img.prop('alt')).toEqual(`${branding.applicationTitle} logo`); - expect(component).toMatchSnapshot(); - }); + component.find('.logoContainer img').simulate('click'); - it('uses default-themed custom logo if branding without dark-mode logo is provided', () => { - const branding = { - logo: { defaultUrl: '/defaultModeLogo' }, - mark: {}, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; - const props = { - ...mockProps(), - branding, - theme: 'dark' as EuiHeaderProps['theme'], - }; - const component = mountWithIntl(); - const img = component.find('.logoContainer img'); - expect(img.prop('src')).toEqual(branding.logo.defaultUrl); - expect(img.prop('alt')).toEqual(`${branding.applicationTitle} logo`); - expect(component).toMatchSnapshot(); + expect(props.navigateToApp).toHaveBeenCalledTimes(1); + expect(props.navigateToApp).toHaveBeenCalledWith('home'); }); - it('uses custom dark-mode logo if branding dark-mode logo is provided', () => { - const branding = { - logo: { defaultUrl: '/defaultModeLogo', darkModeUrl: '/darkModeLogo' }, - mark: {}, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; - const props = { - ...mockProps(), - branding, - theme: 'dark' as EuiHeaderProps['theme'], - }; - const component = mountWithIntl(); - const img = component.find('.logoContainer img'); - expect(img.prop('src')).toEqual(branding.logo.darkModeUrl); - expect(img.prop('alt')).toEqual(`${branding.applicationTitle} logo`); - expect(component).toMatchSnapshot(); - }); + // ToDo: Add tests for onClick + // https://github.com/opensearch-project/OpenSearch-Dashboards/issues/4692 + it.todo('performs all the complications'); }); }); diff --git a/src/core/public/chrome/ui/header/header_logo.tsx b/src/core/public/chrome/ui/header/header_logo.tsx index 00de679f518..b5fddee0e59 100644 --- a/src/core/public/chrome/ui/header/header_logo.tsx +++ b/src/core/public/chrome/ui/header/header_logo.tsx @@ -29,29 +29,13 @@ */ import './header_logo.scss'; -import { EuiHeaderProps } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import React from 'react'; import useObservable from 'react-use/lib/useObservable'; import { Observable } from 'rxjs'; import { ChromeNavLink } from '../..'; import { ChromeBranding } from '../../chrome_service'; -import { HttpStart } from '../../../http'; - -function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { - let current = element; - while (current) { - if (current.tagName === 'A') { - return current as HTMLAnchorElement; - } - - if (!current.parentElement || current.parentElement === document.body) { - return undefined; - } - - current = current.parentElement; - } -} +import type { Logos } from '../../../../common/types'; function onClick( event: React.MouseEvent, @@ -59,7 +43,7 @@ function onClick( navLinks: ChromeNavLink[], navigateToApp: (appId: string) => void ) { - const anchor = findClosestAnchor((event as any).nativeEvent.target); + const anchor = (event.nativeEvent.target as HTMLAnchorElement)?.closest('a'); if (!anchor) { return; } @@ -98,44 +82,36 @@ function onClick( } } -export const DEFAULT_LOGO = 'ui/logos/opensearch_dashboards.svg'; -export const DEFAULT_DARK_LOGO = 'ui/logos/opensearch_dashboards_darkmode.svg'; - interface Props { href: string; navLinks$: Observable; forceNavigation$: Observable; navigateToApp: (appId: string) => void; branding: ChromeBranding; - basePath: HttpStart['basePath']; - // What background is the logo appearing on; this is independent of theme:darkMode - theme?: EuiHeaderProps['theme']; + logos: Logos; + /* indicates the background color-scheme this element will appear over + * `'normal'` and `'light'` are synonyms of being `undefined`, to mean not `'dark'` + */ + backgroundColorScheme?: 'normal' | 'light' | 'dark'; } export function HeaderLogo({ href, navigateToApp, branding, - basePath, - theme = 'default', + logos, + backgroundColorScheme, ...observables }: Props) { const forceNavigation = useObservable(observables.forceNavigation$, false); const navLinks = useObservable(observables.navLinks$, []); - const { - logo: { defaultUrl: customLogoUrl, darkModeUrl: customDarkLogoUrl } = {}, - applicationTitle = 'opensearch dashboards', - } = branding; + const { applicationTitle = 'opensearch dashboards' } = branding; - // Attempt to find a suitable custom branded logo before falling back on OSD's - let logoSrc = theme === 'dark' && customDarkLogoUrl ? customDarkLogoUrl : customLogoUrl; - let testSubj = 'customLogo'; - - // If no custom branded logo was set, use OSD's - if (!logoSrc) { - logoSrc = `${basePath.serverBasePath}/${theme === 'dark' ? DEFAULT_DARK_LOGO : DEFAULT_LOGO}`; - testSubj = 'defaultLogo'; - } + const { + [backgroundColorScheme === 'dark' ? 'dark' : 'light']: { url: logoURL }, + type: logoType, + } = logos.Application; + const testSubj = `${logoType}Logo`; const alt = `${applicationTitle} logo`; @@ -151,8 +127,8 @@ export function HeaderLogo({ > {alt} { - describe('in condensed light mode ', () => { - it('uses opensearch mark if no mark provided', () => { - const branding = { - darkMode: false, - logo: {}, - mark: {}, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - useExpandedHeader: false, - }; - const component = mountWithIntl(); - const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual(`${branding.assetFolderUrl}/${DEFAULT_MARK}`); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); - expect(component).toMatchSnapshot(); - }); +import { shallow } from 'enzyme'; +import { HomeIcon } from './home_icon'; +import { getLogosMock } from '../../../../common/mocks'; - it('uses opensearch mark if custom logo provided without mark', () => { - const branding = { - darkMode: false, - logo: { defaultUrl: '/defaultModeLogo' }, - mark: {}, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - useExpandedHeader: false, - }; - const component = mountWithIntl(); - const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual(`${branding.assetFolderUrl}/${DEFAULT_MARK}`); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); - expect(component).toMatchSnapshot(); - }); +const mockTitle = 'Page Title'; - it('uses custom mark default mode URL', () => { - const branding = { - darkMode: false, - logo: {}, - mark: { defaultUrl: '/defaultModeMark' }, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - useExpandedHeader: false, - }; - const component = mountWithIntl(); - const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual(branding.mark.defaultUrl); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); - expect(component).toMatchSnapshot(); +describe('Home icon,', () => { + describe('unbranded,', () => { + const mockProps = () => ({ + branding: {}, + logos: getLogosMock.default, }); - }); - describe('in condensed dark mode ', () => { - it('uses opensearch mark if no mark provided', () => { - const branding = { - darkMode: true, - logo: {}, - mark: {}, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - useExpandedHeader: false, - }; - const component = mountWithIntl(); + it('uses the home icon by default', () => { + const props = mockProps(); + const component = shallow(); const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual(`${branding.assetFolderUrl}/${DEFAULT_DARK_MARK}`); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); - expect(component).toMatchSnapshot(); + expect(icon.prop('data-test-subj')).toEqual('homeIcon'); + expect(icon.prop('type')).toEqual('home'); + expect(icon.prop('size')).toEqual('m'); + expect(icon.prop('title')).toEqual('opensearch dashboards home'); }); - it('uses opensearch mark if custom logo provided without mark', () => { - const branding = { - darkMode: true, - logo: { defaultUrl: '/defaultModeLogo' }, - mark: {}, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - useExpandedHeader: false, + it('uses the home icon when header is expanded', () => { + const props = { + ...mockProps(), + branding: { + useExpandedHeader: true, + }, }; - const component = mountWithIntl(); + const component = shallow(); const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual(`${branding.assetFolderUrl}/${DEFAULT_DARK_MARK}`); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); - expect(component).toMatchSnapshot(); - }); + expect(icon.prop('data-test-subj')).toEqual('homeIcon'); + expect(icon.prop('type')).toEqual('home'); + expect(icon.prop('size')).toEqual('m'); + expect(icon.prop('title')).toEqual('opensearch dashboards home'); - it('uses custom mark default mode URL if no dark mode mark provided', () => { - const branding = { - darkMode: true, - logo: {}, - mark: { defaultUrl: '/defaultModeMark' }, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - useExpandedHeader: false, - }; - const component = mountWithIntl(); - const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual(branding.mark.defaultUrl); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); expect(component).toMatchSnapshot(); }); - it('uses custom mark dark mode URL', () => { - const branding = { - darkMode: true, - logo: {}, - mark: { defaultUrl: '/defaultModeMark', darkModeUrl: '/darkModeMark' }, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - useExpandedHeader: false, + it('uses the mark logo when header is not expanded', () => { + const props = { + ...mockProps(), + branding: { + useExpandedHeader: false, + }, }; - const component = mountWithIntl(); + const component = shallow(); const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual(branding.mark.darkModeUrl); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); + expect(icon.prop('data-test-subj')).toEqual('defaultMark'); + expect(icon.prop('type')).toEqual(props.logos.Mark.url); + expect(icon.prop('size')).toEqual('l'); + expect(icon.prop('title')).toEqual('opensearch dashboards home'); + expect(component).toMatchSnapshot(); }); }); - describe('in light mode ', () => { - it('uses home icon if no branding provided', () => { - const branding = {}; - const component = mountWithIntl(); - const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual('home'); - expect(icon.prop('size')).toEqual(`m`); - expect(icon.prop('title')).toEqual(`opensearch dashboards home`); - expect(component).toMatchSnapshot(); + describe('custom branded,', () => { + const mockProps = () => ({ + branding: { + applicationTitle: mockTitle, + }, + logos: getLogosMock.branded, }); - it('uses home icon if no mark provided', () => { - const branding = { - darkMode: false, - logo: {}, - mark: {}, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; - const component = mountWithIntl(); + it('uses the custom logo by default', () => { + const props = mockProps(); + const component = shallow(); const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual('home'); - expect(icon.prop('size')).toEqual(`m`); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); - expect(component).toMatchSnapshot(); + expect(icon.prop('data-test-subj')).toEqual('customMark'); + expect(icon.prop('type')).toEqual(props.logos.Mark.url); + expect(icon.prop('size')).toEqual('l'); + expect(icon.prop('title')).toEqual(`${mockTitle} home`); }); - it('uses home icon if custom logo provided without mark', () => { - const branding = { - darkMode: false, - logo: { defaultUrl: '/defaultModeLogo' }, - mark: {}, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; - const component = mountWithIntl(); - const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual('home'); - expect(icon.prop('size')).toEqual(`m`); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); - expect(component).toMatchSnapshot(); - }); + it('uses the custom logo when header is expanded', () => { + const props = mockProps(); + // @ts-expect-error + props.branding.useExpandedHeader = true; - it('uses custom mark default mode URL', () => { - const branding = { - darkMode: false, - logo: {}, - mark: { defaultUrl: '/defaultModeMark' }, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; - const component = mountWithIntl(); + const component = shallow(); const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual(branding.mark.defaultUrl); - expect(icon.prop('size')).toEqual(`l`); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); - expect(component).toMatchSnapshot(); - }); - }); + expect(icon.prop('data-test-subj')).toEqual('customMark'); + expect(icon.prop('type')).toEqual(props.logos.Mark.url); + expect(icon.prop('size')).toEqual('l'); + expect(icon.prop('title')).toEqual(`${mockTitle} home`); - describe('in dark mode ', () => { - it('uses home icon if no mark provided', () => { - const branding = { - darkMode: true, - logo: {}, - mark: {}, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; - const component = mountWithIntl(); - const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual('home'); - expect(icon.prop('size')).toEqual(`m`); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); expect(component).toMatchSnapshot(); }); - it('uses home icon if custom logo provided without mark', () => { - const branding = { - darkMode: true, - logo: { defaultUrl: '/defaultModeLogo' }, - mark: {}, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; - const component = mountWithIntl(); - const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual('home'); - expect(icon.prop('size')).toEqual(`m`); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); - expect(component).toMatchSnapshot(); - }); + it('uses the custom logo when header is not expanded', () => { + const props = mockProps(); + // @ts-expect-error + props.branding.useExpandedHeader = false; - it('uses custom mark default mode URL if no dark mode mark provided', () => { - const branding = { - darkMode: true, - logo: {}, - mark: { defaultUrl: '/defaultModeMark' }, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; - const component = mountWithIntl(); + const component = shallow(); const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual(branding.mark.defaultUrl); - expect(icon.prop('size')).toEqual(`l`); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); - expect(component).toMatchSnapshot(); - }); + expect(icon.prop('data-test-subj')).toEqual('customMark'); + expect(icon.prop('type')).toEqual(props.logos.Mark.url); + expect(icon.prop('size')).toEqual('l'); + expect(icon.prop('title')).toEqual(`${mockTitle} home`); - it('uses custom mark dark mode URL', () => { - const branding = { - darkMode: true, - logo: {}, - mark: { defaultUrl: '/defaultModeMark', darkModeUrl: '/darkModeMark' }, - applicationTitle: 'custom title', - assetFolderUrl: 'base/ui/default_branding', - }; - const component = mountWithIntl(); - const icon = component.find('EuiIcon'); - expect(icon.prop('type')).toEqual(branding.mark.darkModeUrl); - expect(icon.prop('size')).toEqual(`l`); - expect(icon.prop('title')).toEqual(`${branding.applicationTitle} home`); expect(component).toMatchSnapshot(); }); }); diff --git a/src/core/public/chrome/ui/header/home_icon.tsx b/src/core/public/chrome/ui/header/home_icon.tsx index 9260fc19cca..2c0beb5a26c 100644 --- a/src/core/public/chrome/ui/header/home_icon.tsx +++ b/src/core/public/chrome/ui/header/home_icon.tsx @@ -4,51 +4,46 @@ */ import React from 'react'; -import { EuiIcon } from '@elastic/eui'; +import { EuiIcon, IconSize } from '@elastic/eui'; import { ChromeBranding } from '../../chrome_service'; +import type { Logos } from '../../../../common/types'; -export const DEFAULT_MARK = 'opensearch_mark_default_mode.svg'; -export const DEFAULT_DARK_MARK = 'opensearch_mark_dark_mode.svg'; +interface Props { + branding: ChromeBranding; + logos: Logos; +} /** * Use branding configurations to render the header mark on the nav bar. - * - * @param {ChromeBranding} - branding object consist of mark, darkmode selection, asset path and title - * @returns Mark component which is going to be rendered on the main page header bar. */ -export const HomeIcon = ({ - darkMode, - assetFolderUrl = '', - mark, - applicationTitle = 'opensearch dashboards', - useExpandedHeader = true, -}: ChromeBranding) => { - const { defaultUrl: markUrl, darkModeUrl: darkMarkUrl } = mark ?? {}; - - const customMark = darkMode ? darkMarkUrl ?? markUrl : markUrl; - const defaultMark = darkMode ? DEFAULT_DARK_MARK : DEFAULT_MARK; - - const getIconProps = () => { - const iconType = customMark - ? customMark - : useExpandedHeader - ? 'home' - : `${assetFolderUrl}/${defaultMark}`; - const testSubj = customMark ? 'customMark' : useExpandedHeader ? 'homeIcon' : 'defaultMark'; - const title = `${applicationTitle} home`; - // marks look better at the large size, but the home icon should be medium to fit in with other icons - const size = iconType === 'home' ? ('m' as const) : ('l' as const); - - return { - 'data-test-subj': testSubj, - 'data-test-image-url': iconType, - type: iconType, - title, - size, - }; - }; - - const props = getIconProps(); - - return ; +export const HomeIcon = ({ branding, logos }: Props) => { + const { applicationTitle = 'opensearch dashboards', useExpandedHeader = true } = branding; + + const { url: markURL, type: markType } = logos.Mark; + + let markIcon = markURL; + let testSubj = `${markType}Mark`; + // Marks look better at the large size + let markIconSize: IconSize = 'l'; + + // If no custom branded mark was set, use `home` icon only for expanded headers + if (markType !== 'custom' && useExpandedHeader) { + markIcon = 'home'; + testSubj = 'homeIcon'; + // Home icon should be medium to fit in with other icons + markIconSize = 'm'; + } + + const alt = `${applicationTitle} home`; + + return ( + + ); }; diff --git a/src/core/public/chrome/ui/header/home_loader.tsx b/src/core/public/chrome/ui/header/home_loader.tsx index 0083df43ff8..9463b41060f 100644 --- a/src/core/public/chrome/ui/header/home_loader.tsx +++ b/src/core/public/chrome/ui/header/home_loader.tsx @@ -39,6 +39,7 @@ import { ChromeNavLink } from '../..'; import { ChromeBranding } from '../../chrome_service'; import { LoadingIndicator } from '../loading_indicator'; import { HomeIcon } from './home_icon'; +import type { Logos } from '../../../../common/types'; function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { let current = element; @@ -107,9 +108,10 @@ interface Props { loadingCount$: Observable; navigateToApp: (appId: string) => void; branding: ChromeBranding; + logos: Logos; } -export function HomeLoader({ href, navigateToApp, branding, ...observables }: Props) { +export function HomeLoader({ href, navigateToApp, branding, logos, ...observables }: Props) { const forceNavigation = useObservable(observables.forceNavigation$, false); const navLinks = useObservable(observables.navLinks$, []); const loadingCount = useObservable(observables.loadingCount$, 0); @@ -130,7 +132,7 @@ export function HomeLoader({ href, navigateToApp, branding, ...observables }: Pr > {!(loadingCount > 0) && (
- +
)}
diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 9a38771f513..03ef6b6392f 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -50,7 +50,6 @@ import './index.scss'; import { ChromeBadge, - ChromeBrand, ChromeBreadcrumb, ChromeHelpExtension, ChromeHelpExtensionMenuLink, @@ -89,6 +88,7 @@ import { } from './context'; import { Branding } from '../types'; +export type { Logos } from '../common'; export { PackageInfo, EnvironmentMode } from '../server/types'; /** @interal */ export { CoreContext, CoreSystem } from './core_system'; @@ -193,7 +193,7 @@ export { ErrorToastOptions, } from './notifications'; -export { MountPoint, UnmountCallback, PublicUiSettingsParams } from './types'; +export { MountPoint, UnmountCallback, PublicUiSettingsParams, ChromeBrand } from './types'; export { URL_MAX_LENGTH } from './core_app'; @@ -298,7 +298,6 @@ export interface CoreStart { export { Capabilities, ChromeBadge, - ChromeBrand, ChromeBreadcrumb, ChromeHelpExtension, ChromeHelpExtensionMenuLink, diff --git a/src/core/public/types.ts b/src/core/public/types.ts index 0c1d6b70c72..ebbdd53f3cf 100644 --- a/src/core/public/types.ts +++ b/src/core/public/types.ts @@ -37,6 +37,8 @@ export { StringValidationRegex, } from '../../core/types'; +export type { Logos } from '../common/types'; + /** * A function that should mount DOM content inside the provided container element * and return a handler to unmount it. @@ -55,3 +57,12 @@ export type MountPoint = (element: T) => Un * @public */ export type UnmountCallback = () => void; + +/** + * @deprecated: Use Branding instead + * @public + */ +export interface ChromeBrand { + logo?: string; + smallLogo?: string; +} diff --git a/src/core/server/core_app/assets/favicons/android-chrome-192x192.png b/src/core/server/core_app/assets/favicons/android-chrome-192x192.png index 3fb291f8830..a256e84b28b 100644 Binary files a/src/core/server/core_app/assets/favicons/android-chrome-192x192.png and b/src/core/server/core_app/assets/favicons/android-chrome-192x192.png differ diff --git a/src/core/server/core_app/assets/favicons/android-chrome-512x512.png b/src/core/server/core_app/assets/favicons/android-chrome-512x512.png index 47bc1a2bd52..404fbfad398 100644 Binary files a/src/core/server/core_app/assets/favicons/android-chrome-512x512.png and b/src/core/server/core_app/assets/favicons/android-chrome-512x512.png differ diff --git a/src/core/server/core_app/assets/favicons/apple-touch-icon.png b/src/core/server/core_app/assets/favicons/apple-touch-icon.png index 3fdcfd3f09e..00b8c190b08 100644 Binary files a/src/core/server/core_app/assets/favicons/apple-touch-icon.png and b/src/core/server/core_app/assets/favicons/apple-touch-icon.png differ diff --git a/src/core/server/core_app/assets/favicons/favicon-32x32.png b/src/core/server/core_app/assets/favicons/favicon-32x32.png index ce2d8fe187f..3046f5df76f 100644 Binary files a/src/core/server/core_app/assets/favicons/favicon-32x32.png and b/src/core/server/core_app/assets/favicons/favicon-32x32.png differ diff --git a/src/core/server/core_app/assets/favicons/manifest.json b/src/core/server/core_app/assets/favicons/manifest.json index 9153b046420..1318668560a 100644 --- a/src/core/server/core_app/assets/favicons/manifest.json +++ b/src/core/server/core_app/assets/favicons/manifest.json @@ -3,12 +3,12 @@ "short_name": "", "icons": [ { - "src": "/android-chrome-192x192.png", + "src": "android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { - "src": "/android-chrome-512x512.png", + "src": "android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } diff --git a/src/core/server/core_app/assets/favicons/mstile-144x144.png b/src/core/server/core_app/assets/favicons/mstile-144x144.png index 577ecfed1c0..5a379489e08 100644 Binary files a/src/core/server/core_app/assets/favicons/mstile-144x144.png and b/src/core/server/core_app/assets/favicons/mstile-144x144.png differ diff --git a/src/core/server/core_app/assets/favicons/mstile-150x150.png b/src/core/server/core_app/assets/favicons/mstile-150x150.png index 938310ed553..b064558a1ed 100644 Binary files a/src/core/server/core_app/assets/favicons/mstile-150x150.png and b/src/core/server/core_app/assets/favicons/mstile-150x150.png differ diff --git a/src/core/server/core_app/assets/favicons/mstile-310x150.png b/src/core/server/core_app/assets/favicons/mstile-310x150.png index 6be5bf98693..d938be9ee09 100644 Binary files a/src/core/server/core_app/assets/favicons/mstile-310x150.png and b/src/core/server/core_app/assets/favicons/mstile-310x150.png differ diff --git a/src/core/server/core_app/assets/favicons/mstile-310x310.png b/src/core/server/core_app/assets/favicons/mstile-310x310.png index b45858ef4ba..0790cc1a7f5 100644 Binary files a/src/core/server/core_app/assets/favicons/mstile-310x310.png and b/src/core/server/core_app/assets/favicons/mstile-310x310.png differ diff --git a/src/core/server/core_app/assets/favicons/mstile-70x70.png b/src/core/server/core_app/assets/favicons/mstile-70x70.png index 399a42ff6ae..5155640f097 100644 Binary files a/src/core/server/core_app/assets/favicons/mstile-70x70.png and b/src/core/server/core_app/assets/favicons/mstile-70x70.png differ diff --git a/src/core/server/core_app/assets/favicons/safari-pinned-tab.svg b/src/core/server/core_app/assets/favicons/safari-pinned-tab.svg index 6ea5f91851f..5f8b7cfd857 100644 --- a/src/core/server/core_app/assets/favicons/safari-pinned-tab.svg +++ b/src/core/server/core_app/assets/favicons/safari-pinned-tab.svg @@ -1,45 +1 @@ - - - - -Created by potrace 1.14, written by Peter Selinger 2001-2017 - - - - - - - + diff --git a/src/core/server/core_app/assets/logos/opensearch.svg b/src/core/server/core_app/assets/logos/opensearch.svg new file mode 100644 index 00000000000..9795f6c332a --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/core/server/core_app/assets/logos/opensearch_center_mark.svg b/src/core/server/core_app/assets/logos/opensearch_center_mark.svg new file mode 100644 index 00000000000..42a29b55050 --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch_center_mark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/core/server/core_app/assets/logos/opensearch_center_mark_on_dark.svg b/src/core/server/core_app/assets/logos/opensearch_center_mark_on_dark.svg new file mode 100644 index 00000000000..43091f7d039 --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch_center_mark_on_dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/core/server/core_app/assets/logos/opensearch_center_mark_on_light.svg b/src/core/server/core_app/assets/logos/opensearch_center_mark_on_light.svg new file mode 100644 index 00000000000..5a0d83c568b --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch_center_mark_on_light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/core/server/core_app/assets/logos/opensearch_dashboards.svg b/src/core/server/core_app/assets/logos/opensearch_dashboards.svg index bb85dcdd10e..35f56544a26 100644 --- a/src/core/server/core_app/assets/logos/opensearch_dashboards.svg +++ b/src/core/server/core_app/assets/logos/opensearch_dashboards.svg @@ -1,5 +1,5 @@ - + diff --git a/src/core/server/core_app/assets/logos/opensearch_dashboards_on_dark.svg b/src/core/server/core_app/assets/logos/opensearch_dashboards_on_dark.svg new file mode 100644 index 00000000000..ba023b5b9a3 --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch_dashboards_on_dark.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/core/server/core_app/assets/logos/opensearch_dashboards_on_light.svg b/src/core/server/core_app/assets/logos/opensearch_dashboards_on_light.svg new file mode 100644 index 00000000000..bb85dcdd10e --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch_dashboards_on_light.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/core/server/core_app/assets/logos/opensearch_mark.svg b/src/core/server/core_app/assets/logos/opensearch_mark.svg new file mode 100644 index 00000000000..b1986db8791 --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch_mark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/core/server/core_app/assets/logos/opensearch_mark_on_dark.svg b/src/core/server/core_app/assets/logos/opensearch_mark_on_dark.svg new file mode 100644 index 00000000000..d202064dea3 --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch_mark_on_dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/core/server/core_app/assets/logos/opensearch_mark_on_light.svg b/src/core/server/core_app/assets/logos/opensearch_mark_on_light.svg new file mode 100644 index 00000000000..2c6bc1ee17e --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch_mark_on_light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/core/server/core_app/assets/logos/opensearch_on_dark.svg b/src/core/server/core_app/assets/logos/opensearch_on_dark.svg new file mode 100644 index 00000000000..1830ff7f668 --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch_on_dark.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/core/server/core_app/assets/logos/opensearch_on_light.svg b/src/core/server/core_app/assets/logos/opensearch_on_light.svg new file mode 100644 index 00000000000..f716c67e58f --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch_on_light.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/core/server/core_app/assets/logos/opensearch_spinner.svg b/src/core/server/core_app/assets/logos/opensearch_spinner.svg new file mode 100644 index 00000000000..98c6f2af618 --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch_spinner.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/core/server/core_app/assets/logos/opensearch_spinner_on_dark.svg b/src/core/server/core_app/assets/logos/opensearch_spinner_on_dark.svg new file mode 100644 index 00000000000..8d2b1659512 --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch_spinner_on_dark.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/core/server/core_app/assets/logos/opensearch_spinner_on_light.svg b/src/core/server/core_app/assets/logos/opensearch_spinner_on_light.svg new file mode 100644 index 00000000000..41ab3c960b9 --- /dev/null +++ b/src/core/server/core_app/assets/logos/opensearch_spinner_on_light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index c7c03c1eb72..d0a62555d4b 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -198,7 +198,7 @@ export class RenderingService { /** * Assign values for branding related configurations based on branding validation - * by calling checkBrandingValid(). For dark mode URLs, add additonal validation + * by calling checkBrandingValid(). For dark mode URLs, add additional validation * to see if there is a valid default mode URL exist first. If URL is valid, pass in * the actual URL; if not, pass in undefined. * diff --git a/src/core/server/rendering/views/__snapshots__/template.test.tsx.snap b/src/core/server/rendering/views/__snapshots__/template.test.tsx.snap index 87a00f601a4..36d073992ec 100644 --- a/src/core/server/rendering/views/__snapshots__/template.test.tsx.snap +++ b/src/core/server/rendering/views/__snapshots__/template.test.tsx.snap @@ -13,7 +13,9 @@ Array [ content="width=device-width" name="viewport" />, - , + + OpenSearch Dashboards + , , , @@ -80,9 +82,12 @@ Array [ class="loadingLogoContainer" >
@@ -234,26 +231,13 @@ Array [ id="osd_legacy_browser_error" style="display:none" > - - - - - +

@@ -289,7 +273,9 @@ Array [ content="width=device-width" name="viewport" />, - , + <title> + OpenSearch Dashboards + , , , @@ -356,9 +342,12 @@ Array [ class="loadingLogoContainer" >

@@ -516,26 +497,13 @@ Array [ id="osd_legacy_browser_error" style="display:none" > - - - - - +

@@ -571,7 +539,9 @@ Array [ content="width=device-width" name="viewport" />, - , + <title> + OpenSearch Dashboards + , , , @@ -634,36 +604,18 @@ Array [ class="osdLoaderWrap" data-test-subj="loadingLogo" > - - - - - - - - +

@@ -815,26 +757,13 @@ Array [ id="osd_legacy_browser_error" style="display:none" > - - - - - +

@@ -895,7 +824,7 @@ Array [ rel="manifest" />, , @@ -941,6 +870,9 @@ Array [

@@ -958,26 +890,13 @@ Array [ id="osd_legacy_browser_error" style="display:none" > - - - - - +

@@ -1038,7 +957,7 @@ Array [ rel="manifest" />, , @@ -1078,36 +997,18 @@ Array [ class="osdLoaderWrap" data-test-subj="loadingLogo" > - - - - - - - - +

-
- - - - - - -
- - -`; - -exports[`Welcome page should render welcome logo in default mode using the original OpenSearch Dashboards logo 1`] = ` - -
-
-
- - - - -

diff --git a/src/plugins/home/public/application/components/home.js b/src/plugins/home/public/application/components/home.js index 2852ed4cda1..7024d73080e 100644 --- a/src/plugins/home/public/application/components/home.js +++ b/src/plugins/home/public/application/components/home.js @@ -163,6 +163,7 @@ export class Home extends Component { solutions={solutions} directories={directories} branding={getServices().injectedMetadata.getBranding()} + logos={getServices().chrome.logos} /> ) : null} @@ -202,6 +203,7 @@ export class Home extends Component { urlBasePath={this.props.urlBasePath} telemetry={this.props.telemetry} branding={getServices().injectedMetadata.getBranding()} + logos={getServices().chrome.logos} /> ); } diff --git a/src/plugins/home/public/application/components/solutions_section/__snapshots__/solution_title.test.tsx.snap b/src/plugins/home/public/application/components/solutions_section/__snapshots__/solution_title.test.tsx.snap index 2fb95d11d5c..c88537eac65 100644 --- a/src/plugins/home/public/application/components/solutions_section/__snapshots__/solution_title.test.tsx.snap +++ b/src/plugins/home/public/application/components/solutions_section/__snapshots__/solution_title.test.tsx.snap @@ -1,186 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`SolutionTitle in dark mode renders the home dashboard logo using mark dark mode URL 1`] = ` - - -
- custom title logo -
- -

- custom title -

-
- -

- Visualize & analyze - - -

-
-
-
-`; - -exports[`SolutionTitle in dark mode renders the home dashboard logo using mark default mode URL 1`] = ` - - -
- custom title logo -
- -

- custom title -

-
- -

- Visualize & analyze - - -

-
-
-
-`; - -exports[`SolutionTitle in dark mode renders the home dashboard logo using original in and out door logo 1`] = ` - - - - -

- custom title -

-
- -

- Visualize & analyze - - -

-
-
-
-`; - -exports[`SolutionTitle in default mode renders the home dashboard logo using mark default mode URL 1`] = ` - - -
- custom title logo -
- -

- custom title -

-
- -

- Visualize & analyze - - -

-
-
-
-`; - -exports[`SolutionTitle in default mode renders the home dashboard logo using original in and out door logo 1`] = ` +exports[`SolutionTitle renders correctly by default 1`] = `

- custom title + Page Title

`; -exports[`SolutionTitle in default mode renders the title section of the solution panel 1`] = ` +exports[`SolutionTitle renders correctly when branded 1`] = ` custom title logo

- custom title + Page Title

= ({ addBasePath, solution, apps = [], branding }) => ( +export const SolutionPanel: FC = ({ addBasePath, solution, apps = [], branding, logos }) => ( = ({ addBasePath, solution, apps = [], bra title={solution.title} subtitle={solution.subtitle} branding={branding} + logos={logos} /> diff --git a/src/plugins/home/public/application/components/solutions_section/solution_title.test.tsx b/src/plugins/home/public/application/components/solutions_section/solution_title.test.tsx index 5d4a6ebf079..9d8f7662f8f 100644 --- a/src/plugins/home/public/application/components/solutions_section/solution_title.test.tsx +++ b/src/plugins/home/public/application/components/solutions_section/solution_title.test.tsx @@ -31,6 +31,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { SolutionTitle } from './solution_title'; +import { getLogosMock } from '../../../../../../core/common/mocks'; const solutionEntry = { id: 'opensearchDashboards', @@ -42,121 +43,51 @@ const solutionEntry = { order: 1, }; +const mockTitle = 'Page Title'; +const makeProps = () => ({ + title: solutionEntry.title, + subtitle: solutionEntry.subtitle, + iconType: solutionEntry.icon, + branding: { + applicationTitle: mockTitle, + }, + logos: getLogosMock.default, +}); + describe('SolutionTitle ', () => { - describe('in default mode', () => { - test('renders the title section of the solution panel', () => { - const branding = { - darkMode: false, - mark: { - defaultUrl: '/defaultModeUrl', - darkModeUrl: '/darkModeUrl', - }, - applicationTitle: 'custom title', - }; - const component = shallow( - - ); - expect(component).toMatchSnapshot(); - }); + it('renders correctly by default', () => { + const props = { + ...makeProps(), + }; + const component = shallow(); + const elements = component.find('EuiToken'); + expect(elements.length).toEqual(1); - test('renders the home dashboard logo using mark default mode URL', () => { - const branding = { - darkMode: false, - mark: { - defaultUrl: '/defaultModeUrl', - darkModeUrl: '/darkModeUrl', - }, - applicationTitle: 'custom title', - }; - const component = shallow( - - ); - expect(component).toMatchSnapshot(); - }); + const img = elements.first(); + expect(img.prop('iconType')).toEqual(props.logos.Mark.url); - test('renders the home dashboard logo using original in and out door logo', () => { - const branding = { - darkMode: false, - mark: {}, - applicationTitle: 'custom title', - }; - const component = shallow( - - ); - expect(component).toMatchSnapshot(); - }); + const titles = component.find('EuiTitle > h3'); + expect(titles.length).toEqual(1); + expect(titles.first().text()).toEqual(mockTitle); + + expect(component).toMatchSnapshot(); }); - describe('in dark mode', () => { - test('renders the home dashboard logo using mark dark mode URL', () => { - const branding = { - darkMode: true, - mark: { - defaultUrl: '/defaultModeUrl', - darkModeUrl: '/darkModeUrl', - }, - applicationTitle: 'custom title', - }; - const component = shallow( - - ); - expect(component).toMatchSnapshot(); + it('renders correctly when branded', () => { + const props = { + ...makeProps(), + logos: getLogosMock.branded, + }; + const component = shallow(); + const elements = component.find({ + 'data-test-subj': 'dashboardCustomLogo', }); + expect(elements.length).toEqual(1); - test('renders the home dashboard logo using mark default mode URL', () => { - const branding = { - darkMode: true, - mark: { - defaultUrl: '/defaultModeUrl', - }, - applicationTitle: 'custom title', - }; - const component = shallow( - - ); - expect(component).toMatchSnapshot(); - }); + const img = elements.first(); + expect(img.prop('src')).toEqual(props.logos.Mark.url); + expect(img.prop('alt')).toEqual(`${mockTitle} logo`); - test('renders the home dashboard logo using original in and out door logo', () => { - const branding = { - darkMode: true, - mark: {}, - applicationTitle: 'custom title', - }; - const component = shallow( - - ); - expect(component).toMatchSnapshot(); - }); + expect(component).toMatchSnapshot(); }); }); diff --git a/src/plugins/home/public/application/components/solutions_section/solution_title.tsx b/src/plugins/home/public/application/components/solutions_section/solution_title.tsx index 8a0db3ac1af..f2a7af96d71 100644 --- a/src/plugins/home/public/application/components/solutions_section/solution_title.tsx +++ b/src/plugins/home/public/application/components/solutions_section/solution_title.tsx @@ -29,6 +29,7 @@ */ import React, { FC } from 'react'; +import type { Logos } from 'opensearch-dashboards/public'; import { EuiFlexGroup, EuiFlexItem, @@ -53,97 +54,36 @@ interface Props { */ iconType: IconType; branding: HomePluginBranding; + logos: Logos; } /** - * Use branding configurations to check which URL to use for rendering - * home card logo in default mode. In default mode, home card logo will - * proritize default mode mark URL. If it is invalid, default opensearch logo - * will be rendered. - * - * @param {HomePluginBranding} - pass in custom branding configurations - * @returns a valid custom URL or undefined if no valid URL is provided - */ -const customHomeLogoDefaultMode = (branding: HomePluginBranding) => { - return branding.mark?.defaultUrl ?? undefined; -}; - -/** - * Use branding configurations to check which URL to use for rendering - * home logo in dark mode. In dark mode, home logo will render - * dark mode mark URL if valid. Otherwise, it will render the default - * mode mark URL if valid. If both dark mode mark URL and default mode mark - * URL are invalid, the default opensearch logo will be rendered. - * - * @param {HomePluginBranding} - pass in custom branding configurations - * @returns {string|undefined} a valid custom URL or undefined if no valid URL is provided + * The component that renders the blue dashboard card on home page. + * `title` and `iconType` are deprecated because SolutionTitle component will only be rendered once + * as the home dashboard card. */ -const customHomeLogoDarkMode = (branding: HomePluginBranding) => { - return branding.mark?.darkModeUrl ?? branding.mark?.defaultUrl ?? undefined; -}; - -/** - * Render custom home logo for both default mode and dark mode - * - * @param {HomePluginBranding} - pass in custom branding configurations - * @returns {string|undefined} a valid custom loading logo URL, or undefined - */ -const customHomeLogo = (branding: HomePluginBranding) => { - return branding.darkMode ? customHomeLogoDarkMode(branding) : customHomeLogoDefaultMode(branding); -}; - -/** - * Check if we render a custom home logo or the default opensearch spinner. - * If customWelcomeLogo() returns undefined(no valid custom URL is found), we - * render the default opensearch logo - * - * @param {HomePluginBranding} - pass in custom branding configurations - * @returns a image component with custom logo URL, or the default opensearch logo - */ -const renderBrandingEnabledOrDisabledLogo = (branding: HomePluginBranding) => { - const customLogo = customHomeLogo(branding); - if (customLogo) { - return ( -
- {branding.applicationTitle -
- ); - } - const DEFAULT_OPENSEARCH_MARK = `${branding.assetFolderUrl}/opensearch_mark_default_mode.svg`; - const DARKMODE_OPENSEARCH_MARK = `${branding.assetFolderUrl}/opensearch_mark_dark_mode.svg`; - - return ( - - ); -}; - -/** - * - * @param {string} title - * @param {string} subtitle - * @param {IconType} iconType - will always be inputOutput icon type here - * @param {HomePluginBranding} branding - custom branding configurations - * - * @returns - a EUI component that renders the blue dashboard card on home page, - * title and iconType are deprecated here because SolutionTitle component will only be rendered once - * as the home dashboard card, and we are now in favor of using custom branding configurations. - */ -export const SolutionTitle: FC = ({ title, subtitle, iconType, branding }) => ( +export const SolutionTitle: FC = ({ subtitle, branding, logos }) => ( - {renderBrandingEnabledOrDisabledLogo(branding)} + {logos.Mark.type === 'custom' ? ( +
+ {branding.applicationTitle +
+ ) : ( + + )} = ({ addBasePath, solutions, directories, branding }) => { +export const SolutionsSection: FC = ({ + addBasePath, + solutions, + directories, + branding, + logos, +}) => { // Separate OpenSearch Dashboards from other solutions const opensearchDashboards = solutions.find(({ id }) => id === 'opensearchDashboards'); const opensearchDashboardsApps = directories @@ -78,6 +86,7 @@ export const SolutionsSection: FC = ({ addBasePath, solutions, directorie solution={solution} addBasePath={addBasePath} branding={branding} + logos={logos} /> ))}
@@ -89,6 +98,7 @@ export const SolutionsSection: FC = ({ addBasePath, solutions, directorie addBasePath={addBasePath} apps={opensearchDashboardsApps.length ? opensearchDashboardsApps : undefined} branding={branding} + logos={logos} /> ) : null}
diff --git a/src/plugins/home/public/application/components/welcome.test.tsx b/src/plugins/home/public/application/components/welcome.test.tsx index 6a08c977e15..c03bdff6aae 100644 --- a/src/plugins/home/public/application/components/welcome.test.tsx +++ b/src/plugins/home/public/application/components/welcome.test.tsx @@ -32,6 +32,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { Welcome } from './welcome'; import { telemetryPluginMock } from '../../../../telemetry/public/mocks'; +import { getLogosMock } from '../../../../../core/common/mocks'; jest.mock('../opensearch_dashboards_services', () => ({ getServices: () => ({ @@ -48,120 +49,95 @@ test('should render a Welcome screen with the telemetry disclaimer', () => { }); */ -const branding = { - darkMode: false, - mark: { - defaultUrl: '/', +const mockTitle = 'Page Title'; +const makeProps = () => ({ + urlBasePath: '/', + onSkip: jest.fn(), + branding: { + applicationTitle: mockTitle, }, - applicationTitle: 'OpenSearch Dashboards', -}; + logos: getLogosMock.default, +}); -describe('Welcome page ', () => { - describe('should render a Welcome screen ', () => { - test('with the telemetry disclaimer when optIn is true', () => { +describe('Welcome page', () => { + describe('renders the Welcome screen', () => { + it('with the telemetry disclaimer when optIn is true', () => { const telemetry = telemetryPluginMock.createStartContract(); telemetry.telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true); - const component = shallow( - {}} telemetry={telemetry} branding={branding} /> - ); + const props = { + ...makeProps(), + telemetry, + }; + const component = shallow(); expect(component).toMatchSnapshot(); }); - test('with the telemetry disclaimer when optIn is false', () => { + it('with the telemetry disclaimer when optIn is false', () => { const telemetry = telemetryPluginMock.createStartContract(); telemetry.telemetryService.getIsOptedIn = jest.fn().mockReturnValue(false); - const component = shallow( - {}} telemetry={telemetry} branding={branding} /> - ); + const props = { + ...makeProps(), + telemetry, + }; + const component = shallow(); expect(component).toMatchSnapshot(); }); - test('with no telemetry disclaimer', () => { - const component = shallow( {}} branding={branding} />); - + it('with no telemetry disclaimer', () => { + const props = { + ...makeProps(), + }; + const component = shallow(); expect(component).toMatchSnapshot(); }); - test('fires opt-in seen when mounted', () => { + it('and fires opt-in seen when mounted', () => { const telemetry = telemetryPluginMock.createStartContract(); const mockSetOptedInNoticeSeen = jest.fn(); telemetry.telemetryNotifications.setOptedInNoticeSeen = mockSetOptedInNoticeSeen; - shallow( - {}} telemetry={telemetry} branding={branding} /> - ); + + const props = { + ...makeProps(), + telemetry, + }; + shallow(); expect(mockSetOptedInNoticeSeen).toHaveBeenCalled(); }); }); - describe('should render welcome logo in default mode ', () => { - test('using mark default mode URL', () => { - const customBranding = { - darkMode: false, - mark: { - defaultUrl: '/defaultModeMark', - }, - applicationTitle: 'custom title', + describe('renders the welcome logo', () => { + it('with OpenSearch center mark when not branded', () => { + const props = { + ...makeProps(), }; - const component = shallow( - {}} branding={customBranding} /> - ); - expect(component).toMatchSnapshot(); - }); + const component = shallow(); - test('using the original OpenSearch Dashboards logo', () => { - const defaultBranding = { - darkMode: false, - mark: {}, - applicationTitle: 'OpenSearch Dashboards', - }; - const component = shallow( - {}} branding={defaultBranding} /> - ); - expect(component).toMatchSnapshot(); - }); - }); - describe('should render welcome logo in dark mode ', () => { - test('using mark dark mode URL', () => { - const customBranding = { - darkMode: true, - mark: { - defaultUrl: '/defaultModeMark', - darkModeUrl: '/darkModeMark', - }, - title: 'custom title', - }; - const component = shallow( - {}} branding={customBranding} /> - ); - expect(component).toMatchSnapshot(); - }); + const elements = component.find('EuiIcon'); + expect(elements.length).toEqual(1); + expect(elements.first().prop('type')).toEqual(props.logos.CenterMark.url); - test('using mark default mode URL', () => { - const customBranding = { - darkMode: true, - mark: { - defaultUrl: '/defaultModeMark', - }, - title: 'custom title', - }; - const component = shallow( - {}} branding={customBranding} /> - ); expect(component).toMatchSnapshot(); }); - test('using the original opensearch logo', () => { - const customBranding = { - darkMode: true, - mark: {}, - title: 'custom title', + it('with custom branded logo when branded', () => { + const props = { + ...makeProps(), + logos: getLogosMock.branded, }; - const component = shallow( - {}} branding={customBranding} /> - ); + const component = shallow(); + + const elements = component.find({ + 'data-test-subj': 'welcomeCustomLogo', + }); + expect(elements.length).toEqual(1); + + const img = elements.first(); + expect(img.prop('src')).toEqual(props.logos.CenterMark.url); + expect(img.prop('alt')).toEqual(`${mockTitle} logo`); + expect(component).toMatchSnapshot(); }); }); diff --git a/src/plugins/home/public/application/components/welcome.tsx b/src/plugins/home/public/application/components/welcome.tsx index 1a364ce732b..108180cacd9 100644 --- a/src/plugins/home/public/application/components/welcome.tsx +++ b/src/plugins/home/public/application/components/welcome.tsx @@ -47,23 +47,19 @@ import { } from '@elastic/eui'; import { METRIC_TYPE } from '@osd/analytics'; import { FormattedMessage } from '@osd/i18n/react'; +import { Logos } from 'opensearch-dashboards/public'; import { getServices } from '../opensearch_dashboards_services'; import { TelemetryPluginStart } from '../../../../telemetry/public'; - import { SampleDataCard } from './sample_data'; -import OpenSearchMarkCentered from '../../assets/logos/opensearch_mark_centered.svg'; + interface Props { urlBasePath: string; onSkip: () => void; telemetry?: TelemetryPluginStart; branding: { - darkMode: boolean; - mark: { - defaultUrl?: string; - darkModeUrl?: string; - }; applicationTitle?: string; }; + logos: Logos; } /** @@ -146,72 +142,32 @@ export class Welcome extends React.Component { } }; - private darkMode = this.props.branding.darkMode; - private markDefault = this.props.branding.mark.defaultUrl; - private markDarkMode = this.props.branding.mark.darkModeUrl; private applicationTitle = this.props.branding.applicationTitle; /** - * Use branding configurations to check which URL to use for rendering - * welcome logo in default mode. In default mode, welcome logo will - * proritize default mode mark URL. If it is invalid, default opensearch logo - * will be rendered. - * - * @returns a valid custom URL or undefined if no valid URL is provided - */ - private customWelcomeLogoDefaultMode = () => { - return this.markDefault ?? undefined; - }; - - /** - * Use branding configurations to check which URL to use for rendering - * welcome logo in dark mode. In dark mode, welcome logo will render - * dark mode mark URL if valid. Otherwise, it will render the default - * mode mark URL if valid. If both dark mode mark URL and default mode mark - * URL are invalid, the default opensearch logo will be rendered. - * - * @returns a valid custom URL or undefined if no valid URL is provided + * Returns Welcome logo, wrapped in a container, using the custom branded logos or, + * the centered opensearch mark if no branding is provided. */ - private customWelcomeLogoDarkMode = () => { - return this.markDarkMode ?? this.markDefault ?? undefined; - }; + private renderWelcomeLogo = () => { + const { url: centerMarkURL, type: centerMarkType } = this.props.logos.CenterMark; - /** - * Render custom welcome logo for both default mode and dark mode - * - * @returns a valid custom loading logo URL, or undefined - */ - private customWelcomeLogo = () => { - if (this.darkMode) { - return this.customWelcomeLogoDarkMode(); - } - return this.customWelcomeLogoDefaultMode(); - }; - - /** - * Check if we render a custom welcome logo or the default opensearch spinner. - * If customWelcomeLogo() returns undefined(no valid custom URL is found), we - * render the default opensearch logo - * - * @returns a image component with custom logo URL, or the default opensearch logo - */ - private renderBrandingEnabledOrDisabledLogo = () => { - if (this.customWelcomeLogo()) { + if (centerMarkType === 'custom') { return (
{this.applicationTitle
); } + return ( - + ); }; @@ -224,7 +180,7 @@ export class Welcome extends React.Component {
- {this.renderBrandingEnabledOrDisabledLogo()} + {this.renderWelcomeLogo()} - - - - \ No newline at end of file diff --git a/src/plugins/home/public/assets/logos/opensearch_mark_default.svg b/src/plugins/home/public/assets/logos/opensearch_mark_default.svg deleted file mode 100644 index e185d0fc8c3..00000000000 --- a/src/plugins/home/public/assets/logos/opensearch_mark_default.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/plugins/home/server/tutorials/opensearch_logs/index.ts b/src/plugins/home/server/tutorials/opensearch_logs/index.ts index 59589e59682..86f8caebd0a 100644 --- a/src/plugins/home/server/tutorials/opensearch_logs/index.ts +++ b/src/plugins/home/server/tutorials/opensearch_logs/index.ts @@ -58,7 +58,7 @@ export function opensearchLogsSpecProvider(context: TutorialContext): TutorialSc learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-opensearch.html', }, }), - euiIconType: '/plugins/home/assets/logos/opensearch_mark_default.svg', + euiIconType: '/ui/logos/opensearch_mark.svg', artifacts: { application: { label: i18n.translate('home.tutorials.opensearchLogs.artifacts.application.label', { diff --git a/src/plugins/home/server/tutorials/opensearch_metrics/index.ts b/src/plugins/home/server/tutorials/opensearch_metrics/index.ts index e7a2559d2df..38694ca9d58 100644 --- a/src/plugins/home/server/tutorials/opensearch_metrics/index.ts +++ b/src/plugins/home/server/tutorials/opensearch_metrics/index.ts @@ -57,7 +57,7 @@ export function opensearchMetricsSpecProvider(context: TutorialContext): Tutoria learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-opensearch.html', }, }), - euiIconType: '/plugins/home/assets/logos/opensearch_mark_default.svg', + euiIconType: '/ui/logos/opensearch_mark.svg', artifacts: { application: { label: i18n.translate('home.tutorials.opensearchMetrics.artifacts.application.label', { diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index 4cc1c4aa7f8..81a970a0fc4 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -76,7 +76,7 @@ export class ManagementPlugin implements Plugin { diff --git a/src/plugins/opensearch_dashboards_overview/public/application.tsx b/src/plugins/opensearch_dashboards_overview/public/application.tsx index f6d0e172e39..26bf7294263 100644 --- a/src/plugins/opensearch_dashboards_overview/public/application.tsx +++ b/src/plugins/opensearch_dashboards_overview/public/application.tsx @@ -51,7 +51,6 @@ export const renderApp = ( .filter(({ id }) => id !== 'opensearchDashboards') .filter(({ id }) => navLinks.find(({ category, hidden }) => !hidden && category?.id === id)); const features = home.featureCatalogue.get(); - const branding = core.injectedMetadata.getBranding(); ReactDOM.render( @@ -64,7 +63,7 @@ export const renderApp = ( newsfeed$={newsfeed$} solutions={solutions} features={features} - branding={branding} + logos={core.chrome.logos} /> , diff --git a/src/plugins/opensearch_dashboards_overview/public/components/app.tsx b/src/plugins/opensearch_dashboards_overview/public/components/app.tsx index dfd3b717981..def278cdd20 100644 --- a/src/plugins/opensearch_dashboards_overview/public/components/app.tsx +++ b/src/plugins/opensearch_dashboards_overview/public/components/app.tsx @@ -32,12 +32,11 @@ import React, { useEffect, useState } from 'react'; import { Observable } from 'rxjs'; import { I18nProvider } from '@osd/i18n/react'; import { HashRouter as Router, Switch, Route } from 'react-router-dom'; -import { CoreStart } from 'src/core/public'; +import { CoreStart, Logos } from 'src/core/public'; import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; import { FetchResult } from 'src/plugins/newsfeed/public'; import { FeatureCatalogueEntry, FeatureCatalogueSolution } from 'src/plugins/home/public'; import { Overview } from './overview'; -import { OverviewPluginBranding } from '../plugin'; interface OpenSearchDashboardsOverviewAppDeps { basename: string; @@ -47,7 +46,7 @@ interface OpenSearchDashboardsOverviewAppDeps { newsfeed$?: Observable; solutions: FeatureCatalogueSolution[]; features: FeatureCatalogueEntry[]; - branding: OverviewPluginBranding; + logos: Logos; } export const OpenSearchDashboardsOverviewApp = ({ @@ -55,7 +54,7 @@ export const OpenSearchDashboardsOverviewApp = ({ newsfeed$, solutions, features, - branding, + logos, }: OpenSearchDashboardsOverviewAppDeps) => { const [newsFetchResult, setNewsFetchResult] = useState(null); @@ -78,7 +77,7 @@ export const OpenSearchDashboardsOverviewApp = ({ newsFetchResult={newsFetchResult} solutions={solutions} features={features} - branding={branding} + logos={logos} /> diff --git a/src/plugins/opensearch_dashboards_overview/public/components/overview/__snapshots__/overview.test.tsx.snap b/src/plugins/opensearch_dashboards_overview/public/components/overview/__snapshots__/overview.test.tsx.snap index e352dd58236..6d92c875924 100644 --- a/src/plugins/opensearch_dashboards_overview/public/components/overview/__snapshots__/overview.test.tsx.snap +++ b/src/plugins/opensearch_dashboards_overview/public/components/overview/__snapshots__/overview.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Overview render 1`] = ` +exports[`Overview renders with solutions and features 1`] = `
`; -exports[`Overview without features 1`] = ` +exports[`Overview renders with solutions and without features 1`] = `
`; -exports[`Overview without solutions 1`] = ` +exports[`Overview renders without solutions and with features 1`] = `
({ useOpenSearchDashboards: jest.fn().mockReturnValue({ @@ -163,46 +164,42 @@ const mockFeatures = [ }, ]; -const mockBranding = { - darkMode: false, - mark: { - defaultUrl: '/defaultModeUrl', - darkModeUrl: '/darkModeUrl', - }, -}; +const makeProps = () => ({ + newsFetchResult: mockNewsFetchResult, + solutions: mockSolutions, + features: mockFeatures, + logos: getLogosMock.default, +}); describe('Overview', () => { - test('render', () => { - const component = shallowWithIntl( - - ); - expect(component).toMatchSnapshot(); - }); - test('without solutions', () => { - const component = shallowWithIntl( - - ); - expect(component).toMatchSnapshot(); - }); - test('without features', () => { - const component = shallowWithIntl( - - ); - expect(component).toMatchSnapshot(); + describe('renders', () => { + it('with solutions and features', () => { + const props = { + ...makeProps(), + }; + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('without solutions and with features', () => { + const props = { + ...makeProps(), + solutions: [], + }; + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); + it('with solutions and without features', () => { + const props = { + ...makeProps(), + features: [], + }; + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); }); + + // ToDo: Add tests for all the complications of Overview + // https://github.com/opensearch-project/OpenSearch-Dashboards/issues/4693 + it.todo('renders each of the complications of Overview'); }); diff --git a/src/plugins/opensearch_dashboards_overview/public/components/overview/overview.tsx b/src/plugins/opensearch_dashboards_overview/public/components/overview/overview.tsx index 3df491b1b42..30df4dbe814 100644 --- a/src/plugins/opensearch_dashboards_overview/public/components/overview/overview.tsx +++ b/src/plugins/opensearch_dashboards_overview/public/components/overview/overview.tsx @@ -41,7 +41,7 @@ import { EuiToken, } from '@elastic/eui'; import { FormattedMessage } from '@osd/i18n/react'; -import { CoreStart } from 'opensearch-dashboards/public'; +import { CoreStart, Logos } from 'opensearch-dashboards/public'; import { RedirectAppLinks, useOpenSearchDashboards, @@ -60,18 +60,18 @@ import { AddData } from '../add_data'; import { GettingStarted } from '../getting_started'; import { ManageData } from '../manage_data'; import { NewsFeed } from '../news_feed'; -import { OverviewPluginBranding } from '../../plugin'; const sortByOrder = (featureA: FeatureCatalogueEntry, featureB: FeatureCatalogueEntry) => (featureA.order || Infinity) - (featureB.order || Infinity); + interface Props { newsFetchResult: FetchResult | null | void; solutions: FeatureCatalogueSolution[]; features: FeatureCatalogueEntry[]; - branding: OverviewPluginBranding; + logos: Logos; } -export const Overview: FC = ({ newsFetchResult, solutions, features, branding }) => { +export const Overview: FC = ({ newsFetchResult, solutions, features, logos }) => { const [isNewOpenSearchDashboardsInstance, setNewOpenSearchDashboardsInstance] = useState(false); const { services: { http, data, uiSettings, application }, @@ -147,14 +147,14 @@ export const Overview: FC = ({ newsFetchResult, solutions, features, bran } - branding={branding} + logos={logos} />
diff --git a/src/plugins/opensearch_dashboards_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap b/src/plugins/opensearch_dashboards_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap index 028a23d69e7..1a88da2e58f 100644 --- a/src/plugins/opensearch_dashboards_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap +++ b/src/plugins/opensearch_dashboards_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap @@ -1,96 +1,178 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`is rendered 1`] = ` - +exports[`ExitFullScreenButton is rendered using the dark theme's mark by default 1`] = ` +
+ +

+ In full screen mode, press ESC to exit. +

+
- -

+ - In full screen mode, press ESC to exit. -

-
-
-
+ + + + + + +
+
+`; + +exports[`ExitFullScreenButton is rendered using the dark theme's mark when light color-scheme is applied 1`] = ` +
+ +

+ In full screen mode, press ESC to exit. +

+
+
+ +
+
+`; + +exports[`ExitFullScreenButton is rendered using the light theme's mark when dark color-scheme is applied 1`] = ` +
+ +

+ In full screen mode, press ESC to exit. +

+
+
+ -
+

+ Exit full screen +

+ +
+ + + + + +
- +
`; diff --git a/src/plugins/opensearch_dashboards_react/public/exit_full_screen_button/exit_full_screen_button.test.tsx b/src/plugins/opensearch_dashboards_react/public/exit_full_screen_button/exit_full_screen_button.test.tsx index cae20349cf3..fd5a11011bd 100644 --- a/src/plugins/opensearch_dashboards_react/public/exit_full_screen_button/exit_full_screen_button.test.tsx +++ b/src/plugins/opensearch_dashboards_react/public/exit_full_screen_button/exit_full_screen_button.test.tsx @@ -29,36 +29,80 @@ */ import React from 'react'; -import sinon from 'sinon'; import { ExitFullScreenButton } from './exit_full_screen_button'; import { keys } from '@elastic/eui'; -import { mount } from 'enzyme'; +import { shallow } from 'enzyme'; +import { getLogosMock } from '../../../../core/common/mocks'; +import { ColorScheme } from '../../../../core/common'; -test('is rendered', () => { - const component = mount( {}} />); - - expect(component).toMatchSnapshot(); +const mockProps = () => ({ + onExitFullScreenMode: jest.fn(), + logos: getLogosMock.default, }); -describe('onExitFullScreenMode', () => { - test('is called when the button is pressed', () => { - const onExitHandler = sinon.stub(); +describe('ExitFullScreenButton', () => { + it("is rendered using the dark theme's mark by default", () => { + const props = { + ...mockProps(), + }; + const component = shallow(); + // In light color-scheme, the button has a dark background + const icons = component.find(`EuiIcon[type="${props.logos.Mark.dark.url}"]`); - const component = mount(); + expect(icons.length).toEqual(1); - component.find('button').simulate('click'); + expect(component).toMatchSnapshot(); + }); - sinon.assert.calledOnce(onExitHandler); + it("is rendered using the dark theme's mark when light color-scheme is applied", () => { + const props = { + ...mockProps(), + logos: { ...getLogosMock.default, colorScheme: ColorScheme.LIGHT }, + }; + const component = shallow(); + // In light color-scheme, the button has a dark background + const icons = component.find(`EuiIcon[type="${props.logos.Mark.dark.url}"]`); + + expect(icons.length).toEqual(1); + + expect(component).toMatchSnapshot(); }); - test('is called when the ESC key is pressed', () => { - const onExitHandler = sinon.stub(); + it("is rendered using the light theme's mark when dark color-scheme is applied", () => { + const props = { + ...mockProps(), + logos: { ...getLogosMock.default, colorScheme: ColorScheme.DARK }, + }; + const component = shallow(); + // In light color-scheme, the button has a dark background + const icons = component.find(`EuiIcon[type="${props.logos.Mark.light.url}"]`); + + expect(icons.length).toEqual(1); + + expect(component).toMatchSnapshot(); + }); +}); + +describe('onExitFullScreenMode', () => { + it('is called when the button is pressed', () => { + const props = { + ...mockProps(), + }; + const component = shallow(); + component.find('button').simulate('click'); + + expect(props.onExitFullScreenMode).toHaveBeenCalledTimes(1); + }); - mount(); + it('is called when the ESC key is pressed', () => { + const props = { + ...mockProps(), + }; + shallow(); const escapeKeyEvent = new KeyboardEvent('keydown', { key: keys.ESCAPE } as any); document.dispatchEvent(escapeKeyEvent); - sinon.assert.calledOnce(onExitHandler); + expect(props.onExitFullScreenMode).toHaveBeenCalledTimes(1); }); }); diff --git a/src/plugins/opensearch_dashboards_react/public/exit_full_screen_button/exit_full_screen_button.tsx b/src/plugins/opensearch_dashboards_react/public/exit_full_screen_button/exit_full_screen_button.tsx index 523f8345088..cf13d0f224c 100644 --- a/src/plugins/opensearch_dashboards_react/public/exit_full_screen_button/exit_full_screen_button.tsx +++ b/src/plugins/opensearch_dashboards_react/public/exit_full_screen_button/exit_full_screen_button.tsx @@ -32,14 +32,14 @@ import { i18n } from '@osd/i18n'; import React, { PureComponent } from 'react'; import { EuiScreenReaderOnly, keys } from '@elastic/eui'; import { EuiIcon, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { Logos } from 'opensearch-dashboards/public'; export interface ExitFullScreenButtonProps { onExitFullScreenMode: () => void; + logos: Logos; } import './index.scss'; -// eslint-disable-next-line @osd/eslint/no-restricted-paths -import OpenSearchMarkDarkMode from '../../../home/public/assets/logos/opensearch_mark_darkmode.svg'; class ExitFullScreenButtonUi extends PureComponent { public onKeyDown = (e: KeyboardEvent) => { @@ -57,6 +57,7 @@ class ExitFullScreenButtonUi extends PureComponent { } public render() { + const colorScheme = this.props.logos.colorScheme !== 'dark' ? 'dark' : 'light'; return (
@@ -83,7 +84,7 @@ class ExitFullScreenButtonUi extends PureComponent { > - +
diff --git a/src/plugins/opensearch_dashboards_react/public/overview_page/overview_page_header/__snapshots__/overview_page_header.test.tsx.snap b/src/plugins/opensearch_dashboards_react/public/overview_page/overview_page_header/__snapshots__/overview_page_header.test.tsx.snap index 72a25d779de..326a5b51718 100644 --- a/src/plugins/opensearch_dashboards_react/public/overview_page/overview_page_header/__snapshots__/overview_page_header.test.tsx.snap +++ b/src/plugins/opensearch_dashboards_react/public/overview_page/overview_page_header/__snapshots__/overview_page_header.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`OverviewPageHeader in dark mode render logo as custom dark mode logo 1`] = ` +exports[`OverviewPageHeader renders with the toolbar by default 1`] = `
@@ -38,7 +38,7 @@ exports[`OverviewPageHeader in dark mode render logo as custom dark mode logo className="osdOverviewPageHeader__actionItem" grow={false} > - Add data - - - - - -
-
-`; - -exports[`OverviewPageHeader in dark mode render logo as custom default mode logo 1`] = ` -
-
- - - - - -

- Page Title -

-
-
-
-
- - - - - - Add data - - - - - -
-
-
-`; - -exports[`OverviewPageHeader in dark mode render logo as original dark mode opensearch mark 1`] = ` -
-
- - - - - -

- Page Title -

-
-
-
-
- - - - - - Add data - - - - - -
-
-
-`; - -exports[`OverviewPageHeader in default mode render logo as custom default mode logo 1`] = ` -
-
- - - - - -

- Page Title -

-
-
-
-
- - - - - - Add data - - - - - -
-
-
-`; - -exports[`OverviewPageHeader in default mode render logo as original default mode opensearch mark 1`] = ` -
-
- - - - - -

- Page Title -

-
-
-
-
- - - - - - Add data - - + diff --git a/src/plugins/opensearch_dashboards_react/public/overview_page/overview_page_header/overview_page_header.test.tsx b/src/plugins/opensearch_dashboards_react/public/overview_page/overview_page_header/overview_page_header.test.tsx index d8290f0dbcb..2e27ebd0cb6 100644 --- a/src/plugins/opensearch_dashboards_react/public/overview_page/overview_page_header/overview_page_header.test.tsx +++ b/src/plugins/opensearch_dashboards_react/public/overview_page/overview_page_header/overview_page_header.test.tsx @@ -31,95 +31,265 @@ import React from 'react'; import { OverviewPageHeader } from './overview_page_header'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { useOpenSearchDashboards } from '../../context'; +import { getLogosMock } from '../../../../../core/common/mocks'; -jest.mock('../../app_links', () => ({ - RedirectAppLinks: jest.fn((element: JSX.Element) => element), -})); - -jest.mock('../../context', () => ({ - useOpenSearchDashboards: jest.fn().mockReturnValue({ - services: { - application: { capabilities: { navLinks: { management: true, dev_tools: true } } }, - notifications: { toast: { addSuccess: jest.fn() } }, - }, - }), -})); - -afterAll(() => jest.clearAllMocks()); +jest.mock('../../context', () => ({ useOpenSearchDashboards: jest.fn() })); const mockTitle = 'Page Title'; const addBasePathMock = jest.fn((path: string) => (path ? path : 'path')); +const mockProps = () => ({ + addBasePath: addBasePathMock, + title: mockTitle, + branding: {}, + logos: getLogosMock.default, +}); + describe('OverviewPageHeader ', () => { - describe('in default mode ', () => { - test('render logo as custom default mode logo', () => { - const branding = { - darkMode: false, - mark: { - defaultUrl: '/defaultModeLogo', - darkModeUrl: '/darkModeLogo', - }, - }; - - const component = shallowWithIntl( - - ); - expect(component).toMatchSnapshot(); - }); + beforeAll(() => { + // @ts-ignore + useOpenSearchDashboards.mockImplementation(() => ({ + services: { + application: { capabilities: { navLinks: { management: true, dev_tools: true } } }, + notifications: { toast: { addSuccess: jest.fn() } }, + }, + })); + }); - test('render logo as original default mode opensearch mark', () => { - const branding = { - darkMode: false, - mark: {}, - }; + afterAll(() => { + jest.clearAllMocks(); + }); - const component = shallowWithIntl( - - ); - expect(component).toMatchSnapshot(); - }); + it('renders without overlap by default', () => { + const props = { + ...mockProps(), + }; + + const component = shallowWithIntl(); + + const header = component.find('header'); + expect(header.hasClass('osdOverviewPageHeader--hasOverlap')).toBeFalsy(); + expect(header.hasClass('osdOverviewPageHeader--noOverlap')).toBeTruthy(); }); - describe('in dark mode ', () => { - test('render logo as custom dark mode logo', () => { - const branding = { - darkMode: false, - mark: { - defaultUrl: '/defaultModeLogo', - darkModeUrl: '/darkModeLogo', - }, - }; - - const component = shallowWithIntl( - - ); - expect(component).toMatchSnapshot(); - }); + it('renders with overlap', () => { + const props = { + ...mockProps(), + overlap: true, + }; + + const component = shallowWithIntl(); + + const header = component.find('header'); + expect(header.hasClass('osdOverviewPageHeader--hasOverlap')).toBeTruthy(); + expect(header.hasClass('osdOverviewPageHeader--noOverlap')).toBeFalsy(); + }); + + it('renders without an icon by default', () => { + const props = { + ...mockProps(), + }; + + const component = shallowWithIntl(); + + const icons = component.find({ 'data-test-subj': 'osdOverviewPageHeaderLogo' }); + expect(icons.length).toBe(0); + }); + + it('renders the mark icon when asked to', () => { + const props = { + ...mockProps(), + showIcon: true, + }; + + const component = shallowWithIntl(); + + const icons = component.find({ 'data-test-subj': 'osdOverviewPageHeaderLogo' }); + expect(icons.length).toBe(1); + expect(icons.first().prop('type')).toEqual(props.logos.Mark.url); + }); + + it('uses the provided title in the header', () => { + const props = { + ...mockProps(), + }; + + const component = shallowWithIntl(); + + const head = component.find('h1'); + expect(head.length).toBe(1); + expect(head.first().text()).toEqual(mockTitle); + }); + + it('renders with the toolbar by default', () => { + const props = { + ...mockProps(), + }; + + const component = shallowWithIntl(); + + const items = component.find('header > div > EuiFlexGroup > EuiFlexItem'); + expect(items.length).toBe(2); + + const buttons = component.find({ className: 'osdOverviewPageHeader__actionButton' }); + // This also validates the order of the items + const btnAddData = buttons.at(0); + expect(btnAddData.prop('iconType')).toEqual('indexOpen'); + expect(btnAddData.prop('href')).toEqual('/app/home#/tutorial_directory'); + + // Would contain only the "Add Data" button + expect(component).toMatchSnapshot(); + }); + + it('renders with the toolbar when it is explicitly asked not to be hidden', () => { + const props = { + ...mockProps(), + hideToolbar: false, + }; + + const component = shallowWithIntl(); + + const items = component.find('header > div > EuiFlexGroup > EuiFlexItem'); + expect(items.length).toBe(2); + + const buttons = component.find({ className: 'osdOverviewPageHeader__actionButton' }); + // This also validates the order of the items + const btnAddData = buttons.at(0); + expect(btnAddData.prop('iconType')).toEqual('indexOpen'); + expect(btnAddData.prop('href')).toEqual('/app/home#/tutorial_directory'); + }); + + it('renders without the toolbar when it is explicitly asked to be hidden', () => { + const props = { + ...mockProps(), + hideToolbar: true, + }; - test('render logo as custom default mode logo', () => { - const branding = { - darkMode: false, - mark: { - defaultUrl: '/defaultModeLogo', - }, - }; - - const component = shallowWithIntl( - - ); - expect(component).toMatchSnapshot(); + const component = shallowWithIntl(); + + const items = component.find('header > div > EuiFlexGroup > EuiFlexItem'); + expect(items.length).toBe(1); + + const buttons = component.find({ className: 'osdOverviewPageHeader__actionButton' }); + expect(buttons.length).toBe(0); + }); +}); + +describe('OverviewPageHeader toolbar items - Management', () => { + afterAll(() => { + jest.clearAllMocks(); + }); + + const setupAndGetButton = (management: boolean, showManagementLink?: boolean) => { + // @ts-ignore + useOpenSearchDashboards.mockImplementation(() => ({ + services: { + application: { capabilities: { navLinks: { management, dev_tools: true } } }, + notifications: { toast: { addSuccess: jest.fn() } }, + }, + })); + + const props = mockProps(); + if (showManagementLink !== undefined) { + // @ts-ignore + props.showManagementLink = showManagementLink; + } + + const component = shallowWithIntl(); + + return component.find({ + className: 'osdOverviewPageHeader__actionButton', + href: '/app/management', }); + }; + + it('renders without management when the management plugin is disabled', () => { + const btnManagement = setupAndGetButton(false); + expect(btnManagement.length).toEqual(0); + }); + + it('renders without management when the management plugin is disabled and asked not to show', () => { + const btnManagement = setupAndGetButton(false, false); + expect(btnManagement.length).toEqual(0); + }); + + it('renders without management when the management plugin is disabled even if asked to show', () => { + const btnManagement = setupAndGetButton(false, true); + expect(btnManagement.length).toEqual(0); + }); + + it('renders without management when the management plugin is enabled', () => { + const btnManagement = setupAndGetButton(true); + expect(btnManagement.length).toEqual(0); + }); + + it('renders without management when the management plugin is enabled but asked not to show', () => { + const btnManagement = setupAndGetButton(true, false); + expect(btnManagement.length).toEqual(0); + }); + + it('renders with management when the management plugin is enabled and asked to show', () => { + const btnManagement = setupAndGetButton(true, true); + expect(btnManagement.length).toEqual(1); + }); +}); + +describe('OverviewPageHeader toolbar items - DevTools', () => { + afterAll(() => { + jest.clearAllMocks(); + }); + + const setupAndGetButton = (devTools: boolean, showDevToolsLink?: boolean) => { + // @ts-ignore + useOpenSearchDashboards.mockImplementation(() => ({ + services: { + application: { capabilities: { navLinks: { management: true, dev_tools: devTools } } }, + notifications: { toast: { addSuccess: jest.fn() } }, + }, + })); + + const props = mockProps(); + if (showDevToolsLink !== undefined) { + // @ts-ignore + props.showDevToolsLink = showDevToolsLink; + } - test('render logo as original dark mode opensearch mark', () => { - const branding = { - darkMode: false, - mark: {}, - }; + const component = shallowWithIntl(); - const component = shallowWithIntl( - - ); - expect(component).toMatchSnapshot(); + return component.find({ + className: 'osdOverviewPageHeader__actionButton', + href: '/app/dev_tools#/console', }); + }; + + it('renders without dev_tools when the dev_tools plugin is disabled', () => { + const btnDevTools = setupAndGetButton(false); + expect(btnDevTools.length).toEqual(0); + }); + + it('renders without dev_tools when the dev_tools plugin is disabled and asked not to show', () => { + const btnDevTools = setupAndGetButton(false, false); + expect(btnDevTools.length).toEqual(0); + }); + + it('renders without dev_tools when the dev_tools plugin is disabled even if asked to show', () => { + const btnDevTools = setupAndGetButton(false, true); + expect(btnDevTools.length).toEqual(0); + }); + + it('renders without dev_tools when the dev_tools plugin is enabled', () => { + const btnDevTools = setupAndGetButton(true); + expect(btnDevTools.length).toEqual(0); + }); + + it('renders without dev_tools when the dev_tools plugin is enabled but asked not to show', () => { + const btnDevTools = setupAndGetButton(true, false); + expect(btnDevTools.length).toEqual(0); + }); + + it('renders with dev_tools when the dev_tools plugin is enabled and asked to show', () => { + const btnDevTools = setupAndGetButton(true, true); + expect(btnDevTools.length).toEqual(1); }); }); diff --git a/src/plugins/opensearch_dashboards_react/public/overview_page/overview_page_header/overview_page_header.tsx b/src/plugins/opensearch_dashboards_react/public/overview_page/overview_page_header/overview_page_header.tsx index 00273fcf993..a636f7ecdb7 100644 --- a/src/plugins/opensearch_dashboards_react/public/overview_page/overview_page_header/overview_page_header.tsx +++ b/src/plugins/opensearch_dashboards_react/public/overview_page/overview_page_header/overview_page_header.tsx @@ -38,7 +38,7 @@ import { IconType, } from '@elastic/eui'; import { i18n } from '@osd/i18n'; -import { CoreStart } from 'opensearch-dashboards/public'; +import { CoreStart, Logos } from 'opensearch-dashboards/public'; import { RedirectAppLinks } from '../../app_links'; import { useOpenSearchDashboards } from '../../context'; import { ReactPluginBranding } from '../..'; @@ -47,24 +47,28 @@ import './index.scss'; interface Props { hideToolbar?: boolean; + /** @deprecated use showIcon */ iconType?: IconType; + showIcon?: boolean; overlap?: boolean; showDevToolsLink?: boolean; showManagementLink?: boolean; title: JSX.Element | string; addBasePath: (path: string) => string; - branding: ReactPluginBranding; + logos: Logos; + /** @deprecated use logos */ + branding?: ReactPluginBranding; } export const OverviewPageHeader: FC = ({ hideToolbar, - iconType, + showIcon = false, overlap, showDevToolsLink, showManagementLink, title, addBasePath, - branding, + logos, }) => { const { services: { application }, @@ -75,58 +79,6 @@ export const OverviewPageHeader: FC = ({ dev_tools: isDevToolsEnabled, } = application.capabilities.navLinks; - const DEFAULT_OPENSEARCH_MARK = `${branding.assetFolderUrl}/opensearch_mark_default_mode.svg`; - const DARKMODE_OPENSEARCH_MARK = `${branding.assetFolderUrl}/opensearch_mark_dark_mode.svg`; - - const darkMode = branding.darkMode; - const markDefault = branding.mark?.defaultUrl; - const markDarkMode = branding.mark?.darkModeUrl; - - /** - * Use branding configurations to check which URL to use for rendering - * overview logo in default mode. In default mode, overview logo will - * proritize default mode mark URL. If it is invalid, default opensearch logo - * will be rendered. - * - * @returns a valid custom URL or undefined if no valid URL is provided - */ - const customOverviewLogoDefaultMode = () => { - return markDefault ?? DEFAULT_OPENSEARCH_MARK; - }; - - /** - * Use branding configurations to check which URL to use for rendering - * overview logo in dark mode. In dark mode, overview logo will render - * dark mode mark URL if valid. Otherwise, it will render the default - * mode mark URL if valid. If both dark mode mark URL and default mode mark - * URL are invalid, the default opensearch logo will be rendered. - * - * @returns a valid custom URL or undefined if no valid URL is provided - */ - const customOverviewLogoDarkMode = () => { - return markDarkMode ?? markDefault ?? DARKMODE_OPENSEARCH_MARK; - }; - - /** - * Render custom overview logo for both default mode and dark mode - * - * @returns a valid custom loading logo URL, or undefined - */ - const customOverviewLogo = () => { - return darkMode ? customOverviewLogoDarkMode() : customOverviewLogoDefaultMode(); - }; - - /** - * Check if we render a custom overview logo or the default opensearch spinner. - * If customOverviewLogo() returns undefined(no valid custom URL is found), we - * render the default opensearch logo - * - * @returns a image component with custom logo URL, or the default opensearch logo - */ - const renderBrandingEnabledOrDisabledLogo = (iconTypeInput?: IconType) => { - return customOverviewLogo() ?? iconTypeInput ?? ''; - }; - return (
= ({ - {iconType && ( + {showIcon && ( )}