@atlaskit/link-datasource 7.12.2 → 7.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/dist/cjs/ui/assets-modal/search-container/loading-state.js +12 -1
  3. package/dist/cjs/ui/common/modal/basic-search-input/index.js +8 -3
  4. package/dist/cjs/ui/common/modal/mode-switcher/ModeSwitcher.compiled.css +1 -0
  5. package/dist/cjs/ui/common/modal/mode-switcher/ModeSwitcher.js +3 -1
  6. package/dist/cjs/ui/common/modal/popup-select/index.js +34 -5
  7. package/dist/cjs/ui/common/modal/popup-select/useInlinePopupLayout.js +123 -0
  8. package/dist/cjs/ui/datasource-table-view/datasourceTableView.js +9 -3
  9. package/dist/cjs/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.js +4 -1
  10. package/dist/cjs/ui/jira-issues-modal/basic-filters/ui/index.js +5 -1
  11. package/dist/cjs/ui/jira-issues-modal/jira-search-container/JiraSearchContainerWithWrappers.js +19 -0
  12. package/dist/cjs/ui/jira-issues-modal/jira-search-container/buildJQL.js +12 -2
  13. package/dist/cjs/ui/jira-issues-modal/jira-search-container/embedded.compiled.css +11 -0
  14. package/dist/cjs/ui/jira-issues-modal/jira-search-container/embedded.js +321 -0
  15. package/dist/cjs/ui/jira-issues-modal/jira-search-container/index.js +12 -0
  16. package/dist/cjs/ui/jira-issues-modal/jql-editor/index.js +9 -1
  17. package/dist/cjs/ui/jira-search-state/index.compiled.css +12 -0
  18. package/dist/cjs/ui/jira-search-state/index.js +90 -0
  19. package/dist/es2019/ui/assets-modal/search-container/loading-state.js +12 -1
  20. package/dist/es2019/ui/common/modal/basic-search-input/index.js +5 -2
  21. package/dist/es2019/ui/common/modal/mode-switcher/ModeSwitcher.compiled.css +1 -0
  22. package/dist/es2019/ui/common/modal/mode-switcher/ModeSwitcher.js +3 -1
  23. package/dist/es2019/ui/common/modal/popup-select/index.js +34 -4
  24. package/dist/es2019/ui/common/modal/popup-select/useInlinePopupLayout.js +103 -0
  25. package/dist/es2019/ui/datasource-table-view/datasourceTableView.js +8 -3
  26. package/dist/es2019/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.js +3 -1
  27. package/dist/es2019/ui/jira-issues-modal/basic-filters/ui/index.js +4 -1
  28. package/dist/es2019/ui/jira-issues-modal/jira-search-container/JiraSearchContainerWithWrappers.js +11 -0
  29. package/dist/es2019/ui/jira-issues-modal/jira-search-container/buildJQL.js +10 -2
  30. package/dist/es2019/ui/jira-issues-modal/jira-search-container/embedded.compiled.css +11 -0
  31. package/dist/es2019/ui/jira-issues-modal/jira-search-container/embedded.js +283 -0
  32. package/dist/es2019/ui/jira-issues-modal/jira-search-container/index.js +12 -0
  33. package/dist/es2019/ui/jira-issues-modal/jql-editor/index.js +7 -1
  34. package/dist/es2019/ui/jira-search-state/index.compiled.css +12 -0
  35. package/dist/es2019/ui/jira-search-state/index.js +82 -0
  36. package/dist/esm/ui/assets-modal/search-container/loading-state.js +12 -1
  37. package/dist/esm/ui/common/modal/basic-search-input/index.js +8 -3
  38. package/dist/esm/ui/common/modal/mode-switcher/ModeSwitcher.compiled.css +1 -0
  39. package/dist/esm/ui/common/modal/mode-switcher/ModeSwitcher.js +3 -1
  40. package/dist/esm/ui/common/modal/popup-select/index.js +35 -5
  41. package/dist/esm/ui/common/modal/popup-select/useInlinePopupLayout.js +116 -0
  42. package/dist/esm/ui/datasource-table-view/datasourceTableView.js +9 -3
  43. package/dist/esm/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.js +4 -1
  44. package/dist/esm/ui/jira-issues-modal/basic-filters/ui/index.js +5 -1
  45. package/dist/esm/ui/jira-issues-modal/jira-search-container/JiraSearchContainerWithWrappers.js +13 -0
  46. package/dist/esm/ui/jira-issues-modal/jira-search-container/buildJQL.js +12 -2
  47. package/dist/esm/ui/jira-issues-modal/jira-search-container/embedded.compiled.css +11 -0
  48. package/dist/esm/ui/jira-issues-modal/jira-search-container/embedded.js +312 -0
  49. package/dist/esm/ui/jira-issues-modal/jira-search-container/index.js +12 -0
  50. package/dist/esm/ui/jira-issues-modal/jql-editor/index.js +9 -1
  51. package/dist/esm/ui/jira-search-state/index.compiled.css +12 -0
  52. package/dist/esm/ui/jira-search-state/index.js +83 -0
  53. package/dist/types/ui/common/modal/basic-search-input/index.d.ts +5 -1
  54. package/dist/types/ui/common/modal/mode-switcher/ModeSwitcher.d.ts +1 -0
  55. package/dist/types/ui/common/modal/popup-select/index.d.ts +2 -1
  56. package/dist/types/ui/common/modal/popup-select/useInlinePopupLayout.d.ts +9 -0
  57. package/dist/types/ui/datasource-table-view/datasourceTableView.d.ts +5 -1
  58. package/dist/types/ui/datasource-table-view/types.d.ts +5 -0
  59. package/dist/types/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.d.ts +2 -1
  60. package/dist/types/ui/jira-issues-modal/basic-filters/ui/index.d.ts +2 -1
  61. package/dist/types/ui/jira-issues-modal/jira-search-container/JiraSearchContainerWithWrappers.d.ts +4 -0
  62. package/dist/types/ui/jira-issues-modal/jira-search-container/buildJQL.d.ts +1 -0
  63. package/dist/types/ui/jira-issues-modal/jira-search-container/embedded.d.ts +9 -0
  64. package/dist/types/ui/jira-issues-modal/jira-search-container/index.d.ts +11 -0
  65. package/dist/types/ui/jira-issues-modal/jql-editor/index.d.ts +4 -1
  66. package/dist/types/ui/jira-search-state/index.d.ts +9 -0
  67. package/jira-search-container/package.json +10 -0
  68. package/jira-search-state/package.json +10 -0
  69. package/package.json +4 -4
