Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-05-23 12:08:01 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-23 12:08:01 +0300
commitc71c2ba4c29ed3cc483e528a32f34816c98c39f4 (patch)
tree56a8a8355631b9d58544bb74816082529ce0044d /app
parent5534414cd55a3e85d8f60e1a0883ed32f190df6b (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/access_tokens/components/access_token_table_app.vue204
-rw-r--r--app/assets/javascripts/access_tokens/components/new_access_token_app.vue125
-rw-r--r--app/assets/javascripts/access_tokens/index.js68
-rw-r--r--app/assets/javascripts/pages/profiles/personal_access_tokens/index.js10
-rw-r--r--app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue20
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss16
-rw-r--r--app/assets/stylesheets/page_bundles/build.scss7
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss9
-rw-r--r--app/controllers/profiles/personal_access_tokens_controller.rb33
-rw-r--r--app/controllers/projects/google_cloud/base_controller.rb1
-rw-r--r--app/graphql/resolvers/concerns/issue_resolver_arguments.rb4
-rw-r--r--app/graphql/resolvers/tree_resolver.rb2
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml8
-rw-r--r--app/views/projects/_merge_request_merge_method_settings.html.haml58
-rw-r--r--app/views/shared/access_tokens/_form.html.haml3
17 files changed, 491 insertions, 83 deletions
diff --git a/app/assets/javascripts/access_tokens/components/access_token_table_app.vue b/app/assets/javascripts/access_tokens/components/access_token_table_app.vue
new file mode 100644
index 00000000000..e936ad8aa14
--- /dev/null
+++ b/app/assets/javascripts/access_tokens/components/access_token_table_app.vue
@@ -0,0 +1,204 @@
+<script>
+import { GlButton, GlIcon, GlLink, GlTable, GlTooltipDirective } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { __, s__, sprintf } from '~/locale';
+import DomElementListener from '~/vue_shared/components/dom_element_listener.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import UserDate from '~/vue_shared/components/user_date.vue';
+
+const FORM_SELECTOR = '#js-new-access-token-form';
+const SUCCESS_EVENT = 'ajax:success';
+
+export default {
+ FORM_SELECTOR,
+ SUCCESS_EVENT,
+ name: 'AccessTokenTableApp',
+ components: {
+ DomElementListener,
+ GlButton,
+ GlIcon,
+ GlLink,
+ GlTable,
+ TimeAgoTooltip,
+ UserDate,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ lastUsedHelpLink: helpPagePath('/user/profile/personal_access_tokens.md', {
+ anchor: 'view-the-last-time-a-token-was-used',
+ }),
+ i18n: {
+ emptyField: __('Never'),
+ expired: __('Expired'),
+ header: __('Active %{accessTokenTypePlural} (%{totalAccessTokens})'),
+ modalMessage: __(
+ 'Are you sure you want to revoke this %{accessTokenType}? This action cannot be undone.',
+ ),
+ revokeButton: __('Revoke'),
+ tokenValidity: __('Token valid until revoked'),
+ },
+ fields: [
+ {
+ key: 'name',
+ label: __('Token name'),
+ sortable: true,
+ tdClass: `gl-text-black-normal`,
+ thClass: `gl-text-black-normal`,
+ },
+ {
+ formatter(scopes) {
+ return scopes?.length ? scopes.join(', ') : __('no scopes selected');
+ },
+ key: 'scopes',
+ label: __('Scopes'),
+ sortable: true,
+ tdClass: `gl-text-black-normal`,
+ thClass: `gl-text-black-normal`,
+ },
+ {
+ key: 'createdAt',
+ label: s__('AccessTokens|Created'),
+ sortable: true,
+ tdClass: `gl-text-black-normal`,
+ thClass: `gl-text-black-normal`,
+ },
+ {
+ key: 'lastUsedAt',
+ label: __('Last Used'),
+ sortable: true,
+ tdClass: `gl-text-black-normal`,
+ thClass: `gl-text-black-normal`,
+ },
+ {
+ key: 'expiresAt',
+ label: __('Expires'),
+ sortable: true,
+ tdClass: `gl-text-black-normal`,
+ thClass: `gl-text-black-normal`,
+ },
+ {
+ key: 'role',
+ label: __('Role'),
+ tdClass: `gl-text-black-normal`,
+ thClass: `gl-text-black-normal`,
+ sortable: true,
+ },
+ {
+ key: 'action',
+ label: __('Action'),
+ thClass: `gl-text-black-normal`,
+ },
+ ],
+ inject: [
+ 'accessTokenType',
+ 'accessTokenTypePlural',
+ 'initialActiveAccessTokens',
+ 'noActiveTokensMessage',
+ 'showRole',
+ ],
+ data() {
+ return {
+ activeAccessTokens: this.initialActiveAccessTokens,
+ };
+ },
+ computed: {
+ filteredFields() {
+ return this.showRole
+ ? this.$options.fields
+ : this.$options.fields.filter((field) => field.key !== 'role');
+ },
+ header() {
+ return sprintf(this.$options.i18n.header, {
+ accessTokenTypePlural: this.accessTokenTypePlural,
+ totalAccessTokens: this.activeAccessTokens.length,
+ });
+ },
+ modalMessage() {
+ return sprintf(this.$options.i18n.modalMessage, {
+ accessTokenType: this.accessTokenType,
+ });
+ },
+ },
+ methods: {
+ onSuccess(event) {
+ const [{ active_access_tokens: activeAccessTokens }] = event.detail;
+ this.activeAccessTokens = convertObjectPropsToCamelCase(activeAccessTokens, { deep: true });
+ },
+ sortingChanged(aRow, bRow, key) {
+ if (['createdAt', 'lastUsedAt', 'expiresAt'].includes(key)) {
+ // Transform `null` value to the latest possible date
+ // https://stackoverflow.com/a/11526569/18428169
+ const maxEpoch = 8640000000000000;
+ const a = new Date(aRow[key] ?? maxEpoch).getTime();
+ const b = new Date(bRow[key] ?? maxEpoch).getTime();
+ return a - b;
+ }
+
+ // For other columns the default sorting works OK
+ return false;
+ },
+ },
+};
+</script>
+
+<template>
+ <dom-element-listener :selector="$options.FORM_SELECTOR" @[$options.SUCCESS_EVENT]="onSuccess">
+ <div>
+ <hr />
+ <h5>{{ header }}</h5>
+
+ <gl-table
+ data-testid="active-tokens"
+ :empty-text="noActiveTokensMessage"
+ :fields="filteredFields"
+ :items="activeAccessTokens"
+ :sort-compare="sortingChanged"
+ show-empty
+ >
+ <template #cell(createdAt)="{ item: { createdAt } }">
+ <user-date :date="createdAt" />
+ </template>
+
+ <template #head(lastUsedAt)="{ label }">
+ <span>{{ label }}</span>
+ <gl-link :href="$options.lastUsedHelpLink"
+ ><gl-icon name="question-o" /><span class="gl-sr-only">{{
+ s__('AccessTokens|The last time a token was used')
+ }}</span></gl-link
+ >
+ </template>
+
+ <template #cell(lastUsedAt)="{ item: { lastUsedAt } }">
+ <time-ago-tooltip v-if="lastUsedAt" :time="lastUsedAt" />
+ <template v-else> {{ $options.i18n.emptyField }}</template>
+ </template>
+
+ <template #cell(expiresAt)="{ item: { expiresAt, expired, expiresSoon } }">
+ <template v-if="expiresAt">
+ <span v-if="expired" class="text-danger">{{ $options.i18n.expired }}</span>
+ <time-ago-tooltip v-else :class="{ 'text-warning': expiresSoon }" :time="expiresAt" />
+ </template>
+ <span v-else v-gl-tooltip :title="$options.i18n.tokenValidity">{{
+ $options.i18n.emptyField
+ }}</span>
+ </template>
+
+ <template #cell(action)="{ item: { revokePath, expiresAt } }">
+ <gl-button
+ variant="danger"
+ :category="expiresAt ? 'primary' : 'secondary'"
+ :aria-label="$options.i18n.revokeButton"
+ :data-confirm="modalMessage"
+ data-confirm-btn-variant="danger"
+ data-qa-selector="revoke_button"
+ data-method="put"
+ :href="revokePath"
+ icon="remove"
+ />
+ </template>
+ </gl-table>
+ </div>
+ </dom-element-listener>
+</template>
diff --git a/app/assets/javascripts/access_tokens/components/new_access_token_app.vue b/app/assets/javascripts/access_tokens/components/new_access_token_app.vue
new file mode 100644
index 00000000000..69a4fedabae
--- /dev/null
+++ b/app/assets/javascripts/access_tokens/components/new_access_token_app.vue
@@ -0,0 +1,125 @@
+<script>
+import { GlAlert } from '@gitlab/ui';
+import { createAlert, VARIANT_INFO } from '~/flash';
+import { __, n__, sprintf } from '~/locale';
+import DomElementListener from '~/vue_shared/components/dom_element_listener.vue';
+import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue';
+
+const ERROR_EVENT = 'ajax:error';
+const FORM_SELECTOR = '#js-new-access-token-form';
+const SUCCESS_EVENT = 'ajax:success';
+
+export default {
+ ERROR_EVENT,
+ FORM_SELECTOR,
+ SUCCESS_EVENT,
+ name: 'NewAccessTokenApp',
+ components: { DomElementListener, GlAlert, InputCopyToggleVisibility },
+ i18n: {
+ alertInfoMessage: __('Your new %{accessTokenType} has been created.'),
+ copyButtonTitle: __('Copy %{accessTokenType}'),
+ description: __("Make sure you save it - you won't be able to access it again."),
+ label: __('Your new %{accessTokenType}'),
+ },
+ inject: ['accessTokenType'],
+ data() {
+ return { errors: null, infoAlert: null, newToken: null };
+ },
+ computed: {
+ alertInfoMessage() {
+ return sprintf(this.$options.i18n.alertInfoMessage, {
+ accessTokenType: this.accessTokenType,
+ });
+ },
+ alertDangerTitle() {
+ return n__(
+ 'The form contains the following error:',
+ 'The form contains the following errors:',
+ this.errors?.length ?? 0,
+ );
+ },
+ copyButtonTitle() {
+ return sprintf(this.$options.i18n.copyButtonTitle, { accessTokenType: this.accessTokenType });
+ },
+ label() {
+ return sprintf(this.$options.i18n.label, { accessTokenType: this.accessTokenType });
+ },
+ },
+ mounted() {
+ /** @type {HTMLFormElement} */
+ this.form = document.querySelector(this.$options.FORM_SELECTOR);
+
+ /** @type {HTMLInputElement} */
+ this.submitButton = this.form.querySelector('input[type=submit]');
+ },
+ methods: {
+ beforeDisplayResults() {
+ this.infoAlert?.dismiss();
+ this.$refs.container.scrollIntoView(false);
+
+ this.errors = null;
+ this.newToken = null;
+ },
+ onError(event) {
+ this.beforeDisplayResults();
+
+ const [{ errors }] = event.detail;
+ this.errors = errors;
+
+ this.submitButton.classList.remove('disabled');
+ },
+ onSuccess(event) {
+ this.beforeDisplayResults();
+
+ const [{ new_token: newToken }] = event.detail;
+ this.newToken = newToken;
+
+ this.infoAlert = createAlert({ message: this.alertInfoMessage, variant: VARIANT_INFO });
+
+ this.form.reset();
+ },
+ },
+};
+</script>
+
+<template>
+ <dom-element-listener
+ :selector="$options.FORM_SELECTOR"
+ @[$options.ERROR_EVENT]="onError"
+ @[$options.SUCCESS_EVENT]="onSuccess"
+ >
+ <div ref="container">
+ <template v-if="newToken">
+ <!--
+ After issue https://gitlab.com/gitlab-org/gitlab/-/issues/360921 is
+ closed remove the `initial-visibility` and `input-class` props.
+ -->
+ <input-copy-toggle-visibility
+ data-testid="new-access-token"
+ :copy-button-title="copyButtonTitle"
+ :label="label"
+ :value="newToken"
+ initial-visibility
+ input-class="qa-created-access-token"
+ qa-selector="created_access_token_field"
+ >
+ <template #description>
+ {{ $options.i18n.description }}
+ </template>
+ </input-copy-toggle-visibility>
+ <hr />
+ </template>
+
+ <template v-if="errors">
+ <gl-alert :title="alertDangerTitle" variant="danger" @dismiss="errors = null">
+ <ul class="m-0">
+ <li v-for="error in errors" :key="error">
+ {{ error }}
+ </li>
+ </ul>
+ </gl-alert>
+ <hr />
+ </template>
+ </div>
+ </dom-element-listener>
+</template>
diff --git a/app/assets/javascripts/access_tokens/index.js b/app/assets/javascripts/access_tokens/index.js
index fb5c5521ce9..a7a03523e7f 100644
--- a/app/assets/javascripts/access_tokens/index.js
+++ b/app/assets/javascripts/access_tokens/index.js
@@ -3,12 +3,57 @@ import Vue from 'vue';
import createFlash from '~/flash';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { parseRailsFormFields } from '~/lib/utils/forms';
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
+import AccessTokenTableApp from './components/access_token_table_app.vue';
import ExpiresAtField from './components/expires_at_field.vue';
+import NewAccessTokenApp from './components/new_access_token_app.vue';
import TokensApp from './components/tokens_app.vue';
import { FEED_TOKEN, INCOMING_EMAIL_TOKEN, STATIC_OBJECT_TOKEN } from './constants';
+export const initAccessTokenTableApp = () => {
+ const el = document.querySelector('#js-access-token-table-app');
+
+ if (!el) {
+ return null;
+ }
+
+ const {
+ accessTokenType,
+ accessTokenTypePlural,
+ initialActiveAccessTokens: initialActiveAccessTokensJson,
+ noActiveTokensMessage: noTokensMessage,
+ } = el.dataset;
+
+ // Default values
+ const noActiveTokensMessage =
+ noTokensMessage ||
+ sprintf(__('This user has no active %{accessTokenTypePlural}.'), { accessTokenTypePlural });
+ const showRole = 'showRole' in el.dataset;
+
+ const initialActiveAccessTokens = convertObjectPropsToCamelCase(
+ JSON.parse(initialActiveAccessTokensJson),
+ {
+ deep: true,
+ },
+ );
+
+ return new Vue({
+ el,
+ name: 'AccessTokenTableRoot',
+ provide: {
+ accessTokenType,
+ accessTokenTypePlural,
+ initialActiveAccessTokens,
+ noActiveTokensMessage,
+ showRole,
+ },
+ render(h) {
+ return h(AccessTokenTableApp);
+ },
+ });
+};
+
export const initExpiresAtField = () => {
const el = document.querySelector('.js-access-tokens-expires-at');
@@ -33,6 +78,27 @@ export const initExpiresAtField = () => {
});
};
+export const initNewAccessTokenApp = () => {
+ const el = document.querySelector('#js-new-access-token-app');
+
+ if (!el) {
+ return null;
+ }
+
+ const { accessTokenType } = el.dataset;
+
+ return new Vue({
+ el,
+ name: 'NewAccessTokenRoot',
+ provide: {
+ accessTokenType,
+ },
+ render(h) {
+ return h(NewAccessTokenApp);
+ },
+ });
+};
+
export const initProjectsField = () => {
const el = document.querySelector('.js-access-tokens-projects');
diff --git a/app/assets/javascripts/pages/profiles/personal_access_tokens/index.js b/app/assets/javascripts/pages/profiles/personal_access_tokens/index.js
index 37e9b7e99d4..3fae9809e51 100644
--- a/app/assets/javascripts/pages/profiles/personal_access_tokens/index.js
+++ b/app/assets/javascripts/pages/profiles/personal_access_tokens/index.js
@@ -1,5 +1,13 @@
-import { initExpiresAtField, initProjectsField, initTokensApp } from '~/access_tokens';
+import {
+ initAccessTokenTableApp,
+ initExpiresAtField,
+ initNewAccessTokenApp,
+ initProjectsField,
+ initTokensApp,
+} from '~/access_tokens';
+initAccessTokenTableApp();
initExpiresAtField();
+initNewAccessTokenApp();
initProjectsField();
initTokensApp();
diff --git a/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue b/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue
index 69548f0e7a8..11fcc697602 100644
--- a/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue
+++ b/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue
@@ -52,6 +52,20 @@ export default {
return {};
},
},
+ /*
+ `inputClass` prop should be removed after https://gitlab.com/gitlab-org/gitlab/-/issues/357848
+ is implemented.
+ */
+ inputClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ qaSelector: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
},
data() {
return {
@@ -73,6 +87,9 @@ export default {
displayedValue() {
return this.computedValueIsVisible ? this.value : '*'.repeat(this.value.length || 20);
},
+ classInput() {
+ return `gl-font-monospace! gl-cursor-default! ${this.inputClass}`.trimEnd();
+ },
},
methods: {
handleToggleVisibilityButtonClick() {
@@ -98,7 +115,8 @@ export default {
<gl-form-group v-bind="$attrs">
<gl-form-input-group
:value="displayedValue"
- input-class="gl-font-monospace! gl-cursor-default!"
+ :input-class="classInput"
+ :data-qa-selector="qaSelector"
select-on-click
readonly
v-bind="formInputGroupProps"
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index dd9581c4692..217c6a7567c 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -24,10 +24,6 @@
&:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper {
padding-right: $gutter-collapsed-width;
}
-
- .merge-request-tabs-holder.affix {
- right: $gutter-collapsed-width;
- }
}
}
@@ -81,14 +77,6 @@
.content-wrapper {
padding-right: $gutter-width;
}
-
- &:not(.with-overlay) .merge-request-tabs-holder.affix {
- right: $gutter-width;
- }
-
- &.with-overlay .merge-request-tabs-holder.affix {
- right: $gutter-collapsed-width;
- }
}
}
@@ -110,10 +98,6 @@
}
}
-.with-performance-bar .right-sidebar.affix {
- top: calc(#{$header-height} + #{$performance-bar-height});
-}
-
@mixin maintain-sidebar-dimensions {
display: block;
width: $gutter-width;
diff --git a/app/assets/stylesheets/page_bundles/build.scss b/app/assets/stylesheets/page_bundles/build.scss
index d55d6b27576..9213754d419 100644
--- a/app/assets/stylesheets/page_bundles/build.scss
+++ b/app/assets/stylesheets/page_bundles/build.scss
@@ -44,13 +44,6 @@
}
}
- &.affix-top {
- position: absolute;
- right: 0;
- left: 0;
- top: 0;
- }
-
.controllers {
@include build-controllers(15px, center, false, 0, inline, 0);
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 5062a7bb8fa..451b4022d41 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -263,10 +263,6 @@
}
}
- &.affix-top .issuable-sidebar {
- height: 100%;
- }
-
&.right-sidebar-expanded {
&:not(.right-sidebar-merge-requests) {
width: $gutter-width;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 543ae1df1af..fbfb1398889 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -247,12 +247,6 @@ $tabs-holder-z-index: 250;
}
}
-.merge-request-tabs-holder.affix .merge-request-tabs-container,
-.epic-tabs-holder.affix .epic-tabs-container {
- padding-left: $gl-padding;
- padding-right: $gl-padding;
-}
-
.with-performance-bar {
.merge-request-tabs-holder,
.epic-tabs-holder {
@@ -331,8 +325,7 @@ $tabs-holder-z-index: 250;
}
.limit-container-width:not(.container-limited) {
- .merge-request-tabs-holder:not(.affix) .merge-request-tabs-container,
- .epic-tabs-holder:not(.affix) .epic-tabs-container {
+ .merge-request-tabs-holder .merge-request-tabs-container {
max-width: $limited-layout-width - ($gl-padding * 2);
}
}
diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb
index ad2e384077a..5c5d4008d00 100644
--- a/app/controllers/profiles/personal_access_tokens_controller.rb
+++ b/app/controllers/profiles/personal_access_tokens_controller.rb
@@ -23,12 +23,21 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
@personal_access_token = result.payload[:personal_access_token]
- if result.success?
- PersonalAccessToken.redis_store!(current_user.id, @personal_access_token.token)
- redirect_to profile_personal_access_tokens_path, notice: _("Your new personal access token has been created.")
+ if Feature.enabled?(:access_token_ajax)
+ if result.success?
+ render json: { new_token: @personal_access_token.token,
+ active_access_tokens: active_personal_access_tokens }, status: :ok
+ else
+ render json: { errors: result.errors }, status: :unprocessable_entity
+ end
else
- set_index_vars
- render :index
+ if result.success?
+ PersonalAccessToken.redis_store!(current_user.id, @personal_access_token.token)
+ redirect_to profile_personal_access_tokens_path, notice: _("Your new personal access token has been created.")
+ else
+ set_index_vars
+ render :index
+ end
end
end
@@ -52,14 +61,20 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
def set_index_vars
@scopes = Gitlab::Auth.available_scopes_for(current_user)
-
- @inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = active_personal_access_tokens
- @new_personal_access_token = PersonalAccessToken.redis_getdel(current_user.id)
+ if Feature.disabled?(:access_token_ajax)
+ @new_personal_access_token = PersonalAccessToken.redis_getdel(current_user.id)
+ end
end
def active_personal_access_tokens
- finder(state: 'active', sort: 'expires_at_asc').execute
+ tokens = finder(state: 'active', sort: 'expires_at_asc').execute
+
+ if Feature.enabled?(:access_token_ajax)
+ ::API::Entities::PersonalAccessTokenWithDetails.represent(tokens)
+ else
+ tokens
+ end
end
end
diff --git a/app/controllers/projects/google_cloud/base_controller.rb b/app/controllers/projects/google_cloud/base_controller.rb
index 0d65431d870..980e9bdcdad 100644
--- a/app/controllers/projects/google_cloud/base_controller.rb
+++ b/app/controllers/projects/google_cloud/base_controller.rb
@@ -2,6 +2,7 @@
class Projects::GoogleCloud::BaseController < Projects::ApplicationController
feature_category :five_minute_production_app
+ urgency :low
before_action :admin_project_google_cloud!
before_action :google_oauth2_enabled!
diff --git a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
index de44dbb26d7..ea737f54a95 100644
--- a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
+++ b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
@@ -66,7 +66,6 @@ module IssueResolverArguments
description: 'Filter for confidential issues. If "false", excludes confidential issues. If "true", returns only confidential issues.'
argument :not, Types::Issues::NegatedIssueFilterInputType,
description: 'Negated arguments.',
- prepare: ->(negated_args, ctx) { negated_args.to_h },
required: false
argument :crm_contact_id, GraphQL::Types::String,
required: false,
@@ -85,6 +84,7 @@ module IssueResolverArguments
# Will need to be made group & namespace aware with
# https://gitlab.com/gitlab-org/gitlab-foss/issues/54520
+ args[:not] = args[:not].to_h if args[:not].present?
args[:iids] ||= [args.delete(:iid)].compact if args[:iid]
args[:attempt_project_search_optimizations] = true if args[:search].present?
@@ -98,6 +98,8 @@ module IssueResolverArguments
end
def ready?(**args)
+ args[:not] = args[:not].to_h if args[:not].present?
+
params_not_mutually_exclusive(args, mutually_exclusive_assignee_username_args)
params_not_mutually_exclusive(args, mutually_exclusive_milestone_args)
params_not_mutually_exclusive(args.fetch(:not, {}), mutually_exclusive_milestone_args)
diff --git a/app/graphql/resolvers/tree_resolver.rb b/app/graphql/resolvers/tree_resolver.rb
index f02eb226810..553f9aa6cd9 100644
--- a/app/graphql/resolvers/tree_resolver.rb
+++ b/app/graphql/resolvers/tree_resolver.rb
@@ -16,7 +16,6 @@ module Resolvers
description: 'Used to get a recursive tree. Default is false.'
argument :ref, GraphQL::Types::String,
required: false,
- default_value: :head,
description: 'Commit ref to get the tree for. Default value is HEAD.'
alias_method :repository, :object
@@ -24,6 +23,7 @@ module Resolvers
def resolve(**args)
return unless repository.exists?
+ args[:ref] ||= :head
repository.tree(args[:ref], args[:path], recursive: args[:recursive])
end
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index e62b6fa5fc5..bed0eab5a58 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
- VERSION = '0.39.0'
+ VERSION = '0.41.0'
self.table_name = 'clusters_applications_runners'
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 887d07f7a20..5be862257b4 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -1,3 +1,4 @@
+- ajax = Feature.enabled?(:access_token_ajax)
- breadcrumb_title s_('AccessTokens|Access Tokens')
- page_title s_('AccessTokens|Personal Access Tokens')
- type = _('personal access token')
@@ -15,19 +16,24 @@
= s_('AccessTokens|They are the only accepted password when you have Two-Factor Authentication (2FA) enabled.')
.col-lg-8
+ #js-new-access-token-app{ data: { access_token_type: type } }
- if @new_personal_access_token
= render 'shared/access_tokens/created_container',
type: type,
new_token_value: @new_personal_access_token
= render 'shared/access_tokens/form',
+ ajax: ajax,
type: type,
path: profile_personal_access_tokens_path,
token: @personal_access_token,
scopes: @scopes,
help_path: help_page_path('user/profile/personal_access_tokens.md', anchor: 'personal-access-token-scopes')
- = render 'shared/access_tokens/table',
+ - if ajax
+ #js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_personal_access_tokens.to_json } }
+ - else
+ = render 'shared/access_tokens/table',
type: type,
type_plural: type_plural,
active_tokens: @active_personal_access_tokens,
diff --git a/app/views/projects/_merge_request_merge_method_settings.html.haml b/app/views/projects/_merge_request_merge_method_settings.html.haml
index cb660750632..f205fe2b9bf 100644
--- a/app/views/projects/_merge_request_merge_method_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_method_settings.html.haml
@@ -1,38 +1,34 @@
- form = local_assigns.fetch(:form)
+- labelMerge = s_('ProjectSettings|Merge commit')
+- everyMergeCommit = s_('ProjectSettings|Every merge creates a merge commit.')
+
+- labelRebase = s_('ProjectSettings|Merge commit with semi-linear history')
+- rebaseUpToDate = s_('ProjectSettings|Merging is only allowed when the source branch is up-to-date with its target.')
+- rebaseSemiLinear = s_('ProjectSettings|When semi-linear merge is not possible, the user is given the option to rebase.')
+
+- labelFastForward = s_('ProjectSettings|Fast-forward merge')
+- noMergeCommit = s_('ProjectSettings|No merge commits are created.')
+- ffOnly = s_('ProjectSettings|Fast-forward merges only.')
+- ffConflictRebase = s_('ProjectSettings|When there is a merge conflict, the user is given the option to rebase.')
+- ffTrains = s_('ProjectSettings|If merge trains are enabled, merging is only possible if the branch can be rebased without conflicts.')
+- ffTrainsHelp = link_to s_('ProjectSettings|What are merge trains?'), help_page_path('ci/pipelines/merge_trains.md', anchor: 'enable-merge-trains'), target: '_blank', rel: 'noopener noreferrer'
+
.form-group
%b= s_('ProjectSettings|Merge method')
%p.text-secondary
= s_('ProjectSettings|Determine what happens to the commit history when you merge a merge request.')
= link_to s_('ProjectSettings|How do they differ?'), help_page_path('user/project/merge_requests/methods/index.md'), target: '_blank', rel: 'noopener noreferrer'
- .form-check.mb-2
- = form.radio_button :merge_method, :merge, class: "js-merge-method-radio form-check-input"
- = label_tag :project_merge_method_merge, class: 'form-check-label' do
- = s_('ProjectSettings|Merge commit')
- .text-secondary
- = s_('ProjectSettings|Every merge creates a merge commit.')
-
- .form-check.mb-2
- = form.radio_button :merge_method, :rebase_merge, class: "js-merge-method-radio form-check-input"
- = label_tag :project_merge_method_rebase_merge, class: 'form-check-label' do
- = s_('ProjectSettings|Merge commit with semi-linear history')
- .text-secondary
- = s_('ProjectSettings|Every merge creates a merge commit.')
- %br
- = s_('ProjectSettings|Merging is only allowed when the source branch is up-to-date with its target.')
- %br
- = s_('ProjectSettings|When semi-linear merge is not possible, the user is given the option to rebase.')
-
- .form-check.mb-2
- = form.radio_button :merge_method, :ff, class: "js-merge-method-radio form-check-input", data: { qa_selector: 'merge_ff_radio' }
- = label_tag :project_merge_method_ff, class: 'form-check-label' do
- = s_('ProjectSettings|Fast-forward merge')
- .text-secondary
- = s_('ProjectSettings|No merge commits are created.')
- %br
- = s_('ProjectSettings|Fast-forward merges only.')
- %br
- = s_('ProjectSettings|When there is a merge conflict, the user is given the option to rebase.')
- %div
- = s_('ProjectSettings|If merge trains are enabled, merging is only possible if the branch can be rebased without conflicts.')
- = link_to s_('ProjectSettings|What are merge trains?'), help_page_path('ci/pipelines/merge_trains.md', anchor: 'enable-merge-trains'), target: '_blank', rel: 'noopener noreferrer'
+ = form.gitlab_ui_radio_component :merge_method,
+ :merge,
+ labelMerge,
+ help_text: everyMergeCommit
+ = form.gitlab_ui_radio_component :merge_method,
+ :rebase_merge,
+ labelRebase,
+ help_text: (everyMergeCommit + "<br />" + rebaseUpToDate + "<br />" + rebaseSemiLinear).html_safe
+ = form.gitlab_ui_radio_component :merge_method,
+ :ff,
+ labelFastForward,
+ help_text: (noMergeCommit + "<br />" + ffOnly + "<br />" + ffConflictRebase + "<br />" + ffTrains + " " + ffTrainsHelp).html_safe,
+ radio_options: { data: { qa_selector: 'merge_ff_radio' } }
diff --git a/app/views/shared/access_tokens/_form.html.haml b/app/views/shared/access_tokens/_form.html.haml
index d4106ba4e5d..665b7794b79 100644
--- a/app/views/shared/access_tokens/_form.html.haml
+++ b/app/views/shared/access_tokens/_form.html.haml
@@ -1,3 +1,4 @@
+- ajax = local_assigns.fetch(:ajax, false)
- title = local_assigns.fetch(:title, _('Add a %{type}') % { type: type })
- prefix = local_assigns.fetch(:prefix, :personal_access_token)
- help_path = local_assigns.fetch(:help_path)
@@ -10,7 +11,7 @@
%p.profile-settings-content
= _("Enter the name of your application, and we'll return a unique %{type}.") % { type: type }
-= gitlab_ui_form_for token, as: prefix, url: path, method: :post, html: { class: 'js-requires-input' } do |f|
+= gitlab_ui_form_for token, as: prefix, url: path, method: :post, html: { id: 'js-new-access-token-form', class: 'js-requires-input' }, remote: ajax do |f|
= form_errors(token)