@atlaskit/link-datasource 4.29.4 → 4.30.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @atlaskit/link-datasource
2
2
 
3
+ ## 4.30.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`a477f1382d9c3`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a477f1382d9c3) -
8
+ [ux] Add support for daterange data type in link-datasource to better represents data ranges like
9
+ day, month or quarter
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+
3
15
  ## 4.29.4
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = exports.DATERANGE_TYPE_TEST_ID = void 0;
8
+ exports.getFormattedDateRange = getFormattedDateRange;
9
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
10
+ var _react = _interopRequireDefault(require("react"));
11
+ var _addMonths = _interopRequireDefault(require("date-fns/addMonths"));
12
+ var _differenceInDays = _interopRequireDefault(require("date-fns/differenceInDays"));
13
+ var _endOfDay = _interopRequireDefault(require("date-fns/endOfDay"));
14
+ var _getDaysInMonth = _interopRequireDefault(require("date-fns/getDaysInMonth"));
15
+ var _isFirstDayOfMonth = _interopRequireDefault(require("date-fns/isFirstDayOfMonth"));
16
+ var _isLastDayOfMonth = _interopRequireDefault(require("date-fns/isLastDayOfMonth"));
17
+ var _isSameDay = _interopRequireDefault(require("date-fns/isSameDay"));
18
+ var _isSameMonth = _interopRequireDefault(require("date-fns/isSameMonth"));
19
+ var _isSameYear = _interopRequireDefault(require("date-fns/isSameYear"));
20
+ var _startOfDay = _interopRequireDefault(require("date-fns/startOfDay"));
21
+ var _reactIntlNext = require("react-intl-next");
22
+ var _compiled = require("@atlaskit/primitives/compiled");
23
+ var _messages = require("./messages");
24
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
25
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
26
+ var DATERANGE_TYPE_TEST_ID = exports.DATERANGE_TYPE_TEST_ID = 'link-datasource-render-type--daterange';
27
+ var dateOptions = {
28
+ month: 'short',
29
+ day: 'numeric',
30
+ year: 'numeric'
31
+ };
32
+ var timeOptions = {
33
+ hour12: false,
34
+ hour: '2-digit',
35
+ minute: '2-digit'
36
+ };
37
+ var isDayRange = function isDayRange(startDate, endDate) {
38
+ // Naivly, we could just check
39
+ // return startDate.getTime() === endDate.getTime();
40
+ return (0, _isSameYear.default)(startDate, endDate) && (0, _isSameMonth.default)(startDate, endDate) && (0, _isSameDay.default)(startDate, endDate);
41
+ };
42
+ var isMonthRange = function isMonthRange(startDate, endDate) {
43
+ return (0, _isSameYear.default)(startDate, endDate) && (0, _isSameMonth.default)(startDate, endDate) && (0, _isFirstDayOfMonth.default)(startDate) && (0, _isLastDayOfMonth.default)(endDate);
44
+ };
45
+
46
+ /**
47
+ * Checks if the date range is a quarter range.
48
+ * A quarter range is a range of 3 months.
49
+ * The start date must be the first day of the month.
50
+ * The end date must be the last day of the month.
51
+ * Quarters can span across multiple years.
52
+ */
53
+ var isQuarterRange = function isQuarterRange(startDate, endDate) {
54
+ // We need to add 1 to the difference in days
55
+ // because the differenceInDays method returns the number of full days between the two dates
56
+ // and we want to include the start and end dates
57
+ var diffDays = Math.abs((0, _differenceInDays.default)(startDate, endDate)) + 1;
58
+ var firstMonthDays = (0, _getDaysInMonth.default)(startDate);
59
+ var secondMonthDays = (0, _getDaysInMonth.default)((0, _addMonths.default)(startDate, 1));
60
+ var thirdMonthDays = (0, _getDaysInMonth.default)(endDate);
61
+ return diffDays === firstMonthDays + secondMonthDays + thirdMonthDays && (0, _isFirstDayOfMonth.default)(startDate) && (0, _isLastDayOfMonth.default)(endDate);
62
+ };
63
+ var getDateScale = function getDateScale(startDate, endDate) {
64
+ if (isDayRange(startDate, endDate)) {
65
+ // start: 2025-11-01
66
+ // end: 2025-11-01
67
+ return 'day';
68
+ }
69
+ if (isMonthRange(startDate, endDate)) {
70
+ // start: 2025-11-01
71
+ // end: 2025-11-30
72
+ return 'month';
73
+ }
74
+ if (isQuarterRange(startDate, endDate)) {
75
+ // start: 2025-11-01
76
+ // end: 2026-01-31
77
+ return 'quarter';
78
+ }
79
+
80
+ // start: 2025-11-02
81
+ // end: 2026-05-19
82
+ return 'full';
83
+ };
84
+
85
+ // I decided to reuse the same logic as we use currently in the date-time render type
86
+ // Alternatively, we could also use here `parseISO` from `date-fns`
87
+ var shiftUtcToLocal = function shiftUtcToLocal(dateString) {
88
+ // In some cases we get a value of `2023-12-20` which when parsed by JS assumes meantime timezone, causing the date
89
+ // to be one day off in some timezones. We want it to display the date without converting timezones and a solution
90
+ // is to replace the hyphens with slashes. So it should be 20th Dec regardless of the timezone in this case.
91
+ // See https://stackoverflow.com/a/31732581
92
+ var dateValue = /^\d{4}-\d{2}-\d{2}$/.exec(dateString) ? dateString.replace(/-/g, '/') : dateString;
93
+ return new Date(dateValue);
94
+ };
95
+ var isDateTimeString = function isDateTimeString(dateString) {
96
+ return dateString.match(/^\d{4}-\d{2}-\d{2}/) !== null && dateString.includes('T');
97
+ };
98
+ function getFormattedDateRange(startValue, endValue, formatDate, formatMessage) {
99
+ if (!startValue || !endValue) {
100
+ return '';
101
+ }
102
+ var isDateTime = isDateTimeString(startValue) && isDateTimeString(endValue);
103
+ var startDate = isDateTime ? new Date(startValue) : (0, _startOfDay.default)(shiftUtcToLocal(startValue));
104
+ var endDate = isDateTime ? new Date(endValue) : (0, _endOfDay.default)(shiftUtcToLocal(endValue));
105
+ if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
106
+ return '';
107
+ }
108
+
109
+ // If startValue and endValue are valid ISO date strings, we consider it a full range
110
+ var dateScale = isDateTime ? 'full' : getDateScale(startDate, endDate);
111
+ switch (dateScale) {
112
+ case 'day':
113
+ // Nov 1, 2025
114
+ return formatDate(startDate, {
115
+ month: 'short',
116
+ day: 'numeric',
117
+ year: 'numeric'
118
+ });
119
+ case 'month':
120
+ // Nov 2025
121
+ return formatDate(startDate, {
122
+ month: 'short',
123
+ year: 'numeric'
124
+ });
125
+ case 'quarter':
126
+ {
127
+ // Jan-Mar, 2025
128
+ if ((0, _isSameYear.default)(startDate, endDate)) {
129
+ return formatMessage(_messages.messages.quarterRange, {
130
+ startMonth: formatDate(startDate, {
131
+ month: 'short'
132
+ }),
133
+ endMonth: formatDate(endDate, {
134
+ month: 'short'
135
+ }),
136
+ year: formatDate(startDate, {
137
+ year: 'numeric'
138
+ })
139
+ });
140
+ }
141
+ // Dec-Feb, 2025-2026
142
+ return formatMessage(_messages.messages.quarterRangeOverYears, {
143
+ startMonth: formatDate(startDate, {
144
+ month: 'short'
145
+ }),
146
+ endMonth: formatDate(endDate, {
147
+ month: 'short'
148
+ }),
149
+ startYear: formatDate(startDate, {
150
+ year: 'numeric'
151
+ }),
152
+ endYear: formatDate(endDate, {
153
+ year: 'numeric'
154
+ })
155
+ });
156
+ }
157
+ case 'full':
158
+ // Nov 1, 2025 - May 19, 2026
159
+ // Jan 1, 2025, 00:00 - Mar 31, 2025, 23:59
160
+ return formatMessage(_messages.messages.fullRange, {
161
+ start: formatDate(startDate, _objectSpread(_objectSpread({}, dateOptions), timeOptions)),
162
+ end: formatDate(endDate, _objectSpread(_objectSpread({}, dateOptions), timeOptions))
163
+ });
164
+ }
165
+ }
166
+ var DateRangeRenderType = function DateRangeRenderType(_ref) {
167
+ var value = _ref.value,
168
+ _ref$testId = _ref.testId,
169
+ testId = _ref$testId === void 0 ? DATERANGE_TYPE_TEST_ID : _ref$testId;
170
+ var _useIntl = (0, _reactIntlNext.useIntl)(),
171
+ formatDate = _useIntl.formatDate,
172
+ formatMessage = _useIntl.formatMessage;
173
+ var formattedString = getFormattedDateRange(value.start, value.end, formatDate, formatMessage);
174
+ if (formattedString === '') {
175
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null);
176
+ }
177
+ return /*#__PURE__*/_react.default.createElement(_compiled.Text, {
178
+ testId: testId
179
+ }, formattedString);
180
+ };
181
+ var _default = exports.default = DateRangeRenderType;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.messages = void 0;
7
+ var _reactIntlNext = require("react-intl-next");
8
+ var messages = exports.messages = (0, _reactIntlNext.defineMessages)({
9
+ quarterRange: {
10
+ id: 'linkDataSource.issueLikeTable.renderType.dateRange.quarterRange',
11
+ description: 'The formatted date range for a quarter range',
12
+ defaultMessage: '{startMonth}-{endMonth}, {year}'
13
+ },
14
+ quarterRangeOverYears: {
15
+ id: 'linkDataSource.issueLikeTable.renderType.dateRange.quarterRangeOverYears',
16
+ description: 'The formatted date range for a quarter range that spans across multiple years',
17
+ defaultMessage: '{startMonth}-{endMonth}, {startYear}-{endYear}'
18
+ },
19
+ fullRange: {
20
+ id: 'linkDataSource.issueLikeTable.renderType.dateRange.fullRange',
21
+ description: 'The formatted date range for a full range (day, month, quarter, or full)',
22
+ defaultMessage: '{start} - {end}'
23
+ }
24
+ });
@@ -10,6 +10,7 @@ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")
10
10
  var _react = _interopRequireDefault(require("react"));
11
11
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
12
12
  var _boolean = _interopRequireDefault(require("./boolean"));
13
+ var _dateRange = _interopRequireWildcard(require("./date-range"));
13
14
  var _dateTime = _interopRequireWildcard(require("./date-time"));
14
15
  var _icon = _interopRequireDefault(require("./icon"));
15
16
  var _link = _interopRequireDefault(require("./link"));
@@ -32,6 +33,13 @@ var stringifyType = exports.stringifyType = function stringifyType(_ref, formatM
32
33
  return (0, _dateTime.getFormattedDate)(value, 'date', formatDate);
33
34
  case 'datetime':
34
35
  return (0, _dateTime.getFormattedDate)(value, 'datetime', formatDate);
36
+ case 'daterange':
37
+ {
38
+ if ((0, _platformFeatureFlags.fg)('jpd_confluence_date_fields_improvements')) {
39
+ return (0, _dateRange.getFormattedDateRange)(value.start, value.end, formatDate, formatMessage);
40
+ }
41
+ return '';
42
+ }
35
43
  case 'time':
36
44
  return (0, _dateTime.getFormattedDate)(value, 'time', formatDate);
37
45
  case 'icon':
@@ -78,6 +86,17 @@ var renderType = exports.renderType = function renderType(item) {
78
86
  display: "datetime"
79
87
  });
