Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
7fa274753d
commit
c33a9adb70
|
@ -1 +1 @@
|
|||
7ad4fcea44bc4c1a8682d77ad6b46aeacb8c6e1d
|
||||
4f0cd9404f31511f5051e49b363adc06aa3ec365
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -18,7 +18,7 @@ gem 'default_value_for', '~> 3.4.0'
|
|||
gem 'pg', '~> 1.1'
|
||||
|
||||
gem 'rugged', '~> 1.1'
|
||||
gem 'grape-path-helpers', '~> 1.6.1'
|
||||
gem 'grape-path-helpers', '~> 1.6.3'
|
||||
|
||||
gem 'faraday', '~> 1.0'
|
||||
gem 'marginalia', '~> 1.10.0'
|
||||
|
|
|
@ -541,7 +541,7 @@ GEM
|
|||
grape-entity (0.7.1)
|
||||
activesupport (>= 4.0)
|
||||
multi_json (>= 1.3.2)
|
||||
grape-path-helpers (1.6.1)
|
||||
grape-path-helpers (1.6.3)
|
||||
activesupport
|
||||
grape (~> 1.3)
|
||||
rake (> 12)
|
||||
|
@ -1471,7 +1471,7 @@ DEPENDENCIES
|
|||
gpgme (~> 2.0.19)
|
||||
grape (~> 1.5.2)
|
||||
grape-entity (~> 0.7.1)
|
||||
grape-path-helpers (~> 1.6.1)
|
||||
grape-path-helpers (~> 1.6.3)
|
||||
grape_logging (~> 1.7)
|
||||
graphiql-rails (~> 1.4.10)
|
||||
graphlient (~> 0.4.0)
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<script>
|
||||
import { GlTable } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import { GlSkeletonLoader, GlTable } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { convertNodeIdsFromGraphQLIds } from '~/graphql_shared/utils';
|
||||
import { s__, __ } from '~/locale';
|
||||
import UserDate from '~/vue_shared/components/user_date.vue';
|
||||
import getUsersGroupCountsQuery from '../graphql/queries/get_users_group_counts.query.graphql';
|
||||
import UserActions from './user_actions.vue';
|
||||
import UserAvatar from './user_avatar.vue';
|
||||
|
||||
|
@ -11,6 +14,7 @@ const thWidthClass = (width) => `gl-w-${width}p ${DEFAULT_TH_CLASSES}`;
|
|||
|
||||
export default {
|
||||
components: {
|
||||
GlSkeletonLoader,
|
||||
GlTable,
|
||||
UserAvatar,
|
||||
UserActions,
|
||||
|
@ -26,6 +30,45 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
groupCounts: [],
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
groupCounts: {
|
||||
query: getUsersGroupCountsQuery,
|
||||
variables() {
|
||||
return {
|
||||
usernames: this.users.map((user) => user.username),
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
const nodes = data?.users?.nodes || [];
|
||||
const parsedIds = convertNodeIdsFromGraphQLIds(nodes);
|
||||
|
||||
return parsedIds.reduce((acc, { id, groupCount }) => {
|
||||
acc[id] = groupCount || 0;
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
error(error) {
|
||||
createFlash({
|
||||
message: this.$options.i18n.groupCountFetchError,
|
||||
captureError: true,
|
||||
error,
|
||||
});
|
||||
},
|
||||
skip() {
|
||||
return !this.users.length;
|
||||
},
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
groupCountFetchError: s__(
|
||||
'AdminUsers|Could not load user group counts. Please refresh the page to try again.',
|
||||
),
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
key: 'name',
|
||||
|
@ -37,6 +80,11 @@ export default {
|
|||
label: __('Projects'),
|
||||
thClass: thWidthClass(10),
|
||||
},
|
||||
{
|
||||
key: 'groupCount',
|
||||
label: __('Groups'),
|
||||
thClass: thWidthClass(10),
|
||||
},
|
||||
{
|
||||
key: 'createdAt',
|
||||
label: __('Created on'),
|
||||
|
@ -50,7 +98,7 @@ export default {
|
|||
{
|
||||
key: 'settings',
|
||||
label: '',
|
||||
thClass: thWidthClass(20),
|
||||
thClass: thWidthClass(10),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -77,6 +125,13 @@ export default {
|
|||
<user-date :date="lastActivityOn" show-never />
|
||||
</template>
|
||||
|
||||
<template #cell(groupCount)="{ item: { id } }">
|
||||
<div :data-testid="`user-group-count-${id}`">
|
||||
<gl-skeleton-loader v-if="$apollo.loading" :width="40" :lines="1" />
|
||||
<span v-else>{{ groupCounts[id] }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell(projectsCount)="{ item: { id, projectsCount } }">
|
||||
<div :data-testid="`user-project-count-${id}`">{{ projectsCount }}</div>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
query getUsersGroupCounts($usernames: [String!]) {
|
||||
users(usernames: $usernames) {
|
||||
nodes {
|
||||
id
|
||||
groupCount
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,15 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import AdminUsersApp from './components/app.vue';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
|
||||
});
|
||||
|
||||
export const initAdminUsersApp = (el = document.querySelector('#js-admin-users-app')) => {
|
||||
if (!el) {
|
||||
return false;
|
||||
|
@ -11,6 +19,7 @@ export const initAdminUsersApp = (el = document.querySelector('#js-admin-users-a
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
apolloProvider,
|
||||
render: (createElement) =>
|
||||
createElement(AdminUsersApp, {
|
||||
props: {
|
||||
|
|
|
@ -64,8 +64,7 @@ module Types
|
|||
description: 'Group memberships of the user.'
|
||||
field :group_count,
|
||||
resolver: Resolvers::Users::GroupCountResolver,
|
||||
description: 'Group count for the user.',
|
||||
feature_flag: :user_group_counts
|
||||
description: 'Group count for the user.'
|
||||
field :status,
|
||||
type: Types::UserStatusType,
|
||||
null: true,
|
||||
|
|
|
@ -5,6 +5,10 @@ module WebpackHelper
|
|||
javascript_include_tag(*webpack_entrypoint_paths(bundle))
|
||||
end
|
||||
|
||||
def webpack_preload_asset_tag(asset, options = {})
|
||||
preload_link_tag(Gitlab::Webpack::Manifest.asset_paths(asset).first, options)
|
||||
end
|
||||
|
||||
def webpack_controller_bundle_tags
|
||||
chunks = []
|
||||
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
- if page_canonical_link
|
||||
%link{ rel: 'canonical', href: page_canonical_link }
|
||||
|
||||
= webpack_preload_asset_tag("monaco")
|
||||
|
||||
= favicon_link_tag favicon, id: 'favicon', data: { original_href: favicon }, type: 'image/png'
|
||||
|
||||
= render 'layouts/startup_css'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Change conan token expiration from 1 hour to 24 hours
|
||||
merge_request: 60763
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show total group counts in admin users table
|
||||
merge_request: 60998
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Enable Content-Security-Policy header by default
|
||||
merge_request: 56923
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update grape-path-helpers to v1.6.3
|
||||
merge_request: 61196
|
||||
author:
|
||||
type: performance
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/329664
|
|||
milestone: '13.12'
|
||||
type: development
|
||||
group: group::gitaly
|
||||
default_enabled: true
|
||||
default_enabled: false
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: user_group_counts
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44069/
|
||||
rollout_issue_url:
|
||||
milestone: '13.6'
|
||||
type: development
|
||||
group: group::compliance
|
||||
default_enabled: false
|
|
@ -6,7 +6,7 @@ product_stage: manage
|
|||
product_group: group::optimize
|
||||
product_category:
|
||||
value_type: number
|
||||
status: data_available
|
||||
status: removed
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
distribution:
|
||||
|
|
|
@ -6,8 +6,9 @@ product_stage: manage
|
|||
product_group: group::optimize
|
||||
product_category:
|
||||
value_type: number
|
||||
status: data_available
|
||||
time_frame: all
|
||||
status: removed
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_source:
|
||||
distribution:
|
||||
- ce
|
||||
|
|
|
@ -9844,7 +9844,7 @@ A user assigned to a merge request.
|
|||
| <a id="mergerequestassigneebot"></a>`bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. |
|
||||
| <a id="mergerequestassigneecallouts"></a>`callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) |
|
||||
| <a id="mergerequestassigneeemail"></a>`email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). |
|
||||
| <a id="mergerequestassigneegroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. Available only when feature flag `user_group_counts` is enabled. |
|
||||
| <a id="mergerequestassigneegroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
|
||||
| <a id="mergerequestassigneegroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
|
||||
| <a id="mergerequestassigneeid"></a>`id` | [`ID!`](#id) | ID of the user. |
|
||||
| <a id="mergerequestassigneelocation"></a>`location` | [`String`](#string) | The location of the user. |
|
||||
|
@ -10050,7 +10050,7 @@ A user assigned to a merge request as a reviewer.
|
|||
| <a id="mergerequestreviewerbot"></a>`bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. |
|
||||
| <a id="mergerequestreviewercallouts"></a>`callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) |
|
||||
| <a id="mergerequestrevieweremail"></a>`email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). |
|
||||
| <a id="mergerequestreviewergroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. Available only when feature flag `user_group_counts` is enabled. |
|
||||
| <a id="mergerequestreviewergroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
|
||||
| <a id="mergerequestreviewergroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
|
||||
| <a id="mergerequestreviewerid"></a>`id` | [`ID!`](#id) | ID of the user. |
|
||||
| <a id="mergerequestreviewerlocation"></a>`location` | [`String`](#string) | The location of the user. |
|
||||
|
@ -12679,7 +12679,7 @@ Core represention of a GitLab user.
|
|||
| <a id="usercorebot"></a>`bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. |
|
||||
| <a id="usercorecallouts"></a>`callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) |
|
||||
| <a id="usercoreemail"></a>`email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). |
|
||||
| <a id="usercoregroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. Available only when feature flag `user_group_counts` is enabled. |
|
||||
| <a id="usercoregroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
|
||||
| <a id="usercoregroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
|
||||
| <a id="usercoreid"></a>`id` | [`ID!`](#id) | ID of the user. |
|
||||
| <a id="usercorelocation"></a>`location` | [`String`](#string) | The location of the user. |
|
||||
|
@ -15346,7 +15346,7 @@ Implementations:
|
|||
| <a id="userbot"></a>`bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. |
|
||||
| <a id="usercallouts"></a>`callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) |
|
||||
| <a id="useremail"></a>`email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). |
|
||||
| <a id="usergroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. Available only when feature flag `user_group_counts` is enabled. |
|
||||
| <a id="usergroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
|
||||
| <a id="usergroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
|
||||
| <a id="userid"></a>`id` | [`ID!`](#id) | ID of the user. |
|
||||
| <a id="userlocation"></a>`location` | [`String`](#string) | The location of the user. |
|
||||
|
|
|
@ -114,7 +114,7 @@ Unique visitors to /groups/:group/-/analytics/merge_request_analytics
|
|||
|
||||
Group: `group::optimize`
|
||||
|
||||
Status: `data_available`
|
||||
Status: `removed`
|
||||
|
||||
Tiers: `free`
|
||||
|
||||
|
@ -7410,7 +7410,7 @@ Missing description
|
|||
|
||||
Group: `group::optimize`
|
||||
|
||||
Status: `data_available`
|
||||
Status: `removed`
|
||||
|
||||
Tiers: `free`
|
||||
|
||||
|
@ -7422,7 +7422,7 @@ Missing description
|
|||
|
||||
Group: `group::optimize`
|
||||
|
||||
Status: `data_available`
|
||||
Status: `removed`
|
||||
|
||||
Tiers:
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
module Gitlab
|
||||
class ConanToken
|
||||
HMAC_KEY = 'gitlab-conan-packages'
|
||||
CONAN_TOKEN_EXPIRE_TIME = 1.day.freeze
|
||||
|
||||
attr_reader :access_token_id, :user_id
|
||||
|
||||
|
@ -57,7 +58,7 @@ module Gitlab
|
|||
JSONWebToken::HMACToken.new(self.class.secret).tap do |token|
|
||||
token['access_token'] = access_token_id
|
||||
token['user_id'] = user_id
|
||||
token.expire_time = token.issued_at + 1.hour
|
||||
token.expire_time = token.issued_at + CONAN_TOKEN_EXPIRE_TIME
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,11 +8,33 @@ module Gitlab
|
|||
media_src object_src report_uri script_src style_src worker_src).freeze
|
||||
|
||||
def self.default_settings_hash
|
||||
{
|
||||
'enabled' => false,
|
||||
settings_hash = {
|
||||
'enabled' => true,
|
||||
'report_only' => false,
|
||||
'directives' => DIRECTIVES.each_with_object({}) { |directive, hash| hash[directive] = nil }
|
||||
'directives' => {
|
||||
'default_src' => "'self'",
|
||||
'base_uri' => "'self'",
|
||||
'child_src' => "'none'",
|
||||
'connect_src' => "'self'",
|
||||
'font_src' => "'self'",
|
||||
'form_action' => "'self' https: http:",
|
||||
'frame_ancestors' => "'self'",
|
||||
'frame_src' => "'self' https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com",
|
||||
'img_src' => "'self' data: blob: http: https:",
|
||||
'manifest_src' => "'self'",
|
||||
'media_src' => "'self'",
|
||||
'script_src' => "'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.recaptcha.net https://apis.google.com",
|
||||
'style_src' => "'self' 'unsafe-inline'",
|
||||
'worker_src' => "'self'",
|
||||
'object_src' => "'none'",
|
||||
'report_uri' => nil
|
||||
}
|
||||
}
|
||||
|
||||
allow_webpack_dev_server(settings_hash) if Rails.env.development?
|
||||
allow_cdn(settings_hash) if ENV['GITLAB_CDN_HOST'].present?
|
||||
|
||||
settings_hash
|
||||
end
|
||||
|
||||
def initialize(csp_directives)
|
||||
|
@ -38,6 +60,26 @@ module Gitlab
|
|||
|
||||
arguments.strip.split(' ').map(&:strip)
|
||||
end
|
||||
|
||||
def self.allow_webpack_dev_server(settings_hash)
|
||||
secure = Settings.webpack.dev_server['https']
|
||||
host_and_port = "#{Settings.webpack.dev_server['host']}:#{Settings.webpack.dev_server['port']}"
|
||||
http_url = "#{secure ? 'https' : 'http'}://#{host_and_port}"
|
||||
ws_url = "#{secure ? 'wss' : 'ws'}://#{host_and_port}"
|
||||
|
||||
append_to_directive(settings_hash, 'connect_src', "#{http_url} #{ws_url}")
|
||||
end
|
||||
|
||||
def self.allow_cdn(settings_hash)
|
||||
cdn_host = ENV['GITLAB_CDN_HOST']
|
||||
|
||||
append_to_directive(settings_hash, 'script_src', cdn_host)
|
||||
append_to_directive(settings_hash, 'style_src', cdn_host)
|
||||
end
|
||||
|
||||
def self.append_to_directive(settings_hash, directive, text)
|
||||
settings_hash['directives'][directive] = "#{settings_hash['directives'][directive]} #{text}".strip
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2528,6 +2528,9 @@ msgstr ""
|
|||
msgid "AdminUsers|Cohorts"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Could not load user group counts. Please refresh the page to try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Deactivate"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -547,6 +547,32 @@ RSpec.describe 'Admin::Users' do
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: Move to main GET /admin/users block once feature flag is removed. Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/290737
|
||||
context 'with vue_admin_users feature flag enabled', :js do
|
||||
before do
|
||||
stub_feature_flags(vue_admin_users: true)
|
||||
end
|
||||
|
||||
describe 'GET /admin/users' do
|
||||
context 'user group count', :js do
|
||||
before do
|
||||
group = create(:group)
|
||||
group.add_developer(current_user)
|
||||
project = create(:project, group: create(:group))
|
||||
project.add_reporter(current_user)
|
||||
end
|
||||
|
||||
it 'displays count of the users authorized groups' do
|
||||
visit admin_users_path
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page.find("[data-testid='user-group-count-#{current_user.id}']").text).to eq("2")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def click_user_dropdown_toggle(user_id)
|
||||
page.within("[data-testid='user-actions-#{user_id}']") do
|
||||
find("[data-testid='dropdown-toggle']").click
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'New project from template', :js do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
|
||||
visit new_project_path
|
||||
end
|
||||
|
||||
context 'create from template' do
|
||||
before do
|
||||
page.find('a[href="#create_from_template"]').click
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
it 'shows template tabs' do
|
||||
page.within('#create-from-template-pane') do
|
||||
expect(page).to have_link('Built-in', href: '#built-in')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,16 +1,36 @@
|
|||
import { GlTable } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { GlTable, GlSkeletonLoader } from '@gitlab/ui';
|
||||
import { createLocalVue } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
|
||||
import AdminUserActions from '~/admin/users/components/user_actions.vue';
|
||||
import AdminUserAvatar from '~/admin/users/components/user_avatar.vue';
|
||||
import AdminUsersTable from '~/admin/users/components/users_table.vue';
|
||||
import getUsersGroupCountsQuery from '~/admin/users/graphql/queries/get_users_group_counts.query.graphql';
|
||||
import createFlash from '~/flash';
|
||||
import AdminUserDate from '~/vue_shared/components/user_date.vue';
|
||||
|
||||
import { users, paths } from '../mock_data';
|
||||
import { users, paths, createGroupCountResponse } from '../mock_data';
|
||||
|
||||
jest.mock('~/flash');
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueApollo);
|
||||
|
||||
describe('AdminUsersTable component', () => {
|
||||
let wrapper;
|
||||
const user = users[0];
|
||||
|
||||
const createFetchGroupCount = (data) =>
|
||||
jest.fn().mockResolvedValue(createGroupCountResponse(data));
|
||||
const fetchGroupCountsLoading = jest.fn().mockResolvedValue(new Promise(() => {}));
|
||||
const fetchGroupCountsError = jest.fn().mockRejectedValue(new Error('Network error'));
|
||||
const fetchGroupCountsResponse = createFetchGroupCount([{ id: user.id, groupCount: 5 }]);
|
||||
|
||||
const findUserGroupCount = (id) => wrapper.findByTestId(`user-group-count-${id}`);
|
||||
const findUserGroupCountLoader = (id) => findUserGroupCount(id).find(GlSkeletonLoader);
|
||||
const getCellByLabel = (trIdx, label) => {
|
||||
return wrapper
|
||||
.find(GlTable)
|
||||
|
@ -20,8 +40,16 @@ describe('AdminUsersTable component', () => {
|
|||
.find(`[data-label="${label}"][role="cell"]`);
|
||||
};
|
||||
|
||||
const initComponent = (props = {}) => {
|
||||
wrapper = mount(AdminUsersTable, {
|
||||
function createMockApolloProvider(resolverMock) {
|
||||
const requestHandlers = [[getUsersGroupCountsQuery, resolverMock]];
|
||||
|
||||
return createMockApollo(requestHandlers);
|
||||
}
|
||||
|
||||
const initComponent = (props = {}, resolverMock = fetchGroupCountsResponse) => {
|
||||
wrapper = mountExtended(AdminUsersTable, {
|
||||
localVue,
|
||||
apolloProvider: createMockApolloProvider(resolverMock),
|
||||
propsData: {
|
||||
users,
|
||||
paths,
|
||||
|
@ -36,8 +64,6 @@ describe('AdminUsersTable component', () => {
|
|||
});
|
||||
|
||||
describe('when there are users', () => {
|
||||
const user = users[0];
|
||||
|
||||
beforeEach(() => {
|
||||
initComponent();
|
||||
});
|
||||
|
@ -69,4 +95,51 @@ describe('AdminUsersTable component', () => {
|
|||
expect(wrapper.text()).toContain('No users found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('group counts', () => {
|
||||
describe('when fetching the data', () => {
|
||||
beforeEach(() => {
|
||||
initComponent({}, fetchGroupCountsLoading);
|
||||
});
|
||||
|
||||
it('renders a loader for each user', () => {
|
||||
expect(findUserGroupCountLoader(user.id).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the data has been fetched', () => {
|
||||
beforeEach(() => {
|
||||
initComponent();
|
||||
});
|
||||
|
||||
it("renders the user's group count", () => {
|
||||
expect(findUserGroupCount(user.id).text()).toBe('5');
|
||||
});
|
||||
|
||||
describe("and a user's group count is null", () => {
|
||||
beforeEach(() => {
|
||||
initComponent({}, createFetchGroupCount([{ id: user.id, groupCount: null }]));
|
||||
});
|
||||
|
||||
it("renders the user's group count as 0", () => {
|
||||
expect(findUserGroupCount(user.id).text()).toBe('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there is an error while fetching the data', () => {
|
||||
beforeEach(() => {
|
||||
initComponent({}, fetchGroupCountsError);
|
||||
});
|
||||
|
||||
it('creates a flash message and captures the error', () => {
|
||||
expect(createFlash).toHaveBeenCalledTimes(1);
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
message: 'Could not load user group counts. Please refresh the page to try again.',
|
||||
captureError: true,
|
||||
error: expect.any(Error),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ export const users = [
|
|||
'https://secure.gravatar.com/avatar/054f062d8b1a42b123f17e13a173cda8?s=80\\u0026d=identicon',
|
||||
badges: [
|
||||
{ text: 'Admin', variant: 'success' },
|
||||
{ text: "It's you!", variant: null },
|
||||
{ text: "It's you!", variant: 'muted' },
|
||||
],
|
||||
projectsCount: 0,
|
||||
actions: [],
|
||||
|
@ -31,3 +31,16 @@ export const paths = {
|
|||
deleteWithContributions: '/admin/users/id',
|
||||
adminUser: '/admin/users/id',
|
||||
};
|
||||
|
||||
export const createGroupCountResponse = (groupCounts) => ({
|
||||
data: {
|
||||
users: {
|
||||
nodes: groupCounts.map(({ id, groupCount }) => ({
|
||||
id: `gid://gitlab/User/${id}`,
|
||||
groupCount,
|
||||
__typename: 'UserCore',
|
||||
})),
|
||||
__typename: 'UserCoreConnection',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ RSpec.describe Gitlab::ConanToken do
|
|||
JSONWebToken::HMACToken.new(jwt_secret).tap do |jwt|
|
||||
jwt['access_token'] = access_token_id
|
||||
jwt['user_id'] = user_id || user_id
|
||||
jwt.expire_time = expire_time || jwt.issued_at + 1.hour
|
||||
jwt.expire_time = expire_time || jwt.issued_at + ::Gitlab::ConanToken::CONAN_TOKEN_EXPIRE_TIME
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -75,7 +75,7 @@ RSpec.describe Gitlab::ConanToken do
|
|||
it 'returns nil for expired JWT' do
|
||||
jwt = build_jwt(access_token_id: 123,
|
||||
user_id: 456,
|
||||
expire_time: Time.zone.now - 2.hours)
|
||||
expire_time: Time.zone.now - (::Gitlab::ConanToken::CONAN_TOKEN_EXPIRE_TIME + 1.hour))
|
||||
|
||||
expect(described_class.decode(jwt.encoded)).to be_nil
|
||||
end
|
||||
|
|
|
@ -20,15 +20,34 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
|||
end
|
||||
|
||||
describe '.default_settings_hash' do
|
||||
it 'returns empty defaults' do
|
||||
it 'returns defaults for all keys' do
|
||||
settings = described_class.default_settings_hash
|
||||
|
||||
expect(settings['enabled']).to be_falsey
|
||||
expect(settings['enabled']).to be_truthy
|
||||
expect(settings['report_only']).to be_falsey
|
||||
|
||||
described_class::DIRECTIVES.each do |directive|
|
||||
expect(settings['directives'].has_key?(directive)).to be_truthy
|
||||
expect(settings['directives'][directive]).to be_nil
|
||||
directives = settings['directives']
|
||||
directive_names = (described_class::DIRECTIVES - ['report_uri'])
|
||||
directive_names.each do |directive|
|
||||
expect(directives.has_key?(directive)).to be_truthy
|
||||
expect(directives[directive]).to be_truthy
|
||||
end
|
||||
|
||||
expect(directives.has_key?('report_uri')).to be_truthy
|
||||
expect(directives['report_uri']).to be_nil
|
||||
end
|
||||
|
||||
context 'when GITLAB_CDN_HOST is set' do
|
||||
before do
|
||||
stub_env('GITLAB_CDN_HOST', 'https://example.com')
|
||||
end
|
||||
|
||||
it 'adds GITLAB_CDN_HOST to CSP' do
|
||||
settings = described_class.default_settings_hash
|
||||
directives = settings['directives']
|
||||
|
||||
expect(directives['script_src']).to eq("'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.recaptcha.net https://apis.google.com https://example.com")
|
||||
expect(directives['style_src']).to eq("'self' 'unsafe-inline' https://example.com")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -106,7 +106,7 @@ RSpec.shared_examples 'conan authenticate endpoint' do
|
|||
expect(payload['user_id']).to eq(personal_access_token.user_id)
|
||||
|
||||
duration = payload['exp'] - payload['iat']
|
||||
expect(duration).to eq(1.hour)
|
||||
expect(duration).to eq(::Gitlab::ConanToken::CONAN_TOKEN_EXPIRE_TIME)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -62,6 +62,12 @@ RSpec.describe 'layouts/_head' do
|
|||
expect(rendered).to match('<link rel="stylesheet" media="print" href="/stylesheets/highlight/themes/solarised-light.css" />')
|
||||
end
|
||||
|
||||
it 'preloads Monaco' do
|
||||
render
|
||||
|
||||
expect(rendered).to match('<link rel="preload" href="/assets/webpack/monaco.chunk.js" as="script" type="text/javascript">')
|
||||
end
|
||||
|
||||
context 'when an asset_host is set and snowplow url is set' do
|
||||
let(:asset_host) { 'http://test.host' }
|
||||
let(:snowplow_collector_hostname) { 'www.snow.plow' }
|
||||
|
|
Loading…
Reference in New Issue