@atlaskit/link-datasource 1.16.5 → 1.17.1

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 (32) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/analytics/constants.js +1 -1
  3. package/dist/cjs/hooks/useAssetsClient.js +7 -4
  4. package/dist/cjs/hooks/useDatasourceTableState.js +83 -52
  5. package/dist/cjs/hooks/useValidateAqlText.js +5 -2
  6. package/dist/cjs/services/cmdbService.js +50 -35
  7. package/dist/cjs/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.js +58 -15
  8. package/dist/cjs/ui/jira-issues-modal/basic-filters/ui/async-popup-select/trigger.js +26 -3
  9. package/dist/cjs/ui/jira-issues-modal/basic-filters/ui/index.js +6 -3
  10. package/dist/es2019/analytics/constants.js +1 -1
  11. package/dist/es2019/hooks/useAssetsClient.js +8 -4
  12. package/dist/es2019/hooks/useDatasourceTableState.js +40 -12
  13. package/dist/es2019/hooks/useValidateAqlText.js +6 -2
  14. package/dist/es2019/services/cmdbService.js +27 -12
  15. package/dist/es2019/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.js +41 -16
  16. package/dist/es2019/ui/jira-issues-modal/basic-filters/ui/async-popup-select/trigger.js +23 -3
  17. package/dist/es2019/ui/jira-issues-modal/basic-filters/ui/index.js +5 -3
  18. package/dist/esm/analytics/constants.js +1 -1
  19. package/dist/esm/hooks/useAssetsClient.js +7 -4
  20. package/dist/esm/hooks/useDatasourceTableState.js +83 -52
  21. package/dist/esm/hooks/useValidateAqlText.js +5 -2
  22. package/dist/esm/services/cmdbService.js +50 -35
  23. package/dist/esm/ui/jira-issues-modal/basic-filters/ui/async-popup-select/index.js +59 -16
  24. package/dist/esm/ui/jira-issues-modal/basic-filters/ui/async-popup-select/trigger.js +26 -3
  25. package/dist/esm/ui/jira-issues-modal/basic-filters/ui/index.js +7 -4
  26. package/dist/types/analytics/generated/analytics.types.d.ts +41 -1
  27. package/dist/types/services/cmdbService.d.ts +8 -4
  28. package/dist/types/ui/jira-issues-modal/basic-filters/ui/async-popup-select/trigger.d.ts +2 -1
  29. package/dist/types-ts4.5/analytics/generated/analytics.types.d.ts +41 -1
  30. package/dist/types-ts4.5/services/cmdbService.d.ts +8 -4
  31. package/dist/types-ts4.5/ui/jira-issues-modal/basic-filters/ui/async-popup-select/trigger.d.ts +2 -1
  32. package/package.json +5 -4
@@ -9,6 +9,7 @@ exports.default = exports.SEARCH_DEBOUNCE_MS = void 0;
9
9
  var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
10
10
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
11
11
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
12
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
12
13
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
13
14
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
14
15
  var _react = _interopRequireWildcard(require("react"));
@@ -41,7 +42,6 @@ var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
41
42
  isDisabled = _ref$isDisabled === void 0 ? false : _ref$isDisabled;
42
43
  var _useIntl = (0, _reactIntlNext.useIntl)(),
43
44
  formatMessage = _useIntl.formatMessage;
44
- var pickerRef = (0, _react.useRef)(null);
45
45
  var _useState = (0, _react.useState)(''),
46
46
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
47
47
  searchTerm = _useState2[0],
@@ -50,6 +50,10 @@ var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
50
50
  _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
51
51
  selectedOptions = _useState4[0],
52
52
  setSelectedOptions = _useState4[1];