80
88
  });
89
+ case 'daterange':
90
+ {
91
+ if ((0, _platformFeatureFlags.fg)('jpd_confluence_date_fields_improvements')) {
92
+ return item.values.map(function (dateRangeValue) {
93
+ return /*#__PURE__*/_react.default.createElement(_dateRange.default, {
94
+ value: dateRangeValue
95
+ });
96
+ });
97
+ }
98
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null);
99
+ }
81
100
  case 'icon':
82
101
  return item.values.map(function (iconValue) {
83
102
  return /*#__PURE__*/_react.default.createElement(_icon.default, iconValue);
@@ -0,0 +1,177 @@
1
+ import React from 'react';
2
+ import addMonths from 'date-fns/addMonths';
3
+ import differenceInDays from 'date-fns/differenceInDays';
4
+ import endOfDay from 'date-fns/endOfDay';
5
+ import getDaysInMonth from 'date-fns/getDaysInMonth';
6
+ import isFirstDayOfMonth from 'date-fns/isFirstDayOfMonth';
7
+ import isLastDayOfMonth from 'date-fns/isLastDayOfMonth';
8
+ import isSameDay from 'date-fns/isSameDay';
9
+ import isSameMonth from 'date-fns/isSameMonth';
10
+ import isSameYear from 'date-fns/isSameYear';
11
+ import startOfDay from 'date-fns/startOfDay';
12
+ import { useIntl } from 'react-intl-next';
13
+ import { Text } from '@atlaskit/primitives/compiled';
14
+ import { messages } from './messages';
15
+ export const DATERANGE_TYPE_TEST_ID = 'link-datasource-render-type--daterange';
16
+ const dateOptions = {
17
+ month: 'short',
18
+ day: 'numeric',
19
+ year: 'numeric'
20
+ };
21
+ const timeOptions = {
22
+ hour12: false,
23
+ hour: '2-digit',
24
+ minute: '2-digit'
25
+ };
26
+ const isDayRange = (startDate, endDate) => {
27
+ // Naivly, we could just check
28
+ // return startDate.getTime() === endDate.getTime();
29
+ return isSameYear(startDate, endDate) && isSameMonth(startDate, endDate) && isSameDay(startDate, endDate);
30
+ };
31
+ const isMonthRange = (startDate, endDate) => {
32
+ return isSameYear(startDate, endDate) && isSameMonth(startDate, endDate) && isFirstDayOfMonth(startDate) && isLastDayOfMonth(endDate);
33
+ };
34
+
35
+ /**
36
+ * Checks if the date range is a quarter range.
37
+ * A quarter range is a range of 3 months.
38
+ * The start date must be the first day of the month.
39
+ * The end date must be the last day of the month.
40
+ * Quarters can span across multiple years.
41
+ */
42
+ const isQuarterRange = (startDate, endDate) => {
43
+ // We need to add 1 to the difference in days
44
+ // because the differenceInDays method returns the number of full days between the two dates
45
+ // and we want to include the start and end dates
46
+ const diffDays = Math.abs(differenceInDays(startDate, endDate)) + 1;
47
+ const firstMonthDays = getDaysInMonth(startDate);
48
+ const secondMonthDays = getDaysInMonth(addMonths(startDate, 1));
49
+ const thirdMonthDays = getDaysInMonth(endDate);
50
+ return diffDays === firstMonthDays + secondMonthDays + thirdMonthDays && isFirstDayOfMonth(startDate) && isLastDayOfMonth(endDate);
51
+ };
52
+ const getDateScale = (startDate, endDate) => {
53
+ if (isDayRange(startDate, endDate)) {
54
+ // start: 2025-11-01
55
+ // end: 2025-11-01
56
+ return 'day';
57
+ }
58
+ if (isMonthRange(startDate, endDate)) {
59
+ // start: 2025-11-01
60
+ // end: 2025-11-30
61
+ return 'month';
62
+ }
63
+ if (isQuarterRange(startDate, endDate)) {
64
+ // start: 2025-11-01
65
+ // end: 2026-01-31
66
+ return 'quarter';
67
+ }
68
+
69
+ // start: 2025-11-02
70
+ // end: 2026-05-19
71
+ return 'full';
72
+ };
73
+
74
+ // I decided to reuse the same logic as we use currently in the date-time render type
75
+ // Alternatively, we could also use here `parseISO` from `date-fns`
76
+ const shiftUtcToLocal = dateString => {
77
+ // In some cases we get a value of `2023-12-20` which when parsed by JS assumes meantime timezone, causing the date
78
+ // to be one day off in some timezones. We want it to display the date without converting timezones and a solution
79
+ // is to replace the hyphens with slashes. So it should be 20th Dec regardless of the timezone in this case.
80
+ // See https://stackoverflow.com/a/31732581
81
+ const dateValue = /^\d{4}-\d{2}-\d{2}$/.exec(dateString) ? dateString.replace(/-/g, '/') : dateString;
82
+ return new Date(dateValue);
83
+ };
84
+ const isDateTimeString = dateString => {
85
+ return dateString.match(/^\d{4}-\d{2}-\d{2}/) !== null && dateString.includes('T');
86
+ };
87
+ export function getFormattedDateRange(startValue, endValue, formatDate, formatMessage) {
88
+ if (!startValue || !endValue) {
89
+ return '';
90
+ }
91
+ const isDateTime = isDateTimeString(startValue) && isDateTimeString(endValue);
92
+ const startDate = isDateTime ? new Date(startValue) : startOfDay(shiftUtcToLocal(startValue));
93
+ const endDate = isDateTime ? new Date(endValue) : endOfDay(shiftUtcToLocal(endValue));
94
+ if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
95
+ return '';
96
+ }
97
+
98
+ // If startValue and endValue are valid ISO date strings, we consider it a full range
99
+ const dateScale = isDateTime ? 'full' : getDateScale(startDate, endDate);
100
+ switch (dateScale) {
101
+ case 'day':
102
+ // Nov 1, 2025
103
+ return formatDate(startDate, {
104
+ month: 'short',
105
+ day: 'numeric',
106
+ year: 'numeric'
107
+ });
108
+ case 'month':
109
+ // Nov 2025
110
+ return formatDate(startDate, {
111
+ month: 'short',
112
+ year: 'numeric'
113
+ });
114
+ case 'quarter':
115
+ {
116
+ // Jan-Mar, 2025
117
+ if (isSameYear(startDate, endDate)) {
118
+ return formatMessage(messages.quarterRange, {
119
+ startMonth: formatDate(startDate, {
120
+ month: 'short'
121
+ }),
122
+ endMonth: formatDate(endDate, {
123
+ month: 'short'
124
+ }),
125
+ year: formatDate(startDate, {
126
+ year: 'numeric'
127
+ })
128
+ });
129
+ }
130
+ // Dec-Feb, 2025-2026
131
+ return formatMessage(messages.quarterRangeOverYears, {
132
+ startMonth: formatDate(startDate, {
133
+ month: 'short'
134
+ }),
135
+ endMonth: formatDate(endDate, {
136
+ month: 'short'
137
+ }),
138
+ startYear: formatDate(startDate, {
139
+ year: 'numeric'
140
+ }),
141
+ endYear: formatDate(endDate, {
142
+ year: 'numeric'
143
+ })
144
+ });
145
+ }
146
+ case 'full':
147
+ // Nov 1, 2025 - May 19, 2026
148
+ // Jan 1, 2025, 00:00 - Mar 31, 2025, 23:59
149
+ return formatMessage(messages.fullRange, {
150
+ start: formatDate(startDate, {
151
+ ...dateOptions,
152
+ ...timeOptions
153
+ }),
154
+ end: formatDate(endDate, {
155
+ ...dateOptions,
156
+ ...timeOptions
157
+ })
158
+ });
159
+ }
160
+ }
161
+ const DateRangeRenderType = ({
162
+ value,
163
+ testId = DATERANGE_TYPE_TEST_ID
164
+ }) => {
165
+ const {
166
+ formatDate,
167
+ formatMessage
168
+ } = useIntl();
169
+ const formattedString = getFormattedDateRange(value.start, value.end, formatDate, formatMessage);
170
+ if (formattedString === '') {
171
+ return /*#__PURE__*/React.createElement(React.Fragment, null);
172
+ }
173
+ return /*#__PURE__*/React.createElement(Text, {
174
+ testId: testId
175
+ }, formattedString);
176
+ };
177
+ export default DateRangeRenderType;
@@ -0,0 +1,18 @@
1
+ import { defineMessages } from 'react-intl-next';
2
+ export const messages = defineMessages({
3
+ quarterRange: {
4
+ id: 'linkDataSource.issueLikeTable.renderType.dateRange.quarterRange',
5
+ description: 'The formatted date range for a quarter range',
6
+ defaultMessage: '{startMonth}-{endMonth}, {year}'
7
+ },
8
+ quarterRangeOverYears: {
9
+ id: 'linkDataSource.issueLikeTable.renderType.dateRange.quarterRangeOverYears',
10
+ description: 'The formatted date range for a quarter range that spans across multiple years',
11
+ defaultMessage: '{startMonth}-{endMonth}, {startYear}-{endYear}'
12
+ },
13
+ fullRange: {
14
+ id: 'linkDataSource.issueLikeTable.renderType.dateRange.fullRange',
15
+ description: 'The formatted date range for a full range (day, month, quarter, or full)',
16
+ defaultMessage: '{start} - {end}'
17
+ }
18
+ });
@@ -2,6 +2,7 @@ import _extends from "@babel/runtime/helpers/extends";
2
2
  import React from 'react';
3
3
  import { fg } from '@atlaskit/platform-feature-flags';
4
4
  import BooleanRenderType from './boolean';
5
+ import DateRangeRenderType, { getFormattedDateRange } from './date-range';
5
6
  import DateTimeRenderType, { getFormattedDate } from './date-time';
6
7
  import IconRenderType from './icon';
7
8
  import LinkRenderType from './link';
@@ -24,6 +25,13 @@ export const stringifyType = ({
24
25
  return getFormattedDate(value, 'date', formatDate);
25
26
  case 'datetime':
26
27
  return getFormattedDate(value, 'datetime', formatDate);
28
+ case 'daterange':
29
+ {
30
+ if (fg('jpd_confluence_date_fields_improvements')) {
31
+ return getFormattedDateRange(value.start, value.end, formatDate, formatMessage);
32
+ }
33
+ return '';
34
+ }
27
35
  case 'time':
28
36
  return getFormattedDate(value, 'time', formatDate);
29
37
  case 'icon':
@@ -64,6 +72,15 @@ export const renderType = item => {
64
72
  value: datTimeValue,
65
73
  display: "datetime"
66
74
  }));
75
+ case 'daterange':
76
+ {
77
+ if (fg('jpd_confluence_date_fields_improvements')) {
78
+ return item.values.map(dateRangeValue => /*#__PURE__*/React.createElement(DateRangeRenderType, {
79
+ value: dateRangeValue
80
+ }));
81
+ }
82
+ return /*#__PURE__*/React.createElement(React.Fragment, null);
83
+ }
67
84
  case 'icon':
68
85
  return item.values.map(iconValue => /*#__PURE__*/React.createElement(IconRenderType, iconValue));
69
86
  case 'link':
@@ -0,0 +1,173 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ import React from 'react';
5
+ import addMonths from 'date-fns/addMonths';
6
+ import differenceInDays from 'date-fns/differenceInDays';
7
+ import endOfDay from 'date-fns/endOfDay';
8
+ import getDaysInMonth from 'date-fns/getDaysInMonth';
9
+ import isFirstDayOfMonth from 'date-fns/isFirstDayOfMonth';
10
+ import isLastDayOfMonth from 'date-fns/isLastDayOfMonth';
11
+ import isSameDay from 'date-fns/isSameDay';
12
+ import isSameMonth from 'date-fns/isSameMonth';
13
+ import isSameYear from 'date-fns/isSameYear';
14
+ import startOfDay from 'date-fns/startOfDay';
15
+ import { useIntl } from 'react-intl-next';
16
+ import { Text } from '@atlaskit/primitives/compiled';
17
+ import { messages } from './messages';
18
+ export var DATERANGE_TYPE_TEST_ID = 'link-datasource-render-type--daterange';
19
+ var dateOptions = {
20
+ month: 'short',
21
+ day: 'numeric',
22
+ year: 'numeric'
23
+ };
24
+ var timeOptions = {
25
+ hour12: false,
26
+ hour: '2-digit',
27
+ minute: '2-digit'
28
+ };
29
+ var isDayRange = function isDayRange(startDate, endDate) {
30
+ // Naivly, we could just check
31
+ // return startDate.getTime() === endDate.getTime();
32
+ return isSameYear(startDate, endDate) && isSameMonth(startDate, endDate) && isSameDay(startDate, endDate);
33
+ };
34
+ var isMonthRange = function isMonthRange(startDate, endDate) {
35
+ return isSameYear(startDate, endDate) && isSameMonth(startDate, endDate) && isFirstDayOfMonth(startDate) && isLastDayOfMonth(endDate);
36
+ };
37
+
38
+ /**
39
+ * Checks if the date range is a quarter range.
40
+ * A quarter range is a range of 3 months.
41
+ * The start date must be the first day of the month.
42
+ * The end date must be the last day of the month.
43
+ * Quarters can span across multiple years.
44
+ */
45
+ var isQuarterRange = function isQuarterRange(startDate, endDate) {
46
+ // We need to add 1 to the difference in days
47
+ // because the differenceInDays method returns the number of full days between the two dates
48
+ // and we want to include the start and end dates
49
+ var diffDays = Math.abs(differenceInDays(startDate, endDate)) + 1;
50
+ var firstMonthDays = getDaysInMonth(startDate);
51
+ var secondMonthDays = getDaysInMonth(addMonths(startDate, 1));
52
+ var thirdMonthDays = getDaysInMonth(endDate);
53
+ return diffDays === firstMonthDays + secondMonthDays + thirdMonthDays && isFirstDayOfMonth(startDate) && isLastDayOfMonth(endDate);
54
+ };
55
+ var getDateScale = function getDateScale(startDate, endDate) {
56
+ if (isDayRange(startDate, endDate)) {
57
+ // start: 2025-11-01
58
+ // end: 2025-11-01
59
+ return 'day';
60
+ }
61
+ if (isMonthRange(startDate, endDate)) {
62
+ // start: 2025-11-01
63
+ // end: 2025-11-30
64
+ return 'month';
65
+ }
66
+ if (isQuarterRange(startDate, endDate)) {
67
+ // start: 2025-11-01
68
+ // end: 2026-01-31
69
+ return 'quarter';
70
+ }
71
+
72
+ // start: 2025-11-02
73
+ // end: 2026-05-19
74
+ return 'full';
75
+ };
76
+
77
+ // I decided to reuse the same logic as we use currently in the date-time render type
78
+ // Alternatively, we could also use here `parseISO` from `date-fns`
79
+ var shiftUtcToLocal = function shiftUtcToLocal(dateString) {
80
+ // In some cases we get a value of `2023-12-20` which when parsed by JS assumes meantime timezone, causing the date
81
+ // to be one day off in some timezones. We want it to display the date without converting timezones and a solution
82
+ // is to replace the hyphens with slashes. So it should be 20th Dec regardless of the timezone in this case.
83
+ // See https://stackoverflow.com/a/31732581
84
+ var dateValue = /^\d{4}-\d{2}-\d{2}$/.exec(dateString) ? dateString.replace(/-/g, '/') : dateString;
85
+ return new Date(dateValue);
86
+ };
87
+ var isDateTimeString = function isDateTimeString(dateString) {
88
+ return dateString.match(/^\d{4}-\d{2}-\d{2}/) !== null && dateString.includes('T');
89
+ };
90
+ export function getFormattedDateRange(startValue, endValue, formatDate, formatMessage) {
91
+ if (!startValue || !endValue) {
92
+ return '';
93
+ }
94
+ var isDateTime = isDateTimeString(startValue) && isDateTimeString(endValue);
95
+ var startDate = isDateTime ? new Date(startValue) : startOfDay(shiftUtcToLocal(startValue));
96
+ var endDate = isDateTime ? new Date(endValue) : endOfDay(shiftUtcToLocal(endValue));
97
+ if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
98
+ return '';
99
+ }
100
+
101
+ // If startValue and endValue are valid ISO date strings, we consider it a full range
102
+ var dateScale = isDateTime ? 'full' : getDateScale(startDate, endDate);
103
+ switch (dateScale) {
104
+ case 'day':
105
+ // Nov 1, 2025
106
+ return formatDate(startDate, {
107
+ month: 'short',
108
+ day: 'numeric',
109
+ year: 'numeric'
110
+ });
111
+ case 'month':
112
+ // Nov 2025
113
+ return formatDate(startDate, {
114
+ month: 'short',
115
+ year: 'numeric'
116
+ });
117
+ case 'quarter':
118
+ {
119
+ // Jan-Mar, 2025
120
+ if (isSameYear(startDate, endDate)) {
121
+ return formatMessage(messages.quarterRange, {
122
+ startMonth: formatDate(startDate, {
123
+ month: 'short'
124
+ }),
125
+ endMonth: formatDate(endDate, {
126
+ month: 'short'
127
+ }),
128
+ year: formatDate(startDate, {
129
+ year: 'numeric'
130
+ })
131
+ });
132
+ }
133
+ // Dec-Feb, 2025-2026
134
+ return formatMessage(messages.quarterRangeOverYears, {
135
+ startMonth: formatDate(startDate, {
136
+ month: 'short'
137
+ }),
138
+ endMonth: formatDate(endDate, {
139
+ month: 'short'
140
+ }),
141
+ startYear: formatDate(startDate, {
142
+ year: 'numeric'
143
+ }),
144
+ endYear: formatDate(endDate, {
145
+ year: 'numeric'
146
+ })
147
+ });
148
+ }
149
+ case 'full':
150
+ // Nov 1, 2025 - May 19, 2026
151
+ // Jan 1, 2025, 00:00 - Mar 31, 2025, 23:59
152
+ return formatMessage(messages.fullRange, {
153
+ start: formatDate(startDate, _objectSpread(_objectSpread({}, dateOptions), timeOptions)),
154
+ end: formatDate(endDate, _objectSpread(_objectSpread({}, dateOptions), timeOptions))
155
+ });
156
+ }
157
+ }
158
+ var DateRangeRenderType = function DateRangeRenderType(_ref) {
159
+ var value = _ref.value,
160
+ _ref$testId = _ref.testId,
161
+ testId = _ref$testId === void 0 ? DATERANGE_TYPE_TEST_ID : _ref$testId;
162
+ var _useIntl = useIntl(),
163
+ formatDate = _useIntl.formatDate,
164
+ formatMessage = _useIntl.formatMessage;
165
+ var formattedString = getFormattedDateRange(value.start, value.end, formatDate, formatMessage);
166
+ if (formattedString === '') {
167
+ return /*#__PURE__*/React.createElement(React.Fragment, null);
168
+ }
169
+ return /*#__PURE__*/React.createElement(Text, {
170
+ testId: testId
171
+ }, formattedString);
172
+ };
173
+ export default DateRangeRenderType;
@@ -0,0 +1,18 @@
1
+ import { defineMessages } from 'react-intl-next';
2
+ export var messages = defineMessages({
3
+ quarterRange: {
4
+ id: 'linkDataSource.issueLikeTable.renderType.dateRange.quarterRange',
5
+ description: 'The formatted date range for a quarter range',
6
+ defaultMessage: '{startMonth}-{endMonth}, {year}'
7
+ },
8
+ quarterRangeOverYears: {
9
+ id: 'linkDataSource.issueLikeTable.renderType.dateRange.quarterRangeOverYears',
10
+ description: 'The formatted date range for a quarter range that spans across multiple years',
11
+ defaultMessage: '{startMonth}-{endMonth}, {startYear}-{endYear}'
12
+ },
13
+ fullRange: {
14
+ id: 'linkDataSource.issueLikeTable.renderType.dateRange.fullRange',
15
+ description: 'The formatted date range for a full range (day, month, quarter, or full)',
16
+ defaultMessage: '{start} - {end}'
17
+ }
18
+ });
@@ -2,6 +2,7 @@ import _extends from "@babel/runtime/helpers/extends";
2
2
  import React from 'react';
3
3
  import { fg } from '@atlaskit/platform-feature-flags';
4
4
  import BooleanRenderType from './boolean';
5
+ import DateRangeRenderType, { getFormattedDateRange } from './date-range';
5
6
  import DateTimeRenderType, { getFormattedDate } from './date-time';
6
7
  import IconRenderType from './icon';
7
8
  import LinkRenderType from './link';
@@ -23,6 +24,13 @@ export var stringifyType = function stringifyType(_ref, formatMessage, formatDat
23
24
  return getFormattedDate(value, 'date', formatDate);
24
25
  case 'datetime':
25
26
  return getFormattedDate(value, 'datetime', formatDate);
27
+ case 'daterange':
28
+ {
29
+ if (fg('jpd_confluence_date_fields_improvements')) {
30
+ return getFormattedDateRange(value.start, value.end, formatDate, formatMessage);
31
+ }
32
+ return '';
33
+ }
26
34
  case 'time':
27
35
  return getFormattedDate(value, 'time', formatDate);
28
36
  case 'icon':
@@ -69,6 +77,17 @@ export var renderType = function renderType(item) {
69
77
  display: "datetime"
70
78
  });
71
79
  });
80
+ case 'daterange':
81
+ {
82
+ if (fg('jpd_confluence_date_fields_improvements')) {
83
+ return item.values.map(function (dateRangeValue) {
84
+ return /*#__PURE__*/React.createElement(DateRangeRenderType, {
85
+ value: dateRangeValue
86
+ });
87
+ });
88
+ }
89
+ return /*#__PURE__*/React.createElement(React.Fragment, null);
90
+ }
72
91
  case 'icon':
