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:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/api/user_api.js12
-rw-r--r--app/assets/javascripts/boards/components/config_toggle.vue4
-rw-r--r--app/assets/javascripts/boards/index.js1
-rw-r--r--app/assets/javascripts/boards/stores/getters.js14
-rw-r--r--app/assets/javascripts/graphql_shared/possible_types.json4
-rw-r--r--app/assets/javascripts/lib/utils/users_cache.js11
-rw-r--r--app/assets/javascripts/runner/components/registration/registration_dropdown.vue2
-rw-r--r--app/assets/javascripts/user_popovers.js15
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue89
-rw-r--r--app/controllers/repositories/lfs_storage_controller.rb14
-rw-r--r--app/helpers/application_settings_helper.rb26
-rw-r--r--app/helpers/boards_helper.rb1
-rw-r--r--app/views/admin/application_settings/_signin.html.haml4
13 files changed, 173 insertions, 24 deletions
diff --git a/app/assets/javascripts/api/user_api.js b/app/assets/javascripts/api/user_api.js
index 09995fad628..c743b18d572 100644
--- a/app/assets/javascripts/api/user_api.js
+++ b/app/assets/javascripts/api/user_api.js
@@ -10,6 +10,8 @@ const USER_PATH = '/api/:version/users/:id';
const USER_STATUS_PATH = '/api/:version/users/:id/status';
const USER_PROJECTS_PATH = '/api/:version/users/:id/projects';
const USER_POST_STATUS_PATH = '/api/:version/user/status';
+const USER_FOLLOW_PATH = '/api/:version/users/:id/follow';
+const USER_UNFOLLOW_PATH = '/api/:version/users/:id/unfollow';
export function getUsers(query, options) {
const url = buildApiUrl(USERS_PATH);
@@ -69,3 +71,13 @@ export function updateUserStatus({ emoji, message, availability, clearStatusAfte
clear_status_after: clearStatusAfter,
});
}
+
+export function followUser(userId) {
+ const url = buildApiUrl(USER_FOLLOW_PATH).replace(':id', encodeURIComponent(userId));
+ return axios.post(url);
+}
+
+export function unfollowUser(userId) {
+ const url = buildApiUrl(USER_UNFOLLOW_PATH).replace(':id', encodeURIComponent(userId));
+ return axios.post(url);
+}
diff --git a/app/assets/javascripts/boards/components/config_toggle.vue b/app/assets/javascripts/boards/components/config_toggle.vue
index 4746f598ab7..7002fd44294 100644
--- a/app/assets/javascripts/boards/components/config_toggle.vue
+++ b/app/assets/javascripts/boards/components/config_toggle.vue
@@ -1,5 +1,6 @@
<script>
import { GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
+import { mapGetters } from 'vuex';
import { formType } from '~/boards/constants';
import eventHub from '~/boards/eventhub';
import { s__, __ } from '~/locale';
@@ -14,8 +15,9 @@ export default {
GlModalDirective,
},
mixins: [Tracking.mixin()],
- inject: ['canAdminList', 'hasScope'],
+ inject: ['canAdminList'],
computed: {
+ ...mapGetters(['hasScope']),
buttonText() {
return this.canAdminList ? s__('Boards|Edit board') : s__('Boards|View scope');
},
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 77c5994b5a1..8af7da1e0aa 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -69,7 +69,6 @@ function mountBoardApp(el) {
timeTrackingLimitToHours: parseBoolean(el.dataset.timeTrackingLimitToHours),
issuableType: issuableTypes.issue,
emailsDisabled: parseBoolean(el.dataset.emailsDisabled),
- hasScope: parseBoolean(el.dataset.hasScope),
hasMissingBoards: parseBoolean(el.dataset.hasMissingBoards),
weights: el.dataset.weights ? JSON.parse(el.dataset.weights) : [],
// Permissions
diff --git a/app/assets/javascripts/boards/stores/getters.js b/app/assets/javascripts/boards/stores/getters.js
index cb31eb4b008..e1891a4d954 100644
--- a/app/assets/javascripts/boards/stores/getters.js
+++ b/app/assets/javascripts/boards/stores/getters.js
@@ -51,4 +51,18 @@ export default {
isEpicBoard: () => {
return false;
},
+
+ hasScope: (state) => {
+ const { boardConfig } = state;
+ if (boardConfig.labels?.length > 0) {
+ return true;
+ }
+ let hasScope = false;
+ ['assigneeId', 'iterationCadenceId', 'iterationId', 'milestoneId', 'weight'].forEach((attr) => {
+ if (boardConfig[attr] !== null && boardConfig[attr] !== undefined) {
+ hasScope = true;
+ }
+ });
+ return hasScope;
+ },
};
diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json
index 3d6360fc4f8..7ca3f20ec1c 100644
--- a/app/assets/javascripts/graphql_shared/possible_types.json
+++ b/app/assets/javascripts/graphql_shared/possible_types.json
@@ -76,6 +76,10 @@
"Discussion",
"Note"
],
+ "SecurityPolicySource": [
+ "GroupSecurityPolicySource",
+ "ProjectSecurityPolicySource"
+ ],
"Service": [
"BaseService",
"JiraService"
diff --git a/app/assets/javascripts/lib/utils/users_cache.js b/app/assets/javascripts/lib/utils/users_cache.js
index 54f69ef8e1b..bd000bb26fe 100644
--- a/app/assets/javascripts/lib/utils/users_cache.js
+++ b/app/assets/javascripts/lib/utils/users_cache.js
@@ -35,6 +35,17 @@ class UsersCache extends Cache {
// missing catch is intentional, error handling depends on use case
}
+ updateById(userId, data) {
+ if (!this.hasData(userId)) {
+ return;
+ }
+
+ this.internalStorage[userId] = {
+ ...this.internalStorage[userId],
+ ...data,
+ };
+ }
+
retrieveStatusById(userId) {
if (this.hasData(userId) && this.get(userId).status) {
return Promise.resolve(this.get(userId).status);
diff --git a/app/assets/javascripts/runner/components/registration/registration_dropdown.vue b/app/assets/javascripts/runner/components/registration/registration_dropdown.vue
index 3fbe3c1be74..bb2a8ddf151 100644
--- a/app/assets/javascripts/runner/components/registration/registration_dropdown.vue
+++ b/app/assets/javascripts/runner/components/registration/registration_dropdown.vue
@@ -96,7 +96,7 @@ export default {
<runner-instructions-modal
v-if="instructionsModalOpened"
ref="runnerInstructionsModal"
- :registration-token="registrationToken"
+ :registration-token="currentRegistrationToken"
data-testid="runner-instructions-modal"
/>
</gl-dropdown-item>
diff --git a/app/assets/javascripts/user_popovers.js b/app/assets/javascripts/user_popovers.js
index 4413be384e5..438ae2bc1bc 100644
--- a/app/assets/javascripts/user_popovers.js
+++ b/app/assets/javascripts/user_popovers.js
@@ -32,6 +32,7 @@ const populateUserInfo = (user) => {
([userData, status]) => {
if (userData) {
Object.assign(user, {
+ id: userId,
avatarUrl: userData.avatar_url,
bot: userData.bot,
username: userData.username,
@@ -42,6 +43,7 @@ const populateUserInfo = (user) => {
websiteUrl: userData.website_url,
pronouns: userData.pronouns,
localTime: userData.local_time,
+ isFollowed: userData.is_followed,
loaded: true,
});
}
@@ -97,6 +99,7 @@ export default function addPopovers(elements = document.querySelectorAll('.js-us
bio: null,
workInformation: null,
status: null,
+ isFollowed: false,
loaded: false,
};
const renderedPopover = new UserPopoverComponent({
@@ -107,6 +110,18 @@ export default function addPopovers(elements = document.querySelectorAll('.js-us
},
});
+ const { userId } = el.dataset;
+
+ renderedPopover.$on('follow', () => {
+ UsersCache.updateById(userId, { is_followed: true });
+ user.isFollowed = true;
+ });
+
+ renderedPopover.$on('unfollow', () => {
+ UsersCache.updateById(userId, { is_followed: false });
+ user.isFollowed = false;
+ });
+
initializedPopovers.set(el, renderedPopover);
renderedPopover.$mount();
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index 2c09fa71230..01a0b134b7f 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -6,9 +6,13 @@ import {
GlIcon,
GlSafeHtmlDirective,
GlSprintf,
+ GlButton,
} from '@gitlab/ui';
+import { __ } from '~/locale';
import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
import { glEmojiTag } from '~/emoji';
+import createFlash from '~/flash';
+import { followUser, unfollowUser } from '~/rest_api';
import UserAvatarImage from '../user_avatar/user_avatar_image.vue';
const MAX_SKELETON_LINES = 4;
@@ -24,6 +28,7 @@ export default {
UserAvatarImage,
UserNameWithStatus,
GlSprintf,
+ GlButton,
},
directives: {
SafeHtml: GlSafeHtmlDirective,
@@ -44,6 +49,11 @@ export default {
default: 'top',
},
},
+ data() {
+ return {
+ toggleFollowLoading: false,
+ };
+ },
computed: {
statusHtml() {
if (!this.user.status) {
@@ -64,6 +74,69 @@ export default {
availabilityStatus() {
return this.user?.status?.availability || '';
},
+ isNotCurrentUser() {
+ return !this.userIsLoading && this.user.username !== gon.current_username;
+ },
+ shouldRenderToggleFollowButton() {
+ return (
+ /*
+ * We're using `gon` to access feature flag because this component
+ * gets initialized dynamically multiple times from `user_popovers.js`
+ * for each user link present on the page, and using `glFeatureFlagMixin()`
+ * doesn't inject available feature flags into the component during init.
+ */
+ gon?.features?.followInUserPopover &&
+ this.isNotCurrentUser &&
+ typeof this.user?.isFollowed !== 'undefined'
+ );
+ },
+ toggleFollowButtonText() {
+ if (this.toggleFollowLoading) return null;
+
+ return this.user?.isFollowed ? __('Unfollow') : __('Follow');
+ },
+ toggleFollowButtonVariant() {
+ return this.user?.isFollowed ? 'default' : 'confirm';
+ },
+ },
+ methods: {
+ async toggleFollow() {
+ if (this.user.isFollowed) {
+ this.unfollow();
+ } else {
+ this.follow();
+ }
+ },
+ async follow() {
+ this.toggleFollowLoading = true;
+ try {
+ await followUser(this.user.id);
+ this.$emit('follow');
+ } catch (error) {
+ createFlash({
+ message: __('An error occurred while trying to follow this user, please try again.'),
+ error,
+ captureError: true,
+ });
+ } finally {
+ this.toggleFollowLoading = false;
+ }
+ },
+ async unfollow() {
+ this.toggleFollowLoading = true;
+ try {
+ await unfollowUser(this.user.id);
+ this.$emit('unfollow');
+ } catch (error) {
+ createFlash({
+ message: __('An error occurred while trying to unfollow this user, please try again.'),
+ error,
+ captureError: true,
+ });
+ } finally {
+ this.toggleFollowLoading = false;
+ }
+ },
},
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
};
@@ -73,10 +146,22 @@ export default {
<!-- 200ms delay so not every mouseover triggers Popover -->
<gl-popover :target="target" :delay="200" :placement="placement" boundary="viewport">
<div class="gl-p-3 gl-line-height-normal gl-display-flex" data-testid="user-popover">
- <div class="gl-p-2 flex-shrink-1">
+ <div
+ class="gl-p-2 flex-shrink-1 gl-display-flex gl-flex-direction-column align-items-center gl-w-70p"
+ >
<user-avatar-image :img-src="user.avatarUrl" :size="64" css-classes="gl-mr-3!" />
+ <div v-if="shouldRenderToggleFollowButton" class="gl-mt-3">
+ <gl-button
+ :variant="toggleFollowButtonVariant"
+ :loading="toggleFollowLoading"
+ size="small"
+ data-testid="toggle-follow-button"
+ @click="toggleFollow"
+ >{{ toggleFollowButtonText }}</gl-button
+ >
+ </div>
</div>
- <div class="gl-p-2 gl-w-full gl-min-w-0">
+ <div class="gl-w-full gl-min-w-0">
<template v-if="userIsLoading">
<gl-skeleton-loader
:lines="$options.maxSkeletonLines"
diff --git a/app/controllers/repositories/lfs_storage_controller.rb b/app/controllers/repositories/lfs_storage_controller.rb
index 252b604dcb0..d54b51b463a 100644
--- a/app/controllers/repositories/lfs_storage_controller.rb
+++ b/app/controllers/repositories/lfs_storage_controller.rb
@@ -6,6 +6,8 @@ module Repositories
include WorkhorseRequest
include SendFileUpload
+ InvalidUploadedFile = Class.new(StandardError)
+
skip_before_action :verify_workhorse_api!, only: :download
# added here as a part of the refactor, will be removed
@@ -44,6 +46,8 @@ module Repositories
end
def upload_finalize
+ validate_uploaded_file!
+
if store_file!(oid, size)
head 200, content_type: LfsRequest::CONTENT_TYPE
else
@@ -55,6 +59,8 @@ module Repositories
render_lfs_forbidden
rescue ObjectStorage::RemoteStoreError
render_lfs_forbidden
+ rescue InvalidUploadedFile
+ render plain: 'SHA256 or size mismatch', status: :bad_request
end
private
@@ -117,5 +123,13 @@ module Repositories
lfs_object: object
)
end
+
+ def validate_uploaded_file!
+ return unless uploaded_file
+
+ if size != uploaded_file.size || oid != uploaded_file.sha256
+ raise InvalidUploadedFile
+ end
+ end
end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 456a678ce77..dbdfa0c1eab 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -97,24 +97,18 @@ module ApplicationSettingsHelper
end
end
- def oauth_providers_checkboxes
+ def oauth_providers_checkboxes(form)
button_based_providers.map do |source|
- disabled = @application_setting.disabled_oauth_sign_in_sources.include?(source.to_s)
+ checked = !@application_setting.disabled_oauth_sign_in_sources.include?(source.to_s)
name = Gitlab::Auth::OAuth::Provider.label_for(source)
- checkbox_name = 'application_setting[enabled_oauth_sign_in_sources][]'
- checkbox_id = "application_setting_enabled_oauth_sign_in_sources_#{name.parameterize(separator: '_')}"
-
- content_tag :div, class: 'form-check' do
- check_box_tag(
- checkbox_name,
- source,
- !disabled,
- autocomplete: 'off',
- id: checkbox_id,
- class: 'form-check-input'
- ) +
- label_tag(checkbox_id, name, class: 'form-check-label')
- end
+
+ form.gitlab_ui_checkbox_component(
+ :enabled_oauth_sign_in_sources,
+ name,
+ checkbox_options: { checked: checked, multiple: true, autocomplete: 'off' },
+ checked_value: source,
+ unchecked_value: nil
+ )
end
end
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index f849f36bf84..f98e70e41d8 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -25,7 +25,6 @@ module BoardsHelper
labels_manage_path: labels_manage_path,
releases_fetch_path: releases_fetch_path,
board_type: board.to_type,
- has_scope: board.scoped?.to_s,
has_missing_boards: has_missing_boards?.to_s,
multiple_boards_available: multiple_boards_available?.to_s,
board_base_url: board_base_url
diff --git a/app/views/admin/application_settings/_signin.html.haml b/app/views/admin/application_settings/_signin.html.haml
index bce210d28d3..28e0ee25a5d 100644
--- a/app/views/admin/application_settings/_signin.html.haml
+++ b/app/views/admin/application_settings/_signin.html.haml
@@ -1,4 +1,4 @@
-= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-signin-settings'), html: { class: 'fieldset-form', id: 'signin-settings' } do |f|
+= gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-signin-settings'), html: { class: 'fieldset-form', id: 'signin-settings' } do |f|
= form_errors(@application_setting)
%fieldset
@@ -23,7 +23,7 @@
%fieldset.form-group
%legend.gl-font-base.gl-mb-3.gl-border-none.gl-font-weight-bold= _('Enabled OAuth authentication sources')
= hidden_field_tag 'application_setting[enabled_oauth_sign_in_sources][]'
- - oauth_providers_checkboxes.each do |source|
+ - oauth_providers_checkboxes(f).each do |source|
= source
.form-group
= f.label :two_factor_authentication, _('Two-factor authentication'), class: 'label-bold'