From 194ae83826b0d80297933e95af2501e901c6befb Mon Sep 17 00:00:00 2001 From: Shridhar TL Date: Sun, 21 May 2023 08:27:08 +0530 Subject: [PATCH 1/6] #300 - Fixed issue for Timer controls not adding to board --- package.json | 12 ++++++------ src/content-scripts/jira-board.js | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 652315c1..e5636005 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,8 @@ } }, "dependencies": { - "@atlaskit/avatar": "^21.3.0", - "@atlaskit/side-navigation": "^1.8.0", + "@atlaskit/avatar": "^21.3.2", + "@atlaskit/side-navigation": "^1.8.1", "@coreui/coreui": "^2.1.16", "@forge/api": "^2.15.3", "@forge/bridge": "^2.5.6", @@ -78,7 +78,7 @@ "jquery": "^3.7.0", "jsd-report": "^0.1.11", "jspdf": "^2.5.1", - "jspdf-autotable": "^3.5.28", + "jspdf-autotable": "^3.5.29", "moment": "^2.29.4", "moment-timezone": "^0.5.43", "papaparse": "^5.4.1", @@ -93,7 +93,7 @@ "react-dnd": "^14.0.4", "react-dnd-html5-backend": "^14.0.2", "react-dom": "^18.2.0", - "react-router-dom": "^6.11.1", + "react-router-dom": "^6.11.2", "react-scripts": "^5.0.1", "reactstrap": "^9.1.10", "static-eval": "^2.1.0" @@ -102,10 +102,10 @@ "@craco/craco": "^7.1.0", "concurrently": "^8.0.1", "cross-env": "^7.0.3", - "electron": "^24.3.0", + "electron": "^24.3.1", "electron-builder": "^23.6.0", "electronmon": "^2.0.2", - "eslint": "^8.40.0", + "eslint": "^8.41.0", "eslint-plugin-react-hooks": "^4.6.0", "gh-pages": "^5.0.0", "sass": "^1.62.1", diff --git a/src/content-scripts/jira-board.js b/src/content-scripts/jira-board.js index 30bc3796..39bbb067 100644 --- a/src/content-scripts/jira-board.js +++ b/src/content-scripts/jira-board.js @@ -5,11 +5,11 @@ import { waitAndGet } from "./utils"; export async function applyBoardLogic(currentPage, settings, firstTime, applyModifications) { if (currentPage === Pages.Board) { - $('.ghx-columns .ui-sortable div.js-issue .ja-issue-el').remove(); + $('.ghx-columns div.js-issue .ja-issue-el').remove(); const triggerFunc = triggerWLTracking.bind({ settings, applyModifications }); - const selector = '.ghx-columns .ui-sortable div.js-issue'; + const selector = '.ghx-columns div.js-issue'; const issues = firstTime ? (await waitAndGet(selector)) : $(selector); issues.each((i, el) => { el = $(el); From a58795b2ed5f55123eaa7258585934c549dd7db7 Mon Sep 17 00:00:00 2001 From: Scott Date: Sun, 21 May 2023 13:01:44 +1000 Subject: [PATCH 2/6] #298 - Resolve minor issue with worklog timer entries and rounding --- src/services/worklog-timer-service.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/worklog-timer-service.js b/src/services/worklog-timer-service.js index b85d8000..44e56965 100644 --- a/src/services/worklog-timer-service.js +++ b/src/services/worklog-timer-service.js @@ -144,11 +144,11 @@ export default class WorklogTimerService extends BaseService { const minTime = parseTimeInMins(await this.$settings.get('TR_MinTime')); if (timeInMins >= minTime) { - const TR_RoundTime = await this.$settings.get('TR_RoundTime'); - const TR_RoundOpr = (await this.$settings.get('TR_RoundOpr')) || 5; + const TR_RoundTime = (await this.$settings.get('TR_RoundTime')) || 5; + const TR_RoundOpr = await this.$settings.get('TR_RoundOpr'); if (TR_RoundOpr && Math[TR_RoundOpr]) { - timeInMins = parseInt(Math[TR_RoundOpr](timeInMins / TR_RoundTime) * TR_RoundTime); + timeInMins = parseInt(Math.max(1, Math[TR_RoundOpr](timeInMins / TR_RoundTime)) * TR_RoundTime); } if (timeInMins >= 1) { From 649c3ee4f0f3130f5625e890f9961558187ae5c2 Mon Sep 17 00:00:00 2001 From: Shridhar TL Date: Sun, 21 May 2023 10:02:05 +0530 Subject: [PATCH 3/6] #301 - Worklog report - Grouped tab - Added option to show or hide issues by default based on settings --- src/gadgets/WorklogReport/datastore.js | 3 +++ .../WorklogReport/settings/FormattingSettings.js | 10 +++++++++- src/gadgets/WorklogReport/userdaywise/UserRow.js | 5 +++-- src/gadgets/WorklogReport/userdaywise/actions.js | 7 ++++--- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/gadgets/WorklogReport/datastore.js b/src/gadgets/WorklogReport/datastore.js index 1c53573a..b808d777 100644 --- a/src/gadgets/WorklogReport/datastore.js +++ b/src/gadgets/WorklogReport/datastore.js @@ -15,6 +15,7 @@ const initialData = { daysToHide: '1', // 1=show all days, 2=hide non working days,3=hide days without worklog rIndicator: '1', // 1=thermometer, 2=bg highlight,0=none timeZone: '1', + expandUsers: false, sprintStartRounding: '1', sprintEndRounding: '1', @@ -84,6 +85,7 @@ export function getSettingsObj(data, opts) { fields, daysToHide, rIndicator, + expandUsers, jql, logFilterType, filterThrsType, @@ -113,6 +115,7 @@ export function getSettingsObj(data, opts) { fields, daysToHide, rIndicator, + expandUsers, jql, logFilterType, filterThrsType, diff --git a/src/gadgets/WorklogReport/settings/FormattingSettings.js b/src/gadgets/WorklogReport/settings/FormattingSettings.js index f0988f76..8a0c9641 100644 --- a/src/gadgets/WorklogReport/settings/FormattingSettings.js +++ b/src/gadgets/WorklogReport/settings/FormattingSettings.js @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { renderRadioButton } from './actions'; import { ShowMoreLink } from './Common'; +import { Checkbox } from '../../../controls'; function FormattingSettings({ setValue, state, state: { logFormat, timeZone, daysToHide } }) { const [showMore, setShowMore] = useState(false); @@ -77,7 +78,7 @@ function FormattingSettings({ setValue, state, state: { logFormat, timeZone, day export default FormattingSettings; -function MoreFormattingSettings({ setValue, state: { breakupMode, userDisplayFormat, rIndicator } }) { +function MoreFormattingSettings({ setValue, state: { breakupMode, userDisplayFormat, rIndicator, expandUsers } }) { return (<>
@@ -136,5 +137,12 @@ function MoreFormattingSettings({ setValue, state: { breakupMode, userDisplayFor
+
+
+ setValue('expandUsers', val)} + label="Expand user row by default in grouped report" /> +
+
); } \ No newline at end of file diff --git a/src/gadgets/WorklogReport/userdaywise/UserRow.js b/src/gadgets/WorklogReport/userdaywise/UserRow.js index eeee8832..e5a55d9c 100644 --- a/src/gadgets/WorklogReport/userdaywise/UserRow.js +++ b/src/gadgets/WorklogReport/userdaywise/UserRow.js @@ -55,11 +55,12 @@ function UserRow({ export default connect(UserRow, (state, { boardId, groupIndex, user }) => { - const { userDisplayFormat, timeframeType, userExpnState } = state; + const { userDisplayFormat, timeframeType, userExpnState, expandUsers } = state; const isSprint = timeframeType === '1'; const uid = getUserName(user); const expandKey = `${boardId}_${groupIndex}_${uid}`; - const result = { isSprint, userDisplayFormat, uid, expanded: !!userExpnState[expandKey] }; + const isUserExpanded = userExpnState[expandKey] ?? expandUsers; + const result = { isSprint, userDisplayFormat, uid, expanded: !!isUserExpanded }; if (isSprint) { result.sprintsList = state[`sprintsList_${boardId}`]; diff --git a/src/gadgets/WorklogReport/userdaywise/actions.js b/src/gadgets/WorklogReport/userdaywise/actions.js index 8c777229..b3df560e 100644 --- a/src/gadgets/WorklogReport/userdaywise/actions.js +++ b/src/gadgets/WorklogReport/userdaywise/actions.js @@ -1,11 +1,12 @@ export function toggleUserExpanded(setState, getState) { return function (boardId, groupIndex, uid) { - const userExpnState = { ...getState('userExpnState') }; + const state = getState(); + const userExpnState = { ...state.userExpnState }; const key = `${boardId}_${groupIndex}_${uid}`; const { [key]: isExpanded } = userExpnState; - if (isExpanded) { - delete userExpnState[key]; + if (isExpanded ?? state.expandUsers) { + userExpnState[key] = false; } else { userExpnState[key] = true; } From 45d67fa6987e98dd48e0c9d472741d0dc9725e3a Mon Sep 17 00:00:00 2001 From: Shridhar TL Date: Sun, 21 May 2023 10:03:30 +0530 Subject: [PATCH 4/6] #301 - Added Issue Age as custom column in worklog report --- src/display-controls/index.js | 1 + src/jira-controls/JiraFieldMultiSelect.js | 12 ++++++------ src/services/jira-service.js | 15 ++++++++++++++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/display-controls/index.js b/src/display-controls/index.js index 700b7a7a..a8b481c2 100644 --- a/src/display-controls/index.js +++ b/src/display-controls/index.js @@ -20,6 +20,7 @@ export { export function getComponentFor(type) { switch (type) { + case 'ageindays': return { Component: DateDisplay, props: { quick: true } }; case 'date': return { Component: DateDisplay, props: { dateOnly: true } }; case 'datetime': return { Component: DateDisplay }; case 'parent': return { Component: IssueDisplay, props: { settings: { valueType: 'both' } } }; diff --git a/src/jira-controls/JiraFieldMultiSelect.js b/src/jira-controls/JiraFieldMultiSelect.js index 71410e67..742cb3bb 100644 --- a/src/jira-controls/JiraFieldMultiSelect.js +++ b/src/jira-controls/JiraFieldMultiSelect.js @@ -7,13 +7,13 @@ function JiraFieldMultiSelect({ value, field, valueOnly, onChange, hideTypes, hi if (!value || valueOnly) { return value; } else { - return value.map(v => v.key); + return value.map(v => v.id || v.key); } }, [value, valueOnly]); const [fields, setFields] = useState(); const fieldsMap = useMemo(() => fields?.reduce((obj, f) => { - obj[f.key] = f; + obj[f.id] = f; return obj; }, {}), [fields]); @@ -24,10 +24,10 @@ function JiraFieldMultiSelect({ value, field, valueOnly, onChange, hideTypes, hi const changeHandler = useCallback((value, field) => { const selValue = value.map(v => { const f = fieldsMap[v]; - const { key, name } = f; + const { id, key, name } = f; const type = getFieldType(f); - return { key, name, type }; + return { id, key, name, type }; }); onChange(selValue, field); @@ -38,7 +38,7 @@ function JiraFieldMultiSelect({ value, field, valueOnly, onChange, hideTypes, hi } return (); @@ -55,7 +55,7 @@ async function getCustomFields(hideTypes, hideKeys) { } if (Array.isArray(hideKeys) && hideKeys.length) { - fields = fields.filter(f => !hideKeys.includes(f.key)); + fields = fields.filter(f => !hideKeys.includes(f.id)); } return fields; diff --git a/src/services/jira-service.js b/src/services/jira-service.js index cd0a35c5..551fa648 100644 --- a/src/services/jira-service.js +++ b/src/services/jira-service.js @@ -131,10 +131,23 @@ export default class JiraService { result = await this.$ajax.get(ApiUrls.getCustomFields); + const createdFieldIndex = result.findIndex(f => f.id === 'created'); + if (createdFieldIndex > 0) { + const createdField = result[createdFieldIndex]; + const ageField = { + ...createdField, + custom: true, + id: 'issueAge', + name: 'Issue Age', + schema: { type: 'ageindays' } + }; + + result.splice(createdFieldIndex + 1, 0, ageField); + } + this.$jaCache.session.set("customFields", result, 10); return result; - } async getRapidViews() { From d806e8d875f39212510c370aa6563c37219d733f Mon Sep 17 00:00:00 2001 From: Shridhar TL Date: Sun, 21 May 2023 23:26:14 +0530 Subject: [PATCH 5/6] #293 - Option added to adjust remaining estimate from add worklog popup --- src/constants/api-urls.js | 4 +- src/dialogs/AddWorklog.js | 135 +++++++++++++++++--------------- src/gadgets/PendingWorklog.js | 5 +- src/services/worklog-service.js | 49 +++++++++--- 4 files changed, 113 insertions(+), 80 deletions(-) diff --git a/src/constants/api-urls.js b/src/constants/api-urls.js index 5e3f33e4..8ae05afc 100644 --- a/src/constants/api-urls.js +++ b/src/constants/api-urls.js @@ -11,9 +11,9 @@ export const ApiUrls = { getIssueMetadata: "~/rest/api/2/issue/{0}/editmeta", individualIssue: "~/rest/api/2/issue/{0}", getAllIssueTypes: "~/rest/api/2/issuetype", - addIssueWorklog: "~/rest/api/2/issue/{0}/worklog?adjustEstimate=AUTO", + addIssueWorklog: "~/rest/api/2/issue/{0}/worklog", issueWorklog: "~/rest/api/2/issue/{0}/worklog", - updateIndividualWorklog: "~/rest/api/2/issue/{0}/worklog/{1}?adjustEstimate=AUTO", + updateIndividualWorklog: "~/rest/api/2/issue/{0}/worklog/{1}", individualWorklog: "~/rest/api/2/issue/{0}/worklog/{1}", searchUser: "~/rest/api/2/user/search?maxResults={1}&startAt={2}&query={0}", searchUser_Alt: "~/rest/api/2/user/search?maxResults={1}&startAt={2}&username={0}", diff --git a/src/dialogs/AddWorklog.js b/src/dialogs/AddWorklog.js index 5a0bef6b..80472c06 100644 --- a/src/dialogs/AddWorklog.js +++ b/src/dialogs/AddWorklog.js @@ -3,7 +3,7 @@ import moment from 'moment'; import { InputMask } from 'primereact/inputmask'; import BaseDialog from './BaseDialog'; import { inject } from '../services/injector-service'; -import { Button, Checkbox, DatePicker, TextBox } from '../controls'; +import { Button, Checkbox, DatePicker, SelectBox, TextBox } from '../controls'; import { GadgetActionType } from '../gadgets'; import { EventCategory } from '../constants/settings'; import { IssuePicker } from '../jira-controls/IssuePicker'; @@ -17,6 +17,13 @@ function convertHours(value) { return `${h.pad(2)}:${m.pad(2)}`; } +const adjustEstimateTypes = [ + { value: '', label: 'Auto adjust' }, + { value: 'leave', label: 'No changes' }, + { value: 'manual', label: 'Reduce by' }, + { value: 'new', label: 'Set new estimate' } +]; + class AddWorklog extends BaseDialog { constructor(props) { const { editTracker } = props; @@ -66,8 +73,7 @@ class AddWorklog extends BaseDialog { newState.log = { ticketNo: obj.ticketNo, dateStarted: moment(obj.startTime || obj.dateStarted || new Date()).toDate(), - description: obj.description?.trim() || '', - allowOverride: obj.allowOverride + description: obj.description?.trim() || '' }; if (obj.parentId) { @@ -80,17 +86,8 @@ class AddWorklog extends BaseDialog { timeSpent = this.$utils.formatSecs(timeSpent, false, true); } - if (!obj.allowOverride && timeSpent) { - newState.log.timeSpent = timeSpent; - } - if (timeSpent) { - newState.log.overrideTimeSpent = timeSpent; - } - else { - newState.log.overrideTimeSpent = this.defaultTimeSpent; - newState.log.allowOverride = true; - } + newState.log.timeSpent = timeSpent || this.defaultTimeSpent; this.validateData(newState.log, newState.vald, newState.ctlClass); } @@ -106,10 +103,6 @@ class AddWorklog extends BaseDialog { d.timeSpent = null; } } - if (d.overrideTimeSpent) { - d.overrideTimeSpent = d.overrideTimeSpent.substring(0, 5); - d.allowOverride = true; - } if (copy) { this.$analytics.trackEvent("Worklog copy", EventCategory.UserActions, (d.isUploaded ? "Uploaded worklog" : "Pending worklog")); @@ -133,9 +126,8 @@ class AddWorklog extends BaseDialog { // eslint-disable-next-line complexity validateData(log, vald, ctlClass) { - if (log.allowOverride) { - log.overrideTimeSpent = log.overrideTimeSpent || log.timeSpent || "00:00"; - } + log.timeSpent = log.overrideTimeSpent || log.timeSpent || "00:00"; + delete log.overrideTimeSpent; let validation = true; @@ -147,20 +139,26 @@ class AddWorklog extends BaseDialog { validation = (vald.dateStarted = ds.isValid() && ds.isBetween(startOfDay, new Date(), undefined, '[]')) && validation; } else { validation = (vald.dateStarted = !(!log.dateStarted || log.dateStarted.length < 16)) && validation; - vald.overrideTimeSpent = (log.allowOverride && log.overrideTimeSpent && log.overrideTimeSpent.length >= 4); - vald.overrideTimeSpent = vald.overrideTimeSpent && this.$worklog.getTimeSpent(log.overrideTimeSpent) > 0; - validation = (vald.overrideTimeSpent = vald.overrideTimeSpent || (!log.allowOverride && log.timeSpent && log.timeSpent.length >= 4)) && validation; + vald.timeSpent = (log.timeSpent && log.timeSpent.length >= 4); + vald.timeSpent = vald.timeSpent && this.$worklog.getTimeSpent(log.timeSpent) > 0; + validation = vald.timeSpent && validation; + const { adjustEstimate, estimate } = this.state; + vald.estimate = (!!estimate && this.$worklog.getTimeSpent(estimate) > 0) || !adjustEstimate || adjustEstimate === 'leave'; + validation = vald.estimate && validation; } validation = (vald.description = this.minCommentLength < 1 || !(!log.description || log.description.length < this.minCommentLength)) && validation; ctlClass.ticketNo = !vald.ticketNo ? 'is-invalid' : 'is-valid'; ctlClass.dateStarted = !vald.dateStarted ? 'is-invalid' : 'is-valid'; - ctlClass.overrideTimeSpent = !vald.overrideTimeSpent ? 'is-invalid' : 'is-valid'; + ctlClass.timeSpent = !vald.timeSpent ? 'is-invalid' : 'is-valid'; ctlClass.description = !vald.description ? 'is-invalid' : 'is-valid'; + ctlClass.estimate = !vald.estimate ? 'is-invalid' : 'is-valid'; return validation; } + + saveWorklog = (worklog, vald, upload) => { if (!this.validateData(worklog, vald, this.state.ctlClass)) { this.$message.warning("Please provide value for all the mandatory fields", "Incomplete worklog details"); @@ -173,18 +171,26 @@ class AddWorklog extends BaseDialog { const { dateStarted, description } = this.state.log; this.$wltimer.editTrackerInfo(this.getTicketNo(this.state.log), dateStarted, description).then(this.props.onDone); } else { + const { adjustEstimate, estimate } = this.state; + const timeSpent = worklog.overrideTimeSpent || worklog.timeSpent; + + if (adjustEstimate) { + upload = { adjustEstimate, estimate }; + } + this.$worklog.saveWorklog({ ticketNo: this.getTicketNo(worklog), dateStarted: worklog.dateStarted, - overrideTimeSpent: worklog.overrideTimeSpent, description: worklog.description, worklogId: worklog.worklogId, isUploaded: worklog.isUploaded, - timeSpent: worklog.timeSpent, + timeSpent, parentId: worklog.parentId, id: worklog.id - }, upload).then((result) => { - this.props.onDone(worklog.id > 0 ? { type: GadgetActionType.WorklogModified, edited: result, previousTime: this.previousTime } : { type: GadgetActionType.WorklogModified, added: result }); + }, upload).then(result => { + this.props.onDone(worklog.id > 0 ? + { type: GadgetActionType.WorklogModified, edited: result, previousTime: this.previousTime } + : { type: GadgetActionType.WorklogModified, added: result }); this.onHide(); }, (e) => { this.setState({ isLoading: false }); @@ -242,11 +248,12 @@ class AddWorklog extends BaseDialog { getFooter() { const { - state: { isLoading, log, vald, uploadImmediately } + state: { isLoading, log, vald, uploadImmediately, adjustEstimate } } = this; return <> - {!log.id && this.setState({ uploadImmediately: chk })} />} + {!log.id && this.setState({ uploadImmediately: chk })} />} {log.id > 0 &&