diff options
Diffstat (limited to 'app/assets/javascripts')
16 files changed, 225 insertions, 24 deletions
diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js index 3178bda93b8..27f7edaab5c 100644 --- a/app/assets/javascripts/boards/boards_util.js +++ b/app/assets/javascripts/boards/boards_util.js @@ -1,7 +1,28 @@ +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import ListIssue from 'ee_else_ce/boards/models/issue'; + export function getMilestone() { return null; } +export function formatListIssues(listIssues) { + return listIssues.nodes.reduce((map, list) => { + return { + ...map, + [list.id]: list.issues.nodes.map( + i => + new ListIssue({ + ...i, + id: getIdFromGraphQLId(i.id), + labels: i.labels?.nodes || [], + assignees: i.assignees?.nodes || [], + }), + ), + }; + }, {}); +} + export default { getMilestone, + formatListIssues, }; diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue index 6ac7fdce6a7..b7ca56e6a6f 100644 --- a/app/assets/javascripts/boards/components/board_content.vue +++ b/app/assets/javascripts/boards/components/board_content.vue @@ -42,7 +42,7 @@ export default { }, }, computed: { - ...mapState(['isShowingEpicsSwimlanes']), + ...mapState(['isShowingEpicsSwimlanes', 'boardLists']), isSwimlanesOn() { return this.glFeatures.boardsWithSwimlanes && this.isShowingEpicsSwimlanes; }, @@ -73,11 +73,12 @@ export default { <epics-swimlanes v-else ref="swimlanes" - :lists="lists" + :lists="boardLists" :can-admin-list="canAdminList" :disabled="disabled" :board-id="boardId" :group-id="groupId" + :root-path="rootPath" /> </div> </template> diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index 7bc8a8a3b0e..3a0a1d4a484 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -117,7 +117,7 @@ export default () => { boardId: this.boardId, fullPath: $boardApp.dataset.fullPath, }; - this.setEndpoints(endpoints); + this.setInitialBoardData({ ...endpoints, boardType: this.parent }); boardsStore.setEndpoints(endpoints); boardsStore.rootPath = this.boardsEndpoint; @@ -189,7 +189,7 @@ export default () => { } }, methods: { - ...mapActions(['setEndpoints']), + ...mapActions(['setInitialBoardData']), updateTokens() { this.filterManager.updateTokens(); }, diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index 00f58ca0e98..bf25481cd69 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -60,7 +60,9 @@ class List { this.title = this.milestone.title; } - if (!typeInfo.isBlank && this.id) { + // doNotFetchIssues is a temporary workaround until issues are fetched using GraphQL on issue boards + // Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/229416 + if (!typeInfo.isBlank && this.id && !obj.doNotFetchIssues) { this.getIssues().catch(() => { // TODO: handle request error }); diff --git a/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql b/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql index 5b532906f6a..8abd79332fb 100644 --- a/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql +++ b/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql @@ -4,6 +4,7 @@ fragment BoardListShared on BoardList { position listType collapsed + maxIssueCount label { id title diff --git a/app/assets/javascripts/boards/queries/group_lists_issues.query.graphql b/app/assets/javascripts/boards/queries/group_lists_issues.query.graphql new file mode 100644 index 00000000000..724c7884c58 --- /dev/null +++ b/app/assets/javascripts/boards/queries/group_lists_issues.query.graphql @@ -0,0 +1,18 @@ +#import "./issue.fragment.graphql" + +query GroupListIssues($fullPath: ID!, $boardId: ID!) { + group(fullPath: $fullPath) { + board(id: $boardId) { + lists { + nodes { + id + issues { + nodes { + ...IssueNode + } + } + } + } + } + } +} diff --git a/app/assets/javascripts/boards/queries/issue.fragment.graphql b/app/assets/javascripts/boards/queries/issue.fragment.graphql new file mode 100644 index 00000000000..89d56b895a4 --- /dev/null +++ b/app/assets/javascripts/boards/queries/issue.fragment.graphql @@ -0,0 +1,31 @@ +#import "~/graphql_shared/fragments/user.fragment.graphql" + +fragment IssueNode on Issue { + id + iid + title + referencePath: reference(full: true) + dueDate + timeEstimate + weight + confidential + webUrl + subscribed + blocked + epic { + id + } + assignees { + nodes { + ...User + } + } + labels { + nodes { + id + title + color + description + } + } +} diff --git a/app/assets/javascripts/boards/queries/project_lists_issues.query.graphql b/app/assets/javascripts/boards/queries/project_lists_issues.query.graphql new file mode 100644 index 00000000000..149b76848ef --- /dev/null +++ b/app/assets/javascripts/boards/queries/project_lists_issues.query.graphql @@ -0,0 +1,18 @@ +#import "./issue.fragment.graphql" + +query ProjectListIssues($fullPath: ID!, $boardId: ID!) { + project(fullPath: $fullPath) { + board(id: $boardId) { + lists { + nodes { + id + issues { + nodes { + ...IssueNode + } + } + } + } + } + } +} diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index 49d2a5c546f..b4be7546252 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -1,4 +1,11 @@ import * as types from './mutation_types'; +import createDefaultClient from '~/lib/graphql'; +import { BoardType } from '~/boards/constants'; +import { formatListIssues } from '../boards_util'; +import groupListsIssuesQuery from '../queries/group_lists_issues.query.graphql'; +import projectListsIssuesQuery from '../queries/project_lists_issues.query.graphql'; + +const gqlClient = createDefaultClient(); const notImplemented = () => { /* eslint-disable-next-line @gitlab/require-i18n-strings */ @@ -6,8 +13,8 @@ const notImplemented = () => { }; export default { - setEndpoints: ({ commit }, endpoints) => { - commit(types.SET_ENDPOINTS, endpoints); + setInitialBoardData: ({ commit }, data) => { + commit(types.SET_INITIAL_BOARD_DATA, data); }, setActiveId({ commit }, id) { @@ -38,6 +45,32 @@ export default { notImplemented(); }, + fetchIssuesForAllLists: ({ state, commit }) => { + commit(types.REQUEST_ISSUES_FOR_ALL_LISTS); + + const { endpoints, boardType } = state; + const { fullPath, boardId } = endpoints; + + const query = boardType === BoardType.group ? groupListsIssuesQuery : projectListsIssuesQuery; + + const variables = { + fullPath, + boardId: `gid://gitlab/Board/${boardId}`, + }; + + return gqlClient + .query({ + query, + variables, + }) + .then(({ data }) => { + const { lists } = data[boardType]?.board; + const listIssues = formatListIssues(lists); + commit(types.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS, listIssues); + }) + .catch(() => commit(types.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE)); + }, + moveIssue: () => { notImplemented(); }, diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 8a51c5f4fcf..30c71d64085 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -81,7 +81,7 @@ const boardsStore = { showPage(page) { this.state.currentPage = page; }, - addList(listObj) { + updateListPosition(listObj) { const listType = listObj.listType || listObj.list_type; let { position } = listObj; if (listType === ListType.closed) { @@ -91,6 +91,10 @@ const boardsStore = { } const list = new List({ ...listObj, position }); + return list; + }, + addList(listObj) { + const list = this.updateListPosition(listObj); this.state.lists = sortBy([...this.state.lists, list], 'position'); return list; }, @@ -850,19 +854,28 @@ const boardsStore = { }, refreshIssueData(issue, obj) { - issue.id = obj.id; - issue.iid = obj.iid; - issue.title = obj.title; - issue.confidential = obj.confidential; - issue.dueDate = obj.due_date; - issue.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; - issue.referencePath = obj.reference_path; - issue.path = obj.real_path; - issue.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint; + // issue.id = obj.id; + // issue.iid = obj.iid; + // issue.title = obj.title; + // issue.confidential = obj.confidential; + // issue.dueDate = obj.due_date || obj.dueDate; + // issue.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; + // issue.referencePath = obj.reference_path || obj.referencePath; + // issue.path = obj.real_path || obj.webUrl; + // issue.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint; + // issue.project_id = obj.project_id; + // issue.timeEstimate = obj.time_estimate || obj.timeEstimate; + // issue.assignableLabelsEndpoint = obj.assignable_labels_endpoint; + // issue.blocked = obj.blocked; + // issue.epic = obj.epic; + + const convertedObj = convertObjectPropsToCamelCase(obj, { + dropKeys: ['issue_sidebar_endpoint', 'real_path', 'webUrl'], + }); + convertedObj.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; + issue.path = obj.real_path || obj.webUrl; issue.project_id = obj.project_id; - issue.timeEstimate = obj.time_estimate; - issue.assignableLabelsEndpoint = obj.assignable_labels_endpoint; - issue.blocked = obj.blocked; + Object.assign(issue, convertedObj); if (obj.project) { issue.project = new IssueProject(obj.project); diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js index 8deda166909..0f96dc2e287 100644 --- a/app/assets/javascripts/boards/stores/mutation_types.js +++ b/app/assets/javascripts/boards/stores/mutation_types.js @@ -1,4 +1,4 @@ -export const SET_ENDPOINTS = 'SET_ENDPOINTS'; +export const SET_INITIAL_BOARD_DATA = 'SET_INITIAL_BOARD_DATA'; export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST'; export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS'; export const RECEIVE_ADD_LIST_ERROR = 'RECEIVE_ADD_LIST_ERROR'; @@ -8,6 +8,9 @@ export const RECEIVE_UPDATE_LIST_ERROR = 'RECEIVE_UPDATE_LIST_ERROR'; export const REQUEST_REMOVE_LIST = 'REQUEST_REMOVE_LIST'; export const RECEIVE_REMOVE_LIST_SUCCESS = 'RECEIVE_REMOVE_LIST_SUCCESS'; export const RECEIVE_REMOVE_LIST_ERROR = 'RECEIVE_REMOVE_LIST_ERROR'; +export const REQUEST_ISSUES_FOR_ALL_LISTS = 'REQUEST_ISSUES_FOR_ALL_LISTS'; +export const RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS = 'RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS'; +export const RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE = 'RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE'; export const REQUEST_ADD_ISSUE = 'REQUEST_ADD_ISSUE'; export const RECEIVE_ADD_ISSUE_SUCCESS = 'RECEIVE_ADD_ISSUE_SUCCESS'; export const RECEIVE_ADD_ISSUE_ERROR = 'RECEIVE_ADD_ISSUE_ERROR'; diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js index 0cf4080cf35..ca9b911ce5b 100644 --- a/app/assets/javascripts/boards/stores/mutations.js +++ b/app/assets/javascripts/boards/stores/mutations.js @@ -6,8 +6,10 @@ const notImplemented = () => { }; export default { - [mutationTypes.SET_ENDPOINTS]: (state, endpoints) => { + [mutationTypes.SET_INITIAL_BOARD_DATA]: (state, data) => { + const { boardType, ...endpoints } = data; state.endpoints = endpoints; + state.boardType = boardType; }, [mutationTypes.SET_ACTIVE_ID](state, id) { @@ -50,6 +52,20 @@ export default { notImplemented(); }, + [mutationTypes.REQUEST_ISSUES_FOR_ALL_LISTS]: state => { + state.isLoadingIssues = true; + }, + + [mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS]: (state, listIssues) => { + state.issuesByListId = listIssues; + state.isLoadingIssues = false; + }, + + [mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE]: state => { + state.listIssueFetchFailure = true; + state.isLoadingIssues = false; + }, + [mutationTypes.REQUEST_ADD_ISSUE]: () => { notImplemented(); }, diff --git a/app/assets/javascripts/boards/stores/state.js b/app/assets/javascripts/boards/stores/state.js index fa5dc7d663d..cb6930774ed 100644 --- a/app/assets/javascripts/boards/stores/state.js +++ b/app/assets/javascripts/boards/stores/state.js @@ -2,6 +2,10 @@ import { inactiveId } from '~/boards/constants'; export default () => ({ endpoints: {}, + boardType: null, isShowingLabels: true, activeId: inactiveId, + issuesByListId: {}, + isLoadingIssues: false, + listIssueFetchFailure: false, }); diff --git a/app/assets/javascripts/graphql_shared/fragments/pageInfoCursorsOnly.fragment.graphql b/app/assets/javascripts/graphql_shared/fragments/pageInfoCursorsOnly.fragment.graphql new file mode 100644 index 00000000000..22bcefbecd3 --- /dev/null +++ b/app/assets/javascripts/graphql_shared/fragments/pageInfoCursorsOnly.fragment.graphql @@ -0,0 +1,4 @@ +fragment PageInfo on PageInfo { + startCursor + endCursor +} diff --git a/app/assets/javascripts/monitoring/components/alert_widget_form.vue b/app/assets/javascripts/monitoring/components/alert_widget_form.vue index ef9dfce4aa9..f961e21984b 100644 --- a/app/assets/javascripts/monitoring/components/alert_widget_form.vue +++ b/app/assets/javascripts/monitoring/components/alert_widget_form.vue @@ -302,7 +302,6 @@ export default { /> </gl-form-group> <gl-form-group - v-if="glFeatures.alertRunbooks" :label="s__('PrometheusAlerts|Runbook URL (optional)')" label-for="alert-runbook" > @@ -312,6 +311,7 @@ export default { :disabled="formDisabled" data-testid="alertRunbookField" type="text" + :placeholder="s__('PrometheusAlerts|https://gitlab.com/gitlab-com/runbooks')" /> </gl-form-group> </div> diff --git a/app/assets/javascripts/monitoring/components/dashboard_panel.vue b/app/assets/javascripts/monitoring/components/dashboard_panel.vue index f57ea49626e..ad320b004bc 100644 --- a/app/assets/javascripts/monitoring/components/dashboard_panel.vue +++ b/app/assets/javascripts/monitoring/components/dashboard_panel.vue @@ -1,18 +1,20 @@ <script> import { mapState } from 'vuex'; -import { pickBy } from 'lodash'; +import { mapValues, pickBy } from 'lodash'; import invalidUrl from '~/lib/utils/invalid_url'; import { convertToFixedRange } from '~/lib/utils/datetime_range'; import { relativePathToAbsolute, getBaseURL, visitUrl, isSafeURL } from '~/lib/utils/url_utility'; import { GlResizeObserverDirective, GlIcon, + GlLink, GlLoadingIcon, GlNewDropdown as GlDropdown, GlNewDropdownItem as GlDropdownItem, GlNewDropdownDivider as GlDropdownDivider, GlModal, GlModalDirective, + GlSprintf, GlTooltip, GlTooltipDirective, } from '@gitlab/ui'; @@ -44,12 +46,14 @@ export default { MonitorEmptyChart, AlertWidget, GlIcon, + GlLink, GlLoadingIcon, GlTooltip, GlDropdown, GlDropdownItem, GlDropdownDivider, GlModal, + GlSprintf, }, directives: { GlResizeObserver: GlResizeObserverDirective, @@ -341,6 +345,19 @@ export default { this.$refs.copyChartLink.$el.firstChild.click(); } }, + getAlertRunbooks(queries) { + const hasRunbook = alert => Boolean(alert.runbookUrl); + const graphAlertsWithRunbooks = pickBy(this.getGraphAlerts(queries), hasRunbook); + const alertToRunbookTransform = alert => { + const alertQuery = queries.find(query => query.metricId === alert.metricId); + return { + key: alert.metricId, + href: alert.runbookUrl, + label: alertQuery.label, + }; + }; + return mapValues(graphAlertsWithRunbooks, alertToRunbookTransform); + }, }, panelTypes, }; @@ -436,6 +453,25 @@ export default { > {{ __('Alerts') }} </gl-dropdown-item> + <gl-dropdown-item + v-for="runbook in getAlertRunbooks(graphData.metrics)" + :key="runbook.key" + :href="safeUrl(runbook.href)" + data-testid="runbookLink" + target="_blank" + rel="noopener noreferrer" + > + <span class="gl-display-flex gl-justify-content-space-between gl-align-items-center"> + <span> + <gl-sprintf :message="s__('Metrics|View runbook - %{label}')"> + <template #label> + {{ runbook.label }} + </template> + </gl-sprintf> + </span> + <gl-icon name="external-link" /> + </span> + </gl-dropdown-item> <template v-if="graphData.links && graphData.links.length"> <gl-dropdown-divider /> |