73
92
  return item.values.map(function (iconValue) {
74
93
  return /*#__PURE__*/React.createElement(IconRenderType, iconValue);
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { type IntlShape } from 'react-intl-next';
3
+ import { type DateRangeType } from '@atlaskit/linking-types';
4
+ export interface DateRangeProps {
5
+ testId?: string;
6
+ value: DateRangeType['value'];
7
+ }
8
+ export declare const DATERANGE_TYPE_TEST_ID = "link-datasource-render-type--daterange";
9
+ export declare function getFormattedDateRange(startValue: string, endValue: string, formatDate: IntlShape['formatDate'], formatMessage: IntlShape['formatMessage']): string;
10
+ declare const DateRangeRenderType: ({ value, testId }: DateRangeProps) => React.JSX.Element;
11
+ export default DateRangeRenderType;
@@ -0,0 +1,17 @@
1
+ export declare const messages: {
2
+ quarterRange: {
3
+ id: string;
4
+ description: string;
5
+ defaultMessage: string;
6
+ };
7
+ quarterRangeOverYears: {
8
+ id: string;
9
+ description: string;
10
+ defaultMessage: string;
11
+ };
12
+ fullRange: {
13
+ id: string;
14
+ description: string;
15
+ defaultMessage: string;
16
+ };
17
+ };
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { type IntlShape } from 'react-intl-next';
3
+ import { type DateRangeType } from '@atlaskit/linking-types';
4
+ export interface DateRangeProps {
5
+ testId?: string;
6
+ value: DateRangeType['value'];
7
+ }
8
+ export declare const DATERANGE_TYPE_TEST_ID = "link-datasource-render-type--daterange";
9
+ export declare function getFormattedDateRange(startValue: string, endValue: string, formatDate: IntlShape['formatDate'], formatMessage: IntlShape['formatMessage']): string;
10
+ declare const DateRangeRenderType: ({ value, testId }: DateRangeProps) => React.JSX.Element;
11
+ export default DateRangeRenderType;
@@ -0,0 +1,17 @@
1
+ export declare const messages: {
2
+ quarterRange: {
3
+ id: string;
4
+ description: string;
5
+ defaultMessage: string;
6
+ };
7
+ quarterRangeOverYears: {
8
+ id: string;
9
+ description: string;
10
+ defaultMessage: string;
11
+ };
12
+ fullRange: {
13
+ id: string;
14
+ description: string;
15
+ defaultMessage: string;
16
+ };
17
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/link-datasource",
3
- "version": "4.29.4",
3
+ "version": "4.30.0",
4
4
  "description": "UI Components to support linking platform dataset feature",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -61,14 +61,14 @@
61
61
  "@atlaskit/jql-ast": "^3.3.0",
62
62
  "@atlaskit/jql-editor": "^5.8.0",
63
63
  "@atlaskit/jql-editor-autocomplete-rest": "^3.0.0",
64
- "@atlaskit/layering": "^3.2.0",
64
+ "@atlaskit/layering": "^3.3.0",
65
65
  "@atlaskit/link": "^3.2.0",
66
66
  "@atlaskit/link-client-extension": "^6.0.0",
67
67
  "@atlaskit/linking-common": "^9.8.0",
68
- "@atlaskit/linking-types": "^14.1.0",
68
+ "@atlaskit/linking-types": "^14.2.0",
69
69
  "@atlaskit/logo": "^19.9.0",
70
70
  "@atlaskit/lozenge": "^13.1.0",
71
- "@atlaskit/modal-dialog": "^14.6.0",
71
+ "@atlaskit/modal-dialog": "^14.7.0",
72
72
  "@atlaskit/outbound-auth-flow-client": "^3.4.0",
73
73
  "@atlaskit/platform-feature-flags": "^1.1.0",
74
74
  "@atlaskit/popup": "^4.6.0",
@@ -77,13 +77,13 @@
77
77
  "@atlaskit/pragmatic-drag-and-drop-react-beautiful-dnd-autoscroll": "^2.0.0",
78
78
  "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^3.2.0",
79
79
  "@atlaskit/primitives": "^16.1.0",
80
- "@atlaskit/react-select": "^3.8.0",
81
- "@atlaskit/select": "^21.3.0",
80
+ "@atlaskit/react-select": "^3.9.0",
81
+ "@atlaskit/select": "^21.4.0",
82
82
  "@atlaskit/smart-card": "^43.6.0",
83
83
  "@atlaskit/smart-user-picker": "^8.4.0",
84
84
  "@atlaskit/spinner": "^19.0.0",
85
85
  "@atlaskit/tag": "^14.1.0",
86
- "@atlaskit/textfield": "^8.0.0",
86
+ "@atlaskit/textfield": "^8.1.0",
87
87
  "@atlaskit/theme": "^21.0.0",
88
88
  "@atlaskit/tokens": "^8.0.0",
89
89
  "@atlaskit/tooltip": "^20.8.0",
@@ -92,6 +92,7 @@
92
92
  "@babel/runtime": "^7.0.0",
93
93
  "@compiled/react": "^0.18.6",
94
94
  "@types/dompurify": "^2.2.3",
95
+ "date-fns": "^2.17.0",
95
96
  "debounce-promise": "^3.1.2",
96
97
  "dompurify": "^2.5.6",
97
98
  "lodash": "^4.17.21",
@@ -110,7 +111,7 @@
110
111
  "@af/visual-regression": "workspace:^",
111
112
  "@atlaskit/json-ld-types": "^1.4.0",
112
113
  "@atlaskit/link-provider": "^4.0.0",
113
- "@atlaskit/link-test-helpers": "^8.4.0",
114
+ "@atlaskit/link-test-helpers": "^8.5.0",
114
115
  "@atlaskit/ssr": "workspace:^",
115
116
  "@atlassian/feature-flags-test-utils": "^1.0.0",
116
117
  "@faker-js/faker": "^7.5.0",
@@ -189,6 +190,9 @@
189
190
  },
190
191
  "platform_navx_jira_sllv_rich_text_gate": {
191
192
  "type": "boolean"
193
+ },
194
+ "jpd_confluence_date_fields_improvements": {
195
+ "type": "boolean"
192
196
  }
193
197
  },
194
198
  "compassUnitTestMetricSourceId": "ari:cloud:compass:a436116f-02ce-4520-8fbb-7301462a1674:metric-source/c5751cc6-3513-4070-9deb-af31e86aed34/9c893299-a527-4457-9b46-f3bc4c828766"