Commit 4e33606f authored by 🤖 GitLab Bot 🤖's avatar 🤖 GitLab Bot 🤖
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 6c516c90
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,
};
......@@ -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>
......@@ -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();
},
......
......@@ -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
});
......
......@@ -4,6 +4,7 @@ fragment BoardListShared on BoardList {
position
listType
collapsed
maxIssueCount
label {
id
title
......
#import "./issue.fragment.graphql"
query GroupListIssues($fullPath: ID!, $boardId: ID!) {
group(fullPath: $fullPath) {
board(id: $boardId) {
lists {
nodes {
id
issues {
nodes {
...IssueNode
}
}
}
}
}
}
}
#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
}
}
}
#import "./issue.fragment.graphql"
query ProjectListIssues($fullPath: ID!, $boardId: ID!) {
project(fullPath: $fullPath) {
board(id: $boardId) {
lists {
nodes {
id
issues {
nodes {
...IssueNode
}
}
}
}
}
}
}
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();
},
......
......@@ -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);
......
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';
......
......@@ -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();
},
......
......@@ -2,6 +2,10 @@ import { inactiveId } from '~/boards/constants';
export default () => ({
endpoints: {},
boardType: null,
isShowingLabels: true,
activeId: inactiveId,
issuesByListId: {},
isLoadingIssues: false,
listIssueFetchFailure: false,
});
......@@ -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>
......
<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 />
......
......@@ -598,17 +598,17 @@
$epic-icons-spacing: 40px;
.board-epic-lane {
max-width: calc(100vw - #{$contextual-sidebar-width} - $epic-icons-spacing);
max-width: calc(100vw - #{$contextual-sidebar-width} - #{$epic-icons-spacing});
.page-with-icon-sidebar & {
max-width: calc(100vw - #{$contextual-sidebar-collapsed-width} - $epic-icons-spacing);
max-width: calc(100vw - #{$contextual-sidebar-collapsed-width} - #{$epic-icons-spacing});
}
.page-with-icon-sidebar .is-compact & {
max-width: calc(100vw - #{$contextual-sidebar-collapsed-width} - #{$gutter-width} - $epic-icons-spacing);
max-width: calc(100vw - #{$contextual-sidebar-collapsed-width} - #{$gutter-width} - #{$epic-icons-spacing});
}
.is-compact & {
max-width: calc(100vw - #{$contextual-sidebar-width} - #{$gutter-width} - $epic-icons-spacing);
max-width: calc(100vw - #{$contextual-sidebar-width} - #{$gutter-width} - #{$epic-icons-spacing});
}
}
......@@ -98,19 +98,10 @@
}
}
.refresh-dashboard-button {
margin-top: 22px;
@media(max-width: map-get($grid-breakpoints, sm)) {
margin-top: 0;
}
}
.metric-area {
opacity: 0.25;
}
.rect-text-metric {
fill: $white;
stroke-width: 1;
......
......@@ -14,7 +14,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
push_frontend_feature_flag(:prometheus_computed_alerts)
push_frontend_feature_flag(:disable_metric_dashboard_refresh_rate)
push_frontend_feature_flag(:alert_runbooks)
push_frontend_feature_flag(:metrics_dashboard_new_panel_page)
end
before_action :authorize_read_environment!, except: [:metrics, :additional_metrics, :metrics_dashboard, :metrics_redirect]
......
......@@ -10,7 +10,6 @@ class MetricsDashboardController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:prometheus_computed_alerts)
push_frontend_feature_flag(:disable_metric_dashboard_refresh_rate)
push_frontend_feature_flag(:alert_runbooks)
push_frontend_feature_flag(:metrics_dashboard_new_panel_page)
end
......
---
title: Add runbook to metric chart dropdown
merge_request: 39288
author:
type: added
---
title: Add runbooks to metric alerts
merge_request: 39315
author:
type: added
......@@ -631,6 +631,35 @@ or `gitlab-ctl promote-to-primary-node`, either:
bug](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22021) was
fixed.
If the above does not work, another possible reason is that you have paused replication
from the original primary node before attempting to promote this node.
To double check this, you can do the following:
- Get the current secondary node's ID using:
```shell
sudo gitlab-rails runner 'puts GeoNode.current_node.id'
```
- Double check that the node is active through the database by running the following
on the secondary node, replacing `ID_FROM_ABOVE`:
```shell
sudo gitlab-rails dbconsole
SELECT enabled FROM geo_nodes WHERE id = ID_FROM_ABOVE;
```
- If the above returned `f` it means that the replication was paused.
You can re-enable it through an `UPDATE` statement in the database:
```shell
sudo gitlab-rails dbconsole
UPDATE geo_nodes SET enabled = 't' WHERE id = ID_FROM_ABOVE;
```
### Message: ``NoMethodError: undefined method `secondary?' for nil:NilClass``
When [promoting a **secondary** node](../disaster_recovery/index.md#step-3-promoting-a-secondary-node),
......
......@@ -902,7 +902,7 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
## Reorder an issue
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211864) as a [community contribution](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35349) in GitLab 13.2.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211864) in GitLab 13.2.
Reorders an issue, you can see the results when sorting issues manually
......
......@@ -14,8 +14,7 @@ This API is a project-specific version of these endpoints:
- [GitLab CI/CD Configuration templates](templates/gitlab_ci_ymls.md)
- [Open source license templates](templates/licenses.md)
- [Issue and merge request templates](../user/project/description_templates.md)
([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37890) in GitLab 13.3 as a
community contribution)
([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37890) in GitLab 13.3)