Skip to content
Snippets Groups Projects
Commit 7ab4239b authored by Alessio Caiazza's avatar Alessio Caiazza
Browse files

Merge remote-tracking branch 'dev/14-0-auto-deploy-2021061703' into 14-0-stable-ee

parents ac2d40d4 e0decc6b
No related branches found
No related tags found
No related merge requests found
Showing
with 312 additions and 103 deletions
<!-- See Pipelines for the GitLab project: https://docs.gitlab.com/ee/development/pipelines.html -->
<!-- When in doubt about a Pipeline configuration change, feel free to ping @gl-quality/eng-prod. -->
## What does this MR do?
<!-- Briefly describe what this MR is about -->
## Related issues
<!-- Link related issues below. -->
## Check-list
### Pre-merge
Consider the effect of the changes in this merge request on the following:
- [ ] Different [pipeline types](https://docs.gitlab.com/ee/development/pipelines.html#pipelines-for-merge-requests)
- Non-canonical projects:
- [ ] `gitlab-foss`
- [ ] `security`
- [ ] `dev`
- [ ] personal forks
- [ ] [Pipeline performance](https://about.gitlab.com/handbook/engineering/quality/performance-indicators/#average-merge-request-pipeline-duration-for-gitlab)
**If new jobs are added:**
- [ ] Change-related rules (e.g. frontend/backend/database file changes): _____
- [ ] Frequency they are running (MRs, main branch, nightly, bi-hourly): _____
- [ ] Add a duration chart to https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations if there are new jobs added to merge request pipelines
This will help keep track of expected cost increases to the [GitLab project average pipeline cost per merge request](https://about.gitlab.com/handbook/engineering/quality/performance-indicators/#gitlab-project-average-pipeline-cost-per-merge-request) RPI
### Post-merge
- [ ] Consider communicating these changes to the broader team following the [communication guideline for pipeline changes](https://about.gitlab.com/handbook/engineering/quality/engineering-productivity-team/#pipeline-changes)
/label ~tooling ~"tooling::pipelines" ~"Engineering Productivity"
Loading
Loading
@@ -13,7 +13,6 @@
# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/322903
Graphql/Descriptions:
Exclude:
- 'ee/app/graphql/ee/types/list_limit_metric_enum.rb'
- 'ee/app/graphql/types/epic_state_enum.rb'
- 'ee/app/graphql/types/health_status_enum.rb'
- 'ee/app/graphql/types/iteration_state_enum.rb'
Loading
Loading
8a6d0e26de9d584941267d2b68c94b37bc30e092
77d6f6e6bee63c41438ec5c186c10fa17b91fd7c
Loading
Loading
@@ -480,7 +480,7 @@ end
gem 'spamcheck', '~> 0.1.0'
 
# Gitaly GRPC protocol definitions
gem 'gitaly', '~> 13.12.0.pre.rc1'
gem 'gitaly', '~> 14.0.0.pre.rc2'
 
# KAS GRPC protocol definitions
gem 'kas-grpc', '~> 0.0.2'
Loading
Loading
Loading
Loading
@@ -454,7 +454,7 @@ GEM
rails (>= 3.2.0)
git (1.7.0)
rchardet (~> 1.8)
gitaly (13.12.0.pre.rc1)
gitaly (14.0.0.pre.rc2)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab (4.16.1)
Loading
Loading
@@ -1483,7 +1483,7 @@ DEPENDENCIES
gettext (~> 3.3)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly (~> 13.12.0.pre.rc1)
gitaly (~> 14.0.0.pre.rc2)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 2.1.2)
Loading
Loading
Loading
Loading
@@ -63,6 +63,15 @@ export default {
:tiptap-editor="contentEditor.tiptapEditor"
@execute="trackToolbarControlExecution"
/>
<toolbar-button
data-testid="strike"
content-type="strike"
icon-name="strikethrough"
editor-command="toggleStrike"
:label="__('Strikethrough')"
:tiptap-editor="contentEditor.tiptapEditor"
@execute="trackToolbarControlExecution"
/>
<toolbar-button
data-testid="code"
content-type="code"
Loading
Loading
import { Strike } from '@tiptap/extension-strike';
export const tiptapExtension = Strike;
export const serializer = {
open: '~~',
close: '~~',
mixable: true,
expelEnclosingWhitespace: true,
};
Loading
Loading
@@ -19,6 +19,7 @@ import * as Link from '../extensions/link';
import * as ListItem from '../extensions/list_item';
import * as OrderedList from '../extensions/ordered_list';
import * as Paragraph from '../extensions/paragraph';
import * as Strike from '../extensions/strike';
import * as Text from '../extensions/text';
import buildSerializerConfig from './build_serializer_config';
import { ContentEditor } from './content_editor';
Loading
Loading
@@ -44,6 +45,7 @@ const builtInContentEditorExtensions = [
ListItem,
OrderedList,
Paragraph,
Strike,
Text,
];
 
Loading
Loading
Loading
Loading
@@ -66,9 +66,7 @@ export default {
return this.isEmpty;
},
canRenderCanaryWeight() {
return (
this.glFeatures.canaryIngressWeightControl && !isEmpty(this.deployBoardData.canary_ingress)
);
return !isEmpty(this.deployBoardData.canary_ingress);
},
instanceCount() {
const { instances } = this.deployBoardData;
Loading
Loading
Loading
Loading
@@ -50,6 +50,9 @@ export default {
},
},
computed: {
issuableId() {
return getIdFromGraphQLId(this.issuable.id);
},
createdInPastDay() {
const createdSecondsAgo = differenceInSeconds(new Date(this.issuable.createdAt), new Date());
return createdSecondsAgo < SECONDS_IN_DAY;
Loading
Loading
@@ -61,7 +64,7 @@ export default {
return this.issuable.gitlabWebUrl || this.issuable.webUrl;
},
authorId() {
return getIdFromGraphQLId(`${this.author.id}`);
return getIdFromGraphQLId(this.author.id);
},
isIssuableUrlExternal() {
return isExternal(this.webUrl);
Loading
Loading
@@ -70,10 +73,10 @@ export default {
return this.issuable.labels?.nodes || this.issuable.labels || [];
},
labelIdsString() {
return JSON.stringify(this.labels.map((label) => label.id));
return JSON.stringify(this.labels.map((label) => getIdFromGraphQLId(label.id)));
},
assignees() {
return this.issuable.assignees || [];
return this.issuable.assignees?.nodes || this.issuable.assignees || [];
},
createdAt() {
return sprintf(__('created %{timeAgo}'), {
Loading
Loading
@@ -157,7 +160,7 @@ export default {
 
<template>
<li
:id="`issuable_${issuable.id}`"
:id="`issuable_${issuableId}`"
class="issue gl-px-5!"
:class="{ closed: issuable.closedAt, today: createdInPastDay }"
:data-labels="labelIdsString"
Loading
Loading
@@ -167,7 +170,7 @@ export default {
<gl-form-checkbox
class="gl-mr-0"
:checked="checked"
:data-id="issuable.id"
:data-id="issuableId"
@input="$emit('checked-input', $event)"
>
<span class="gl-sr-only">{{ issuable.title }}</span>
Loading
Loading
<script>
import { GlSkeletonLoading, GlPagination } from '@gitlab/ui';
import { GlKeysetPagination, GlSkeletonLoading, GlPagination } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
 
Loading
Loading
@@ -19,6 +19,7 @@ export default {
tag: 'ul',
},
components: {
GlKeysetPagination,
GlSkeletonLoading,
IssuableTabs,
FilteredSearchBar,
Loading
Loading
@@ -140,6 +141,21 @@ export default {
required: false,
default: false,
},
useKeysetPagination: {
type: Boolean,
required: false,
default: false,
},
hasNextPage: {
type: Boolean,
required: false,
default: false,
},
hasPreviousPage: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
Loading
Loading
@@ -211,7 +227,7 @@ export default {
},
methods: {
issuableId(issuable) {
return issuable.id || issuable.iid || uniqueId();
return getIdFromGraphQLId(issuable.id) || issuable.iid || uniqueId();
},
issuableChecked(issuable) {
return this.checkedIssuables[this.issuableId(issuable)]?.checked;
Loading
Loading
@@ -315,8 +331,16 @@ export default {
<slot v-else name="empty-state"></slot>
</template>
 
<div v-if="showPaginationControls && useKeysetPagination" class="gl-text-center gl-mt-3">
<gl-keyset-pagination
:has-next-page="hasNextPage"
:has-previous-page="hasPreviousPage"
@next="$emit('next-page')"
@prev="$emit('previous-page')"
/>
</div>
<gl-pagination
v-if="showPaginationControls"
v-else-if="showPaginationControls"
:per-page="defaultPageSize"
:total-items="totalItems"
:value="currentPage"
Loading
Loading
Loading
Loading
@@ -42,6 +42,9 @@ export default {
}
return __('Milestone');
},
milestoneLink() {
return this.issue.milestone.webPath || this.issue.milestone.webUrl;
},
dueDate() {
return this.issue.dueDate && dateInWords(new Date(this.issue.dueDate), true);
},
Loading
Loading
@@ -49,7 +52,7 @@ export default {
return isInPast(new Date(this.issue.dueDate));
},
timeEstimate() {
return this.issue.timeStats?.humanTimeEstimate;
return this.issue.humanTimeEstimate || this.issue.timeStats?.humanTimeEstimate;
},
showHealthStatus() {
return this.hasIssuableHealthStatusFeature && this.issue.healthStatus;
Loading
Loading
@@ -85,7 +88,7 @@ export default {
class="issuable-milestone gl-display-none gl-sm-display-inline-block! gl-mr-3"
data-testid="issuable-milestone"
>
<gl-link v-gl-tooltip :href="issue.milestone.webUrl" :title="milestoneDate">
<gl-link v-gl-tooltip :href="milestoneLink" :title="milestoneDate">
<gl-icon name="clock" />
{{ issue.milestone.title }}
</gl-link>
Loading
Loading
Loading
Loading
@@ -9,7 +9,7 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import { toNumber } from 'lodash';
import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql';
import createFlash from '~/flash';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
Loading
Loading
@@ -17,13 +17,12 @@ import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
import { IssuableListTabs, IssuableStates } from '~/issuable_list/constants';
import {
API_PARAM,
apiSortParams,
CREATED_DESC,
i18n,
initialPageParams,
MAX_LIST_SIZE,
PAGE_SIZE,
PARAM_DUE_DATE,
PARAM_PAGE,
PARAM_SORT,
PARAM_STATE,
RELATIVE_POSITION_DESC,
Loading
Loading
@@ -49,7 +48,8 @@ import {
getSortOptions,
} from '~/issues_list/utils';
import axios from '~/lib/utils/axios_utils';
import { convertObjectPropsToCamelCase, getParameterByName } from '~/lib/utils/common_utils';
import { getParameterByName } from '~/lib/utils/common_utils';
import { scrollUp } from '~/lib/utils/scroll_utils';
import {
DEFAULT_NONE_ANY,
OPERATOR_IS_ONLY,
Loading
Loading
@@ -107,9 +107,6 @@ export default {
emptyStateSvgPath: {
default: '',
},
endpoint: {
default: '',
},
exportCsvPath: {
default: '',
},
Loading
Loading
@@ -173,15 +170,43 @@ export default {
dueDateFilter: getDueDateValue(getParameterByName(PARAM_DUE_DATE)),
exportCsvPathWithQuery: this.getExportCsvPathWithQuery(),
filterTokens: getFilterTokens(window.location.search),
isLoading: false,
issues: [],
page: toNumber(getParameterByName(PARAM_PAGE)) || 1,
pageInfo: {},
pageParams: initialPageParams,
showBulkEditSidebar: false,
sortKey: getSortKey(getParameterByName(PARAM_SORT)) || defaultSortKey,
state: state || IssuableStates.Opened,
totalIssues: 0,
};
},
apollo: {
issues: {
query: getIssuesQuery,
variables() {
return {
projectPath: this.projectPath,
search: this.searchQuery,
sort: this.sortKey,
state: this.state,
...this.pageParams,
...this.apiFilterParams,
};
},
update: ({ project }) => project.issues.nodes,
result({ data }) {
this.pageInfo = data.project.issues.pageInfo;
this.totalIssues = data.project.issues.count;
this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery();
},
error(error) {
createFlash({ message: this.$options.i18n.errorFetchingIssues, captureError: true, error });
},
skip() {
return !this.hasProjectIssues;
},
debounce: 200,
},
},
computed: {
hasSearch() {
return this.searchQuery || Object.keys(this.urlFilterParams).length;
Loading
Loading
@@ -348,7 +373,6 @@ export default {
 
return {
due_date: this.dueDateFilter,
page: this.page,
search: this.searchQuery,
state: this.state,
...urlSortParams[this.sortKey],
Loading
Loading
@@ -361,7 +385,6 @@ export default {
},
mounted() {
eventHub.$on('issuables:toggleBulkEdit', this.toggleBulkEditSidebar);
this.fetchIssues();
},
beforeDestroy() {
eventHub.$off('issuables:toggleBulkEdit', this.toggleBulkEditSidebar);
Loading
Loading
@@ -406,54 +429,11 @@ export default {
fetchUsers(search) {
return axios.get(this.autocompleteUsersPath, { params: { search } });
},
fetchIssues() {
if (!this.hasProjectIssues) {
return undefined;
}
this.isLoading = true;
const filterParams = {
...this.apiFilterParams,
};
if (filterParams.epic_id) {
filterParams.epic_id = filterParams.epic_id.split('::&').pop();
} else if (filterParams['not[epic_id]']) {
filterParams['not[epic_id]'] = filterParams['not[epic_id]'].split('::&').pop();
}
return axios
.get(this.endpoint, {
params: {
due_date: this.dueDateFilter,
page: this.page,
per_page: PAGE_SIZE,
search: this.searchQuery,
state: this.state,
with_labels_details: true,
...apiSortParams[this.sortKey],
...filterParams,
},
})
.then(({ data, headers }) => {
this.page = Number(headers['x-page']);
this.totalIssues = Number(headers['x-total']);
this.issues = data.map((issue) => convertObjectPropsToCamelCase(issue, { deep: true }));
this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery();
})
.catch(() => {
createFlash({ message: this.$options.i18n.errorFetchingIssues });
})
.finally(() => {
this.isLoading = false;
});
},
getExportCsvPathWithQuery() {
return `${this.exportCsvPath}${window.location.search}`;
},
getStatus(issue) {
if (issue.closedAt && issue.movedToId) {
if (issue.closedAt && issue.moved) {
return this.$options.i18n.closedMoved;
}
if (issue.closedAt) {
Loading
Loading
@@ -484,18 +464,26 @@ export default {
},
handleClickTab(state) {
if (this.state !== state) {
this.page = 1;
this.pageParams = initialPageParams;
}
this.state = state;
this.fetchIssues();
},
handleFilter(filter) {
this.filterTokens = filter;
this.fetchIssues();
},
handlePageChange(page) {
this.page = page;
this.fetchIssues();
handleNextPage() {
this.pageParams = {
afterCursor: this.pageInfo.endCursor,
firstPageSize: PAGE_SIZE,
};
scrollUp();
},
handlePreviousPage() {
this.pageParams = {
beforeCursor: this.pageInfo.startCursor,
lastPageSize: PAGE_SIZE,
};
scrollUp();
},
handleReorder({ newIndex, oldIndex }) {
const issueToMove = this.issues[oldIndex];
Loading
Loading
@@ -530,9 +518,11 @@ export default {
createFlash({ message: this.$options.i18n.reorderError });
});
},
handleSort(value) {
this.sortKey = value;
this.fetchIssues();
handleSort(sortKey) {
if (this.sortKey !== sortKey) {
this.pageParams = initialPageParams;
}
this.sortKey = sortKey;
},
toggleBulkEditSidebar(showBulkEditSidebar) {
this.showBulkEditSidebar = showBulkEditSidebar;
Loading
Loading
@@ -556,18 +546,18 @@ export default {
:tabs="$options.IssuableListTabs"
:current-tab="state"
:tab-counts="tabCounts"
:issuables-loading="isLoading"
:issuables-loading="$apollo.queries.issues.loading"
:is-manual-ordering="isManualOrdering"
:show-bulk-edit-sidebar="showBulkEditSidebar"
:show-pagination-controls="showPaginationControls"
:total-items="totalIssues"
:current-page="page"
:previous-page="page - 1"
:next-page="page + 1"
:use-keyset-pagination="true"
:has-next-page="pageInfo.hasNextPage"
:has-previous-page="pageInfo.hasPreviousPage"
:url-params="urlParams"
@click-tab="handleClickTab"
@filter="handleFilter"
@page-change="handlePageChange"
@next-page="handleNextPage"
@previous-page="handlePreviousPage"
@reorder="handleReorder"
@sort="handleSort"
@update-legacy-bulk-edit="handleUpdateLegacyBulkEdit"
Loading
Loading
@@ -646,7 +636,7 @@ export default {
</li>
<blocking-issues-count
class="gl-display-none gl-sm-display-block"
:blocking-issues-count="issuable.blockingIssuesCount"
:blocking-issues-count="issuable.blockedByCount"
:is-list-item="true"
/>
</template>
Loading
Loading
Loading
Loading
@@ -101,10 +101,13 @@ export const i18n = {
export const JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY = 'jira-import-success-alert-hide-map';
 
export const PARAM_DUE_DATE = 'due_date';
export const PARAM_PAGE = 'page';
export const PARAM_SORT = 'sort';
export const PARAM_STATE = 'state';
 
export const initialPageParams = {
firstPageSize: PAGE_SIZE,
};
export const DUE_DATE_NONE = '0';
export const DUE_DATE_ANY = '';
export const DUE_DATE_OVERDUE = 'overdue';
Loading
Loading
Loading
Loading
@@ -73,6 +73,13 @@ export function mountIssuesListApp() {
return false;
}
 
Vue.use(VueApollo);
const defaultClient = createDefaultClient({}, { assumeImmutableResults: true });
const apolloProvider = new VueApollo({
defaultClient,
});
const {
autocompleteAwardEmojisPath,
autocompleteUsersPath,
Loading
Loading
@@ -83,7 +90,6 @@ export function mountIssuesListApp() {
email,
emailsHelpPagePath,
emptyStateSvgPath,
endpoint,
exportCsvPath,
groupEpicsPath,
hasBlockedIssuesFeature,
Loading
Loading
@@ -113,16 +119,13 @@ export function mountIssuesListApp() {
 
return new Vue({
el,
// Currently does not use Vue Apollo, but need to provide {} for now until the
// issue is fixed upstream in https://github.com/vuejs/vue-apollo/pull/1153
apolloProvider: {},
apolloProvider,
provide: {
autocompleteAwardEmojisPath,
autocompleteUsersPath,
calendarPath,
canBulkUpdate: parseBoolean(canBulkUpdate),
emptyStateSvgPath,
endpoint,
groupEpicsPath,
hasBlockedIssuesFeature: parseBoolean(hasBlockedIssuesFeature),
hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature),
Loading
Loading
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "./issue.fragment.graphql"
query getProjectIssues(
$projectPath: ID!
$search: String
$sort: IssueSort
$state: IssuableState
$assigneeId: String
$assigneeUsernames: [String!]
$authorUsername: String
$labelName: [String]
$milestoneTitle: [String]
$not: NegatedIssueFilterInput
$beforeCursor: String
$afterCursor: String
$firstPageSize: Int
$lastPageSize: Int
) {
project(fullPath: $projectPath) {
issues(
search: $search
sort: $sort
state: $state
assigneeId: $assigneeId
assigneeUsernames: $assigneeUsernames
authorUsername: $authorUsername
labelName: $labelName
milestoneTitle: $milestoneTitle
not: $not
before: $beforeCursor
after: $afterCursor
first: $firstPageSize
last: $lastPageSize
) {
count
pageInfo {
...PageInfo
}
nodes {
...IssueFragment
}
}
}
}
fragment IssueFragment on Issue {
id
iid
closedAt
confidential
createdAt
downvotes
dueDate
humanTimeEstimate
moved
title
updatedAt
upvotes
userDiscussionsCount
webUrl
assignees {
nodes {
id
avatarUrl
name
username
webUrl
}
}
author {
id
avatarUrl
name
username
webUrl
}
labels {
nodes {
id
color
title
description
}
}
milestone {
id
dueDate
startDate
webPath
title
}
taskCompletionStatus {
completedCount
count
}
}
import '../webpack';
import setConfigs from '@gitlab/ui/dist/config';
import Vue from 'vue';
import { getLocation, sizeToParent } from '~/jira_connect/utils';
Loading
Loading
import '../webpack';
import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
Loading
Loading
<script>
import { GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import { s__ } from '~/locale';
import RunnerRegistrationTokenReset from '~/runner/components/runner_registration_token_reset.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../constants';
 
export default {
components: {
Loading
Loading
@@ -10,6 +12,7 @@ export default {
GlSprintf,
ClipboardButton,
RunnerInstructions,
RunnerRegistrationTokenReset,
},
directives: {
GlTooltip: GlTooltipDirective,
Loading
Loading
@@ -24,16 +27,40 @@ export default {
type: String,
required: true,
},
typeName: {
type: {
type: String,
required: false,
default: __('shared'),
required: true,
validator(type) {
return [INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE].includes(type);
},
},
},
data() {
return {
currentRegistrationToken: this.registrationToken,
};
},
computed: {
rootUrl() {
return gon.gitlab_url || '';
},
typeName() {
switch (this.type) {
case INSTANCE_TYPE:
return s__('Runners|shared');
case GROUP_TYPE:
return s__('Runners|group');
case PROJECT_TYPE:
return s__('Runners|specific');
default:
return '';
}
},
},
methods: {
onTokenReset(token) {
this.currentRegistrationToken = token;
},
},
};
</script>
Loading
Loading
@@ -65,12 +92,13 @@ export default {
{{ __('And this registration token:') }}
<br />
 
<code data-testid="registration-token">{{ registrationToken }}</code>
<clipboard-button :title="__('Copy token')" :text="registrationToken" />
<code data-testid="registration-token">{{ currentRegistrationToken }}</code>
<clipboard-button :title="__('Copy token')" :text="currentRegistrationToken" />
</li>
</ol>
 
<!-- TODO Implement reset token functionality -->
<runner-registration-token-reset :type="type" @tokenReset="onTokenReset" />
<runner-instructions />
</div>
</template>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment