diff --git a/x-pack/solutions/search/plugins/search_getting_started/jest.config.js b/x-pack/solutions/search/plugins/search_getting_started/jest.config.js new file mode 100644 index 0000000000000..be2388afdf0ee --- /dev/null +++ b/x-pack/solutions/search/plugins/search_getting_started/jest.config.js @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../..', + roots: ['/x-pack/solutions/search/plugins/search_getting_started'], + coverageDirectory: + '/target/kibana-coverage/jest/x-pack/solutions/search/plugins/search_getting_started', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/solutions/search/plugins/search_getting_started/{public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/x-pack/solutions/search/plugins/search_getting_started/moon.yml b/x-pack/solutions/search/plugins/search_getting_started/moon.yml index d53610d2b272a..87b3b147eba98 100644 --- a/x-pack/solutions/search/plugins/search_getting_started/moon.yml +++ b/x-pack/solutions/search/plugins/search_getting_started/moon.yml @@ -49,6 +49,7 @@ tags: - prod - group-search - private + - jest-unit-tests fileGroups: src: - common/**/* @@ -56,4 +57,16 @@ fileGroups: - server/**/* - test/scout/**/* - '!target/**/*' -tasks: {} +tasks: + jest: + args: + - '--config' + - $projectRoot/jest.config.js + inputs: + - '@group(src)' + jestCI: + args: + - '--config' + - $projectRoot/jest.config.js + inputs: + - '@group(src)' diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/common/utils.test.ts b/x-pack/solutions/search/plugins/search_getting_started/public/common/utils.test.ts new file mode 100644 index 0000000000000..3133bcf55e7ce --- /dev/null +++ b/x-pack/solutions/search/plugins/search_getting_started/public/common/utils.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isNew } from './utils'; + +describe('isNew', () => { + const fakeNow = new Date('2025-02-18T12:00:00.000Z'); + + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(fakeNow); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('returns true when publishedAt is less than 30 days ago', () => { + const twentyNineDaysAgo = new Date('2025-01-20T12:00:00.000Z'); + expect(isNew(twentyNineDaysAgo)).toBe(true); + }); + + it('returns true when publishedAt is today', () => { + expect(isNew(fakeNow)).toBe(true); + }); + + it('returns false when publishedAt is more than 30 days ago', () => { + const thirtyOneDaysAgo = new Date('2025-01-18T12:00:00.000Z'); + expect(isNew(thirtyOneDaysAgo)).toBe(false); + }); + + it('returns false when publishedAt is exactly 30 days ago', () => { + const thirtyDaysAgo = new Date('2025-01-19T12:00:00.000Z'); + expect(isNew(thirtyDaysAgo)).toBe(false); + }); +}); diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/common/utils.ts b/x-pack/solutions/search/plugins/search_getting_started/public/common/utils.ts new file mode 100644 index 0000000000000..ea8e124fa0111 --- /dev/null +++ b/x-pack/solutions/search/plugins/search_getting_started/public/common/utils.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * + * @param publishedAt - The date the tutorial was published + * @returns true if the tutorial is new (less than 30 days old) + * This means the "New" badges will largely be timely for serverless customers only. + * https://docs.elastic.dev/kibana-dev-docs/serverless/release-overview#standard-release-cadence + * publishedAt should be set to around the date the tutorial is expected to land in production. + */ +export const isNew = (publishedAt: Date) => { + const thirtyDaysAgo = new Date(Date.now() - 1000 * 60 * 60 * 24 * 30); + return publishedAt > thirtyDaysAgo; +}; diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/components/tutorials/console_tutorials_group.tsx b/x-pack/solutions/search/plugins/search_getting_started/public/components/tutorials/console_tutorials_group.tsx index 423fd16671fa4..75b6ccedf46ef 100644 --- a/x-pack/solutions/search/plugins/search_getting_started/public/components/tutorials/console_tutorials_group.tsx +++ b/x-pack/solutions/search/plugins/search_getting_started/public/components/tutorials/console_tutorials_group.tsx @@ -20,10 +20,11 @@ import React, { useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; -import { sortBy } from 'lodash'; +import { orderBy } from 'lodash'; import { useKibana } from '../../hooks/use_kibana'; import { SearchGettingStartedSectionHeading } from '../section_heading'; import { useAssetBasePath } from '../../hooks/use_asset_base_path'; +import { isNew } from '../../common/utils'; interface TutorialMetadata { title: string; dataTestSubj: string; @@ -31,7 +32,7 @@ interface TutorialMetadata { request: string; image: string; buttonRef: React.RefObject; - isNew?: boolean; + publishedAt: Date; } const EXPAND_LIMIT = 3; @@ -63,6 +64,7 @@ export const ConsoleTutorialsGroup = () => { request: consoleTutorials.basics, image: `${assetBasePath}/search_window_illustration.svg`, buttonRef: React.createRef(), + publishedAt: new Date('2025-10-31'), }, { title: i18n.translate('xpack.searchGettingStarted.consoleTutorials.semanticTitle', { @@ -79,6 +81,7 @@ export const ConsoleTutorialsGroup = () => { request: consoleTutorials.semanticSearch, image: `${assetBasePath}/search_results_illustration.svg`, buttonRef: React.createRef(), + publishedAt: new Date('2025-10-31'), }, { title: i18n.translate('xpack.searchGettingStarted.consoleTutorials.esqlTitle', { @@ -92,6 +95,7 @@ export const ConsoleTutorialsGroup = () => { request: consoleTutorials.esql, image: `${assetBasePath}/search_observe_illustration.svg`, buttonRef: React.createRef(), + publishedAt: new Date('2025-10-31'), }, { title: i18n.translate('xpack.searchGettingStarted.consoleTutorials.agentBuilderTitle', { @@ -101,13 +105,13 @@ export const ConsoleTutorialsGroup = () => { description: i18n.translate( 'xpack.searchGettingStarted.consoleTutorials.agentBuilderDescription', { - defaultMessage: 'Learn how to use the Agent Builder to create and manage agents.', + defaultMessage: 'Learn how to use the Agent Builder APIs to create and manage agents.', } ), request: consoleTutorials.agentBuilder, image: `${assetBasePath}/search_task_automation.svg`, buttonRef: React.createRef(), - isNew: true, + publishedAt: new Date('2026-02-18'), }, { title: i18n.translate('xpack.searchGettingStarted.consoleTutorials.tsdsTitle', { @@ -121,10 +125,13 @@ export const ConsoleTutorialsGroup = () => { request: consoleTutorials.timeSeriesDataStreams, image: `${assetBasePath}/search_hourglass.svg`, buttonRef: React.createRef(), - isNew: true, + publishedAt: new Date('2026-02-04'), }, ]; - return sortBy(items, 'isNew').slice(0, expanded ? undefined : EXPAND_LIMIT); + return orderBy(items, ({ publishedAt }) => publishedAt.getTime(), ['desc']).slice( + 0, + expanded ? undefined : EXPAND_LIMIT + ); }, [assetBasePath, expanded]); return ( @@ -146,7 +153,7 @@ export const ConsoleTutorialsGroup = () => { hasBorder title={tutorial.title} betaBadgeProps={{ - label: tutorial.isNew + label: isNew(tutorial.publishedAt) ? i18n.translate('xpack.searchGettingStarted.consoleTutorials.badge', { defaultMessage: 'New', })