summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-10-18 21:09:37 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-18 21:09:37 +0000
commitcace5e8ff1f766b8098e35adc94abc4402aeb2a9 (patch)
tree96bea3616ee60702be89f4845580f3b3db22f936 /app
parente4220eeccaf1d53444fdd9102a4061336f91784e (diff)
downloadgitlab-ce-cace5e8ff1f766b8098e35adc94abc4402aeb2a9.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/graphql_shared/issuable_client.js26
-rw-r--r--app/assets/javascripts/groups/components/app.vue8
-rw-r--r--app/assets/javascripts/groups/components/groups.vue20
-rw-r--r--app/assets/javascripts/groups/components/overview_tabs.vue17
-rw-r--r--app/assets/javascripts/groups/constants.js2
-rw-r--r--app/assets/javascripts/ide/index.js4
-rw-r--r--app/assets/javascripts/ide/init_gitlab_web_ide.js3
-rw-r--r--app/assets/javascripts/pages/import/fogbugz/new_user_map/components/user_select.vue95
-rw-r--r--app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js20
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue6
-rw-r--r--app/assets/javascripts/sidebar/constants.js7
-rw-r--r--app/assets/javascripts/users_select/index.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js2
-rw-r--r--app/assets/javascripts/webhooks/components/form_url_app.vue73
-rw-r--r--app/assets/javascripts/webhooks/components/form_url_mask_item.vue37
-rw-r--r--app/assets/javascripts/webhooks/index.js9
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue17
-rw-r--r--app/assets/javascripts/work_items/components/work_item_milestone.vue248
-rw-r--r--app/assets/javascripts/work_items/constants.js1
-rw-r--r--app/assets/javascripts/work_items/graphql/typedefs.graphql15
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item.query.graphql11
-rw-r--r--app/assets/stylesheets/page_bundles/work_items.scss3
-rw-r--r--app/controllers/ide_controller.rb3
-rw-r--r--app/controllers/profiles/preferences_controller.rb3
-rw-r--r--app/graphql/types/environment_type.rb2
-rw-r--r--app/helpers/hooks_helper.rb7
-rw-r--r--app/helpers/ide_helper.rb32
-rw-r--r--app/models/application_setting.rb7
-rw-r--r--app/models/ci/pipeline.rb6
-rw-r--r--app/models/clusters/agents/implicit_authorization.rb2
-rw-r--r--app/models/user.rb1
-rw-r--r--app/models/user_preference.rb1
-rw-r--r--app/presenters/ci/build_runner_presenter.rb4
-rw-r--r--app/services/bulk_imports/uploads_export_service.rb5
-rw-r--r--app/services/ci/generate_kubeconfig_service.rb12
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml6
-rw-r--r--app/views/import/fogbugz/new_user_map.html.haml30
-rw-r--r--app/views/shared/web_hooks/_form.html.haml2
38 files changed, 666 insertions, 84 deletions
diff --git a/app/assets/javascripts/graphql_shared/issuable_client.js b/app/assets/javascripts/graphql_shared/issuable_client.js
index 3849bd0289d..3b737dfff33 100644
--- a/app/assets/javascripts/graphql_shared/issuable_client.js
+++ b/app/assets/javascripts/graphql_shared/issuable_client.js
@@ -4,10 +4,14 @@ import { concatPagination } from '@apollo/client/utilities';
import getIssueStateQuery from '~/issues/show/queries/get_issue_state.query.graphql';
import createDefaultClient from '~/lib/graphql';
import typeDefs from '~/work_items/graphql/typedefs.graphql';
+import { WIDGET_TYPE_MILESTONE } from '~/work_items/constants';
export const temporaryConfig = {
typeDefs,
cacheConfig: {
+ possibleTypes: {
+ LocalWorkItemWidget: ['LocalWorkItemMilestone'],
+ },
typePolicies: {
Project: {
fields: {
@@ -18,6 +22,28 @@ export const temporaryConfig = {
},
WorkItem: {
fields: {
+ mockWidgets: {
+ read(widgets) {
+ return (
+ widgets || [
+ {
+ __typename: 'LocalWorkItemMilestone',
+ type: WIDGET_TYPE_MILESTONE,
+ nodes: [
+ {
+ dueDate: null,
+ expired: false,
+ id: 'gid://gitlab/Milestone/30',
+ title: 'v4.0',
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ __typename: 'Milestone',
+ },
+ ],
+ },
+ ]
+ );
+ },
+ },
widgets: {
merge(existing = [], incoming) {
if (existing.length === 0) {
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index d74cb2d8175..15f5a3518a5 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -51,7 +51,6 @@ export default {
isModalVisible: false,
isLoading: true,
isSearchEmpty: false,
- searchEmptyMessage: '',
targetGroup: null,
targetParentGroup: null,
showEmptyState: false,
@@ -88,10 +87,6 @@ export default {
},
},
created() {
- this.searchEmptyMessage = this.hideProjects
- ? COMMON_STR.GROUP_SEARCH_EMPTY
- : COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
-
eventHub.$on(`${this.action}fetchPage`, this.fetchPage);
eventHub.$on(`${this.action}toggleChildren`, this.toggleChildren);
eventHub.$on(`${this.action}showLeaveGroupModal`, this.showLeaveGroupModal);
@@ -259,7 +254,7 @@ export default {
const hasGroups = groups && groups.length > 0;
if (this.renderEmptyState) {
- this.isSearchEmpty = this.filterGroupsBy !== null && !hasGroups;
+ this.isSearchEmpty = fromSearch && !hasGroups;
} else {
this.isSearchEmpty = !hasGroups;
}
@@ -294,7 +289,6 @@ export default {
v-else
:groups="groups"
:search-empty="isSearchEmpty"
- :search-empty-message="searchEmptyMessage"
:page-info="pageInfo"
:action="action"
/>
diff --git a/app/assets/javascripts/groups/components/groups.vue b/app/assets/javascripts/groups/components/groups.vue
index 3a05c308a2a..43aa0753082 100644
--- a/app/assets/javascripts/groups/components/groups.vue
+++ b/app/assets/javascripts/groups/components/groups.vue
@@ -1,11 +1,18 @@
<script>
+import { GlEmptyState } from '@gitlab/ui';
import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
import { getParameterByName } from '~/lib/utils/url_utility';
+import { __ } from '~/locale';
import eventHub from '../event_hub';
export default {
+ i18n: {
+ emptyStateTitle: __('No results found'),
+ emptyStateDescription: __('Edit your search and try again'),
+ },
components: {
PaginationLinks,
+ GlEmptyState,
},
props: {
groups: {
@@ -20,10 +27,6 @@ export default {
type: Boolean,
required: true,
},
- searchEmptyMessage: {
- type: String,
- required: true,
- },
action: {
type: String,
required: false,
@@ -43,12 +46,11 @@ export default {
<template>
<div class="groups-list-tree-container" data-qa-selector="groups_list_tree_container">
- <div
+ <gl-empty-state
v-if="searchEmpty"
- class="has-no-search-results gl-font-style-italic gl-text-center gl-text-gray-600 gl-p-5"
- >
- {{ searchEmptyMessage }}
- </div>
+ :title="$options.i18n.emptyStateTitle"
+ :description="$options.i18n.emptyStateDescription"
+ />
<template v-else>
<group-folder :groups="groups" :action="action" />
<pagination-links
diff --git a/app/assets/javascripts/groups/components/overview_tabs.vue b/app/assets/javascripts/groups/components/overview_tabs.vue
index 15a0c686548..d0c5846ac88 100644
--- a/app/assets/javascripts/groups/components/overview_tabs.vue
+++ b/app/assets/javascripts/groups/components/overview_tabs.vue
@@ -2,6 +2,7 @@
import { GlTabs, GlTab, GlSearchBoxByType, GlSorting, GlSortingItem } from '@gitlab/ui';
import { isString, debounce } from 'lodash';
import { __ } from '~/locale';
+import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
import GroupsStore from '../store/groups_store';
import GroupsService from '../service/groups_service';
import {
@@ -61,11 +62,6 @@ export default {
return this.isAscending ? this.sort.asc : this.sort.desc;
},
},
- watch: {
- search: debounce(async function debouncedSearch() {
- this.handleSearchOrSortChange();
- }, 250),
- },
mounted() {
this.search = this.$route.query?.filter || '';
@@ -137,6 +133,14 @@ export default {
this.handleSearchOrSortChange();
},
+ handleSearchInput(value) {
+ this.search = value;
+
+ this.debouncedSearch();
+ },
+ debouncedSearch: debounce(async function debouncedSearch() {
+ this.handleSearchOrSortChange();
+ }, DEBOUNCE_DELAY),
},
i18n: {
[ACTIVE_TAB_SUBGROUPS_AND_PROJECTS]: __('Subgroups and projects'),
@@ -169,9 +173,10 @@ export default {
<div class="gl-lg-display-flex gl-justify-content-end gl-mx-n2 gl-my-n2">
<div class="gl-p-2 gl-lg-form-input-md gl-w-full">
<gl-search-box-by-type
- v-model="search"
+ :value="search"
:placeholder="$options.i18n.searchPlaceholder"
data-qa-selector="groups_filter_field"
+ @input="handleSearchInput"
/>
</div>
<div class="gl-p-2 gl-w-full gl-lg-w-auto">
diff --git a/app/assets/javascripts/groups/constants.js b/app/assets/javascripts/groups/constants.js
index 33bfcade336..6fb12cd6270 100644
--- a/app/assets/javascripts/groups/constants.js
+++ b/app/assets/javascripts/groups/constants.js
@@ -24,8 +24,6 @@ export const COMMON_STR = {
EDIT_BTN_TITLE: s__('GroupsTree|Edit'),
REMOVE_BTN_TITLE: s__('GroupsTree|Delete'),
OPTIONS_DROPDOWN_TITLE: s__('GroupsTree|Options'),
- GROUP_SEARCH_EMPTY: s__('GroupsTree|No groups matched your search'),
- GROUP_PROJECT_SEARCH_EMPTY: s__('GroupsTree|No groups or projects matched your search'),
};
export const ITEM_TYPE = {
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
index 10e9f6a9488..1a191f6f76f 100644
--- a/app/assets/javascripts/ide/index.js
+++ b/app/assets/javascripts/ide/index.js
@@ -99,7 +99,9 @@ export function startIde(options) {
return;
}
- if (gon.features?.vscodeWebIde) {
+ const useNewWebIde = parseBoolean(ideElement.dataset.useNewWebIde);
+
+ if (useNewWebIde) {
initGitlabWebIDE(ideElement);
} else {
resetServiceWorkersPublicPath();
diff --git a/app/assets/javascripts/ide/init_gitlab_web_ide.js b/app/assets/javascripts/ide/init_gitlab_web_ide.js
index a061da38d4f..140f2895a29 100644
--- a/app/assets/javascripts/ide/init_gitlab_web_ide.js
+++ b/app/assets/javascripts/ide/init_gitlab_web_ide.js
@@ -7,8 +7,7 @@ export const initGitlabWebIDE = async (el) => {
const baseUrl = new URL(process.env.GITLAB_WEB_IDE_PUBLIC_PATH, window.location.origin);
// what: Pull what we need from the element. We will replace it soon.
- const { path_with_namespace: projectPath } = JSON.parse(el.dataset.project);
- const { cspNonce: nonce, branchName: ref } = el.dataset;
+ const { cspNonce: nonce, branchName: ref, projectPath } = el.dataset;
// what: Clean up the element, but preserve id.
// why: This way we don't inherit any `ide-loading` side-effects. This
diff --git a/app/assets/javascripts/pages/import/fogbugz/new_user_map/components/user_select.vue b/app/assets/javascripts/pages/import/fogbugz/new_user_map/components/user_select.vue
new file mode 100644
index 00000000000..20ce296bbec
--- /dev/null
+++ b/app/assets/javascripts/pages/import/fogbugz/new_user_map/components/user_select.vue
@@ -0,0 +1,95 @@
+<script>
+import { GlAvatarLabeled, GlListbox } from '@gitlab/ui';
+import { __ } from '~/locale';
+import searchUsersQuery from '~/graphql_shared/queries/users_search_all.query.graphql';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
+
+const USERS_PER_PAGE = 20;
+
+export default {
+ components: {
+ GlAvatarLabeled,
+ GlListbox,
+ },
+ props: {
+ name: {
+ type: String,
+ required: true,
+ },
+ },
+ apollo: {
+ usersQuery: {
+ query: searchUsersQuery,
+ variables() {
+ return {
+ search: this.search,
+ first: USERS_PER_PAGE,
+ };
+ },
+ update(data) {
+ return data;
+ },
+ debounce: DEFAULT_DEBOUNCE_AND_THROTTLE_MS,
+ },
+ },
+ data() {
+ return {
+ user: '',
+ search: '',
+ };
+ },
+ computed: {
+ userId() {
+ return getIdFromGraphQLId(this.user);
+ },
+ users() {
+ return [
+ { text: __('(no user)'), value: '' },
+ ...(this.usersQuery?.users.nodes || []).map((u) => ({
+ username: `@${u.username}`,
+ avatarUrl: u.avatarUrl,
+ text: u.name,
+ value: u.id,
+ })),
+ ];
+ },
+ },
+ methods: {
+ clearTransform() {
+ // FIXME: workaround for listbox issue
+ // https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1986
+ const { listbox } = this.$refs;
+ if (listbox.querySelector('.dropdown-menu')) {
+ listbox.querySelector('.dropdown-menu').style.transform = '';
+ }
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-listbox
+ ref="listbox"
+ v-model="user"
+ :items="users"
+ searchable
+ is-check-centered
+ :searching="$apollo.loading"
+ @click.capture.native="clearTransform"
+ @search="search = $event"
+ >
+ <template #list-item="{ item }">
+ <gl-avatar-labeled
+ shape="circle"
+ :size="32"
+ :src="item.avatarUrl"
+ :label="item.text"
+ :sub-label="item.username"
+ />
+ </template>
+ </gl-listbox>
+ <input type="hidden" :name="name" :value="userId" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js b/app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js
index 86b80a0ba5b..ef549f20cf3 100644
--- a/app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js
+++ b/app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js
@@ -1,3 +1,19 @@
-import UsersSelect from '~/users_select';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import UserSelect from './components/user_select.vue';
-new UsersSelect(); // eslint-disable-line no-new
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
+
+Array.from(document.querySelectorAll('.js-gitlab-user')).forEach(
+ (node) =>
+ new Vue({
+ el: node,
+ apolloProvider,
+ render: (h) => h(UserSelect, { props: { name: node.dataset.name } }),
+ }),
+);
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
index 7126d69c8c6..c33b1468ca4 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
@@ -25,6 +25,8 @@ import {
Tracking,
IssuableAttributeState,
IssuableAttributeType,
+ LocalizedIssuableAttributeType,
+ IssuableAttributeTypeKeyMap,
issuableAttributesQueries,
noAttributeId,
defaultEpicSort,
@@ -229,7 +231,9 @@ export default {
return timeFor(this.currentAttribute?.dueDate);
},
i18n() {
- return dropdowni18nText(this.issuableAttribute, this.issuableType);
+ const localizedAttribute =
+ LocalizedIssuableAttributeType[IssuableAttributeTypeKeyMap[this.issuableAttribute]];
+ return dropdowni18nText(localizedAttribute, this.issuableType);
},
isEpic() {
// MV to EE https://gitlab.com/gitlab-org/gitlab/-/issues/345311
diff --git a/app/assets/javascripts/sidebar/constants.js b/app/assets/javascripts/sidebar/constants.js
index 60cb4cff727..6248bcb8e2d 100644
--- a/app/assets/javascripts/sidebar/constants.js
+++ b/app/assets/javascripts/sidebar/constants.js
@@ -1,3 +1,4 @@
+import { invert } from 'lodash';
import { s__, __, sprintf } from '~/locale';
import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql';
import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
@@ -251,6 +252,12 @@ export const IssuableAttributeType = {
Milestone: 'milestone',
};
+export const LocalizedIssuableAttributeType = {
+ Milestone: s__('Issuable|milestone'),
+};
+
+export const IssuableAttributeTypeKeyMap = invert(IssuableAttributeType);
+
export const IssuableAttributeState = {
[IssuableAttributeType.Milestone]: 'active',
};
diff --git a/app/assets/javascripts/users_select/index.js b/app/assets/javascripts/users_select/index.js
index 5963568a00b..bd425bdc2a8 100644
--- a/app/assets/javascripts/users_select/index.js
+++ b/app/assets/javascripts/users_select/index.js
@@ -819,13 +819,14 @@ UsersSelect.prototype.renderRow = function (
const tooltipAttributes = tooltip
? `data-container="body" data-placement="left" data-title="${tooltip}"`
: '';
+ const dataUserSuggested = user.suggested ? `data-user-suggested=${user.suggested}` : '';
const name =
user?.availability && isUserBusy(user.availability)
? sprintf(__('%{name} (Busy)'), { name: user.name })
: user.name;
return `
- <li data-user-id=${user.id}>
+ <li data-user-id=${user.id} ${dataUserSuggested}>
<a href="#" class="dropdown-menu-user-link gl-display-flex! gl-align-items-center ${linkClasses}" ${tooltipAttributes}>
${this.renderRowAvatar(issuableType, user, img)}
<span class="gl-display-flex gl-flex-direction-column gl-overflow-hidden">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js
index d67ff11f297..e3f87c08ad4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js
@@ -28,7 +28,7 @@ const nonStandardEvents = {
},
counter: {},
},
- testReport: {
+ testSummary: {
uniqueUser: {
expand: ['i_testing_summary_widget_total'],
},
diff --git a/app/assets/javascripts/webhooks/components/form_url_app.vue b/app/assets/javascripts/webhooks/components/form_url_app.vue
index 62d6c03bbb3..5ec16d4ba15 100644
--- a/app/assets/javascripts/webhooks/components/form_url_app.vue
+++ b/app/assets/javascripts/webhooks/components/form_url_app.vue
@@ -1,5 +1,6 @@
<script>
-import { GlFormGroup, GlFormInput, GlFormRadio, GlFormRadioGroup } from '@gitlab/ui';
+import { isEmpty } from 'lodash';
+import { GlFormGroup, GlFormInput, GlFormRadio, GlFormRadioGroup, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import FormUrlMaskItem from './form_url_mask_item.vue';
@@ -11,19 +12,60 @@ export default {
GlFormInput,
GlFormRadio,
GlFormRadioGroup,
+ GlLink,
+ },
+ props: {
+ initialUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ initialUrlVariables: {
+ type: Array,
+ required: false,
+ default: null,
+ },
},
data() {
return {
- maskEnabled: false,
- url: null,
+ maskEnabled: !isEmpty(this.initialUrlVariables),
+ url: this.initialUrl,
+ items: isEmpty(this.initialUrlVariables) ? [{}] : this.initialUrlVariables,
};
},
computed: {
maskedUrl() {
- return this.url;
+ if (!this.url) {
+ return null;
+ }
+
+ let maskedUrl = this.url;
+
+ this.items.forEach(({ key, value }) => {
+ if (!key || !value) {
+ return;
+ }
+
+ const replacementExpression = new RegExp(value, 'g');
+ maskedUrl = maskedUrl.replace(replacementExpression, `{${key}}`);
+ });
+
+ return maskedUrl;
+ },
+ },
+ methods: {
+ onItemInput({ index, key, value }) {
+ this.$set(this.items, index, { key, value });
+ },
+ addItem() {
+ this.items.push({});
+ },
+ removeItem(index) {
+ this.items.splice(index, 1);
},
},
i18n: {
+ addItem: s__('Webhooks|+ Mask another portion of URL'),
radioFullUrlText: s__('Webhooks|Show full URL'),
radioMaskUrlText: s__('Webhooks|Mask portions of URL'),
radioMaskUrlHelp: s__('Webhooks|Do not show sensitive data such as tokens in the UI.'),
@@ -49,6 +91,7 @@ export default {
v-model="url"
name="hook[url]"
:placeholder="$options.i18n.urlPlaceholder"
+ data-testid="form-url"
/>
</gl-form-group>
<div class="gl-mt-5">
@@ -63,9 +106,27 @@ export default {
</gl-form-radio-group>
<div v-if="maskEnabled" class="gl-ml-6" data-testid="url-mask-section">
- <form-url-mask-item :index="0" />
+ <form-url-mask-item
+ v-for="({ key, value }, index) in items"
+ :key="index"
+ :index="index"
+ :item-key="key"
+ :item-value="value"
+ @input="onItemInput"
+ @remove="removeItem"
+ />
+ <div class="gl-mb-5">
+ <gl-link @click="addItem">{{ $options.i18n.addItem }}</gl-link>
+ </div>
+
<gl-form-group :label="$options.i18n.urlPreview" label-for="webhook-url-preview">
- <gl-form-input id="webhook-url-preview" :value="maskedUrl" readonly />
+ <gl-form-input
+ id="webhook-url-preview"
+ :value="maskedUrl"
+ readonly
+ name="hook[url]"
+ data-testid="form-url-preview"
+ />
</gl-form-group>
</div>
</div>
diff --git a/app/assets/javascripts/webhooks/components/form_url_mask_item.vue b/app/assets/javascripts/webhooks/components/form_url_mask_item.vue
index 1e74b4a8215..3b75f9b6c0d 100644
--- a/app/assets/javascripts/webhooks/components/form_url_mask_item.vue
+++ b/app/assets/javascripts/webhooks/components/form_url_mask_item.vue
@@ -14,6 +14,16 @@ export default {
required: false,
default: null,
},
+ itemKey: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ itemValue: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
keyInputId() {
@@ -30,6 +40,15 @@ export default {
inputName(type) {
return `hook[url_variables][][${type}]`;
},
+ onKeyInput(key) {
+ this.$emit('input', { index: this.index, key, value: this.itemValue });
+ },
+ onValueInput(value) {
+ this.$emit('input', { index: this.index, key: this.itemKey, value });
+ },
+ onRemoveClick() {
+ this.$emit('remove', this.index);
+ },
},
i18n: {
keyLabel: s__('Webhooks|How it looks in the UI'),
@@ -39,14 +58,19 @@ export default {
</script>
<template>
- <div class="gl-display-flex gl-align-items-flex-end gl-gap-3 gl-mb-5">
+ <div class="gl-display-flex gl-align-items-flex-end gl-gap-3 gl-mb-3">
<gl-form-group
:label="$options.i18n.valueLabel"
:label-for="valueInputId"
class="gl-flex-grow-1 gl-mb-0"
data-testid="mask-item-value"
>
- <gl-form-input :id="valueInputId" :name="inputName('value')" />
+ <gl-form-input
+ :id="valueInputId"
+ :name="inputName('value')"
+ :value="itemValue"
+ @input="onValueInput"
+ />
</gl-form-group>
<gl-form-group
:label="$options.i18n.keyLabel"
@@ -54,8 +78,13 @@ export default {
class="gl-flex-grow-1 gl-mb-0"
data-testid="mask-item-key"
>
- <gl-form-input :id="keyInputId" :name="inputName('key')" />
+ <gl-form-input
+ :id="keyInputId"
+ :name="inputName('key')"
+ :value="itemKey"
+ @input="onKeyInput"
+ />
</gl-form-group>
- <gl-button icon="remove" />
+ <gl-button icon="remove" :aria-label="__('Remove')" @click="onRemoveClick" />
</div>
</template>
diff --git a/app/assets/javascripts/webhooks/index.js b/app/assets/javascripts/webhooks/index.js
index bfa33560fa5..1b2b33e44c1 100644
--- a/app/assets/javascripts/webhooks/index.js
+++ b/app/assets/javascripts/webhooks/index.js
@@ -8,11 +8,18 @@ export default () => {
return null;
}
+ const { url: initialUrl, urlVariables } = el.dataset;
+
return new Vue({
el,
name: 'WebhookFormRoot',
render(createElement) {
- return createElement(FormUrlApp, {});
+ return createElement(FormUrlApp, {
+ props: {
+ initialUrl,
+ initialUrlVariables: urlVariables ? JSON.parse(urlVariables) : undefined,
+ },
+ });
},
});
};
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index cf0aafc2eb0..af9b8c6101a 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -23,6 +23,7 @@ import {
WIDGET_TYPE_WEIGHT,
WIDGET_TYPE_HIERARCHY,
WORK_ITEM_VIEWED_STORAGE_KEY,
+ WIDGET_TYPE_MILESTONE,
WIDGET_TYPE_ITERATION,
} from '../constants';
@@ -40,6 +41,7 @@ import WorkItemDescription from './work_item_description.vue';
import WorkItemDueDate from './work_item_due_date.vue';
import WorkItemAssignees from './work_item_assignees.vue';
import WorkItemLabels from './work_item_labels.vue';
+import WorkItemMilestone from './work_item_milestone.vue';
import WorkItemInformation from './work_item_information.vue';
export default {
@@ -67,6 +69,7 @@ export default {
LocalStorageSync,
WorkItemTypeIcon,
WorkItemIteration: () => import('ee_component/work_items/components/work_item_iteration.vue'),
+ WorkItemMilestone,
},
mixins: [glFeatureFlagMixin()],
props: {
@@ -208,6 +211,9 @@ export default {
workItemIteration() {
return this.isWidgetPresent(WIDGET_TYPE_ITERATION);
},
+ workItemMilestone() {
+ return this.workItem?.mockWidgets?.find((widget) => widget.type === WIDGET_TYPE_MILESTONE);
+ },
},
beforeDestroy() {
/** make sure that if the user has not even dismissed the alert ,
@@ -411,6 +417,17 @@ export default {
:work-item-type="workItemType"
@error="updateError = $event"
/>
+ <template v-if="workItemsMvc2Enabled">
+ <work-item-milestone
+ v-if="workItemMilestone"
+ :work-item-id="workItem.id"
+ :work-item-milestone="workItemMilestone.nodes[0]"
+ :work-item-type="workItemType"
+ :can-update="canUpdate"
+ :full-path="fullPath"
+ @error="updateError = $event"
+ />
+ </template>
<work-item-weight
v-if="workItemWeight"
class="gl-mb-5"
diff --git a/app/assets/javascripts/work_items/components/work_item_milestone.vue b/app/assets/javascripts/work_items/components/work_item_milestone.vue
new file mode 100644
index 00000000000..c4a36e36555
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/work_item_milestone.vue
@@ -0,0 +1,248 @@
+<script>
+import {
+ GlFormGroup,
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownDivider,
+ GlSkeletonLoader,
+ GlSearchBoxByType,
+ GlDropdownText,
+} from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
+import { debounce } from 'lodash';
+import Tracking from '~/tracking';
+import { s__ } from '~/locale';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
+import projectMilestonesQuery from '~/sidebar/queries/project_milestones.query.graphql';
+import localUpdateWorkItemMutation from '../graphql/local_update_work_item.mutation.graphql';
+import {
+ I18N_WORK_ITEM_ERROR_UPDATING,
+ sprintfWorkItem,
+ TRACKING_CATEGORY_SHOW,
+} from '../constants';
+
+const noMilestoneId = 'no-milestone-id';
+
+export default {
+ i18n: {
+ MILESTONE: s__('WorkItem|Milestone'),
+ NONE: s__('WorkItem|None'),
+ MILESTONE_PLACEHOLDER: s__('WorkItem|Add to milestone'),
+ NO_MATCHING_RESULTS: s__('WorkItem|No matching results'),
+ NO_MILESTONE: s__('WorkItem|No milestone'),
+ MILESTONE_FETCH_ERROR: s__(
+ 'WorkItem|Something went wrong while fetching milestones. Please try again.',
+ ),
+ },
+ components: {
+ GlFormGroup,
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownDivider,
+ GlSkeletonLoader,
+ GlSearchBoxByType,
+ GlDropdownText,
+ },
+ mixins: [Tracking.mixin()],
+ props: {
+ workItemId: {
+ type: String,
+ required: true,
+ },
+ workItemMilestone: {
+ type: Object,
+ required: false,
+ default: () => {},
+ },
+ workItemType: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ canUpdate: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ fullPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ localMilestone: this.workItemMilestone,
+ searchTerm: '',
+ shouldFetch: false,
+ updateInProgress: false,
+ isFocused: false,
+ milestones: [],
+ };
+ },
+ computed: {
+ tracking() {
+ return {
+ category: TRACKING_CATEGORY_SHOW,
+ label: 'item_milestone',
+ property: `type_${this.workItemType}`,
+ };
+ },
+ emptyPlaceholder() {
+ return this.canUpdate ? this.$options.i18n.MILESTONE_PLACEHOLDER : this.$options.i18n.NONE;
+ },
+ dropdownText() {
+ return this.localMilestone?.title || this.emptyPlaceholder;
+ },
+ isLoadingMilestones() {
+ return this.$apollo.queries.milestones.loading;
+ },
+ isNoMilestone() {
+ return this.localMilestone?.id === noMilestoneId || !this.localMilestone?.id;
+ },
+ dropdownClasses() {
+ return {
+ 'gl-text-gray-500!': this.canUpdate && this.isNoMilestone,
+ 'is-not-focused': !this.isFocused,
+ };
+ },
+ },
+ created() {
+ this.debouncedSearchKeyUpdate = debounce(this.setSearchKey, DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
+ },
+ apollo: {
+ milestones: {
+ query: projectMilestonesQuery,
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ title: this.searchTerm,
+ first: 20,
+ };
+ },
+ skip() {
+ return !this.shouldFetch;
+ },
+ update(data) {
+ return data?.workspace?.attributes?.nodes || [];
+ },
+ error() {
+ this.$emit('error', this.i18n.MILESTONE_FETCH_ERROR);
+ },
+ },
+ },
+ methods: {
+ handleMilestoneClick(milestone) {
+ this.localMilestone = milestone;
+ },
+ onDropdownShown() {
+ this.$refs.search.focusInput();
+ this.shouldFetch = true;
+ this.isFocused = true;
+ },
+ onDropdownHide() {
+ this.isFocused = false;
+ this.searchTerm = '';
+ this.shouldFetch = false;
+ this.updateMilestone();
+ },
+ setSearchKey(value) {
+ this.searchTerm = value;
+ },
+ isMilestoneChecked(milestone) {
+ return this.localMilestone?.id === milestone?.id;
+ },
+ updateMilestone() {
+ if (this.workItemMilestone?.id === this.localMilestone?.id) {
+ return;
+ }
+
+ this.track('updated_milestone');
+ this.updateInProgress = true;
+ this.$apollo
+ .mutate({
+ mutation: localUpdateWorkItemMutation,
+ variables: {
+ input: {
+ id: this.workItemId,
+ milestone: {
+ milestoneId: this.localMilestone?.id,
+ },
+ },
+ },
+ })
+ .then(({ data }) => {
+ if (data.workItemUpdate.errors.length) {
+ throw new Error(data.workItemUpdate.errors.join('\n'));
+ }
+ })
+ .catch((error) => {
+ const msg = sprintfWorkItem(I18N_WORK_ITEM_ERROR_UPDATING, this.workItemType);
+ this.$emit('error', msg);
+ Sentry.captureException(error);
+ })
+ .finally(() => {
+ this.updateInProgress = false;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-form-group
+ class="work-item-dropdown"
+ :label="$options.i18n.MILESTONE"
+ label-class="gl-pb-0! gl-overflow-wrap-break gl-mt-3"
+ label-cols="3"
+ label-cols-lg="2"
+ >
+ <span
+ v-if="!canUpdate"
+ class="gl-text-secondary gl-ml-4 gl-mt-3 gl-display-inline-block gl-line-height-normal"
+ data-testid="disabled-text"
+ >
+ {{ dropdownText }}
+ </span>
+ <gl-dropdown
+ v-else
+ :toggle-class="dropdownClasses"
+ :text="dropdownText"
+ :loading="updateInProgress"
+ @shown="onDropdownShown"
+ @hide="onDropdownHide"
+ >
+ <template #header>
+ <gl-search-box-by-type ref="search" :value="searchTerm" @input="debouncedSearchKeyUpdate" />
+ </template>
+ <gl-dropdown-item
+ data-testid="no-milestone"
+ is-check-item
+ :is-checked="isNoMilestone"
+ @click="handleMilestoneClick({ id: 'no-milestone-id' })"
+ >
+ {{ $options.i18n.NO_MILESTONE }}
+ </gl-dropdown-item>
+ <gl-dropdown-divider />
+ <gl-dropdown-text v-if="isLoadingMilestones">
+ <gl-skeleton-loader :height="90">
+ <rect width="380" height="10" x="10" y="15" rx="4" />
+ <rect width="280" height="10" x="10" y="30" rx="4" />
+ <rect width="380" height="10" x="10" y="50" rx="4" />
+ <rect width="280" height="10" x="10" y="65" rx="4" />
+ </gl-skeleton-loader>
+ </gl-dropdown-text>
+ <template v-else-if="milestones.length">
+ <gl-dropdown-item
+ v-for="milestone in milestones"
+ :key="milestone.id"
+ is-check-item
+ :is-checked="isMilestoneChecked(milestone)"
+ @click="handleMilestoneClick(milestone)"
+ >
+ {{ milestone.title }}
+ </gl-dropdown-item>
+ </template>
+ <gl-dropdown-text v-else>{{ $options.i18n.NO_MATCHING_RESULTS }}</gl-dropdown-text>
+ </gl-dropdown>
+ </gl-form-group>
+</template>
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index 0d426299408..7737c535650 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -17,6 +17,7 @@ export const WIDGET_TYPE_LABELS = 'LABELS';
export const WIDGET_TYPE_START_AND_DUE_DATE = 'START_AND_DUE_DATE';
export const WIDGET_TYPE_WEIGHT = 'WEIGHT';
export const WIDGET_TYPE_HIERARCHY = 'HIERARCHY';
+export const WIDGET_TYPE_MILESTONE = 'MILESTONE';
export const WIDGET_TYPE_ITERATION = 'ITERATION';
export const WORK_ITEM_VIEWED_STORAGE_KEY = 'gl-show-work-item-banner';
diff --git a/app/assets/javascripts/work_items/graphql/typedefs.graphql b/app/assets/javascripts/work_items/graphql/typedefs.graphql
index d3712da1329..36779dfe11e 100644
--- a/app/assets/javascripts/work_items/graphql/typedefs.graphql
+++ b/app/assets/javascripts/work_items/graphql/typedefs.graphql
@@ -1,5 +1,6 @@
enum LocalWidgetType {
ASSIGNEES
+ MILESTONE
}
interface LocalWorkItemWidget {
@@ -11,6 +12,15 @@ type LocalWorkItemAssignees implements LocalWorkItemWidget {
nodes: [UserCore]
}
+type LocalWorkItemMilestone implements LocalWorkItemWidget {
+ type: LocalWidgetType!
+ nodes: [Milestone!]
+}
+
+extend type WorkItem {
+ mockWidgets: [LocalWorkItemWidget]
+}
+
input LocalUserInput {
id: ID!
name: String
@@ -19,9 +29,14 @@ input LocalUserInput {
avatarUrl: String
}
+input LocalMilestoneInput {
+ milestoneId: ID!
+}
+
input LocalUpdateWorkItemInput {
id: WorkItemID!
assignees: [LocalUserInput!]
+ milestone: LocalMilestoneInput!
}
type LocalWorkItemPayload {
diff --git a/app/assets/javascripts/work_items/graphql/work_item.query.graphql b/app/assets/javascripts/work_items/graphql/work_item.query.graphql
index 3b46fed97ec..fa0ab56df75 100644
--- a/app/assets/javascripts/work_items/graphql/work_item.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item.query.graphql
@@ -3,5 +3,16 @@
query workItem($id: WorkItemID!) {
workItem(id: $id) {
...WorkItem
+ mockWidgets @client {
+ ... on LocalWorkItemMilestone {
+ type
+ nodes {
+ id
+ title
+ expired
+ dueDate
+ }
+ }
+ }
}
}
diff --git a/app/assets/stylesheets/page_bundles/work_items.scss b/app/assets/stylesheets/page_bundles/work_items.scss
index 7a5cc72ceb8..820a1a0b53e 100644
--- a/app/assets/stylesheets/page_bundles/work_items.scss
+++ b/app/assets/stylesheets/page_bundles/work_items.scss
@@ -64,7 +64,7 @@
}
}
-.work-item-iteration {
+.work-item-dropdown {
.gl-dropdown-toggle {
background: none !important;
@@ -82,4 +82,3 @@
}
}
}
-
diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb
index ebd958822ed..fcf6871d137 100644
--- a/app/controllers/ide_controller.rb
+++ b/app/controllers/ide_controller.rb
@@ -11,7 +11,6 @@ class IdeController < ApplicationController
push_frontend_feature_flag(:build_service_proxy)
push_frontend_feature_flag(:schema_linting)
push_frontend_feature_flag(:reject_unsigned_commits_by_gitlab)
- push_frontend_feature_flag(:vscode_web_ide, current_user)
define_index_vars
end
@@ -27,7 +26,7 @@ class IdeController < ApplicationController
namespace: project&.namespace, user: current_user)
end
- render layout: 'fullscreen', locals: { minimal: Feature.enabled?(:vscode_web_ide, current_user) }
+ render layout: 'fullscreen', locals: { minimal: helpers.use_new_web_ide? }
end
private
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index c0360d10392..a57c87bf691 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -56,7 +56,8 @@ class Profiles::PreferencesController < Profiles::ApplicationController
:gitpod_enabled,
:render_whitespace_in_code,
:markdown_surround_selection,
- :markdown_automatic_lists
+ :markdown_automatic_lists,
+ :use_legacy_web_ide
]
end
end
diff --git a/app/graphql/types/environment_type.rb b/app/graphql/types/environment_type.rb
index 2484081a828..dd2286d333d 100644
--- a/app/graphql/types/environment_type.rb
+++ b/app/graphql/types/environment_type.rb
@@ -57,7 +57,7 @@ module Types
field :deployments,
Types::DeploymentType.connection_type,
null: true,
- description: 'Deployments of the environment. This field can only be resolved for one project in any single request.',
+ description: 'Deployments of the environment. This field can only be resolved for one environment in any single request.',
resolver: Resolvers::DeploymentsResolver do
extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
end
diff --git a/app/helpers/hooks_helper.rb b/app/helpers/hooks_helper.rb
index 1e50033e0e0..e050ccc0e40 100644
--- a/app/helpers/hooks_helper.rb
+++ b/app/helpers/hooks_helper.rb
@@ -1,6 +1,13 @@
# frozen_string_literal: true
module HooksHelper
+ def webhook_form_data(hook)
+ {
+ url: hook.url,
+ url_variables: nil
+ }
+ end
+
def link_to_test_hook(hook, trigger)
path = test_hook_path(hook, trigger)
trigger_human_name = trigger.to_s.tr('_', ' ').camelize
diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb
index ec1327cf7ae..5b3ca25b5af 100644
--- a/app/helpers/ide_helper.rb
+++ b/app/helpers/ide_helper.rb
@@ -3,6 +3,32 @@
module IdeHelper
def ide_data
{
+ 'can-use-new-web-ide' => can_use_new_web_ide?.to_s,
+ 'use-new-web-ide' => use_new_web_ide?.to_s,
+ 'user-preferences-path' => profile_preferences_path,
+ 'branch-name' => @branch
+ }.merge(use_new_web_ide? ? new_ide_data : legacy_ide_data)
+ end
+
+ def can_use_new_web_ide?
+ Feature.enabled?(:vscode_web_ide, current_user)
+ end
+
+ def use_new_web_ide?
+ can_use_new_web_ide? && !current_user.use_legacy_web_ide
+ end
+
+ private
+
+ def new_ide_data
+ {
+ 'project-path' => @project&.path_with_namespace,
+ 'csp-nonce' => content_security_policy_nonce
+ }
+ end
+
+ def legacy_ide_data
+ {
'empty-state-svg-path' => image_path('illustrations/multi_file_editor_empty.svg'),
'no-changes-state-svg-path' => image_path('illustrations/multi-editor_no_changes_empty.svg'),
'committed-state-svg-path' => image_path('illustrations/multi-editor_all_changes_committed_empty.svg'),
@@ -13,7 +39,6 @@ module IdeHelper
'clientside-preview-enabled': Gitlab::CurrentSettings.web_ide_clientside_preview_enabled?.to_s,
'render-whitespace-in-code': current_user.render_whitespace_in_code.to_s,
'codesandbox-bundler-url': Gitlab::CurrentSettings.web_ide_clientside_preview_bundler_url,
- 'branch-name' => @branch,
'default-branch' => @project && @project.default_branch,
'file-path' => @path,
'merge-request' => @merge_request,
@@ -24,13 +49,10 @@ module IdeHelper
'web-terminal-svg-path' => image_path('illustrations/web-ide_promotion.svg'),
'web-terminal-help-path' => help_page_path('user/project/web_ide/index.md', anchor: 'interactive-web-terminals-for-the-web-ide'),
'web-terminal-config-help-path' => help_page_path('user/project/web_ide/index.md', anchor: 'web-ide-configuration-file'),
- 'web-terminal-runners-help-path' => help_page_path('user/project/web_ide/index.md', anchor: 'runner-configuration'),
- 'csp-nonce' => content_security_policy_nonce
+ 'web-terminal-runners-help-path' => help_page_path('user/project/web_ide/index.md', anchor: 'runner-configuration')
}
end
- private
-
def convert_to_project_entity_json(project)
return unless project
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index f83aa79b461..361b1a8dca9 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -410,6 +410,13 @@ class ApplicationSetting < ApplicationRecord
allow_nil: false,
inclusion: { in: [true, false], message: N_('must be a boolean value') }
+ # rubocop:disable Cop/StaticTranslationDefinition
+ validates :deactivate_dormant_users_period,
+ presence: true,
+ numericality: { only_integer: true, greater_than_or_equal_to: 90, message: _("'%{value}' days of inactivity must be greater than or equal to 90") },
+ if: :deactivate_dormant_users?
+ # rubocop:enable Cop/StaticTranslationDefinition
+
Gitlab::SSHPublicKey.supported_types.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 4287c0b7884..950e0a583bc 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -1364,9 +1364,9 @@ module Ci
self.builds.latest.build_matchers(project)
end
- def authorized_cluster_agents
- strong_memoize(:authorized_cluster_agents) do
- ::Clusters::AgentAuthorizationsFinder.new(project).execute.map(&:agent)
+ def cluster_agent_authorizations
+ strong_memoize(:cluster_agent_authorizations) do
+ ::Clusters::AgentAuthorizationsFinder.new(project).execute
end
end
diff --git a/app/models/clusters/agents/implicit_authorization.rb b/app/models/clusters/agents/implicit_authorization.rb
index 9f7f653ed65..a365ccdc568 100644
--- a/app/models/clusters/agents/implicit_authorization.rb
+++ b/app/models/clusters/agents/implicit_authorization.rb
@@ -16,7 +16,7 @@ module Clusters
end
def config
- nil
+ {}
end
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index b36b00fcbaf..6d198fc755b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -354,6 +354,7 @@ class User < ApplicationRecord
:markdown_automatic_lists, :markdown_automatic_lists=,
:diffs_deletion_color, :diffs_deletion_color=,
:diffs_addition_color, :diffs_addition_color=,
+ :use_legacy_web_ide, :use_legacy_web_ide=,
to: :user_preference
delegate :path, to: :namespace, allow_nil: true, prefix: true
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
index b8f30413404..c6ebd550daf 100644
--- a/app/models/user_preference.rb
+++ b/app/models/user_preference.rb
@@ -22,6 +22,7 @@ class UserPreference < ApplicationRecord
validates :diffs_deletion_color, :diffs_addition_color,
format: { with: ColorsHelper::HEX_COLOR_PATTERN },
allow_blank: true
+ validates :use_legacy_web_ide, allow_nil: false, inclusion: { in: [true, false] }
ignore_columns :experience_level, remove_with: '14.10', remove_after: '2021-03-22'
diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb
index 71a05ef2c72..706608e3029 100644
--- a/app/presenters/ci/build_runner_presenter.rb
+++ b/app/presenters/ci/build_runner_presenter.rb
@@ -34,7 +34,9 @@ module Ci
def runner_variables
stop_expanding_file_vars = ::Feature.enabled?(:ci_stop_expanding_file_vars_for_runners, project)
- variables.sort_and_expand_all(keep_undefined: true, expand_file_vars: !stop_expanding_file_vars).to_runner_variables
+ variables
+ .sort_and_expand_all(keep_undefined: true, expand_file_vars: !stop_expanding_file_vars, project: project)
+ .to_runner_variables
end
def refspecs
diff --git a/app/services/bulk_imports/uploads_export_service.rb b/app/services/bulk_imports/uploads_export_service.rb
index 7f5ee7b8624..315590bea31 100644
--- a/app/services/bulk_imports/uploads_export_service.rb
+++ b/app/services/bulk_imports/uploads_export_service.rb
@@ -22,8 +22,9 @@ module BulkImports
subdir_path = export_subdir_path(upload)
mkdir_p(subdir_path)
download_or_copy_upload(uploader, File.join(subdir_path, uploader.filename))
- rescue Errno::ENAMETOOLONG => e
- # Do not fail entire export process if downloaded file has filename that exceeds 255 characters.
+ rescue StandardError => e
+ # Do not fail entire project export if something goes wrong during file download
+ # (e.g. downloaded file has filename that exceeds 255 characters).
# Ignore raised exception, skip such upload, log the error and keep going with the export instead.
Gitlab::ErrorTracking.log_exception(e, portable_id: portable.id, portable_class: portable.class.name, upload_id: upload.id)
end
diff --git a/app/services/ci/generate_kubeconfig_service.rb b/app/services/ci/generate_kubeconfig_service.rb
index 894ab8e8505..347bc99dbf5 100644
--- a/app/services/ci/generate_kubeconfig_service.rb
+++ b/app/services/ci/generate_kubeconfig_service.rb
@@ -14,7 +14,8 @@ module Ci
url: Gitlab::Kas.tunnel_url
)
- agents.each do |agent|
+ agent_authorizations.each do |authorization|
+ agent = authorization.agent
user = user_name(agent)
template.add_user(
@@ -24,6 +25,7 @@ module Ci
template.add_context(
name: context_name(agent),
+ namespace: context_namespace(authorization),
cluster: cluster_name,
user: user
)
@@ -36,8 +38,8 @@ module Ci
attr_reader :pipeline, :token, :template
- def agents
- pipeline.authorized_cluster_agents
+ def agent_authorizations
+ pipeline.cluster_agent_authorizations
end
def cluster_name
@@ -52,6 +54,10 @@ module Ci
[agent.project.full_path, agent.name].join(delimiter)
end
+ def context_namespace(authorization)
+ authorization.config['default_namespace']
+ end
+
def agent_token(agent)
['ci', agent.id, token].join(delimiter)
end
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
index b08a549148d..c091a2180c5 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -54,10 +54,10 @@
- dormant_users_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: dormant_users_help_link }
= f.gitlab_ui_checkbox_component :deactivate_dormant_users, _('Deactivate dormant users after a period of inactivity'), help_text: _('Users can reactivate their account by signing in. %{link_start}Learn more.%{link_end}').html_safe % { link_start: dormant_users_help_link_start, link_end: '</a>'.html_safe }
.form-group
- = f.label :deactivate_dormant_users_period, _('Period of inactivity (days)'), class: 'label-light'
- = f.number_field :deactivate_dormant_users_period, class: 'form-control gl-form-input', min: '1'
+ = f.label :deactivate_dormant_users_period, _('Days of inactivity before deactivation'), class: 'label-light'
+ = f.number_field :deactivate_dormant_users_period, class: 'form-control gl-form-input', min: '90', step: '1'
.form-text.text-muted
- = _('Period of inactivity before deactivation.')
+ = _('Must be 90 days or more.')
.form-group
= f.label :personal_access_token_prefix, _('Personal Access Token prefix'), class: 'label-light'
diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml
index 28836055e0e..9d4c0f62134 100644
--- a/app/views/import/fogbugz/new_user_map.html.haml
+++ b/app/views/import/fogbugz/new_user_map.html.haml
@@ -23,23 +23,21 @@
%p
= html_escape(_('Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. "By %{link_open}@johnsmith%{link_close}"). It will also associate and/or assign these issues and comments with the selected user.')) % { link_open: '<a href="#">'.html_safe, link_close: '</a>'.html_safe }
- .table-holder
- %table.table
- %thead
+ %table.table
+ %thead
+ %tr
+ %th= _("ID")
+ %th= _("Name")
+ %th= _("Email")
+ %th= _("GitLab User")
+ %tbody
+ - @user_map.each do |id, user|
%tr
- %th= _("ID")
- %th= _("Name")
- %th= _("Email")
- %th= _("GitLab User")
- %tbody
- - @user_map.each do |id, user|
- %tr
- %td= id
- %td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control'
- %td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control'
- %td
- = users_select_tag("users[#{id}][gitlab_user]", class: 'custom-form-control',
- scope: :all, email_user: true, selected: user[:gitlab_user])
+ %td= id
+ %td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control gl-form-input'
+ %td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control gl-form-input'
+ %td
+ .js-gitlab-user{ data: { name: "users[#{id}][gitlab_user]" } }
.form-actions
= submit_tag _('Continue to the next step'), class: 'gl-button btn btn-confirm'
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index 549436ccabf..c95e63bdc83 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -1,7 +1,7 @@
= form_errors(hook)
- if Feature.enabled?(:webhook_form_mask_url)
- .js-vue-webhook-form
+ .js-vue-webhook-form{ data: webhook_form_data(hook) }
- else
.form-group
= form.label :url, s_('Webhooks|URL'), class: 'label-bold'