packages/lib/reporting-components/resources/resources.js (364 lines of code) (raw):

import ServiceResources from '@jetbrains/hub-widget-ui/dist/service-resources'; import {getCurrentSprint} from '../agile-board-model/agile-board-model'; import BackendTypes from '../backend-types/backend-types'; const REQUESTED_YOUTRACK_VERSION = '2020.1.3111'; const PERMISSION_FIELDS = 'permission,key,global,projects(id)'; const USER_FIELDS = 'id,ringId,login,name,avatarUrl,avatar(url),email,banned,online'; const USER_GROUP_FIELDS = 'id,ringId,name,icon'; const PROJECTS_FIELDS = 'id,name,shortName,template,archived'; const ISSUE_FIELD_VALUE_FIELDS = '$type,id,name,localizedName,fullName,minutes,text,markdownText,presentation,color(id)'; const ISSUE_FIELD_FIELDS = `id,name,value(${ISSUE_FIELD_VALUE_FIELDS}),projectCustomField(id,field(id,name,fieldType(valueType)),emptyFieldText)`; const ISSUE_FIELDS = `id,idReadable,summary,fields(${ISSUE_FIELD_FIELDS})`; const WORK_ITEM_TYPE_FIELDS = 'id,name'; const REPORT_FILTER_FIELDS_FIELDS = 'id,name,presentation,localizedName,customField(fieldType(presentation))'; const REPORT_BASE_FIELDS = `id,name,owner(${USER_FIELDS}),pinned,own,editable`; const SHARING_SETTINGS_FIELDS = `permittedGroups(${USER_GROUP_FIELDS}),permittedUsers(${USER_FIELDS})`; const Y_AXIS_TYPE_FIELDS = 'yaxisType(id,name)'; const BURN_DOWN_REPORT_POINT_FIELDS = 'time,value'; const BURNDOWN_REPORT_DATA_FIELDS = `xlabel,ylabel,sprintFinish,remainingEffortPresentation,ideal(${BURN_DOWN_REPORT_POINT_FIELDS}),remainingEstimation(${BURN_DOWN_REPORT_POINT_FIELDS}),cumulativeSpentTime(${BURN_DOWN_REPORT_POINT_FIELDS}),${Y_AXIS_TYPE_FIELDS}`; const PERIOD_FIELD_VALUE_FIELDS = 'value,presentation'; const REPORT_STATUS_FIELDS = 'id,calculationInProgress,progress,error,errorMessage,isOutdated'; const TIME_SHEET_REPORT_LINE_DATA = `id,entityId,presentation,avatarUrl,spentTime(${PERIOD_FIELD_VALUE_FIELDS}),estimation(${PERIOD_FIELD_VALUE_FIELDS}),cells(${PERIOD_FIELD_VALUE_FIELDS}),totalSpentTime(${PERIOD_FIELD_VALUE_FIELDS})`; const TIME_REPORT_LINE_DATA = `id,issueId,userId,userVisibleName,description,avatarUrl,duration(${PERIOD_FIELD_VALUE_FIELDS}),totalDuration(${PERIOD_FIELD_VALUE_FIELDS}),estimation(${PERIOD_FIELD_VALUE_FIELDS}),cells(${PERIOD_FIELD_VALUE_FIELDS}),typeDurations(duration(${PERIOD_FIELD_VALUE_FIELDS}),workType)`; const TIME_SHEET_GROUP_DATA_FIELDS = `name,meta(linkedIssue(idReadable,summary),linkedUser(ringId,visibleName)),entityId,lineSpentTime(${PERIOD_FIELD_VALUE_FIELDS}),spentTime(${PERIOD_FIELD_VALUE_FIELDS}),estimation(${PERIOD_FIELD_VALUE_FIELDS}),issueLines(${TIME_SHEET_REPORT_LINE_DATA}),userLines(${TIME_SHEET_REPORT_LINE_DATA})`; const TIME_GROUP_DATA_FIELDS = `name,meta(linkedIssue(idReadable,summary),linkedUser(ringId,visibleName)),entityId,typeDurations(duration(${PERIOD_FIELD_VALUE_FIELDS}),workType),spentTime(${PERIOD_FIELD_VALUE_FIELDS}),estimation(${PERIOD_FIELD_VALUE_FIELDS}),duration(${PERIOD_FIELD_VALUE_FIELDS}),totalDuration(${PERIOD_FIELD_VALUE_FIELDS}),lines(${TIME_REPORT_LINE_DATA})`; const GROUP_DATA_FIELDS = `${TIME_SHEET_GROUP_DATA_FIELDS},${TIME_GROUP_DATA_FIELDS}`; const TIME_REPORT_DATA_FIELDS = `duration(${PERIOD_FIELD_VALUE_FIELDS}),typeDurations(workType,duration(${PERIOD_FIELD_VALUE_FIELDS})),groups(${GROUP_DATA_FIELDS})`; const TIME_SHEET_REPORT_DATA_FIELDS = `hasIssueView,headers(start,end,holiday,spentTime(${PERIOD_FIELD_VALUE_FIELDS})),spentTime(${PERIOD_FIELD_VALUE_FIELDS}),groups(${GROUP_DATA_FIELDS})`; const TIME_TRACKING_REPORT_DATA_FIELDS = `${TIME_SHEET_REPORT_DATA_FIELDS},${TIME_REPORT_DATA_FIELDS}`; const ISSUE_DISTRIBUTION_REPORT_DATA_COLUMN_FIELDS = `id,name,size(value,presentation),naturalSortIndex,index,user(${USER_FIELDS}),issue(id,idReadable,resolved,summary),colorIndex(id,foreground,background),issuesQuery,queryUrl`; const ISSUE_DISTRIBUTION_REPORT_DATA_FIELDS = [ 'tooBig', 'total(value,presentation)', 'counts(value,presentation)', 'issuesQueries', `columns(${ISSUE_DISTRIBUTION_REPORT_DATA_COLUMN_FIELDS})`, `xcolumns(${ISSUE_DISTRIBUTION_REPORT_DATA_COLUMN_FIELDS})`, `ycolumns(${ISSUE_DISTRIBUTION_REPORT_DATA_COLUMN_FIELDS})` ].join(','); const REPORT_ITEM_VALUE_FIELDS = 'value,presentation'; const CUMULATIVE_FLOW_REPORT_DATA_FIELDS = `xlabel,ylabel,sample(date,values(${REPORT_ITEM_VALUE_FIELDS})),names,colors,${Y_AXIS_TYPE_FIELDS}`; const REPORT_SPRINT_SHORT_FIELDS = 'id,name,agile(id,name,sprintsSettings(disableSprints))'; const REPORT_WITH_DATA_FIELDS = `${REPORT_BASE_FIELDS},data(${TIME_SHEET_REPORT_DATA_FIELDS},${TIME_REPORT_DATA_FIELDS},${BURNDOWN_REPORT_DATA_FIELDS},${CUMULATIVE_FLOW_REPORT_DATA_FIELDS}),sprint(${REPORT_SPRINT_SHORT_FIELDS}),status(${REPORT_STATUS_FIELDS})`; const TIME_TRACKING_REPORT_SETTINGS_FIELDS = `grouping(id,field(${REPORT_FILTER_FIELDS_FIELDS})),scale(id),projects(${PROJECTS_FIELDS}),authors(${USER_FIELDS}),users(${USER_FIELDS}),workTypes(${WORK_ITEM_TYPE_FIELDS}),range($type,id,range($type,id),from,to)`; const ISSUE_DISTRIBUTION_REPORT_SETTINGS_FIELDS = [ `xaxis(id,field(${REPORT_FILTER_FIELDS_FIELDS}))`, `yaxis(id,field(${REPORT_FILTER_FIELDS_FIELDS}))`, `aggregationPolicy(id,field(${REPORT_FILTER_FIELDS_FIELDS}))`, 'xsortOrder', 'ysortOrder', 'presentation', `customField(${REPORT_FILTER_FIELDS_FIELDS})` ].join(','); const REPORT_WITH_SETTINGS_FIELDS = `${REPORT_BASE_FIELDS},projects(${PROJECTS_FIELDS}),query,own,readSharingSettings(${SHARING_SETTINGS_FIELDS}),updateSharingSettings(${SHARING_SETTINGS_FIELDS})`; const TIME_TRACKING_REPORT_WITH_SETTINGS_FIELDS = `${REPORT_WITH_SETTINGS_FIELDS},${TIME_TRACKING_REPORT_SETTINGS_FIELDS}`; const ISSUE_DISTRIBUTION_REPORT_WITH_SETTINGS_FIELDS = `${REPORT_WITH_SETTINGS_FIELDS},${ISSUE_DISTRIBUTION_REPORT_SETTINGS_FIELDS}`; const QUERY_ASSIST_FIELDS = 'query,caret,styleRanges(start,length,style),suggestions(options,prefix,option,suffix,description,matchingStart,matchingEnd,caret,completionStart,completionEnd,group,icon)'; const SPRINT_FIELDS = 'id,name,start,finish,report(id)'; const AGILE_FIELDS = `id,name,sprints(${SPRINT_FIELDS}),currentSprint(${SPRINT_FIELDS}),sprintsSettings(disableSprints,explicitQuery),columnSettings(field(id,name)),owner(id,ringId,fullName),projects(id,template,archived)`; const AGILE_REPORT_SETTINGS_FIELDS = 'extensions(reportSettings(doNotUseBurndown))'; async function underlineAndSuggest(fetchYouTrack, query, caret) { return await fetchYouTrack(`api/search/assist?fields=${QUERY_ASSIST_FIELDS}`, { method: 'POST', body: {query, caret} }); } async function loadProjects(fetchYouTrack) { const projects = await fetchYouTrack(`api/admin/projects?fields=${PROJECTS_FIELDS}&$top=-1`); return (projects || []).filter( project => !project.template && !project.archived ); } async function loadReportProjects(fetchYouTrack, fetchHub, report) { if (report.id) { return await fetchYouTrack(`api/reports/${report.id}/accessibleProjects?fields=${PROJECTS_FIELDS}&$top=-1`); } const projects = await loadProjects(fetchYouTrack); const permissionCache = await fetchHub( `api/rest/permissions/cache?fields=${PERMISSION_FIELDS}` ); const createReportPermission = permissionCache.find(it => it.permission.key === 'JetBrains.YouTrack.CREATE_REPORT'); if (!createReportPermission || createReportPermission.global) { return projects; } const projectIds = createReportPermission.projects.map(it => it.id); return projects.filter(it => projectIds.indexOf(it.ringId) >= 0); } async function loadAgiles(fetchYouTrack) { const agiles = await fetchYouTrack(`api/agiles?fields=${AGILE_FIELDS}&$top=-1`); return (agiles || []).filter( ({projects}) => projects.some(project => !project.template && !project.archived) ); } async function loadAgileReportSettings(fetchYouTrack, agileId) { const agileSettings = await fetchYouTrack(`api/agiles/${agileId}?fields=${AGILE_REPORT_SETTINGS_FIELDS}&$top=-1`); return agileSettings && agileSettings.extensions && agileSettings.extensions.reportSettings; } async function loadSprint(fetchYouTrack, agileId, sprintId) { if (sprintId) { return await fetchYouTrack(`api/agiles/${agileId}/sprints/${sprintId}?fields=${SPRINT_FIELDS}`); } const agile = await fetchYouTrack(`api/agiles/${agileId}?fields=${AGILE_FIELDS}`); return getCurrentSprint(agile); } async function loadReportWithData( fetchYouTrack, reportId, params, fields = REPORT_WITH_DATA_FIELDS ) { const lineParam = params && params.line ? `&line=${params.line}` : ''; return await fetchYouTrack( `api/reports/${reportId}?fields=${fields}${lineParam}` ); } async function loadTimeTrackingReportWithData( fetchYouTrack, reportId, params ) { const fields = `${TIME_TRACKING_REPORT_WITH_SETTINGS_FIELDS},data(${TIME_TRACKING_REPORT_DATA_FIELDS}),status(${REPORT_STATUS_FIELDS})`; return await loadReportWithData( fetchYouTrack, reportId, params, fields ); } async function loadIssueDistributionReportWithData( fetchYouTrack, reportId, params ) { const fields = `${ISSUE_DISTRIBUTION_REPORT_WITH_SETTINGS_FIELDS},data(${ISSUE_DISTRIBUTION_REPORT_DATA_FIELDS}),status(${REPORT_STATUS_FIELDS})`; return await loadReportWithData( fetchYouTrack, reportId, params, fields ); } async function loadReportWithSettings( fetchYouTrack, reportId, fields = REPORT_WITH_SETTINGS_FIELDS ) { return await fetchYouTrack( `api/reports/${reportId}?fields=${fields}` ); } async function loadTimeTrackingReportWithSettings( fetchYouTrack, reportId ) { return await loadReportWithSettings( fetchYouTrack, reportId, TIME_TRACKING_REPORT_WITH_SETTINGS_FIELDS ); } async function loadIssueDistributionReportWithSettings( fetchYouTrack, reportId ) { return await loadReportWithSettings( fetchYouTrack, reportId, ISSUE_DISTRIBUTION_REPORT_WITH_SETTINGS_FIELDS ); } async function loadReportsList(fetchYouTrack, reportTypes = []) { const typesParameter = reportTypes.map(BackendTypes.toShortType).join(','); return ( await fetchYouTrack(`api/reports?fields=${REPORT_BASE_FIELDS}&$top=300&types=${typesParameter}`) ) || []; } async function loadIndependentBurnDownReports(fetchYouTrack) { return await loadReportsList( fetchYouTrack, [BackendTypes.get().IndependentBurndownReport] ); } async function loadIssuesDistributionReports(fetchYouTrack) { const distributionReportTypes = [ BackendTypes.get().IssuePerProjectReport, BackendTypes.get().IssuePerAssigneeReport, BackendTypes.get().FlatDistributionReport, BackendTypes.get().MatrixReport ]; return await loadReportsList(fetchYouTrack, distributionReportTypes); } async function loadTimeReports(fetchYouTrack) { const timeReportTypes = [ BackendTypes.get().TimeReport, BackendTypes.get().TimeSheetReport ]; return await loadReportsList(fetchYouTrack, timeReportTypes); } async function loadReportsGroupingFilterFields(fetchYouTrack, projects) { const fld = serializeArrayParameter('fld', (projects || []).map(project => project.id) ); const groupingFieldsTypes = [ 'version[1]', 'ownedField[1]', 'state[1]', 'user[1]', 'enum[1]', 'build[1]', 'date', 'integer', 'float', 'period', 'project', 'string' ]; const fieldTypes = serializeArrayParameter('fieldTypes', groupingFieldsTypes); const params = [ fld, '$top=300', `fields=${REPORT_FILTER_FIELDS_FIELDS}`, fieldTypes, 'getUnusedVisibleFields=true' ].filter(param => param.length > 0).join('&'); return await fetchYouTrack(`api/filterFields?${params}`); } async function loadReportsAggregationFilterFields(fetchYouTrack, projects) { const fieldTypes = serializeArrayParameter( 'fieldTypes', ['integer', 'float', 'period'] ); const fld = serializeArrayParameter('fld', (projects || []).map(project => project.id) ); const params = [ fieldTypes, fld, 'includeNonFilterFields=true', '$top=300', 'usage=true', `fields=${REPORT_FILTER_FIELDS_FIELDS}` ].filter(param => param.length > 0).join('&'); const aggregationFilterFields = (await fetchYouTrack(`api/filterFields?${params}`)) || []; const configWithVotesPresentations = await fetchYouTrack('api/config?fields=l10n(predefinedQueries(votes))'); const votersPresentation = configWithVotesPresentations && configWithVotesPresentations.l10n && configWithVotesPresentations.l10n.predefinedQueries && configWithVotesPresentations.l10n.predefinedQueries.votes; if (votersPresentation) { return [{ presentation: votersPresentation, id: votersPresentation, $type: BackendTypes.get().PredefinedFilterField }].concat(aggregationFilterFields); } return aggregationFilterFields; } function serializeArrayParameter(paramName, arr) { return encodeURI( arr.map(item => `${paramName}=${item}`).join('&') ); } async function saveReportSettings( fetchYouTrack, report, isFullReportWithDataResponse ) { const reportIdPart = report.id ? `/${report.id}` : ''; const fields = isFullReportWithDataResponse ? REPORT_WITH_DATA_FIELDS : REPORT_BASE_FIELDS; return await fetchYouTrack(`api/reports${reportIdPart}?fields=${fields}`, { method: 'POST', body: report }); } async function recalculateReport(fetchYouTrack, report) { return await fetchYouTrack(`api/reports/${report.id}/status?fields=calculationInProgress,progress`, { method: 'POST', body: { calculationInProgress: true } }); } async function loadUserGroups(fetchYouTrack) { return await fetchYouTrack( `api/groups?fields=${USER_GROUP_FIELDS}` ); } async function loadVisibilityUserGroups(fetchYouTrack, {query}) { const result = await fetchYouTrack( `api/visibilityGroups?fields=visibilityGroups(${USER_GROUP_FIELDS})`, { method: 'POST', body: {prefix: query} } ); return (result || {}).visibilityGroups || []; } async function loadCurrentUser(fetchHub) { return await loadUser(fetchHub); } async function loadUserGeneralProfile(fetchYouTrack, userId = 'me') { const EXTENDED_USER_FIELDS = 'dateFieldFormat(datePattern,dateNoYearPattern)'; return await fetchYouTrack( `api/users/${userId}/profiles/general?fields=${EXTENDED_USER_FIELDS}` ); } async function loadUser(fetchHub, ringId = 'me', fields = USER_FIELDS) { return await fetchHub( `api/rest/users/${ringId}?fields=${fields}` ); } async function loadUsers( fetchYouTrack, {permission, projectIds, query, $skip = 0} ) { const queryParams = {$skip, $top: 20, permission, query}; if (projectIds && projectIds.length) { queryParams.projectId = projectIds.map( projectId => `projectId=${projectId}` ).join('&'); } return await fetchYouTrack( `api/users?fields=${USER_FIELDS}`, { query: queryParams } ); } async function loadIssue(fetchYoutrack, issueId) { return await fetchYoutrack( `api/issues/${issueId}?fields=${ISSUE_FIELDS}` ); } async function loadWorkItemTypes(fetchYouTrack) { return await fetchYouTrack( `api/admin/timeTrackingSettings/workItemTypes?fields=${WORK_ITEM_TYPE_FIELDS}&top=-1`, {} ); } async function getYouTrackServices(dashboardApi) { return ServiceResources.getYouTrackServices( dashboardApi, REQUESTED_YOUTRACK_VERSION ); } async function getYouTrackService(dashboardApi, optionalYtId) { let services = await getYouTrackServices(dashboardApi); if (optionalYtId) { services = services.filter(service => service.id === optionalYtId); } return services[0]; } function makeYouTrackFetcher(dashboardApi, youTrack) { return async (url, params) => await dashboardApi.fetch(youTrack.id, url, params); } export { loadReportWithData, loadReportsList, loadIndependentBurnDownReports, loadIssuesDistributionReports, loadTimeReports, loadReportWithSettings, loadTimeTrackingReportWithSettings, loadIssueDistributionReportWithSettings, loadReportsAggregationFilterFields, loadReportsGroupingFilterFields, loadTimeTrackingReportWithData, loadIssueDistributionReportWithData, saveReportSettings, recalculateReport, getYouTrackServices, getYouTrackService, underlineAndSuggest, loadProjects, loadReportProjects, loadVisibilityUserGroups, loadAgiles, loadAgileReportSettings, loadSprint, loadUserGroups, loadCurrentUser, loadUserGeneralProfile, loadUser, loadUsers, loadIssue, loadWorkItemTypes, makeYouTrackFetcher };