diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx index edad877782c1f..098221eed6587 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx +++ b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx @@ -19,6 +19,7 @@ import { i18n } from '@kbn/i18n'; import { getStateFromKbnUrl, setStateToKbnUrl, unhashUrl } from '@kbn/kibana-utils-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; +import { LocatorPublic } from '@kbn/share-plugin/common'; import { convertPanelMapToPanelsArray } from '../../../../common/lib/dashboard_panel_converters'; import { DashboardPanelMap } from '../../../../common'; import { @@ -272,5 +273,11 @@ export function ShowShareModal({ ? shareModalStrings.getSnapshotShareWarning() : undefined, toasts: coreServices.notifications.toasts, + shareableUrlLocatorParams: { + locator: shareService.url.locators.get( + DASHBOARD_APP_LOCATOR + ) as LocatorPublic, + params: locatorParams, + }, }); } diff --git a/src/platform/plugins/shared/share/public/components/tabs/link/index.tsx b/src/platform/plugins/shared/share/public/components/tabs/link/index.tsx index 3b602d43c1dfd..b456134414c9f 100644 --- a/src/platform/plugins/shared/share/public/components/tabs/link/index.tsx +++ b/src/platform/plugins/shared/share/public/components/tabs/link/index.tsx @@ -108,7 +108,7 @@ const LinkTabContent: ILinkTab['content'] = ({ state, dispatch }) => { export const linkTab: ILinkTab = { id: 'link', name: i18n.translate('share.contextMenu.permalinksTab', { - defaultMessage: 'Links', + defaultMessage: 'Link', }), description: i18n.translate('share.dashboard.link.description', { defaultMessage: 'Share a direct link to this search.', diff --git a/src/platform/plugins/shared/share/public/components/tabs/link/link_content.tsx b/src/platform/plugins/shared/share/public/components/tabs/link/link_content.tsx index ca15a71bc2828..937cb33224d48 100644 --- a/src/platform/plugins/shared/share/public/components/tabs/link/link_content.tsx +++ b/src/platform/plugins/shared/share/public/components/tabs/link/link_content.tsx @@ -14,12 +14,13 @@ import { EuiFlexItem, EuiForm, EuiSpacer, - EuiText, + EuiSwitchEvent, EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useCallback, useState, useRef, useEffect } from 'react'; +import { TimeTypeSection } from './time_type_section'; import type { IShareContext, ShareContextObjectTypeConfig } from '../../context'; type LinkProps = Pick< @@ -53,9 +54,11 @@ export const LinkContent = ({ const [snapshotUrl, setSnapshotUrl] = useState(''); const [isTextCopied, setTextCopied] = useState(false); const [isLoading, setIsLoading] = useState(false); + const [isAbsoluteTime, setIsAbsoluteTime] = useState(true); const urlParamsRef = useRef(undefined); const urlToCopy = useRef(undefined); const copiedTextToolTipCleanupIdRef = useRef>(); + const timeRange = shareableUrlLocatorParams?.params?.timeRange; const getUrlWithUpdatedParams = useCallback((tempUrl: string): string => { const urlWithUpdatedParams = urlParamsRef.current @@ -83,12 +86,15 @@ export const LinkContent = ({ const shortUrlService = urlService.shortUrls.get(null); if (shareableUrlLocatorParams) { - const shortUrl = await shortUrlService.createWithLocator(shareableUrlLocatorParams); + const shortUrl = await shortUrlService.createWithLocator( + shareableUrlLocatorParams, + isAbsoluteTime + ); return shortUrl.locator.getUrl(shortUrl.params, { absolute: true }); } else { - return (await shortUrlService.createFromLongUrl(snapshotUrl)).url; + return (await shortUrlService.createFromLongUrl(snapshotUrl, isAbsoluteTime)).url; } - }, [shareableUrlLocatorParams, urlService.shortUrls, snapshotUrl]); + }, [shareableUrlLocatorParams, urlService, snapshotUrl, isAbsoluteTime]); const copyUrlHelper = useCallback(async () => { setIsLoading(true); @@ -117,17 +123,21 @@ export const LinkContent = ({ }, [snapshotUrl, delegatedShareUrlHandler, allowShortUrl, createShortUrl]); const { draftModeCallOut: DraftModeCallout } = objectConfig; + const changeTimeType = (e: EuiSwitchEvent) => { + setIsAbsoluteTime(e.target.checked); + if (urlToCopy?.current && e.target.checked !== isAbsoluteTime) { + urlToCopy.current = undefined; + } + }; return ( <> - - - + {isDirty && DraftModeCallout && ( <> diff --git a/src/platform/plugins/shared/share/public/components/tabs/link/time_type_section.test.tsx b/src/platform/plugins/shared/share/public/components/tabs/link/time_type_section.test.tsx new file mode 100644 index 0000000000000..8de87235845b2 --- /dev/null +++ b/src/platform/plugins/shared/share/public/components/tabs/link/time_type_section.test.tsx @@ -0,0 +1,146 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { ComponentProps } from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { render, screen } from '@testing-library/react'; +import { TimeTypeSection } from './time_type_section'; +import * as timeUtils from '../../../lib/time_utils'; + +const renderComponent = (props: ComponentProps) => { + render( + + + + ); +}; + +describe('TimeTypeSection', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest + .spyOn(timeUtils, 'convertRelativeTimeStringToAbsoluteTimeDate') + .mockReturnValue(new Date()); + jest + .spyOn(timeUtils, 'getRelativeTimeValueAndUnitFromTimeString') + .mockImplementation((time) => { + if (time === 'now') return { value: 0, unit: 'second', roundingUnit: undefined }; + if (time === 'now-1m') return { value: -1, unit: 'minute', roundingUnit: undefined }; + if (time === 'now-30m') return { value: -30, unit: 'minute', roundingUnit: undefined }; + return { value: 0, unit: 'second', roundingUnit: undefined }; + }); + jest.spyOn(timeUtils, 'isTimeRangeAbsoluteTime').mockReturnValue(false); + }); + + it('renders null when timeRange is not provided', () => { + const changeTimeType = jest.fn(); + + renderComponent({ + isAbsoluteTime: false, + changeTimeType, + }); + + const timeRangeSwitch = screen.queryByRole('switch'); + + expect(timeRangeSwitch).not.toBeInTheDocument(); + }); + + it('renders with absolute time range', () => { + const timeRange = { from: '2022-01-01T00:00:00.000Z', to: '2022-01-02T00:00:00.000Z' }; + const changeTimeType = jest.fn(); + + renderComponent({ + timeRange, + isAbsoluteTime: true, + changeTimeType, + }); + + const timeRangeSwitch = screen.getByRole('switch'); + + expect(timeRangeSwitch).toBeChecked(); + + const absoluteTimeInfoText = screen.getByTestId('absoluteTimeInfoText'); + + expect(absoluteTimeInfoText).toBeInTheDocument(); + }); + + it('renders with relative time range (from now to specific time)', () => { + const timeRange = { from: 'now', to: 'now+15m' }; + const changeTimeType = jest.fn(); + + renderComponent({ + timeRange, + isAbsoluteTime: false, + changeTimeType, + }); + + const timeRangeSwitch = screen.getByRole('switch'); + + expect(timeRangeSwitch).not.toBeChecked(); + + const relativeTimeFromNowInfoText = screen.getByTestId('relativeTimeInfoTextFromNow'); + + expect(relativeTimeFromNowInfoText).toBeInTheDocument(); + }); + + it('renders with relative time range (from specific time to now)', () => { + const timeRange = { from: 'now-30m', to: 'now' }; + const changeTimeType = jest.fn(); + + renderComponent({ + timeRange, + isAbsoluteTime: false, + changeTimeType, + }); + + const timeRangeSwitch = screen.getByRole('switch'); + + expect(timeRangeSwitch).not.toBeChecked(); + + const relativeTimeToNowInfoText = screen.getByTestId('relativeTimeInfoTextToNow'); + + expect(relativeTimeToNowInfoText).toBeInTheDocument(); + }); + + it('renders with relative time range (between two relative times)', () => { + const timeRange = { from: 'now-30m', to: 'now-1m' }; + const changeTimeType = jest.fn(); + + renderComponent({ + timeRange, + isAbsoluteTime: false, + changeTimeType, + }); + + const timeRangeSwitch = screen.getByRole('switch'); + + expect(timeRangeSwitch).not.toBeChecked(); + + const relativeTimeInfoText = screen.getByTestId('relativeTimeInfoTextDefault'); + + expect(relativeTimeInfoText).toBeInTheDocument(); + }); + + it('disables switch when timeRange is already absolute', () => { + const timeRange = { from: '2022-01-01T00:00:00.000Z', to: '2022-01-02T00:00:00.000Z' }; + const changeTimeType = jest.fn(); + + jest.spyOn(timeUtils, 'isTimeRangeAbsoluteTime').mockReturnValue(true); + + renderComponent({ + timeRange, + isAbsoluteTime: false, + changeTimeType, + }); + + const timeRangeSwitch = screen.queryByRole('switch'); + + expect(timeRangeSwitch).not.toBeInTheDocument(); + }); +}); diff --git a/src/platform/plugins/shared/share/public/components/tabs/link/time_type_section.tsx b/src/platform/plugins/shared/share/public/components/tabs/link/time_type_section.tsx new file mode 100644 index 0000000000000..01f3979f9b49a --- /dev/null +++ b/src/platform/plugins/shared/share/public/components/tabs/link/time_type_section.tsx @@ -0,0 +1,214 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { + EuiCallOut, + EuiSpacer, + EuiSwitch, + EuiSwitchEvent, + EuiText, + useEuiTheme, +} from '@elastic/eui'; +import React, { ReactNode, useEffect, useState } from 'react'; +import { FormattedMessage, FormattedRelativeTime, FormattedDate } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import { + convertRelativeTimeStringToAbsoluteTimeDate, + getRelativeTimeValueAndUnitFromTimeString, + isTimeRangeAbsoluteTime, +} from '../../../lib/time_utils'; + +const BoldText = ({ children }: { children: ReactNode }) => { + const { euiTheme } = useEuiTheme(); + const boldText = css` + font-weight: ${euiTheme.font.weight.bold}; + `; + + return ( + + {children} + + ); +}; + +const AbsoluteTimeText = ({ date }: { date: string }) => { + const absoluteDate = convertRelativeTimeStringToAbsoluteTimeDate(date); + + return ( + + + + ); +}; + +const RelativeTimeText = ({ + value, + unit, + roundingUnit, +}: { + value?: number; + unit?: string; + roundingUnit?: string; +}) => ( + + + {roundingUnit && ( + + )} + +); + +interface TimeRange { + from: string; + to: string; +} + +interface Props { + timeRange?: TimeRange; + isAbsoluteTime: boolean; + changeTimeType: (e: EuiSwitchEvent) => void; +} + +const getRelativeTimeText = (timeRange: TimeRange) => { + // FormattedRelativeTime doesn't support "now" as a value, it will render "0 seconds" instead + const from = getRelativeTimeValueAndUnitFromTimeString(timeRange.from); + const to = getRelativeTimeValueAndUnitFromTimeString(timeRange.to); + + if (!from?.value) { + return ( +
+ + ), + bold: (chunks) => {chunks}, + }} + /> +
+ ); + } + + if (!to?.value) { + return ( +
+ + ), + bold: (chunks) => {chunks}, + }} + /> +
+ ); + } + + return ( +
+ + ), + to: ( + + ), + }} + /> +
+ ); +}; + +export const TimeTypeSection = ({ timeRange, isAbsoluteTime, changeTimeType }: Props) => { + const [isAbsoluteTimeByDefault, setIsAbsoluteTimeByDefault] = useState(false); + + useEffect(() => { + setIsAbsoluteTimeByDefault(isTimeRangeAbsoluteTime(timeRange)); + }, [timeRange]); + + if (!timeRange) { + return null; + } + + return ( + <> + {!isAbsoluteTimeByDefault && ( + <> + + + + )} + + {isAbsoluteTime ? ( +
+ , + to: , + }} + /> +
+ ) : ( + getRelativeTimeText(timeRange) + )} +
+ + {isAbsoluteTimeByDefault && ( + + )} + + ); +}; diff --git a/src/platform/plugins/shared/share/public/lib/time_utils.test.ts b/src/platform/plugins/shared/share/public/lib/time_utils.test.ts new file mode 100644 index 0000000000000..5304f50bf5630 --- /dev/null +++ b/src/platform/plugins/shared/share/public/lib/time_utils.test.ts @@ -0,0 +1,141 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { + convertRelativeTimeStringToAbsoluteTimeDate, + convertRelativeTimeStringToAbsoluteTimeString, + getRelativeTimeValueAndUnitFromTimeString, + isTimeRangeAbsoluteTime, +} from './time_utils'; + +describe('Time Utils', () => { + describe('getRelativeTimeValueAndUnitFromTimeString', () => { + it('returns correct value and unit', () => { + const date = 'now-1m'; + const date2 = 'now+1h'; + + const result = getRelativeTimeValueAndUnitFromTimeString(date); + expect(result).toEqual({ value: -1, unit: 'minute' }); + const result2 = getRelativeTimeValueAndUnitFromTimeString(date2); + expect(result2).toEqual({ value: 1, unit: 'hour' }); + }); + + it('returns undefined for invalid date', () => { + const date = 'invalid-date'; + + const result = getRelativeTimeValueAndUnitFromTimeString(date); + expect(result).toBeUndefined(); + }); + + it('returns undefined for empty date', () => { + const date = ''; + + const result = getRelativeTimeValueAndUnitFromTimeString(date); + expect(result).toBeUndefined(); + }); + + it('returns undefined for date without relative time', () => { + const date = '2023-10-01T00:00:00Z'; + + const result = getRelativeTimeValueAndUnitFromTimeString(date); + expect(result).toBeUndefined(); + }); + + it('returns rounding unit if its valid', () => { + const date = 'now-1m/h'; + + const result = getRelativeTimeValueAndUnitFromTimeString(date); + expect(result).toEqual({ value: -1, unit: 'minute', roundingUnit: 'hour' }); + }); + + it('returns undefined for invalid rounding unit', () => { + const date = 'now-1m/invalid-unit'; + + const result = getRelativeTimeValueAndUnitFromTimeString(date); + expect(result).toEqual({ value: -1, unit: 'minute', roundingUnit: undefined }); + }); + }); + + describe('convertRelativeTimeStringToAbsoluteTimeDate', () => { + it('returns absolute date for relative date', () => { + const date = 'now-1m'; + + const result = convertRelativeTimeStringToAbsoluteTimeDate(date); + expect(result).toBeInstanceOf(Date); + }); + + it('returns absolute date for absolute date', () => { + const date = '2023-10-01T00:00:00Z'; + + const result = convertRelativeTimeStringToAbsoluteTimeDate(date); + expect(result).toBeInstanceOf(Date); + }); + + it('returns undefined for invalid date', () => { + const date = 'invalid-date'; + + const result = convertRelativeTimeStringToAbsoluteTimeDate(date); + expect(result).toBeUndefined(); + }); + }); + + describe('isTimeRangeAbsoluteTime', () => { + it('returns true for absolute time range', () => { + const timeRange = { from: '2023-10-01T00:00:00Z', to: '2023-10-02T00:00:00Z' }; + + const result = isTimeRangeAbsoluteTime(timeRange); + expect(result).toBe(true); + }); + + it('returns false for time range with one relative date', () => { + const timeRange = { from: 'now-1m', to: '2023-10-02T00:00:00Z' }; + + const result = isTimeRangeAbsoluteTime(timeRange); + expect(result).toBe(false); + }); + + it('returns false for time range with both relative dates', () => { + const timeRange = { from: 'now-1m', to: 'now-2m' }; + + const result = isTimeRangeAbsoluteTime(timeRange); + expect(result).toBe(false); + }); + }); + + describe('convertRelativeTimeStringToAbsoluteTimeString', () => { + it('returns absolute date string for relative date', () => { + const date = 'now-1m'; + + // Freeze current time to a fixed point + const fixedNow = new Date('2025-04-16T19:14:54.027Z'); + jest.spyOn(Date, 'now').mockImplementation(() => fixedNow.getTime()); + + const result = convertRelativeTimeStringToAbsoluteTimeString(date); + + expect(result).toBe('2025-04-16T19:13:54.027Z'); + + // Restore Date.now() + jest.restoreAllMocks(); + }); + + it('returns absolute date string for absolute date', () => { + const date = '2023-10-01T00:00:00.000Z'; + + const result = convertRelativeTimeStringToAbsoluteTimeString(date); + expect(result).toBe('2023-10-01T00:00:00.000Z'); + }); + + it('returns original date string for invalid date', () => { + const date = 'invalid-date'; + + const result = convertRelativeTimeStringToAbsoluteTimeString(date); + expect(result).toBe(date); + }); + }); +}); diff --git a/src/platform/plugins/shared/share/public/lib/time_utils.tsx b/src/platform/plugins/shared/share/public/lib/time_utils.tsx new file mode 100644 index 0000000000000..ebb583edf8958 --- /dev/null +++ b/src/platform/plugins/shared/share/public/lib/time_utils.tsx @@ -0,0 +1,55 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import dateMath from '@kbn/datemath'; + +const unitMap = new Map([ + ['s', 'second'], + ['m', 'minute'], + ['h', 'hour'], + ['d', 'day'], + ['w', 'week'], + ['M', 'month'], + ['y', 'year'], +]); + +export const getRelativeTimeValueAndUnitFromTimeString = (dateString?: string) => { + if (!dateString) return; + + const mathPart = dateString.split('/')[0]; // Ignore rounding part for matching + const roundingPart = dateString.includes('/') ? dateString.split('/')[1] : undefined; + + const match = mathPart.match(/^now([+-]\d+)([smhdwMy])$/); + if (!match) return; + + const [, signAndNumber, unit] = match; + + return { + value: Number(signAndNumber), + unit: unitMap.get(unit), + roundingUnit: roundingPart ? unitMap.get(roundingPart) : undefined, + }; +}; + +export const convertRelativeTimeStringToAbsoluteTimeDate = (dateString?: string) => { + if (!dateString) return; + const valueParsed = dateMath.parse(dateString); + + return valueParsed?.isValid() ? valueParsed.toDate() : undefined; +}; + +export const convertRelativeTimeStringToAbsoluteTimeString = (dateString?: string) => { + if (!dateString) return dateString; + const valueParsed = dateMath.parse(dateString); + + return valueParsed && valueParsed.isValid() ? valueParsed.toISOString() : dateString; +}; + +export const isTimeRangeAbsoluteTime = (timeRange?: { from: string; to: string }) => + !(timeRange?.from?.includes('now') || timeRange?.to?.includes('now')); diff --git a/src/platform/plugins/shared/share/public/url_service/short_urls/short_url_client.ts b/src/platform/plugins/shared/share/public/url_service/short_urls/short_url_client.ts index 1cf087ef172b6..92ae201926903 100644 --- a/src/platform/plugins/shared/share/public/url_service/short_urls/short_url_client.ts +++ b/src/platform/plugins/shared/share/public/url_service/short_urls/short_url_client.ts @@ -9,6 +9,7 @@ import { parse as parseUrl } from 'url'; import type { SerializableRecord } from '@kbn/utility-types'; +import { convertRelativeTimeStringToAbsoluteTimeString } from '../../lib/time_utils'; import { LegacyShortUrlLocatorParams, LEGACY_SHORT_URL_LOCATOR_ID, @@ -75,9 +76,26 @@ export class BrowserShortUrlClient implements IShortUrlClient { } public async createWithLocator

( - params: ShortUrlCreateParams

+ params: ShortUrlCreateParams

, + isAbsoluteTime?: boolean ): Promise> { - const result = await this.create(params); + const getUpdatedParams = (inputParams: ShortUrlCreateParams

) => { + const timeRange = inputParams.params?.timeRange as SerializableRecord; + if (isAbsoluteTime && timeRange?.from && timeRange?.to) + return { + ...inputParams, + params: { + ...inputParams.params, + timeRange: { + from: convertRelativeTimeStringToAbsoluteTimeString(timeRange.from as string), + to: convertRelativeTimeStringToAbsoluteTimeString(timeRange.to as string), + }, + }, + }; + return inputParams; + }; + + const result = await this.create(getUpdatedParams(params)); const redirectLocator = this.dependencies.locators.get( SHORT_URL_REDIRECT_LOCATOR )!; @@ -92,7 +110,10 @@ export class BrowserShortUrlClient implements IShortUrlClient { }; } - public async createFromLongUrl(longUrl: string): Promise { + public async createFromLongUrl( + longUrl: string, + isAbsoluteTime?: boolean + ): Promise { const parsedUrl = parseUrl(longUrl); if (!parsedUrl || !parsedUrl.path) { @@ -110,12 +131,16 @@ export class BrowserShortUrlClient implements IShortUrlClient { throw new Error(`Locator "${LEGACY_SHORT_URL_LOCATOR_ID}" not found`); } - const result = await this.createWithLocator({ - locator, - params: { - url: relativeUrl, + const result = await this.createWithLocator( + { + locator, + params: { + url: relativeUrl, + }, }, - }); + isAbsoluteTime + ); + const shortUrl = await result.locator.getUrl(result.params, { absolute: true }); return { diff --git a/src/platform/plugins/shared/share/tsconfig.json b/src/platform/plugins/shared/share/tsconfig.json index 409549640ef7c..839a25b846a3a 100644 --- a/src/platform/plugins/shared/share/tsconfig.json +++ b/src/platform/plugins/shared/share/tsconfig.json @@ -26,6 +26,7 @@ "@kbn/core-i18n-browser-mocks", "@kbn/core-notifications-browser-mocks", "@kbn/core-user-profile-browser", + "@kbn/datemath", ], "exclude": [ "target/**/*", diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index ed7317ddedbae..ad3b20a61d1b7 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -7971,7 +7971,6 @@ "share.fileType": "Type de fichier", "share.link.copied": "Lien copié", "share.link.copyLinkButton": "Copier le lien", - "share.link.helpText": "Partager un lien direct vers ce {objectType}.", "share.link.warning.title": "Modifications non enregistrées", "share.modalContent.copyUrlButtonLabel": "Copier l'URL Post", "share.postURLWatcherMessage": "Copiez cette URL POST pour appeler la génération depuis l'extérieur de Kibana ou à partir de Watcher.", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index f291f81eb9035..2f3b0f063485c 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -7962,7 +7962,6 @@ "share.fileType": "ファイルタイプ", "share.link.copied": "リンクがコピーされました", "share.link.copyLinkButton": "リンクをコピー", - "share.link.helpText": "この{objectType}への直接リンクを共有します。", "share.link.warning.title": "保存されていない変更", "share.modalContent.copyUrlButtonLabel": "POST URLをコピー", "share.postURLWatcherMessage": "POST URLをコピーしてKibana外または旧Watcherから生成を呼び出します。", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index 8b9f7e735f1e9..a16e3301d7c01 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -7977,7 +7977,6 @@ "share.fileType": "文件类型", "share.link.copied": "已复制链接", "share.link.copyLinkButton": "复制链接", - "share.link.helpText": "共享指向此 {objectType} 的直接链接。", "share.link.warning.title": "未保存的更改", "share.modalContent.copyUrlButtonLabel": "复制 Post URL", "share.postURLWatcherMessage": "复制此 POST URL 以从 Kibana 外部或从 Watcher 调用生成。",