diff --git a/.github/workflows/build_and_test_workflow.yml b/.github/workflows/build_and_test_workflow.yml index 48c74b29765b..d82d0f45bb04 100644 --- a/.github/workflows/build_and_test_workflow.yml +++ b/.github/workflows/build_and_test_workflow.yml @@ -3,10 +3,10 @@ name: Build and test -# trigger on every commit push and PR for all branches except feature branches +# trigger on every commit push and PR for all branches except feature branches and pushes for backport branches on: push: - branches: [ '**', '!feature/**' ] + branches: [ '**', '!feature/**', '!backport/**' ] pull_request: branches: [ '**', '!feature/**' ] @@ -182,7 +182,7 @@ jobs: working-directory: ./artifacts strategy: matrix: - version: [ osd-2.0.0 ] + version: [ osd-2.0.0, osd-2.1.0 ] steps: - name: Checkout code uses: actions/checkout@v2 @@ -221,6 +221,10 @@ jobs: - name: Skipping tests if: steps.verify-opensearch-exists.outputs.version-exists != 'true' run: echo Tests were skipped because an OpenSearch release build does not exist for this version yet! + + - name: Setting environment variable to run tests for ${{ matrix.version }} + if: steps.verify-opensearch-exists.outputs.version-exists == 'true' + run: echo "BWC_VERSIONS=${{ matrix.version }}" >> $GITHUB_ENV - name: Download OpenSearch Dashboards uses: actions/download-artifact@v3 @@ -233,4 +237,13 @@ jobs: - name: Run tests if: steps.verify-opensearch-exists.outputs.version-exists == 'true' run: | - ./bwctest.sh -s false -o ${{ env.OPENSEARCH_URL }} -d ${{ steps.download.outputs.download-path }}/opensearch-dashboards-${{ env.VERSION }}-linux-x64.tar.gz \ No newline at end of file + ./bwctest.sh -s false -o ${{ env.OPENSEARCH_URL }} -d ${{ steps.download.outputs.download-path }}/opensearch-dashboards-${{ env.VERSION }}-linux-x64.tar.gz + + - uses: actions/upload-artifact@v3 + if: ${{ failure() && steps.verify-opensearch-exists.outputs.version-exists == 'true' }} + with: + name: ${{ matrix.version }}-test-failures + path: | + ./artifacts/bwc_tmp/test/cypress/videos/without-security/* + ./artifacts/bwc_tmp/test/cypress/screenshots/without-security/* + retention-days: 1 diff --git a/.i18nrc.json b/.i18nrc.json index 71c7970affa4..234aa8de0c25 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -22,6 +22,7 @@ "interpreter": "src/legacy/core_plugins/interpreter", "osd": "src/legacy/core_plugins/opensearch-dashboards", "osdDocViews": "src/legacy/core_plugins/osd_doc_views", + "osdDocViewsLinks": "src/legacy/core_plugins/osd_doc_views_links", "management": [ "src/legacy/core_plugins/management", "src/plugins/management" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index bd61be433d04..a0ea08c6ac8a 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,25 +1,4 @@ - -This code of conduct applies to all spaces provided by the OpenSource project including in code, documentation, issue trackers, mailing lists, chat channels, wikis, blogs, social media and any other communication channels used by the project. - - -**Our open source communities endeavor to:** - -* Be Inclusive: We are committed to being a community where everyone can join and contribute. This means using inclusive and welcoming language. -* Be Welcoming: We are committed to maintaining a safe space for everyone to be able to contribute. -* Be Respectful: We are committed to encouraging differing viewpoints, accepting constructive criticism and work collaboratively towards decisions that help the project grow. Disrespectful and unacceptable behavior will not be tolerated. -* Be Collaborative: We are committed to supporting what is best for our community and users. When we build anything for the benefit of the project, we should document the work we do and communicate to others on how this affects their work. - - -**Our Responsibility. As contributors, members, or bystanders we each individually have the responsibility to behave professionally and respectfully at all times. Disrespectful and unacceptable behaviors include, but are not limited to:** - -* The use of violent threats, abusive, discriminatory, or derogatory language; -* Offensive comments related to gender, gender identity and expression, sexual orientation, disability, mental illness, race, political or religious affiliation; -* Posting of sexually explicit or violent content; -* The use of sexualized language and unwelcome sexual attention or advances; -* Public or private harassment of any kind; -* Publishing private information, such as physical or electronic address, without permission; -* Other conduct which could reasonably be considered inappropriate in a professional setting; -* Advocating for or encouraging any of the above behaviors. -* Enforcement and Reporting Code of Conduct Issues: - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported. [Contact us](mailto:opensource-codeofconduct@amazon.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. \ No newline at end of file +## Code of Conduct +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. \ No newline at end of file diff --git a/bwctest.sh b/bwctest.sh index def139795a4b..fce7173a4b19 100755 --- a/bwctest.sh +++ b/bwctest.sh @@ -13,7 +13,7 @@ set -e -DEFAULT_VERSIONS="osd-2.0.0," +DEFAULT_VERSIONS="osd-2.0.0,osd-2.1.0" function usage() { echo "" diff --git a/cypress/integration/with-security/check_filter_and_query.js b/cypress/integration/with-security/check_filter_and_query.js index 0055e5c078ed..e1a486dcfce5 100644 --- a/cypress/integration/with-security/check_filter_and_query.js +++ b/cypress/integration/with-security/check_filter_and_query.js @@ -12,6 +12,8 @@ import { const miscUtils = new MiscUtils(cy); const commonUI = new CommonUI(cy); const loginPage = new LoginPage(cy); +const startDate = 'Nov 1, 2016 @ 00:00:00.000'; +const endDate = `Dec 31, ${new Date().getFullYear()} @ 00:00:00.000`; describe('check dashboards filter and query', () => { beforeEach(() => { @@ -66,7 +68,7 @@ describe('check dashboards filter and query', () => { .find('[class="osdSavedQueryListItem__labelText"]') .should('have.text', 'test-query') .click(); - commonUI.setDateRange('Dec 1, 2021 @ 00:00:00.000', 'Jan 1, 2021 @ 00:00:00.000'); + commonUI.setDateRange(endDate, startDate); //[Logs] vistor chart should show osx 100% cy.get('[data-title="[Logs] Visitors by OS"]') diff --git a/cypress/integration/without-security/check_filter_and_query.js b/cypress/integration/without-security/check_filter_and_query.js index 30911d05ba7e..65b9aaf4a98b 100644 --- a/cypress/integration/without-security/check_filter_and_query.js +++ b/cypress/integration/without-security/check_filter_and_query.js @@ -10,6 +10,8 @@ import { const miscUtils = new MiscUtils(cy); const commonUI = new CommonUI(cy); +const startDate = 'Nov 1, 2016 @ 00:00:00.000'; +const endDate = `Dec 31, ${new Date().getFullYear()} @ 00:00:00.000`; describe('check dashboards filter and query', () => { beforeEach(() => { @@ -56,7 +58,7 @@ describe('check dashboards filter and query', () => { .find('[class="osdSavedQueryListItem__labelText"]') .should('have.text', 'test-query') .click(); - commonUI.setDateRange('Dec 1, 2021 @ 00:00:00.000', 'Jan 1, 2021 @ 00:00:00.000'); + commonUI.setDateRange(endDate, startDate); //[Logs] vistor chart should show osx 100% cy.get('[data-title="[Logs] Visitors by OS"]') diff --git a/cypress/test-data/with-security/osd-2.1.0.tar.gz b/cypress/test-data/with-security/osd-2.1.0.tar.gz new file mode 100644 index 000000000000..c1028e5705a0 Binary files /dev/null and b/cypress/test-data/with-security/osd-2.1.0.tar.gz differ diff --git a/cypress/test-data/without-security/osd-2.1.0.tar.gz b/cypress/test-data/without-security/osd-2.1.0.tar.gz new file mode 100644 index 000000000000..642bca840e60 Binary files /dev/null and b/cypress/test-data/without-security/osd-2.1.0.tar.gz differ diff --git a/packages/osd-std/src/__snapshots__/validate_object.test.ts.snap b/packages/osd-std/src/__snapshots__/validate_object.test.ts.snap new file mode 100644 index 000000000000..937e040c771e --- /dev/null +++ b/packages/osd-std/src/__snapshots__/validate_object.test.ts.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`can't submit {"__proto__":null} 1`] = `"'__proto__' is an invalid key"`; + +exports[`can't submit {"constructor":{"prototype":null}} 1`] = `"'constructor.prototype' is an invalid key"`; + +exports[`can't submit {"foo":{"__proto__":true}} 1`] = `"'__proto__' is an invalid key"`; + +exports[`can't submit {"foo":{"bar":{"__proto__":{}}}} 1`] = `"'__proto__' is an invalid key"`; + +exports[`can't submit {"foo":{"bar":{"constructor":{"prototype":null}}}} 1`] = `"'constructor.prototype' is an invalid key"`; + +exports[`can't submit {"foo":{"constructor":{"prototype":null}}} 1`] = `"'constructor.prototype' is an invalid key"`; diff --git a/packages/osd-std/src/index.ts b/packages/osd-std/src/index.ts index 3f2db99efd5d..49902a2c2479 100644 --- a/packages/osd-std/src/index.ts +++ b/packages/osd-std/src/index.ts @@ -38,4 +38,5 @@ export { withTimeout } from './promise'; export { isRelativeUrl, modifyUrl, getUrlOrigin, URLMeaningfulParts } from './url'; export { unset } from './unset'; export { getFlattenedObject } from './get_flattened_object'; +export { validateObject } from './validate_object'; export * from './rxjs_7'; diff --git a/packages/osd-std/src/validate_object.test.ts b/packages/osd-std/src/validate_object.test.ts new file mode 100644 index 000000000000..4bf3d04302e4 --- /dev/null +++ b/packages/osd-std/src/validate_object.test.ts @@ -0,0 +1,65 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { validateObject } from './validate_object'; + +test(`fails on circular references`, () => { + const foo: Record = {}; + foo.myself = foo; + + expect(() => + validateObject({ + payload: foo, + }) + ).toThrowErrorMatchingInlineSnapshot(`"circular reference detected"`); +}); + +[ + { + foo: true, + bar: '__proto__', + baz: 1.1, + qux: undefined, + quux: () => null, + quuz: Object.create(null), + }, + { + foo: { + foo: true, + bar: '__proto__', + baz: 1.1, + qux: undefined, + quux: () => null, + quuz: Object.create(null), + }, + }, + { constructor: { foo: { prototype: null } } }, + { prototype: { foo: { constructor: null } } }, +].forEach((value) => { + ['headers', 'payload', 'query', 'params'].forEach((property) => { + const obj = { + [property]: value, + }; + test(`can submit ${JSON.stringify(obj)}`, () => { + expect(() => validateObject(obj)).not.toThrowError(); + }); + }); +}); + +// if we use the object literal syntax to create the following values, we end up +// actually reassigning the __proto__ which makes it be a non-enumerable not-own property +// which isn't what we want to test here +[ + JSON.parse(`{ "__proto__": null }`), + JSON.parse(`{ "foo": { "__proto__": true } }`), + JSON.parse(`{ "foo": { "bar": { "__proto__": {} } } }`), + JSON.parse(`{ "constructor": { "prototype" : null } }`), + JSON.parse(`{ "foo": { "constructor": { "prototype" : null } } }`), + JSON.parse(`{ "foo": { "bar": { "constructor": { "prototype" : null } } } }`), +].forEach((value) => { + test(`can't submit ${JSON.stringify(value)}`, () => { + expect(() => validateObject(value)).toThrowErrorMatchingSnapshot(); + }); +}); diff --git a/packages/osd-std/src/validate_object.ts b/packages/osd-std/src/validate_object.ts new file mode 100644 index 000000000000..96b42050a361 --- /dev/null +++ b/packages/osd-std/src/validate_object.ts @@ -0,0 +1,66 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +interface StackItem { + value: any; + previousKey: string | null; +} + +// we have to do Object.prototype.hasOwnProperty because when you create an object using +// Object.create(null), and I assume other methods, you get an object without a prototype, +// so you can't use current.hasOwnProperty +const hasOwnProperty = (obj: any, property: string) => + Object.prototype.hasOwnProperty.call(obj, property); + +const isObject = (obj: any) => typeof obj === 'object' && obj !== null; + +// we're using a stack instead of recursion so we aren't limited by the call stack +export function validateObject(obj: any) { + if (!isObject(obj)) { + return; + } + + const stack: StackItem[] = [ + { + value: obj, + previousKey: null, + }, + ]; + const seen = new WeakSet([obj]); + + while (stack.length > 0) { + const { value, previousKey } = stack.pop()!; + + if (!isObject(value)) { + continue; + } + + if (hasOwnProperty(value, '__proto__')) { + throw new Error(`'__proto__' is an invalid key`); + } + + if (hasOwnProperty(value, 'prototype') && previousKey === 'constructor') { + throw new Error(`'constructor.prototype' is an invalid key`); + } + + // iterating backwards through an array is reportedly more performant + const entries = Object.entries(value); + for (let i = entries.length - 1; i >= 0; --i) { + const [key, childValue] = entries[i]; + if (isObject(childValue)) { + if (seen.has(childValue)) { + throw new Error('circular reference detected'); + } + + seen.add(childValue); + } + + stack.push({ + value: childValue, + previousKey: key, + }); + } + } +} diff --git a/src/core/README.md b/src/core/README.md index f38b9b49db86..3577813e17a0 100644 --- a/src/core/README.md +++ b/src/core/README.md @@ -4,11 +4,11 @@ Core is a set of systems (frontend, backend etc.) that OpenSearch Dashboards and ## Plugin development Core Plugin API Documentation: - - [Core Public API](/docs/development/core/public/opensearch-dashboards-plugin-core-public.md) - - [Core Server API](/docs/development/core/server/opensearch-dashboards-plugin-core-server.md) + - [Core Public API](../core/public/public.api.md) + - [Core Server API](../core/server/server.api.md) - [Conventions for Plugins](./CONVENTIONS.md) - [Testing OpenSearch Dashboards Plugins](./TESTING.md) - - [OpenSearch Dashboards Platform Plugin API](./docs/developer/architecture/kibana-platform-plugin-api.asciidoc ) + - [OpenSearch Dashboards Platform Plugin API](./PRINCIPLES.md) Internal Documentation: - [Saved Objects Migrations](./server/saved_objects/migrations/README.md) diff --git a/src/core/public/_variables.scss b/src/core/public/_variables.scss index 9d8077b50ad4..b36b21eb667c 100644 --- a/src/core/public/_variables.scss +++ b/src/core/public/_variables.scss @@ -1,3 +1,6 @@ @import "@elastic/eui/src/global_styling/variables/header"; $osdHeaderOffset: $euiHeaderHeightCompensation; +$osdHeaderBreadcrumbBlueBackground: #b9d9eb; +$osdHeaderBreadcrumbGrayBackground: #d9e1e2; +$osdHeaderBreadcrumbCollapsedLink: #002a3a; diff --git a/src/core/public/chrome/README.md b/src/core/public/chrome/README.md new file mode 100644 index 000000000000..6ec765a3bb0b --- /dev/null +++ b/src/core/public/chrome/README.md @@ -0,0 +1,130 @@ + +## Chrome Service + +- [About!](#about-) +- [Nav Controls Service](#navcontrolsservice-) +- [Nav Links Service](#navlinksservice-) +- [Recently Accessed Service](#recentlyaccessedservice-) +- [Doc Title Service](#doctitleservice-) +- [UI](#ui-) + +## About : +- **Signature** - `export interface ChromeStart` +The chrome service is a high level UI service that is part of CoreStart (Core services exposed to the Plugin start lifecycle) and offers other plugins a way to add navigation controls to the UI, edit the document title, manipuate navlinks on global header as well as edit the recent accessed tab. It consists of these sub-services/components. + +- NavControlsService : for registering new controls to be displayed in the navigation bar. +- NavLinksService : for manipulating nav links. +- RecentlyAccessedService : for recently accessed history. +- DocTitleService: for accessing and updating the document title +- UI : All the UI components,icons e.g. header, loaders. + + +- How to access ? add in interface `chrome: ChromeStart && chrome.servicesName => e.g chrome.docTitle.method` +- Where it is getting Registered/Executed ? [Staring Point](https://github.com/opensearch-project/OpenSearch-Dashboards/blob/2.1/src/core/public/rendering/rendering_service.tsx) +- How header component is getting rendered ? `const chromeUi = chrome.getHeaderComponent(); ` +- Chrome Methods [See chrome interface/methods](https://github.com/opensearch-project/OpenSearch-Dashboards/blob/2.1/src/core/public/chrome/chrome_service.tsx) + + +### NavControlsService : +- Interface : ChromeNavControls +- **Signature** - `navControls: ChromeNavControls` +- Registering new controller + +Example : +Register a left-side nav control rendered with React. + +```jsx +chrome.navControls.registerLeft({ + mount(targetDomElement) { + ReactDOM.mount(, targetDomElement); + return () => ReactDOM.unmountComponentAtNode(targetDomElement); + } +}) +``` +### NavLinksService : +- Interface : ChromeNavLinks +- **Signature** - `navLinks: ChromeNavLinks` +- e.g. Method : + +Get an observable for a sorted list of navlinks :- + +`getNavLinks$(): Observable>>` + +Get the state of a navlink at this point in time :- + +`get(id: string): ChromeNavLink | undefined` + +Get the current state of all navlinks :- + +`getAll(): Array>` + +Check whether or not a navlink exists :- + +`has(id: string): boolean` + +Remove all navlinks except the one matching the given id :- +`showOnly(id: string): void` + +- How to access + ###### Get the current state of all navlinks: + `core.chrome.navLinks.getAll()` + + ###### Get the state of a navlink at this point in time: + `core.chrome.navLinks.get()` + +### RecentlyAccessedService : + +- Interface : ChromeRecentlyAccessed +- Signature : ```recentlyAccessed: ChromeRecentlyAccessed``` +- The Recently viewed items are stored in the browser's local storage. +- You can go back to the recent search/visualization/dashboard , each item has (link,label,id) +- Methods : + +Adds a new item to the recently accessed history :- + +`add(link: string, label: string, id: string): void` + +Gets an Array of the current recently accessed history :- + +`get(): ChromeRecentlyAccessedHistoryItem[]` + +Gets an Observable of the array of recently accessed history :- + +`get$(): Observable` + +- How to access + ###### Adds a new item to the recently accessed history : + ` + chrome.recentlyAccessed.add('/app/map/1234', 'Map 1234', '1234'); + ` + ###### Gets an Array of the current recently accessed history : + ` + chrome.recentlyAccessed.get().forEach(console.log);; + ` + +### DocTitleService : +- Interface : ChromeDocTitle +- **Signature** - `docTitle: ChromeDocTitle` + ###### - How to change the title of the document + + + ```ts + chrome.docTitle.change('My application title') + chrome.docTitle.change(['My application', 'My section']) + ``` +### UI : +###### consists of tsx/scss files && renders UI components from css Library e.g `````` + +###### Adding/overriding existing css : +- Create scss file and define class e.g .osdCustomClass{} +- pass className prop to UI component. + e.g ` ` + +###### UI Components : + - HeaderBreadcrumbs : Props + + - responsive:boolean Hides extra (above the max) breadcrumbs under a collapsed item as the window gets smaller. + - max: Collapses the inner items past the maximum set here into a single ellipses item. + - breadcrumbs : The array of individual Breadcrumb items + + \ No newline at end of file diff --git a/src/core/public/chrome/public/assets/round_filter.svg b/src/core/public/chrome/public/assets/round_filter.svg new file mode 100644 index 000000000000..84cf36058553 --- /dev/null +++ b/src/core/public/chrome/public/assets/round_filter.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file 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 a3ee7aec2cd1..b2af9f57e011 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 @@ -3595,11 +3595,13 @@ exports[`Header handles visibility and lock changes 1`] = ` breadcrumbs={ Array [ Object { + "className": "osdBreadcrumbs", "data-test-subj": "breadcrumb first last", "text": "test", }, ] } + className="osdHeaderBreadcrumbs" data-test-subj="breadcrumbs" max={10} > @@ -3607,25 +3609,26 @@ exports[`Header handles visibility and lock changes 1`] = ` breadcrumbs={ Array [ Object { + "className": "osdBreadcrumbs", "data-test-subj": "breadcrumb first last", "text": "test", }, ] } - className="euiHeaderBreadcrumbs" + className="euiHeaderBreadcrumbs osdHeaderBreadcrumbs" data-test-subj="breadcrumbs" max={10} truncate={true} >