53
+ var _useState5 = (0, _react.useState)(selectedOptions),
54
+ _useState6 = (0, _slicedToArray2.default)(_useState5, 2),
55
+ sortedOptions = _useState6[0],
56
+ setSortedOptions = _useState6[1];
53
57
  var _useFilterOptions = (0, _useFilterOptions2.useFilterOptions)({
54
58
  filterType: filterType,
55
59
  cloudId: cloudId
@@ -90,14 +94,46 @@ var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
90
94
  setSelectedOptions(newValue);
91
95
  onSelectionChange(newValue);
92
96
  };
93
- var handleOpenPopup = (0, _react.useCallback)(function () {
94
- if (status === 'empty' || status === 'rejected') {
95
- // if user searches and gets status as rejected, we want the dropdown to try load the request with searchString when the user reopens the dropdown
96
- fetchFilterOptions({
97
- searchString: searchTerm
97
+ var sortOptionsOnPopupOpen = (0, _react.useCallback)(function () {
98
+ if (selectedOptions.length === 0) {
99
+ setSortedOptions(filterOptions);
100
+ return;
101
+ }
102
+ var nonSelectedOptions = filterOptions.filter(function (option) {
103
+ return !selectedOptions.find(function (selectedOption) {
104
+ return selectedOption.value === option.value;
105
+ });
106
+ });
107
+ var newOptions = [].concat((0, _toConsumableArray2.default)(selectedOptions), (0, _toConsumableArray2.default)(nonSelectedOptions));
108
+ setSortedOptions(newOptions);
109
+ }, [selectedOptions, filterOptions]);
110
+ var sortOptionsOnResolve = (0, _react.useCallback)(function () {
111
+ var newOptions = filterOptions.filter(function (option) {
112
+ return !sortedOptions.find(function (sortedOption) {
113
+ return sortedOption.value === option.value;
114
+ });
115
+ });
116
+ var shouldSetSortOptions = false;
117
+ if (sortedOptions.length !== filterOptions.length) {
118
+ shouldSetSortOptions = true;
119
+ } else {
120
+ sortedOptions.forEach(function (sortedOption) {
121
+ if (!filterOptions.some(function (filterOption) {
122
+ return filterOption.value === sortedOption.value;
123
+ })) {
124
+ shouldSetSortOptions = true;
125
+ }
98
126
  });
99
127
  }
100
- }, [fetchFilterOptions, searchTerm, status]);
128
+ if (shouldSetSortOptions) {
129
+ var sortedOptionsFiltered = sortedOptions.filter(function (sortedOption) {
130
+ return filterOptions.some(function (filterOption) {
131
+ return filterOption.value === sortedOption.value;
132
+ });
133
+ });
134
+ setSortedOptions([].concat((0, _toConsumableArray2.default)(sortedOptionsFiltered), (0, _toConsumableArray2.default)(newOptions)));
135
+ }
136
+ }, [filterOptions, sortedOptions]);
101
137
  var handleShowMore = (0, _react.useCallback)(function () {
102
138
  if (pageCursor) {
103
139
  fetchFilterOptions({
@@ -106,28 +142,34 @@ var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
106
142
  });
107
143
  }
108
144
  }, [fetchFilterOptions, pageCursor, searchTerm]);
145
+ var handleOpenPopup = (0, _react.useCallback)(function () {
146
+ if (status === 'empty' || status === 'rejected') {
147
+ // if user searches and gets status as rejected, we want the dropdown to try load the request with searchString when the user reopens the dropdown
148
+ fetchFilterOptions({
149
+ searchString: searchTerm
150
+ });
151
+ } else if (status === 'resolved') {
152
+ sortOptionsOnPopupOpen();
153
+ }
154
+ }, [fetchFilterOptions, searchTerm, sortOptionsOnPopupOpen, status]);
109
155
  (0, _react.useEffect)(function () {
110
156
  if (status === 'resolved') {
111
- var _pickerRef$current;
112
- // necessary to refocus the search input after the loading state
113
- pickerRef === null || pickerRef === void 0 || (_pickerRef$current = pickerRef.current) === null || _pickerRef$current === void 0 || (_pickerRef$current = _pickerRef$current.selectRef) === null || _pickerRef$current === void 0 || (_pickerRef$current = _pickerRef$current.inputRef) === null || _pickerRef$current === void 0 || _pickerRef$current.focus();
157
+ sortOptionsOnResolve();
114
158
  }
115
- }, [status]);
159
+ }, [sortOptionsOnResolve, status]);
116
160
  var filterOptionsLength = filterOptions.length;
117
161
  var isError = status === 'rejected';
118
162
  var isLoading = status === 'loading' || status === 'empty';
119
163
  var isLoadingMore = status === 'loadingMore';
120
164
  var isEmpty = status === 'resolved' && filterOptionsLength === 0;
165
+ var popupSelectOptions = isLoading || isError ? [] : sortedOptions; // if not set to [], then on loading, no loading UI will be shown
121
166
  var areAllResultsLoaded = filterOptions.length === totalCount;
122
167
  var shouldShowFooter = (status === 'resolved' || isLoadingMore) && filterOptions.length > 0; // footer should not disappear when there is an inline spinner for loading more data
123
168
  var shouldDisplayShowMoreButton = status === 'resolved' && !!pageCursor && !areAllResultsLoaded;
124
- var options = isLoading || isError ? [] : filterOptions; // if not set to [], for eg: on loading, no loading UI will be shown
125
-
126
169
  return /*#__PURE__*/_react.default.createElement(_select.PopupSelect, {
127
170
  isMulti: true,
128
171
  maxMenuWidth: 300,
129
172
  minMenuWidth: 300,
130
- ref: pickerRef,
131
173
  testId: "jlol-basic-filter-popup-select",
132
174
  inputId: "jlol-basic-filter-popup-select--input"
133
175
  /*
@@ -163,7 +205,7 @@ var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
163
205
  IndicatorSeparator: undefined // disables the | separator between search input and icon
164
206
  },
165
207
 
166
- options: options,
208
+ options: popupSelectOptions,
167
209
  value: selectedOptions,
168
210
  filterOption: noFilterOptions,
169
211
  formatOptionLabel: _formatOptionLabel.default,
@@ -174,6 +216,7 @@ var AsyncPopupSelect = function AsyncPopupSelect(_ref) {
174
216
  triggerProps = (0, _objectWithoutProperties2.default)(_ref3, _excluded);
175
217
  return /*#__PURE__*/_react.default.createElement(_trigger.default, (0, _extends2.default)({}, triggerProps, {
176
218
  filterType: filterType,
219
+ selectedOptions: selectedOptions,
177
220
  isSelected: isOpen,
178
221
  onClick: handleOpenPopup,
179
222
  isDisabled: isDisabled
@@ -6,28 +6,51 @@ Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
8
  exports.default = void 0;
9
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
10
  var _react = _interopRequireWildcard(require("react"));
10
11
  var _reactIntlNext = require("react-intl-next");
12
+ var _badge = _interopRequireDefault(require("@atlaskit/badge"));
11
13
  var _standardButton = _interopRequireDefault(require("@atlaskit/button/standard-button"));
12
14
  var _chevronDown = _interopRequireDefault(require("@atlaskit/icon/glyph/chevron-down"));
15
+ var _primitives = require("@atlaskit/primitives");
13
16
  var _messages = require("./messages");
14
17
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
15
18
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
19
+ var triggerButtonLabelStyles = (0, _primitives.xcss)({
20
+ textOverflow: 'ellipsis',
21
+ overflow: 'hidden',
22
+ maxWidth: '150px'
23
+ });
24
+ var badgeStyles = (0, _primitives.xcss)({
25
+ marginLeft: 'space.050'
26
+ });
16
27
  var PopupTrigger = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref) {
17
28
  var filterType = _ref.filterType,
18
29
  isSelected = _ref.isSelected,
19
30
  isDisabled = _ref.isDisabled,
20
- onClick = _ref.onClick;
31
+ onClick = _ref.onClick,
32
+ selectedOptions = _ref.selectedOptions;
33
+ var _ref2 = selectedOptions || [],
34
+ _ref3 = (0, _slicedToArray2.default)(_ref2, 1),
35
+ firstOption = _ref3[0];
36
+ var hasOptions = selectedOptions && selectedOptions.length > 0;
21
37
  return /*#__PURE__*/_react.default.createElement(_standardButton.default, {
22
38
  ref: ref,
23
39
  appearance: "default",
24
- isSelected: isSelected,
40
+ isSelected: isSelected || hasOptions,
25
41
  isDisabled: isDisabled,
26
42
  onClick: onClick,
27
43
  testId: "jlol-basic-filter-".concat(filterType, "-trigger"),
28
44
  iconAfter: /*#__PURE__*/_react.default.createElement(_chevronDown.default, {
29
45
  label: ""
30
46
  })
31
- }, /*#__PURE__*/_react.default.createElement(_reactIntlNext.FormattedMessage, _messages.asyncPopupSelectMessages["".concat(filterType, "Label")]));
47
+ }, /*#__PURE__*/_react.default.createElement(_primitives.Flex, null, /*#__PURE__*/_react.default.createElement(_primitives.Box, {
48
+ xcss: triggerButtonLabelStyles
49
+ }, /*#__PURE__*/_react.default.createElement(_reactIntlNext.FormattedMessage, _messages.asyncPopupSelectMessages["".concat(filterType, "Label")]), firstOption && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, ": ", firstOption.label)), selectedOptions && selectedOptions.length > 1 && /*#__PURE__*/_react.default.createElement(_primitives.Flex, {
50
+ xcss: badgeStyles,
51
+ alignItems: "center"
52
+ }, /*#__PURE__*/_react.default.createElement(_badge.default, {
53
+ appearance: "primary"
54
+ }, "+", selectedOptions.length - 1))));
32
55
  });
33
56
  var _default = exports.default = PopupTrigger;
@@ -21,14 +21,17 @@ var BasicFilterContainer = function BasicFilterContainer(_ref) {
21
21
  var jql = _ref.jql,
22
22
  cloudId = _ref.cloudId;
23
23
  var _useState = (0, _react.useState)([]),
24
- _useState2 = (0, _slicedToArray2.default)(_useState, 1),
25
- selection = _useState2[0];
24
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
25
+ selection = _useState2[0],
26
+ setSelection = _useState2[1];
26
27
  (0, _react.useEffect)(function () {
27
28
  if ((0, _utils.isValidJql)(jql)) {
28
29
  // hydrate hook call goes in here
29
30
  }
30
31
  }, [jql]);
31
- var handleSelectionChange = function handleSelectionChange() {};
32
+ var handleSelectionChange = (0, _react.useCallback)(function (options) {
33
+ setSelection(options);
34
+ }, [setSelection]);
32
35
  return /*#__PURE__*/_react.default.createElement(_primitives.Flex, {
33
36
  xcss: basicFilterContainerStyles,
34
37
  gap: "space.100",
@@ -1,5 +1,5 @@
1
1
  export const EVENT_CHANNEL = 'media';
2
2
  export const packageMetaData = {
3
3
  packageName: "@atlaskit/link-datasource",
4
- packageVersion: "1.16.5"
4
+ packageVersion: "1.17.1"
5
5
  };
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useState } from 'react';
2
+ import { useDatasourceAnalyticsEvents } from '../analytics';
2
3
  import { fetchObjectSchema, fetchObjectSchemas, getWorkspaceId } from '../services/cmdbService';
3
4
  const handleAssetsClientErrors = (errorSetter, error) => {
4
5
  if (error instanceof Error) {
@@ -16,6 +17,9 @@ export const useAssetsClient = initialParameters => {
16
17
  const [objectSchemas, setObjectSchemas] = useState();
17
18
  const [totalObjectSchemas, setTotalObjectSchemas] = useState();
18
19
  const [objectSchemasError, setObjectSchemasError] = useState();
20
+ const {
21
+ fireEvent
22
+ } = useDatasourceAnalyticsEvents();
19
23
 
20
24
  /*
21
25
  * We wrap this in nested try/catch blocks because we want to handle
@@ -27,19 +31,19 @@ export const useAssetsClient = initialParameters => {
27
31
  setLoading(true);
28
32
  setWorkspaceError(undefined);
29
33
  try {
30
- const workspaceId = await getWorkspaceId();
34
+ const workspaceId = await getWorkspaceId(fireEvent);
31
35
  setWorkspaceId(workspaceId);
32
36
  // Check schema from initial parameters still exists and fetch name/permissions for schema select
33
37
  if (initialParameters !== null && initialParameters !== void 0 && initialParameters.schemaId) {
34
38
  try {
35
- const fetchedObjectSchema = await fetchObjectSchema(workspaceId, initialParameters === null || initialParameters === void 0 ? void 0 : initialParameters.schemaId);
39
+ const fetchedObjectSchema = await fetchObjectSchema(workspaceId, initialParameters === null || initialParameters === void 0 ? void 0 : initialParameters.schemaId, fireEvent);
36
40
  setExistingObjectSchema(fetchedObjectSchema);
37
41
  } catch (fetchObjectSchemaError) {
38
42
  handleAssetsClientErrors(setExistingObjectSchemaError, fetchObjectSchemaError);
39
43
  }
40
44
  }
41
45
  try {
42
- const fetchedObjectSchemasResponse = await fetchObjectSchemas(workspaceId);
46
+ const fetchedObjectSchemasResponse = await fetchObjectSchemas(workspaceId, undefined, fireEvent);
43
47
  setObjectSchemas(fetchedObjectSchemasResponse.values);
44
48
  setTotalObjectSchemas(fetchedObjectSchemasResponse.total);
45
49
  } catch (fetchObjectSchemasError) {
@@ -51,7 +55,7 @@ export const useAssetsClient = initialParameters => {
51
55
  setLoading(false);
52
56
  }
53
57
  })();
54
- }, [initialParameters]);
58
+ }, [initialParameters, fireEvent]);
55
59
  return {
56
60
  workspaceId,
57
61
  workspaceError,
@@ -10,8 +10,13 @@ export const useDatasourceTableState = ({
10
10
  const {
11
11
  fireEvent
12
12
  } = useDatasourceAnalyticsEvents();
13
+ const idFieldCount = 1;
14
+ const keyFieldCount = 1;
13
15
  const [defaultVisibleColumnKeys, setDefaultVisibleColumnKeys] = useState([]);
14
16
  const [lastRequestedFieldKeys, setLastRequestedFieldKeys] = useState([]);
17
+ const [fullSchema, setFullSchema] = useState({
18
+ properties: []
19
+ });
15
20
  const [status, setStatus] = useState('empty');
16
21
  const [responseItems, setResponseItems] = useState([]);
17
22
  const [hasNextPage, setHasNextPage] = useState(true);
@@ -56,19 +61,38 @@ export const useDatasourceTableState = ({
56
61
  setStatus('rejected');
57
62
  }
58
63
  }, [columns, datasourceId, getDatasourceDetails, parameters]);
59
- const applySchemaProperties = useCallback(properties => {
60
- if (!isEqual(columns, properties)) {
61
- setColumns(properties);
64
+ const applySchemaProperties = useCallback((schema, fieldKeys) => {
65
+ let {
66
+ properties,
67
+ defaultProperties = []
68
+ } = schema;
69
+ let propertiesToBeUsed = properties;
70
+ const propertyKeysToBeUsed = Array.isArray(fieldKeys) && fieldKeys.length > 0 ? fieldKeys : defaultProperties;
71
+ if (fieldKeys.length > 0 || defaultProperties.length > 0) {
72
+ propertiesToBeUsed = properties.filter(property => {
73
+ return propertyKeysToBeUsed.includes(property.key);
74
+ });
75
+ }
76
+
77
+ /*Jira adds identifier fields like id and key to all data responses
78
+ Since defaultProperties already send back the keyField, we are accounting only
79
+ for the idField when we are using defaulProperties
80
+ */
81
+ if (properties.length > fieldKeys.length + idFieldCount + keyFieldCount && properties.length > defaultProperties.length + idFieldCount) {
82
+ setFullSchema(schema);
83
+ }
84
+ if (!isEqual(columns, propertiesToBeUsed)) {
85
+ setColumns(propertiesToBeUsed);
62
86
  }
63
- const defaultProperties = properties.map(prop => prop.key);
87
+ const newProperties = propertiesToBeUsed.map(prop => prop.key);
64
88
 
65
89
  // when loading for the first time, we will need to set default visible props as /data does not give you that info
66
90
  // also, since we dont pass any fields, we will need to set this info as lastRequestedFieldKeys
67
- if (!isEqual(defaultVisibleColumnKeys, defaultProperties)) {
68
- setDefaultVisibleColumnKeys(defaultProperties);
91
+ if (!isEqual(defaultVisibleColumnKeys, newProperties)) {
92
+ setDefaultVisibleColumnKeys(newProperties);
69
93
  }
70
- if (!isEqual(lastRequestedFieldKeys, defaultProperties)) {
71
- setLastRequestedFieldKeys(defaultProperties);
94
+ if (!isEqual(lastRequestedFieldKeys, newProperties)) {
95
+ setLastRequestedFieldKeys(newProperties);
72
96
  }
73
97
  }, [columns, defaultVisibleColumnKeys, lastRequestedFieldKeys]);
74
98
  const onNextPage = useCallback(async (requestInfo = {}) => {
@@ -80,12 +104,13 @@ export const useDatasourceTableState = ({
80
104
  shouldRequestFirstPage,
81
105
  shouldForceRequest = false
82
106
  } = requestInfo;
107
+ const isFullSchemaLoaded = fullSchema.properties.length > 0;
83
108
  const datasourceDataRequest = {
84
109
  parameters,
85
110
  pageSize: DEFAULT_GET_DATASOURCE_DATA_PAGE_SIZE,
86
111
  pageCursor: shouldRequestFirstPage ? undefined : nextCursor,
87
112
  fields: fieldKeys,
88
- includeSchema: isSchemaFromData
113
+ includeSchema: isFullSchemaLoaded ? false : isSchemaFromData
89
114
  };
90
115
  setStatus('loading');
91
116
  try {
@@ -120,8 +145,8 @@ export const useDatasourceTableState = ({
120
145
  if (fieldKeys.length > 0) {
121
146
  setLastRequestedFieldKeys(fieldKeys);
122
147
  }
123
- if (isSchemaFromData && schema && items.length > 0) {
124
- applySchemaProperties(schema.properties);
148
+ if ((isSchemaFromData && schema || fullSchema.properties.length > 0) && items.length > 0) {
149
+ applySchemaProperties(schema || fullSchema, fieldKeys);
125
150
  }
126
151
  const isUserLoadingNextPage = (responseItems === null || responseItems === void 0 ? void 0 : responseItems.length) !== 0 && !shouldRequestFirstPage;
127
152
  if (isUserLoadingNextPage) {
@@ -141,7 +166,7 @@ export const useDatasourceTableState = ({
141
166
  }
142
167
  setStatus('rejected');
143
168
  }
144
- }, [parameters, fieldKeys, nextCursor, getDatasourceData, datasourceId, responseItems === null || responseItems === void 0 ? void 0 : responseItems.length, applySchemaProperties, fireEvent]);
169
+ }, [parameters, fieldKeys, nextCursor, getDatasourceData, datasourceId, responseItems === null || responseItems === void 0 ? void 0 : responseItems.length, applySchemaProperties, fireEvent, fullSchema]);
145
170
  const reset = useCallback(options => {
146
171
  setStatus('empty');
147
172
  setResponseItems([]);
@@ -149,6 +174,9 @@ export const useDatasourceTableState = ({
149
174
  setNextCursor(undefined);
150
175
  setTotalCount(undefined);
151
176
  setLastRequestedFieldKeys([]);
177
+ setFullSchema({
178
+ properties: []
179
+ });
152
180
  setShouldForceRequest((options === null || options === void 0 ? void 0 : options.shouldForceRequest) || false);
153
181
  if (options !== null && options !== void 0 && options.shouldResetColumns) {
154
182
  setColumns([]);
@@ -1,9 +1,13 @@
1
1
  import { useCallback, useState } from 'react';
2
+ import { useDatasourceAnalyticsEvents } from '../analytics';
2
3
  import { validateAql } from '../services/cmdbService';
3
4
  export const useValidateAqlText = workspaceId => {
4
5
  const [loading, setLoading] = useState(false);
5
6
  const [isValidAqlText, setIsValidAqlText] = useState(false);
6
7
  const [error, setError] = useState();
8
+ const {
9
+ fireEvent
10
+ } = useDatasourceAnalyticsEvents();
7
11
  const validateAqlText = useCallback(async aql => {
8
12
  setLoading(true);
9
13
  setError(undefined);
@@ -13,7 +17,7 @@ export const useValidateAqlText = workspaceId => {
13
17
  var _validateAqlResponse$;
14
18
  const validateAqlResponse = await validateAql(workspaceId, {
15
19
  qlQuery: aql
16
- });
20
+ }, fireEvent);
17
21
  setIsValidAqlText(validateAqlResponse.isValid);
18
22
  isValid = validateAqlResponse.isValid;
19
23
  message = ((_validateAqlResponse$ = validateAqlResponse.errors) === null || _validateAqlResponse$ === void 0 ? void 0 : _validateAqlResponse$.iql) || null;
@@ -30,7 +34,7 @@ export const useValidateAqlText = workspaceId => {
30
34
  isValid,
31
35
  message
32
36
  };
33
- }, [workspaceId]);
37
+ }, [workspaceId, fireEvent]);
34
38
  return {
35
39
  isValidAqlText,
36
40
  validateAqlText,
@@ -1,6 +1,6 @@
1
1
  import { request } from '@atlaskit/linking-common';
2
2
  import { FetchError, getStatusCodeGroup, mapFetchErrors, PermissionError } from './cmdbService.utils';
3
- export const getWorkspaceId = async () => {
3
+ export const getWorkspaceId = async fireEvent => {
4
4
  const url = '/rest/servicedesk/cmdb/latest/workspace';
5
5
  try {
6
6
  var _workspaceDetailsResp;
@@ -8,12 +8,15 @@ export const getWorkspaceId = async () => {
8
8
  if (!((_workspaceDetailsResp = workspaceDetailsResponse.results) !== null && _workspaceDetailsResp !== void 0 && _workspaceDetailsResp.length)) {
9
9
  throw new PermissionError('No workspace results found');
10
10
  }
11
+ fireEvent && fireEvent('operational.getWorkspaceId.success', {});
11
12
  return workspaceDetailsResponse.results[0].id;
12
13
  } catch (err) {
13
14
  let error = mapFetchErrors(err);
14
15
  if (error instanceof FetchError) {
15
- // TODO Fire error operational event for workspace here before remapping to PermissionError
16
- // Only 429 and 5xx errors will be treated as FetchErrors otherwise PermissionError
16
+ fireEvent && fireEvent('operational.getWorkspaceId.failed', {
17
+ statusCodeGroup: getStatusCodeGroup(error)
18
+ });
19
+ // Only 429 and5xx errors will be treated as FetchErrors otherwise PermissionError
17
20
  if (getStatusCodeGroup(error) !== '5xx' && error.statusCode !== 429) {
18
21
  error = new PermissionError('Failed to fetch workspace');
19
22
  }
@@ -21,17 +24,21 @@ export const getWorkspaceId = async () => {
21
24
  throw error;
22
25
  }
23
26
  };
24
- export const validateAql = async (workspaceId, data) => {
27
+ export const validateAql = async (workspaceId, data, fireEvent) => {
25
28
  const url = `/gateway/api/jsm/assets/workspace/${workspaceId}/v1/aql/validate`;
26
29
  try {
27
- return await request('post', url, {
30
+ const response = await request('post', url, {
28
31
  qlQuery: data.qlQuery,
29
32
  context: 'SMART_LINKS'
30
33
  }, undefined, [200, 201, 202, 203, 204]);
34
+ fireEvent && fireEvent('operational.validateAql.success', {});
35
+ return response;
31
36
  } catch (err) {
32
37
  let error = mapFetchErrors(err);
33
38
  if (error instanceof FetchError) {
34
- // TODO Fire error operational event for aql here before remapping to PermissionError
39
+ fireEvent && fireEvent('operational.validateAql.failed', {
40
+ statusCodeGroup: getStatusCodeGroup(error)
41
+ });
35
42
  if (error.statusCode === 401 || error.statusCode === 403) {
36
43
  error = new PermissionError('Failed to fetch object schemas');
37
44
  }
@@ -39,14 +46,18 @@ export const validateAql = async (workspaceId, data) => {
39
46
  throw error;
40
47
  }
41
48
  };
42
- export const fetchObjectSchema = async (workspaceId, schemaId) => {
49
+ export const fetchObjectSchema = async (workspaceId, schemaId, fireEvent) => {
43
50
  const url = `/gateway/api/jsm/assets/workspace/${workspaceId}/v1/objectschema/${schemaId}`;
44
51
  try {
45
- return await request('get', url, undefined, undefined, [200, 201, 202, 203, 204]);
52
+ const response = await request('get', url, undefined, undefined, [200, 201, 202, 203, 204]);
53
+ fireEvent && fireEvent('operational.objectSchema.success', {});
54
+ return response;
46
55
  } catch (err) {
47
56
  let error = mapFetchErrors(err);
48
57
  if (error instanceof FetchError) {
49
- // TODO Fire error operational event for object schema here before remapping to PermissionError
58
+ fireEvent && fireEvent('operational.objectSchema.failed', {
59
+ statusCodeGroup: getStatusCodeGroup(error)
60
+ });
50
61
  if (error.statusCode === 401 || error.statusCode === 403) {
51
62
  error = new PermissionError('Failed to fetch object schemas');
52
63
  }
@@ -54,18 +65,22 @@ export const fetchObjectSchema = async (workspaceId, schemaId) => {
54
65
  throw error;
55
66
  }
56
67
  };
57
- export const fetchObjectSchemas = async (workspaceId, query) => {
68
+ export const fetchObjectSchemas = async (workspaceId, query, fireEvent) => {
58
69
  const queryParams = new URLSearchParams();
59
70
  queryParams.set('maxResults', '20');
60
71
  queryParams.set('includeCounts', 'false');
61
72
  query && queryParams.set('query', query);
62
73
  const url = `/gateway/api/jsm/assets/workspace/${workspaceId}/v1/objectschema/list?${queryParams}`;
63
74
  try {
64
- return await request('get', url, undefined, undefined, [200, 201, 202, 203, 204]);
75
+ const response = await request('get', url, undefined, undefined, [200, 201, 202, 203, 204]);
76
+ fireEvent && fireEvent('operational.objectSchemas.success', {});
77
+ return response;
65
78
  } catch (err) {
66
79
  let error = mapFetchErrors(err);
67
80
  if (error instanceof FetchError) {
68
- // TODO Fire error operational event for object schemas here before remapping to PermissionError
81
+ fireEvent && fireEvent('operational.objectSchemas.failed', {
82
+ statusCodeGroup: getStatusCodeGroup(error)
83
+ });
69
84
  if (error.statusCode === 401 || error.statusCode === 403) {
70
85
  error = new PermissionError('Failed to fetch object schemas');
71
86
  }
@@ -1,5 +1,5 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
- import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+ import React, { useCallback, useEffect, useState } from 'react';
3
3
  import { useIntl } from 'react-intl-next';
4
4
  import { useDebouncedCallback } from 'use-debounce';
5
5
  import { CheckboxOption, PopupSelect } from '@atlaskit/select';
@@ -24,9 +24,9 @@ const AsyncPopupSelect = ({
24
24
  const {
25
25
  formatMessage
26
26
  } = useIntl();
27
- const pickerRef = useRef(null);
28
27
  const [searchTerm, setSearchTerm] = useState('');
29
28
  const [selectedOptions, setSelectedOptions] = useState(selection);
29
+ const [sortedOptions, setSortedOptions] = useState(selectedOptions);
30
30
  const {
31
31
  filterOptions,
32
32
  fetchFilterOptions,
@@ -53,14 +53,32 @@ const AsyncPopupSelect = ({
53
53
  setSelectedOptions(newValue);
54
54
  onSelectionChange(newValue);
55
55
  };
56
- const handleOpenPopup = useCallback(() => {
57
- if (status === 'empty' || status === 'rejected') {
58
- // if user searches and gets status as rejected, we want the dropdown to try load the request with searchString when the user reopens the dropdown
59
- fetchFilterOptions({
60
- searchString: searchTerm
56
+ const sortOptionsOnPopupOpen = useCallback(() => {
57
+ if (selectedOptions.length === 0) {
58
+ setSortedOptions(filterOptions);
59
+ return;
60
+ }
61
+ const nonSelectedOptions = filterOptions.filter(option => !selectedOptions.find(selectedOption => selectedOption.value === option.value));
62
+ const newOptions = [...selectedOptions, ...nonSelectedOptions];
63
+ setSortedOptions(newOptions);
64
+ }, [selectedOptions, filterOptions]);
65
+ const sortOptionsOnResolve = useCallback(() => {
66
+ const newOptions = filterOptions.filter(option => !sortedOptions.find(sortedOption => sortedOption.value === option.value));
67
+ let shouldSetSortOptions = false;
68
+ if (sortedOptions.length !== filterOptions.length) {
69
+ shouldSetSortOptions = true;
70
+ } else {
71
+ sortedOptions.forEach(sortedOption => {
72
+ if (!filterOptions.some(filterOption => filterOption.value === sortedOption.value)) {
73
+ shouldSetSortOptions = true;
74
+ }
61
75
  });
62
76
  }
63
- }, [fetchFilterOptions, searchTerm, status]);
77
+ if (shouldSetSortOptions) {
78
+ const sortedOptionsFiltered = sortedOptions.filter(sortedOption => filterOptions.some(filterOption => filterOption.value === sortedOption.value));
79
+ setSortedOptions([...sortedOptionsFiltered, ...newOptions]);
80
+ }
81
+ }, [filterOptions, sortedOptions]);
64
82
  const handleShowMore = useCallback(() => {
65
83
  if (pageCursor) {
66
84
  fetchFilterOptions({
@@ -69,28 +87,34 @@ const AsyncPopupSelect = ({
69
87
  });
70
88
  }
71
89
  }, [fetchFilterOptions, pageCursor, searchTerm]);
90
+ const handleOpenPopup = useCallback(() => {
91
+ if (status === 'empty' || status === 'rejected') {
92
+ // if user searches and gets status as rejected, we want the dropdown to try load the request with searchString when the user reopens the dropdown
93
+ fetchFilterOptions({
94
+ searchString: searchTerm
95
+ });
96
+ } else if (status === 'resolved') {
97
+ sortOptionsOnPopupOpen();
98
+ }
99
+ }, [fetchFilterOptions, searchTerm, sortOptionsOnPopupOpen, status]);
72
100
  useEffect(() => {
73
101
  if (status === 'resolved') {
74
- var _pickerRef$current, _pickerRef$current$se, _pickerRef$current$se2;
75
- // necessary to refocus the search input after the loading state
76
- pickerRef === null || pickerRef === void 0 ? void 0 : (_pickerRef$current = pickerRef.current) === null || _pickerRef$current === void 0 ? void 0 : (_pickerRef$current$se = _pickerRef$current.selectRef) === null || _pickerRef$current$se === void 0 ? void 0 : (_pickerRef$current$se2 = _pickerRef$current$se.inputRef) === null || _pickerRef$current$se2 === void 0 ? void 0 : _pickerRef$current$se2.focus();
102
+ sortOptionsOnResolve();
77
103
  }
78
- }, [status]);
104
+ }, [sortOptionsOnResolve, status]);
79
105
  const filterOptionsLength = filterOptions.length;
80
106
  const isError = status === 'rejected';
81
107
  const isLoading = status === 'loading' || status === 'empty';
82
108
  const isLoadingMore = status === 'loadingMore';
83
109
  const isEmpty = status === 'resolved' && filterOptionsLength === 0;
110
+ const popupSelectOptions = isLoading || isError ? [] : sortedOptions; // if not set to [], then on loading, no loading UI will be shown
84
111
  const areAllResultsLoaded = filterOptions.length === totalCount;
85
112
  const shouldShowFooter = (status === 'resolved' || isLoadingMore) && filterOptions.length > 0; // footer should not disappear when there is an inline spinner for loading more data
86
113
  const shouldDisplayShowMoreButton = status === 'resolved' && !!pageCursor && !areAllResultsLoaded;
87
- const options = isLoading || isError ? [] : filterOptions; // if not set to [], for eg: on loading, no loading UI will be shown
88
-
89
114
  return /*#__PURE__*/React.createElement(PopupSelect, {
90
115
  isMulti: true,
91
116
  maxMenuWidth: 300,
92
117
  minMenuWidth: 300,
93
- ref: pickerRef,
94
118
  testId: "jlol-basic-filter-popup-select",
95
119
  inputId: "jlol-basic-filter-popup-select--input"
96
120
  /*
@@ -124,7 +148,7 @@ const AsyncPopupSelect = ({
124
148
  IndicatorSeparator: undefined // disables the | separator between search input and icon
125
149
  },
126
150
 
127
- options: options,
151
+ options: popupSelectOptions,
128
152
  value: selectedOptions,
129
153
  filterOption: noFilterOptions,
130
154
  formatOptionLabel: formatOptionLabel,
@@ -135,6 +159,7 @@ const AsyncPopupSelect = ({
135
159
  ...triggerProps
136
160
  }) => /*#__PURE__*/React.createElement(PopupTrigger, _extends({}, triggerProps, {
137
161
  filterType: filterType,
162
+ selectedOptions: selectedOptions,
138
163
  isSelected: isOpen,
139
164
  onClick: handleOpenPopup,
140
165
  isDisabled: isDisabled