@@ -0,0 +1,103 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ const VIEWPORT_PADDING = 8;
3
+ const TRIGGER_GAP = 8;
4
+ const MAX_MENU_HEIGHT = 300;
5
+ const MIN_PREFERRED_SPACE = 180;
6
+ // Reserve the footer and container padding until the open popup can be measured.
7
+ const INITIAL_EXTRA_HEIGHT = 72;
8
+ const INITIAL_CONTROL_HEIGHT = 48;
9
+ const viewportModifiers = [{
10
+ name: 'offset',
11
+ options: {
12
+ offset: [0, TRIGGER_GAP]
13
+ }
14
+ },
15
+ // Placement and height are calculated together; flipping independently can clip the search box.
16
+ {
17
+ name: 'flip',
18
+ enabled: false
19
+ }, {
20
+ name: 'preventOverflow',
21
+ enabled: true,
22
+ options: {
23
+ rootBoundary: 'viewport',
24
+ padding: VIEWPORT_PADDING,
25
+ altAxis: true,
26
+ tether: false
27
+ }
28
+ }];
29
+ export function useInlinePopupLayout(isInline, popupRef) {
30
+ const [isOpen, setIsOpen] = useState(false);
31
+ const [layout, setLayout] = useState({
32
+ maxMenuHeight: MAX_MENU_HEIGHT,
33
+ placement: 'bottom-start'
34
+ });
35
+ const updateLayout = useCallback(() => {
36
+ var _viewport$offsetTop, _viewport$height, _popup$selectRef$sele, _popup$selectRef, _popup$selectRef$sele2, _popup$selectRef$sele3;
37
+ const popup = popupRef.current;
38
+ const trigger = popup === null || popup === void 0 ? void 0 : popup.targetRef;
39
+ if (!isInline || !trigger) {
40
+ return;
41
+ }
42
+ const viewport = window.visualViewport;
43
+ const viewportTop = (_viewport$offsetTop = viewport === null || viewport === void 0 ? void 0 : viewport.offsetTop) !== null && _viewport$offsetTop !== void 0 ? _viewport$offsetTop : 0;
44
+ const viewportBottom = viewportTop + ((_viewport$height = viewport === null || viewport === void 0 ? void 0 : viewport.height) !== null && _viewport$height !== void 0 ? _viewport$height : window.innerHeight);
45
+ const rect = trigger.getBoundingClientRect();
46
+ const below = Math.max(0, viewportBottom - rect.bottom - TRIGGER_GAP - VIEWPORT_PADDING);
47
+ const above = Math.max(0, rect.top - viewportTop - TRIGGER_GAP - VIEWPORT_PADDING);
48
+ const placement = below >= MIN_PREFERRED_SPACE || below >= above ? 'bottom-start' : 'top-start';
49
+ const available = placement === 'bottom-start' ? below : above;
50
+ const menu = popup.menuRef;
51
+ const list = menu === null || menu === void 0 ? void 0 : menu.querySelector('[role="listbox"]');
52
+ const controlHeight = (_popup$selectRef$sele = (_popup$selectRef = popup.selectRef) === null || _popup$selectRef === void 0 ? void 0 : (_popup$selectRef$sele2 = _popup$selectRef.select) === null || _popup$selectRef$sele2 === void 0 ? void 0 : (_popup$selectRef$sele3 = _popup$selectRef$sele2.controlRef) === null || _popup$selectRef$sele3 === void 0 ? void 0 : _popup$selectRef$sele3.offsetHeight) !== null && _popup$selectRef$sele !== void 0 ? _popup$selectRef$sele : INITIAL_CONTROL_HEIGHT;
53
+ // PopupSelect already subtracts the search control from maxMenuHeight. Reserve the
54
+ // remaining chrome separately, including the pagination footer outside its scroll area.
55
+ const extraHeight = menu && list ? Math.max(0, menu.offsetHeight - list.offsetHeight - controlHeight) : INITIAL_EXTRA_HEIGHT;
56
+ const maxMenuHeight = Math.max(controlHeight, Math.min(MAX_MENU_HEIGHT, available - extraHeight));
57
+ setLayout(previous => previous.placement === placement && previous.maxMenuHeight === maxMenuHeight ? previous : {
58
+ placement,
59
+ maxMenuHeight
60
+ });
61
+ }, [isInline, popupRef]);
62
+ useEffect(() => {
63
+ var _popupRef$current;
64
+ if (!isInline || !isOpen) {
65
+ return;
66
+ }
67
+ updateLayout();
68
+ const viewport = window.visualViewport;
69
+ const observer = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(updateLayout);
70
+ const menu = (_popupRef$current = popupRef.current) === null || _popupRef$current === void 0 ? void 0 : _popupRef$current.menuRef;
71
+ if (menu) {
72
+ observer === null || observer === void 0 ? void 0 : observer.observe(menu);
73
+ }
74
+ window.addEventListener('resize', updateLayout);
75
+ window.addEventListener('scroll', updateLayout, true);
76
+ viewport === null || viewport === void 0 ? void 0 : viewport.addEventListener('resize', updateLayout);
77
+ viewport === null || viewport === void 0 ? void 0 : viewport.addEventListener('scroll', updateLayout);
78
+ return () => {
79
+ observer === null || observer === void 0 ? void 0 : observer.disconnect();
80
+ window.removeEventListener('resize', updateLayout);
81
+ window.removeEventListener('scroll', updateLayout, true);
82
+ viewport === null || viewport === void 0 ? void 0 : viewport.removeEventListener('resize', updateLayout);
83
+ viewport === null || viewport === void 0 ? void 0 : viewport.removeEventListener('scroll', updateLayout);
84
+ };
85
+ }, [isInline, isOpen, popupRef, updateLayout]);
86
+ const onOpen = useCallback(() => {
87
+ if (isInline) {
88
+ updateLayout();
89
+ setIsOpen(true);
90
+ }
91
+ }, [isInline, updateLayout]);
92
+ const onClose = useCallback(() => setIsOpen(false), []);
93
+ const popperProps = useMemo(() => ({
94
+ placement: layout.placement,
95
+ modifiers: viewportModifiers
96
+ }), [layout.placement]);
97
+ return {
98
+ onOpen,
99
+ onClose,
100
+ maxMenuHeight: isInline ? layout.maxMenuHeight : undefined,
101
+ popperProps: isInline ? popperProps : undefined
102
+ };
103
+ }
@@ -44,7 +44,9 @@ const DatasourceTableViewWithoutAnalytics = ({
44
44
  onWrappedColumnChange,
45
45
  onWrappedColumnsChange,
46
46
  scrollableContainerHeight = DefaultScrollableContainerHeight,
47
- onRequestSettled
47
+ onRequestSettled,
48
+ enableRequestSettled = false,
49
+ renderEmptyState
48
50
  }) => {
49
51
  // Local copy lets us apply in-session sort mutations without mutating external parameters.
50
52
  const [sessionParameters, setSessionParameters] = useState(parameters);
@@ -135,7 +137,7 @@ const DatasourceTableViewWithoutAnalytics = ({
135
137
  // settled request is reported exactly once.
136
138
  const lastSettledResultRef = useRef(null);
137
139
  useEffect(() => {
138
- if (!fg('platform_lp_sllv_on_request_settled')) {
140
+ if (!fg('platform_lp_sllv_on_request_settled') && !enableRequestSettled) {
139
141
  return;
140
142
  }
141
143
  if (status !== 'resolved' && status !== 'rejected' && status !== 'unauthorized' && status !== 'forbidden') {
@@ -152,7 +154,7 @@ const DatasourceTableViewWithoutAnalytics = ({
152
154
  }
153
155
  lastSettledResultRef.current = result;
154
156
  onRequestSettled === null || onRequestSettled === void 0 ? void 0 : onRequestSettled(result);
155
- }, [status, responseItems.length, totalCount, onRequestSettled]);
157
+ }, [status, responseItems.length, totalCount, onRequestSettled, enableRequestSettled]);
156
158
  useEffect(() => {
157
159
  const shouldStartUfoExperience = datasourceId && parameters && visibleColumnKeys && status === 'loading';
158
160
  if (shouldStartUfoExperience) {
@@ -200,6 +202,9 @@ const DatasourceTableViewWithoutAnalytics = ({
200
202
  });
201
203
  }, [reset]);
202
204
  const isResolvedWithNoResults = status === 'resolved' && !responseItems.length;
205
+ if (isResolvedWithNoResults && renderEmptyState) {
206
+ return renderEmptyState();
207
+ }
203
208
  // With columns available the table can keep its headers and footer and show the empty state
204
209
  // in place of the rows, instead of replacing the whole view with it.
205
210
  const shouldRenderTableWithNoResults = isResolvedWithNoResults && hasColumns;
@@ -12,7 +12,8 @@ const AsyncPopupSelect = ({
12
12
  selection,
13
13
  isJQLHydrating,
14
14
  onSelectionChange = () => {},
15
- isDisabled = false
15
+ isDisabled = false,
16
+ isInline = false
16
17
  }) => {
17
18
  const {
18
19
  formatMessage
@@ -87,6 +88,7 @@ const AsyncPopupSelect = ({
87
88
  const shouldDisplayShowMoreButton = status === 'resolved' && !!pageCursor && !areAllResultsLoaded;
88
89
  const triggerButtonLabel = formatMessage(asyncPopupSelectMessages[`${filterType}Label`]);
89
90
  return /*#__PURE__*/React.createElement(FilterPopupSelect, {
91
+ isInline: isInline,
90
92
  filterName: filterName,
91
93
  status: status,
92
94
  showLoading: isLoading,
@@ -14,7 +14,8 @@ const BasicFilterContainer = ({
14
14
  site,
15
15
  onChange,
16
16
  selections,
17
- isJQLHydrating
17
+ isJQLHydrating,
18
+ isInline = false
18
19
  }) => {
19
20
  const extractedFilterValues = useMemo(() => isJQLHydrating ? extractValuesFromNonComplexJQL(jql) : {}, [isJQLHydrating, jql]);
20
21
  const {
@@ -23,11 +24,13 @@ const BasicFilterContainer = ({
23
24
  return /*#__PURE__*/React.createElement(Flex, {
24
25
  xcss: styles.basicFilterContainerStyles,
25
26
  gap: "space.100",
27
+ wrap: isInline ? 'wrap' : 'nowrap',
26
28
  testId: "jlol-basic-filter-container"
27
29
  }, availableBasicFilterTypes.map(filter => {
28
30
  var _extractedFilterValue;
29
31
  const shouldShowHydrationLoader = isJQLHydrating && ((_extractedFilterValue = extractedFilterValues[filter]) === null || _extractedFilterValue === void 0 ? void 0 : _extractedFilterValue.length) > 0;
30
32
  return /*#__PURE__*/React.createElement(AsyncPopupSelect, {
33
+ isInline: isInline,
31
34
  site: site,
32
35
  filterType: filter,
33
36
  key: filter,
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import IntlMessagesProvider from '@atlaskit/intl-messages-provider/main';
3
+ import { fetchMessagesForLocale } from '../../../common/utils/locale/fetch-messages-for-locale';
4
+ import i18nEN from '../../../i18n/en';
5
+ import { JiraSearchContainer as SearchContainer } from './index';
6
+
7
+ /** Embedded consumers need the same translated search controls as the configuration modal. */
8
+ export const JiraSearchContainer = props => /*#__PURE__*/React.createElement(IntlMessagesProvider, {
9
+ defaultMessages: i18nEN,
10
+ loaderFn: fetchMessagesForLocale
11
+ }, /*#__PURE__*/React.createElement(SearchContainer, props));
@@ -9,6 +9,10 @@ const fuzzySearchRegExp = /^"(.+)"$/;
9
9
  const jiraIssueKeyRegExp = /[A-Z]+-\d+/;
10
10
  export const fuzzyCharacter = '*';
11
11
  const constructTerminalClause = (field, operator, value) => creators.terminalClause(creators.field(field), creators.operator(operator), creators.valueOperand(value));
12
+
13
+ // The generic AST string sanitizer quotes values but does not escape backslashes.
14
+ // Embedded keyword search accepts literal user text, so quote both delimiters explicitly.
15
+ const escapedValueOperand = value => creators.byText.valueOperand(`"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`);
12
16
  export const buildJQL = input => {
13
17
  /**
14
18
  * Jql ast - Transforming the ast
@@ -20,6 +24,7 @@ export const buildJQL = input => {
20
24
  } = jast;
21
25
  const {
22
26
  rawSearch,
27
+ textSearchField,
23
28
  orderDirection = ORDER_BY_DIRECTION_DESC,
24
29
  orderKey = 'created',
25
30
  filterValues
@@ -33,7 +38,10 @@ export const buildJQL = input => {
33
38
  if (!query) {
34
39
  return '';
35
40
  }
36
- if (trimmedRawSearch) {
41
+ if (trimmedRawSearch && textSearchField === 'textfields') {
42
+ const suffix = /["*?]$/.test(trimmedRawSearch) ? '' : fuzzyCharacter;
43
+ query.appendClause(creators.terminalClause(creators.field('textfields'), creators.operator(OPERATOR_LIKE), escapedValueOperand(`${trimmedRawSearch}${suffix}`)), COMPOUND_OPERATOR_AND);
44
+ } else if (trimmedRawSearch) {
37
45
  const fuzzy = !trimmedRawSearch.match(fuzzySearchRegExp) ? fuzzyCharacter : '';
38
46
  const basicSearch = trimmedRawSearch.replace(/['"?*]+/g, '');
39
47
  const text = constructTerminalClause('text', OPERATOR_LIKE, `${basicSearch}${fuzzy}`);
@@ -51,7 +59,7 @@ export const buildJQL = input => {
51
59
  if (filterFieldValues.length === 0) {
52
60
  return;
53
61
  }
54
- const filterInClause = creators.terminalClause(creators.field(key), creators.operator(OPERATOR_IN), creators.listOperand(filterFieldValues.map(filterFieldValue => creators.valueOperand(filterFieldValue.value))));
62
+ const filterInClause = creators.terminalClause(creators.field(key), creators.operator(OPERATOR_IN), creators.listOperand(filterFieldValues.map(filterFieldValue => textSearchField ? escapedValueOperand(filterFieldValue.value) : creators.valueOperand(filterFieldValue.value))));
55
63
  query.appendClause(filterInClause, COMPOUND_OPERATOR_AND);
56
64
  });
57
65
  }
@@ -0,0 +1,11 @@
1
+ ._16jlkb7n{flex-grow:1}
2
+ ._19pk1b66{margin-top:var(--ds-space-050,4px)}
3
+ ._1bsb1osq{width:100%}
4
+ ._1e0c1txw{display:flex}
5
+ ._1n261g80{flex-wrap:wrap}
6
+ ._1o9zidpf{flex-shrink:0}
7
+ ._1p57u2gc{row-gap:var(--ds-space-100,8px)}
8
+ ._1tket9kd{min-height:72px}
9
+ ._1ul9idpf{min-width:0}
10
+ ._4cvr1y6m{align-items:flex-start}
11
+ ._i0dlghg7{flex-basis:240px}
@@ -0,0 +1,283 @@
1
+ /* embedded.tsx generated by @compiled/babel-plugin v3.0.2 */
2
+ import "./embedded.compiled.css";
3
+ import { ax, ix } from "@compiled/react/runtime";
4
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
+ import { useIntl } from 'react-intl';
6
+ import { useDebouncedCallback } from 'use-debounce';
7
+ import { Box } from '@atlaskit/primitives/compiled/box';
8
+ import { Flex } from '@atlaskit/primitives/compiled/flex';
9
+ import { Text } from '@atlaskit/primitives/compiled/text';
10
+ import { useDatasourceAnalyticsEvents } from '../../../analytics';
11
+ import { BasicSearchInput } from '../../common/modal/basic-search-input';
12
+ import { basicSearchInputMessages } from '../../common/modal/basic-search-input/messages';
13
+ import { ModeSwitcher } from '../../common/modal/mode-switcher/ModeSwitcher';
14
+ import { FILTER_SELECTION_DEBOUNCE_MS } from '../../common/modal/popup-select/constants';
15
+ import { useHydrateJqlQuery } from '../basic-filters/hooks/useHydrateJqlQuery';
16
+ import { default as BasicFilters } from '../basic-filters/ui/index';
17
+ import { isQueryTooComplex } from '../basic-filters/utils/isQueryTooComplex';
18
+ import { JiraJQLEditor } from '../jql-editor';
19
+ import { buildJQL } from './buildJQL';
20
+ import { modeSwitcherMessages } from './messages';
21
+ const styles = {
22
+ inlineSearch: "_16jlkb7n _i0dlghg7 _1ul9idpf",
23
+ inlineSwitcher: "_1o9zidpf",
24
+ basicSearchInputBoxStyles: "_1bsb1osq",
25
+ basicSearchInputContainerStyles: "_16jlkb7n _1n261g80 _1p57u2gc",
26
+ inputContainerStyles: "_4cvr1y6m _1e0c1txw _1tket9kd",
27
+ modeSwitcherContainerStyles: "_19pk1b66"
28
+ };
29
+ export const EmbeddedJiraSearchContainer = props => {
30
+ const {
31
+ defaultJql: DEFAULT_JQL_QUERY,
32
+ allowedOrderByKeys: ALLOWED_ORDER_BY_KEYS,
33
+ isSearching,
34
+ parameters,
35
+ onSearch,
36
+ onSearchMethodChange: onSearchMethodChangeCallback,
37
+ initialSearchMethod,
38
+ setSearchBarJql,
39
+ searchBarJql = DEFAULT_JQL_QUERY,
40
+ site,
41
+ setHasJqlSyntaxErrors,
42
+ layout = 'modal',
43
+ initialBasicSearchTerm = '',
44
+ allowBasicModeReset = false,
45
+ showBasicSearchButton = true,
46
+ textSearchField
47
+ } = props;
48
+ const {
49
+ cloudId: currentCloudId
50
+ } = parameters || {};
51
+ const {
52
+ formatMessage
53
+ } = useIntl();
54
+ const {
55
+ fireEvent
56
+ } = useDatasourceAnalyticsEvents();
57
+ const [basicSearchTerm, setBasicSearchTerm] = useState(initialBasicSearchTerm);
58
+ const [currentSearchMethod, setCurrentSearchMethod] = useState(initialSearchMethod);
59
+ const [cloudId, setCloudId] = useState(currentCloudId);
60
+ const [isComplexQuery, setIsComplexQuery] = useState(false);
61
+ const [orderKey, setOrderKey] = useState();
62
+ const [orderDirection, setOrderDirection] = useState();
63
+ const [filterSelections, setFilterSelections] = useState({});
64
+ const basicDraft = useRef({
65
+ jql: initialSearchMethod === 'basic' ? searchBarJql : buildJQL({
66
+ rawSearch: initialBasicSearchTerm,
67
+ textSearchField
68
+ }),
69
+ term: initialBasicSearchTerm,
70
+ selections: {}
71
+ });
72
+ const advancedDraft = useRef();
73
+ const modeSwitcherOptionsMap = useMemo(() => ({
74
+ jql: {
75
+ label: 'JQL',
76
+ value: 'jql'
77
+ },
78
+ basic: {
79
+ label: formatMessage(modeSwitcherMessages.basicTextSearchLabel),
80
+ value: 'basic',
81
+ disabled: isComplexQuery && !allowBasicModeReset,
82
+ tooltipText: isComplexQuery && !allowBasicModeReset ? formatMessage(modeSwitcherMessages.basicModeSwitchDisabledTooltipText) : ''
83
+ }
84
+ }), [formatMessage, isComplexQuery, allowBasicModeReset]);
85
+ const modeSwitcherOptions = useMemo(() => [modeSwitcherOptionsMap.basic, modeSwitcherOptionsMap.jql], [modeSwitcherOptionsMap]);
86
+ const {
87
+ hydratedOptions,
88
+ fetchHydratedJqlOptions,
89
+ status: basicFilterHydrationStatus
90
+ } = useHydrateJqlQuery(cloudId || '', searchBarJql);
91
+ const onSearchMethodChange = useCallback(searchMethod => {
92
+ if (allowBasicModeReset) {
93
+ if (searchMethod === 'jql') {
94
+ var _advancedDraft$curren;
95
+ basicDraft.current = {
96
+ jql: searchBarJql,
97
+ term: basicSearchTerm,
98
+ selections: filterSelections
99
+ };
100
+ setSearchBarJql((_advancedDraft$curren = advancedDraft.current) !== null && _advancedDraft$curren !== void 0 ? _advancedDraft$curren : searchBarJql);
101
+ } else if (isQueryTooComplex(searchBarJql)) {
102
+ advancedDraft.current = searchBarJql;
103
+ setSearchBarJql(basicDraft.current.jql);
104
+ setBasicSearchTerm(basicDraft.current.term);
105
+ setFilterSelections(basicDraft.current.selections);
106
+ }
107
+ }
108
+ onSearchMethodChangeCallback(searchMethod);
109
+ setCurrentSearchMethod(searchMethod);
110
+ }, [onSearchMethodChangeCallback, allowBasicModeReset, searchBarJql, basicSearchTerm, filterSelections, setSearchBarJql]);
111
+ const handleBasicSearchChange = useCallback(e => {
112
+ const rawSearch = e.currentTarget.value;
113
+ setBasicSearchTerm(rawSearch);
114
+ setSearchBarJql(buildJQL({
115
+ rawSearch,
116
+ textSearchField,
117
+ filterValues: filterSelections,
118
+ orderDirection,
119
+ orderKey
120
+ }));
121
+ }, [setSearchBarJql, filterSelections, orderDirection, orderKey, textSearchField]);
122
+ const onQueryChange = useCallback((query, jast) => {
123
+ var _query$split$map$filt, _fragments$at, _fragments$at2, _fragments$at3;
124
+ setHasJqlSyntaxErrors === null || setHasJqlSyntaxErrors === void 0 ? void 0 : setHasJqlSyntaxErrors(jast.errors.length > 0);
125
+ // determine if order keys have been set so they can be saved and persisted when changes occur in basic search
126
+ const fragments = (_query$split$map$filt = query === null || query === void 0 ? void 0 : query.split(/(^| )(order by)( |$)/i).map(item => item.trim()).filter(Boolean)) !== null && _query$split$map$filt !== void 0 ? _query$split$map$filt : [];
127
+ const hasOrder = ((_fragments$at = fragments.at(-2)) === null || _fragments$at === void 0 ? void 0 : _fragments$at.toLowerCase()) === 'order by';
128
+ const key = hasOrder ? (_fragments$at2 = fragments.at(-1)) === null || _fragments$at2 === void 0 ? void 0 : _fragments$at2.split(' ').at(-2) : undefined;
129
+ const order = hasOrder ? (_fragments$at3 = fragments.at(-1)) === null || _fragments$at3 === void 0 ? void 0 : _fragments$at3.split(' ').at(-1) : undefined;
130
+
131
+ // TODO: confirm if these are the only order keys we want to preserve - existing whiteboard logic
132
+ if (key && ALLOWED_ORDER_BY_KEYS.includes(key)) {
133
+ setOrderKey(key);
134
+ setOrderDirection(order);
135
+ }
136
+ setSearchBarJql(query);
137
+ }, [setSearchBarJql, setHasJqlSyntaxErrors]);
138
+ const handleSearch = useCallback(() => {
139
+ const isCurrentQueryComplex = isQueryTooComplex(searchBarJql);
140
+ if (currentSearchMethod === 'basic') {
141
+ advancedDraft.current = undefined;
142
+ }
143
+ onSearch({
144
+ jql: searchBarJql
145
+ }, {
146
+ searchMethod: currentSearchMethod,
147
+ basicFilterSelections: filterSelections,
148
+ isQueryComplex: isCurrentQueryComplex
149
+ });
150
+ if (currentSearchMethod === 'jql') {
151
+ fireEvent('ui.jqlEditor.searched', {
152
+ isQueryComplex: isCurrentQueryComplex
153
+ });
154
+ setIsComplexQuery(isCurrentQueryComplex);
155
+ if (!isCurrentQueryComplex) {
156
+ fetchHydratedJqlOptions();
157
+ }
158
+ }
159
+ }, [currentSearchMethod, fetchHydratedJqlOptions, filterSelections, fireEvent, searchBarJql, onSearch]);
160
+ const [debouncedBasicFilterSelectionChange] = useDebouncedCallback(filterValues => {
161
+ const jqlWithFilterValues = buildJQL({
162
+ rawSearch: basicSearchTerm,
163
+ textSearchField,
164
+ filterValues,
165
+ orderDirection,
166
+ orderKey
167
+ });
168
+ advancedDraft.current = undefined;
169
+ setSearchBarJql(jqlWithFilterValues);
170
+ const isCurrentQueryComplex = isQueryTooComplex(jqlWithFilterValues);
171
+ onSearch({
172
+ jql: jqlWithFilterValues
173
+ }, {
174
+ searchMethod: currentSearchMethod,
175
+ basicFilterSelections: filterValues,
176
+ isQueryComplex: isCurrentQueryComplex
177
+ });
178
+ }, FILTER_SELECTION_DEBOUNCE_MS);
179
+ const handleBasicFilterSelectionChange = useCallback((filterType, options) => {
180
+ const updatedSelection = {
181
+ ...filterSelections,
182
+ [filterType]: options
183
+ };
184
+ setFilterSelections(updatedSelection);
185
+ debouncedBasicFilterSelectionChange(updatedSelection);
186
+ }, [debouncedBasicFilterSelectionChange, filterSelections]);
187
+ useEffect(() => {
188
+ const isCurrentQueryComplex = isQueryTooComplex(searchBarJql);
189
+ setIsComplexQuery(isCurrentQueryComplex);
190
+ if (!isCurrentQueryComplex && searchBarJql !== DEFAULT_JQL_QUERY) {
191
+ fetchHydratedJqlOptions();
192
+ }
193
+ // eslint-disable-next-line react-hooks/exhaustive-deps
194
+ }, []);
195
+ useEffect(() => {
196
+ if (basicFilterHydrationStatus === 'resolved') {
197
+ const {
198
+ basicInputTextValue,
199
+ ...hydratedFilterOptions
200
+ } = hydratedOptions;
201
+ setFilterSelections(hydratedFilterOptions);
202
+ if (basicInputTextValue) {
203
+ setBasicSearchTerm(basicInputTextValue);
204
+ }
205
+ }
206
+ }, [hydratedOptions, basicFilterHydrationStatus]);
207
+ useEffect(() => {
208
+ if (currentCloudId !== cloudId) {
209
+ basicDraft.current = {
210
+ jql: DEFAULT_JQL_QUERY,
211
+ term: '',
212
+ selections: {}
213
+ };
214
+ advancedDraft.current = undefined;
215
+ setBasicSearchTerm('');
216
+ setSearchBarJql(DEFAULT_JQL_QUERY);
217
+ setIsComplexQuery(false);
218
+ setOrderKey(undefined);
219
+ setOrderDirection(undefined);
220
+ setFilterSelections({});
221
+ setCloudId(currentCloudId);
222
+ }
223
+ }, [currentCloudId, cloudId, setSearchBarJql]);
224
+ const searchInput = currentSearchMethod === 'basic' ? /*#__PURE__*/React.createElement(Box, {
225
+ xcss: layout === 'inline' ? styles.inlineSearch : styles.basicSearchInputBoxStyles
226
+ }, /*#__PURE__*/React.createElement(Flex, {
227
+ alignItems: "center",
228
+ xcss: styles.basicSearchInputContainerStyles
229
+ }, /*#__PURE__*/React.createElement(BasicSearchInput, {
230
+ isCompact: layout === 'inline',
231
+ isSearching: isSearching,
232
+ onChange: handleBasicSearchChange,
233
+ onSearch: handleSearch,
234
+ searchTerm: basicSearchTerm,
235
+ showSearchButton: showBasicSearchButton,
236
+ ariaLabel: basicSearchInputMessages.basicTextSearchLabel,
237
+ testId: "jira-datasource-modal",
238
+ fullWidth: false
239
+ }), /*#__PURE__*/React.createElement(BasicFilters, {
240
+ jql: searchBarJql,
241
+ site: site,
242
+ onChange: handleBasicFilterSelectionChange,
243
+ selections: filterSelections,
244
+ isJQLHydrating: basicFilterHydrationStatus === 'loading',
245
+ isInline: layout === 'inline'
246
+ })), layout !== 'inline' && /*#__PURE__*/React.createElement(Text, {
247
+ size: "small",
248
+ color: "color.text.subtlest",
249
+ testId: "jira-search-placeholder"
250
+ }, formatMessage(basicSearchInputMessages.basicTextSearchLabel))) : /*#__PURE__*/React.createElement(JiraJQLEditor, {
251
+ isCompact: layout === 'inline',
252
+ cloudId: cloudId || '',
253
+ autoFocus: layout !== 'inline',
254
+ isSearching: isSearching,
255
+ onChange: onQueryChange,
256
+ onSearch: handleSearch,
257
+ query: searchBarJql
258
+ });
259
+ const modeSwitcher = /*#__PURE__*/React.createElement(ModeSwitcher, {
260
+ isInline: layout === 'inline',
261
+ onOptionValueChange: onSearchMethodChange,
262
+ selectedOptionValue: currentSearchMethod,
263
+ options: modeSwitcherOptions
264
+ });
265
+ if (layout === 'inline') {
266
+ return /*#__PURE__*/React.createElement(Flex, {
267
+ alignItems: "start",
268
+ gap: "space.100",
269
+ wrap: "wrap",
270
+ testId: "jira-search-container"
271
+ }, /*#__PURE__*/React.createElement(Box, {
272
+ xcss: styles.inlineSwitcher
273
+ }, modeSwitcher), currentSearchMethod === 'basic' ? searchInput : /*#__PURE__*/React.createElement(Box, {
274
+ xcss: styles.inlineSearch
275
+ }, searchInput));
276
+ }
277
+ return /*#__PURE__*/React.createElement("div", {
278
+ "data-testid": "jira-search-container",
279
+ className: ax([styles.inputContainerStyles])
280
+ }, searchInput, /*#__PURE__*/React.createElement(Box, {
281
+ xcss: styles.modeSwitcherContainerStyles
282
+ }, modeSwitcher));
283
+ };
@@ -1,4 +1,5 @@
1
1
  /* index.tsx generated by @compiled/babel-plugin v3.0.2 */
2
+ import _extends from "@babel/runtime/helpers/extends";
2
3
  import "./index.compiled.css";
3
4
  import { ax, ix } from "@compiled/react/runtime";
4
5
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
@@ -17,6 +18,7 @@ import { default as BasicFilters } from '../basic-filters/ui/index';
17
18
  import { isQueryTooComplex } from '../basic-filters/utils/isQueryTooComplex';
18
19
  import { JiraJQLEditor } from '../jql-editor';
19
20
  import { buildJQL } from './buildJQL';
21
+ import { EmbeddedJiraSearchContainer } from './embedded';
20
22
  import { modeSwitcherMessages } from './messages';
21
23
  const styles = {
22
24
  basicSearchInputBoxStyles: "_1bsb1osq",
@@ -26,7 +28,17 @@ const styles = {
26
28
  };
27
29
  export const DEFAULT_JQL_QUERY = 'ORDER BY created DESC';
28
30
  export const ALLOWED_ORDER_BY_KEYS = ['key', 'summary', 'assignee', 'status', 'created'];
31
+ /** Opt-in consumers use a separate component; the configuration modal retains its existing path. */
29
32
  export const JiraSearchContainer = props => {
33
+ if (props.layout === 'inline' || props.allowBasicModeReset || props.initialBasicSearchTerm !== undefined || props.showBasicSearchButton === false || props.textSearchField !== undefined) {
34
+ return /*#__PURE__*/React.createElement(EmbeddedJiraSearchContainer, _extends({}, props, {
35
+ defaultJql: DEFAULT_JQL_QUERY,
36
+ allowedOrderByKeys: ALLOWED_ORDER_BY_KEYS
37
+ }));
38
+ }
39
+ return /*#__PURE__*/React.createElement(ModalJiraSearchContainer, props);
40
+ };
41
+ const ModalJiraSearchContainer = props => {
30
42
  const {
31
43
  isSearching,
32
44
  parameters,
@@ -6,6 +6,8 @@ import { makeGetJqlAutocompleteData } from '../../../services/makeGetJqlAutocomp
6
6
  import { makeGetJqlSuggestionsData } from '../../../services/makeGetJqlSuggestionsData';
7
7
  export const JiraJQLEditor = ({
8
8
  cloudId,
9
+ autoFocus = true,
10
+ isCompact = false,
9
11
  isSearching,
10
12
  onChange,
11
13
  onSearch,
@@ -18,11 +20,14 @@ export const JiraJQLEditor = ({
18
20
  focus: () => {}
19
21
  });
20
22
  useEffect(() => {
23
+ if (!autoFocus) {
24
+ return;
25
+ }
21
26
  requestAnimationFrame(() => {
22
27
  var _inputRef$current;
23
28
  (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
24
29
  });
25
- }, []);
30
+ }, [autoFocus]);
26
31
  const searchIfValidJql = (_, jast) => {
27
32
  if (jast.errors.length === 0) {
28
33
  onSearch();
@@ -34,6 +39,7 @@ export const JiraJQLEditor = ({
34
39
  onSearch: searchIfValidJql,
35
40
  onUpdate: onChange,
36
41
  isSearching: isSearching,
42
+ isCompact: isCompact,
37
43
  inputRef: inputRef,
38
44
  query: query,
39
45
  "aria-label": /*#__PURE__*/React.createElement(FormattedMessage, {
@@ -0,0 +1,12 @@
1
+ ._18zrpxbi{padding-inline:var(--ds-space-200,1pc)}
2
+ ._1rjcpxbi{padding-block:var(--ds-space-200,1pc)}._18m915vq{overflow-y:hidden}
3
+ ._1pglindk{animation-timing-function:var(--ds-easing-out-practical,cubic-bezier(.4,1,.6,1))}
4
+ ._1reo15vq{overflow-x:hidden}
5
+ ._1tke194s{min-height:14pc}
6
+ ._1tkeva7c{min-height:281px}
7
+ ._4t3i1osq{height:100%}
8
+ ._5sag1ign{animation-duration:var(--ds-duration-short,.15s)}
9
+ ._j7hq1bym{animation-name:var(--ds-keyframe-fade-in,FadeIn0to100)}
10
+ ._p12f1gwv{max-width:360px}
11
+ ._y3gn1h6o{text-align:center}
12
+ @media (prefers-reduced-motion:reduce){._bl8aglyw{animation-name:none}}