Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-11-25 18:14:13 +00:00
parent d03aeb1110
commit 1c635e68ea
33 changed files with 622 additions and 68 deletions

View File

@ -0,0 +1,127 @@
<script>
import { GlIcon, GlLink } from '@gitlab/ui';
import { __ } from '~/locale';
import createFlash from '~/flash';
import { helpPagePath } from '~/helpers/help_page_helper';
import codeOwnersInfoQuery from '../queries/code_owners_info.query.graphql';
import getRefMixin from '../mixins/get_ref';
export default {
i18n: {
title: __('Code owners'),
about: __('About this feature'),
andSeparator: __('and'),
errorMessage: __('An error occurred while loading code owners.'),
},
codeOwnersHelpPath: helpPagePath('user/project/code_owners'),
components: {
GlIcon,
GlLink,
},
mixins: [getRefMixin],
apollo: {
project: {
query: codeOwnersInfoQuery,
variables() {
return {
projectPath: this.projectPath,
filePath: this.filePath,
ref: this.ref,
};
},
skip() {
return !this.filePath;
},
result() {
this.isFetching = false;
},
error() {
createFlash({ message: this.$options.i18n.errorMessage });
},
},
},
props: {
projectPath: {
type: String,
required: true,
},
filePath: {
type: String,
required: false,
default: null,
},
},
data() {
return {
isFetching: false,
project: {
repository: {
blobs: {
nodes: [
{
codeOwners: [],
},
],
},
},
},
};
},
computed: {
blobInfo() {
return this.project?.repository?.blobs?.nodes[0];
},
codeOwners() {
return this.blobInfo?.codeOwners || [];
},
hasCodeOwners() {
return this.filePath && Boolean(this.codeOwners.length);
},
commaSeparateList() {
return this.codeOwners.length > 2;
},
showAndSeparator() {
return this.codeOwners.length > 1;
},
lastListItem() {
return this.codeOwners.length - 1;
},
},
watch: {
filePath() {
this.isFetching = true;
this.$apollo.queries.project.refetch();
},
},
};
</script>
<template>
<div
v-if="hasCodeOwners && !isFetching"
class="well-segment blob-auxiliary-viewer file-owner-content qa-file-owner-content"
>
<gl-icon name="users" data-testid="users-icon" />
<strong>{{ $options.i18n.title }}</strong>
<gl-link :href="$options.codeOwnersHelpPath" target="_blank" :title="$options.i18n.about">
<gl-icon name="question-o" data-testid="help-icon" />
</gl-link>
:
<div
v-for="(owner, index) in codeOwners"
:key="index"
:class="[
{ 'gl-display-inline-block': commaSeparateList, 'gl-display-inline': !commaSeparateList },
]"
data-testid="code-owners"
>
<span v-if="commaSeparateList && index > 0" data-testid="comma-separator">,</span>
<span v-if="showAndSeparator && index === lastListItem" data-testid="and-separator">{{
$options.i18n.andSeparator
}}</span>
<gl-link :href="owner.webPath" target="_blank" :title="$options.i18n.about">
{{ owner.name }}
</gl-link>
</div>
</div>
</template>

View File

@ -111,7 +111,7 @@ export default {
</script>
<template>
<div class="info-well d-none d-sm-flex project-last-commit commit p-3">
<div class="well-segment commit gl-p-5 gl-w-full">
<gl-loading-icon v-if="isLoading" size="md" color="dark" class="m-auto" />
<template v-else-if="commit">
<user-avatar-link

View File

@ -9,6 +9,7 @@ import App from './components/app.vue';
import Breadcrumbs from './components/breadcrumbs.vue';
import DirectoryDownloadLinks from './components/directory_download_links.vue';
import LastCommit from './components/last_commit.vue';
import CodeOwners from './components/code_owners.vue';
import apolloProvider from './graphql';
import commitsQuery from './queries/commits.query.graphql';
import projectPathQuery from './queries/project_path.query.graphql';
@ -71,7 +72,23 @@ export default function setupVueRepositoryList() {
},
});
const initCodeOwnersApp = () =>
new Vue({
el: document.getElementById('js-code-owners'),
router,
apolloProvider,
render(h) {
return h(CodeOwners, {
props: {
filePath: this.$route.params.path,
projectPath,
},
});
},
});
initLastCommitApp();
initCodeOwnersApp();
router.afterEach(({ params: { path } }) => {
setTitle(path, ref, fullName);

View File

@ -0,0 +1,15 @@
query getCodeOwnersInfo($projectPath: ID!, $filePath: String!, $ref: String!) {
project(fullPath: $projectPath) {
id
repository {
blobs(paths: [$filePath], ref: $ref) {
nodes {
codeOwners {
name
webPath
}
}
}
}
}
}

View File

@ -1,5 +1,6 @@
<script>
import { GlTable, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
@ -32,6 +33,7 @@ export default {
components: {
GlTable,
GlSkeletonLoader,
TooltipOnTruncate,
TimeAgo,
RunnerActionsCell,
RunnerSummaryCell,
@ -101,11 +103,15 @@ export default {
</template>
<template #cell(version)="{ item: { version } }">
{{ version }}
<tooltip-on-truncate class="gl-display-block gl-text-truncate" :title="version">
{{ version }}
</tooltip-on-truncate>
</template>
<template #cell(ipAddress)="{ item: { ipAddress } }">
{{ ipAddress }}
<tooltip-on-truncate class="gl-display-block gl-text-truncate" :title="ipAddress">
{{ ipAddress }}
</tooltip-on-truncate>
</template>
<template #cell(tagList)="{ item: { tagList } }">

View File

@ -104,16 +104,7 @@ export default {
},
},
mounted() {
this.fetchCollapsedData(this.$props)
.then((data) => {
this.collapsedData = data;
this.loadingState = null;
})
.catch((e) => {
this.loadingState = LOADING_STATES.collapsedError;
Sentry.captureException(e);
});
this.loadCollapsedData();
},
methods: {
triggerRedisTracking: once(function triggerRedisTracking() {
@ -126,6 +117,20 @@ export default {
this.triggerRedisTracking();
},
loadCollapsedData() {
this.loadingState = LOADING_STATES.collapsedLoading;
this.fetchCollapsedData(this.$props)
.then((data) => {
this.collapsedData = data;
this.loadingState = null;
})
.catch((e) => {
this.loadingState = LOADING_STATES.collapsedError;
Sentry.captureException(e);
});
},
loadAllData() {
if (this.hasFullData) return;

View File

@ -71,6 +71,10 @@ module Types
field :pipeline_editor_path, GraphQL::Types::String, null: true,
description: 'Web path to edit .gitlab-ci.yml file.'
field :code_owners, [Types::UserType], null: true,
description: 'List of code owners for the blob.',
calls_gitaly: true
field :file_type, GraphQL::Types::String, null: true,
description: 'Expected format of the blob based on the extension.'

View File

@ -20,13 +20,13 @@ module Packages
belongs_to :component, class_name: "Packages::Debian::#{container_type.capitalize}Component", inverse_of: :files
belongs_to :architecture, class_name: "Packages::Debian::#{container_type.capitalize}Architecture", inverse_of: :files, optional: true
enum file_type: { packages: 1, source: 2, di_packages: 3 }
enum file_type: { packages: 1, sources: 2, di_packages: 3 }
enum compression_type: { gz: 1, bz2: 2, xz: 3 }
validates :component, presence: true
validates :file_type, presence: true
validates :architecture, presence: true, unless: :source?
validates :architecture, absence: true, if: :source?
validates :architecture, presence: true, unless: :sources?
validates :architecture, absence: true, if: :sources?
validates :file, length: { minimum: 0, allow_nil: false }
validates :size, presence: true
validates :file_store, presence: true
@ -81,7 +81,7 @@ module Packages
case file_type
when 'packages'
"#{component.name}/binary-#{architecture.name}/#{file_name}#{extension}"
when 'source'
when 'sources'
"#{component.name}/source/#{file_name}#{extension}"
when 'di_packages'
"#{component.name}/debian-installer/binary-#{architecture.name}/#{file_name}#{extension}"

View File

@ -66,6 +66,10 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
project_ci_pipeline_editor_path(project, branch_name: blob.commit_id) if can_collaborate_with_project?(project) && blob.path == project.ci_config_path_or_default
end
def code_owners
Gitlab::CodeOwners.for_blob(project, blob)
end
def fork_and_edit_path
fork_path_for_current_user(project, edit_blob_path)
end

View File

@ -91,7 +91,7 @@ module Packages
generate_component_file(component, :packages, architecture, :deb)
generate_component_file(component, :di_packages, architecture, :udeb)
end
generate_component_file(component, :source, nil, :dsc)
generate_component_file(component, :sources, nil, :dsc)
end
end

View File

@ -10,10 +10,11 @@
.nav-block.gl-display-flex.gl-xs-flex-direction-column.gl-align-items-stretch
= render 'projects/tree/tree_header', tree: @tree
#js-last-commit
.info-well.gl-display-none.gl-sm-display-flex.project-last-commit
.info-well.gl-display-none.gl-sm-display-flex.project-last-commit.gl-flex-direction-column
#js-last-commit.gl-m-auto
.gl-spinner-container.m-auto
= loading_icon(size: 'md', color: 'dark', css_class: 'align-text-bottom')
#js-code-owners
- if is_project_overview
.project-buttons.gl-mb-3.js-show-on-project-root

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
class ChangeIndexUsersOnPublicEmail < Gitlab::Database::Migration[1.0]
INDEX_NAME = 'index_users_on_public_email'
INDEX_EXCLUDING_NULL_NAME = 'index_users_on_public_email_excluding_null_and_empty'
disable_ddl_transaction!
def up
index_condition = "public_email != '' AND public_email IS NOT NULL"
add_concurrent_index :users, [:public_email], where: index_condition, name: INDEX_EXCLUDING_NULL_NAME
remove_concurrent_index_by_name :users, INDEX_NAME
end
def down
index_condition = "public_email != ''"
add_concurrent_index :users, [:public_email], where: index_condition, name: INDEX_NAME
remove_concurrent_index_by_name :users, INDEX_EXCLUDING_NULL_NAME
end
end

View File

@ -0,0 +1 @@
4eacad00017890c71f3354d80061fae7af40499256475cdf035bdf41b916e5f3

View File

@ -27534,7 +27534,7 @@ CREATE INDEX index_users_on_name ON users USING btree (name);
CREATE INDEX index_users_on_name_trigram ON users USING gin (name gin_trgm_ops);
CREATE INDEX index_users_on_public_email ON users USING btree (public_email) WHERE ((public_email)::text <> ''::text);
CREATE INDEX index_users_on_public_email_excluding_null_and_empty ON users USING btree (public_email) WHERE (((public_email)::text <> ''::text) AND (public_email IS NOT NULL));
CREATE INDEX index_users_on_require_two_factor_authentication_from_group ON users USING btree (require_two_factor_authentication_from_group) WHERE (require_two_factor_authentication_from_group = true);

View File

@ -14073,6 +14073,7 @@ Returns [`Tree`](#tree).
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="repositoryblobcanmodifyblob"></a>`canModifyBlob` | [`Boolean`](#boolean) | Whether the current user can modify the blob. |
| <a id="repositoryblobcodeowners"></a>`codeOwners` | [`[UserCore!]`](#usercore) | List of code owners for the blob. |
| <a id="repositoryblobeditblobpath"></a>`editBlobPath` | [`String`](#string) | Web path to edit the blob in the old-style editor. |
| <a id="repositoryblobexternalstorageurl"></a>`externalStorageUrl` | [`String`](#string) | Web path to download the raw blob via external storage, if enabled. |
| <a id="repositoryblobfiletype"></a>`fileType` | [`String`](#string) | Expected format of the blob based on the extension. |

View File

@ -109,6 +109,7 @@ GET /projects/:id/members/all
| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](index.md#namespaced-path-encoding) owned by the authenticated user |
| `query` | string | no | A query string to search for members |
| `user_ids` | array of integers | no | Filter the results on the given user IDs |
| `state` | string | no | Filter results by member state, one of `awaiting`, `active` or `created` **(PREMIUM)** |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/all"

View File

@ -294,7 +294,12 @@ POST /projects/:id/approval_rules
| `rule_type` | string | no | The type of rule. `any_approver` is a pre-configured default rule with `approvals_required` at `0`. Other rules are `regular`.
| `user_ids` | Array | no | The ids of users as approvers |
| `group_ids` | Array | no | The ids of groups as approvers |
| `protected_branch_ids` | Array | no | **(PREMIUM)** The ids of protected branches to scope the rule by. To identify the ID, [use the API](protected_branches.md#list-protected-branches). |
| `protected_branch_ids` | Array | no | The IDs of protected branches to scope the rule by. To identify the ID, [use the API](protected_branches.md#list-protected-branches). |
| `report_type` | string | no | The report type required when the rule type is `report_approver`. The supported report types are: `vulnerability`, `license_scanning`, `code_coverage`. |
| `scanners` | Array | no | The security scanners the `Vulnerability-Check` approval rule considers. The supported scanners are: `sast`, `secret_detection`, `dependency_scanning`, `container_scanning`, `dast`, `coverage_fuzzing`, `api_fuzzing`. Defaults to all supported scanners. |
| `severity_levels` | Array | no | The severity levels the `Vulnerability-Check` approval rule considers. The supported severity levels are: `info`, `unknown`, `low`, `medium`, `high`, `critical`. Defaults to `unknown`, `high`, and `critical`. |
| `vulnerabilities_allowed` | integer | no | The number of vulnerabilities allowed for the `Vulnerability-Check` approval rule. Defaults to `0`. |
| `vulnerability_states` | Array | no | The vulnerability states the `Vulnerability-Check` approval rule considers. The supported vulnerability states are: `newly_detected` (default), `detected`, `confirmed`, `resolved`, `dismissed`. |
```json
{
@ -417,7 +422,11 @@ PUT /projects/:id/approval_rules/:approval_rule_id
| `approvals_required` | integer | yes | The number of required approvals for this rule |
| `user_ids` | Array | no | The ids of users as approvers |
| `group_ids` | Array | no | The ids of groups as approvers |
| `protected_branch_ids` | Array | no | **(PREMIUM)** The ids of protected branches to scope the rule by. To identify the ID, [use the API](protected_branches.md#list-protected-branches). |
| `protected_branch_ids` | Array | no | The IDs of protected branches to scope the rule by. To identify the ID, [use the API](protected_branches.md#list-protected-branches). |
| `scanners` | Array | no | The security scanners the `Vulnerability-Check` approval rule considers. The supported scanners are: `sast`, `secret_detection`, `dependency_scanning`, `container_scanning`, `dast`, `coverage_fuzzing`, `api_fuzzing`. Defaults to all supported scanners. |
| `severity_levels` | Array | no | The severity levels the `Vulnerability-Check` approval rule considers. The supported severity levels are: `info`, `unknown`, `low`, `medium`, `high`, `critical`. Defaults to `unknown`, `high`, and `critical`. |
| `vulnerabilities_allowed` | integer | no | The number of vulnerabilities allowed for the `Vulnerability-Check` approval rule. Defaults to `0`. |
| `vulnerability_states` | Array | no | The vulnerability states the `Vulnerability-Check` approval rule considers. The supported vulnerability states are: `newly_detected` (default), `detected`, `confirmed`, `resolved`, `dismissed`. |
```json
{

View File

@ -465,7 +465,14 @@ If you regenerate 2FA recovery codes, save them. You can't use any previously cr
### Have 2FA disabled on your account
If you cannot use a saved recovery code or generate new recovery codes then please submit a [support ticket](https://support.gitlab.com/hc/en-us/requests/new) requesting that a GitLab global administrator disables two-factor authentication for your account. Please note that only the actual owner of the account can make this request and that disabling this setting will temporarily leave your account in a less secure state. You should therefore sign in and re-enable two-factor authentication as soon as possible.
If you can't use a saved recovery code or generate new recovery codes, submit a [support ticket](https://support.gitlab.com/hc/en-us/requests/new) to
request a GitLab global administrator disable two-factor authentication for your account. Note that:
- Only the owner of the account can make this request.
- This service is only available for accounts that have a GitLab.com subscription. For more information, see our
[blog post](https://about.gitlab.com/blog/2020/08/04/gitlab-support-no-longer-processing-mfa-resets-for-free-users/).
- Disabling this setting temporarily leaves your account in a less secure state. You should sign in and re-enable two-factor authentication
as soon as possible.
## Note to GitLab administrators

View File

@ -8,6 +8,9 @@ module API
params :optional_filter_params_ee do
end
params :optional_state_filter_ee do
end
def find_source(source_type, id)
public_send("find_#{source_type}!", id) # rubocop:disable GitlabSecurity/PublicSend
end

View File

@ -41,6 +41,7 @@ module API
optional :query, type: String, desc: 'A query string to search for members'
optional :user_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Array of user ids to look up for membership'
optional :show_seat_info, type: Boolean, desc: 'Show seat information for members'
use :optional_state_filter_ee
use :pagination
end

View File

@ -776,6 +776,9 @@ msgstr ""
msgid "%{name} is already being used for another emoji"
msgstr ""
msgid "%{name} is reserved for %{type} report type"
msgstr ""
msgid "%{name} is scheduled for %{action}"
msgstr ""
@ -1014,6 +1017,9 @@ msgstr ""
msgid "%{total} warnings found: showing first %{warningsDisplayed}"
msgstr ""
msgid "%{type} only supports %{name} name"
msgstr ""
msgid "%{userName} (cannot merge)"
msgstr ""
@ -3746,6 +3752,9 @@ msgstr ""
msgid "An error occurred while loading chart data"
msgstr ""
msgid "An error occurred while loading code owners."
msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
@ -40600,9 +40609,6 @@ msgstr ""
msgid "cannot be enabled until a valid credit card is on file"
msgstr ""
msgid "cannot be modified"
msgstr ""
msgid "cannot be used for user namespace"
msgstr ""

View File

@ -10,7 +10,7 @@ class JobFinder
pipeline_query: {}.freeze,
job_query: {}.freeze
).freeze
MAX_PIPELINES_TO_ITERATE = 200
MAX_PIPELINES_TO_ITERATE = 20
def initialize(options)
@project = options.delete(:project)
@ -51,7 +51,7 @@ class JobFinder
end
end
raise 'Job not found!'
warn 'Job not found!'
end
def find_job_with_filtered_pipelines
@ -63,7 +63,7 @@ class JobFinder
end
end
raise 'Job not found!'
warn 'Job not found!'
end
def find_job_in_pipeline
@ -73,7 +73,7 @@ class JobFinder
return job if found_job_by_name?(job) # rubocop:disable Cop/AvoidReturnFromBlocks
end
raise 'Job not found!'
warn 'Job not found!'
end
def found_job_with_artifact?(job)
@ -87,7 +87,7 @@ class JobFinder
end
def pipeline_query_params
@pipeline_query_params ||= { per_page: 100, **pipeline_query }
@pipeline_query_params ||= { per_page: MAX_PIPELINES_TO_ITERATE, **pipeline_query }
end
def job_query_params

View File

@ -108,12 +108,7 @@ function retrieve_frontend_fixtures_mapping() {
local job_name="generate-frontend-fixtures-mapping"
local test_metadata_with_mapping_job_id
# On the MR that introduces 'generate-frontend-fixtures-mapping', we cannot retrieve the file from a master scheduled pipeline, so we take it from a known MR pipeline
if [[ "${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}" == "339343-execute-related-jests-specs-for-mrs-with-backend-changes" ]]; then
test_metadata_with_mapping_job_id=$(scripts/api/get_job_id.rb --endpoint "https://gitlab.com/api/v4" --project "${project_path}" --pipeline-id "414921396" -Q "scope=success" --job-name "${job_name}")
else
test_metadata_with_mapping_job_id=$(scripts/api/get_job_id.rb --endpoint "https://gitlab.com/api/v4" --project "${project_path}" -q "ref=${artifact_branch}" -q "username=${username}" -Q "scope=success" --job-name "${job_name}")
fi
test_metadata_with_mapping_job_id=$(scripts/api/get_job_id.rb --endpoint "https://gitlab.com/api/v4" --project "${project_path}" -q "ref=${artifact_branch}" -q "username=${username}" -Q "scope=success" --job-name "${job_name}")
if [[ $? -eq 0 ]] && [[ -n "${test_metadata_with_mapping_job_id}" ]]; then
echo "test_metadata_with_mapping_job_id: ${test_metadata_with_mapping_job_id}"

View File

@ -27,8 +27,8 @@ FactoryBot.define do
file_type { :packages }
end
trait(:source) do
file_type { :source }
trait(:sources) do
file_type { :sources }
architecture { nil }
end

View File

@ -0,0 +1,220 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Code owners component matches the snapshot 1`] = `<!---->`;
exports[`Code owners component matches the snapshot 2`] = `
<div
class="well-segment blob-auxiliary-viewer file-owner-content qa-file-owner-content"
>
<gl-icon-stub
data-testid="users-icon"
name="users"
size="16"
/>
<strong>
Code owners
</strong>
<gl-link-stub
href="/help/user/project/code_owners"
target="_blank"
title="About this feature"
>
<gl-icon-stub
data-testid="help-icon"
name="question-o"
size="16"
/>
</gl-link-stub>
:
<div
class="gl-display-inline"
data-testid="code-owners"
>
<!---->
<!---->
<gl-link-stub
href="path/to/@johnDoe"
target="_blank"
title="About this feature"
>
John Doe
</gl-link-stub>
</div>
</div>
`;
exports[`Code owners component matches the snapshot 3`] = `
<div
class="well-segment blob-auxiliary-viewer file-owner-content qa-file-owner-content"
>
<gl-icon-stub
data-testid="users-icon"
name="users"
size="16"
/>
<strong>
Code owners
</strong>
<gl-link-stub
href="/help/user/project/code_owners"
target="_blank"
title="About this feature"
>
<gl-icon-stub
data-testid="help-icon"
name="question-o"
size="16"
/>
</gl-link-stub>
:
<div
class="gl-display-inline"
data-testid="code-owners"
>
<!---->
<!---->
<gl-link-stub
href="path/to/@johnDoe"
target="_blank"
title="About this feature"
>
John Doe
</gl-link-stub>
</div>
<div
class="gl-display-inline"
data-testid="code-owners"
>
<!---->
<span
data-testid="and-separator"
>
and
</span>
<gl-link-stub
href="path/to/@johnDoe"
target="_blank"
title="About this feature"
>
John Doe
</gl-link-stub>
</div>
</div>
`;
exports[`Code owners component matches the snapshot 4`] = `
<div
class="well-segment blob-auxiliary-viewer file-owner-content qa-file-owner-content"
>
<gl-icon-stub
data-testid="users-icon"
name="users"
size="16"
/>
<strong>
Code owners
</strong>
<gl-link-stub
href="/help/user/project/code_owners"
target="_blank"
title="About this feature"
>
<gl-icon-stub
data-testid="help-icon"
name="question-o"
size="16"
/>
</gl-link-stub>
:
<div
class="gl-display-inline-block"
data-testid="code-owners"
>
<!---->
<!---->
<gl-link-stub
href="path/to/@johnDoe"
target="_blank"
title="About this feature"
>
John Doe
</gl-link-stub>
</div>
<div
class="gl-display-inline-block"
data-testid="code-owners"
>
<span
data-testid="comma-separator"
>
,
</span>
<!---->
<gl-link-stub
href="path/to/@johnDoe"
target="_blank"
title="About this feature"
>
John Doe
</gl-link-stub>
</div>
<div
class="gl-display-inline-block"
data-testid="code-owners"
>
<span
data-testid="comma-separator"
>
,
</span>
<span
data-testid="and-separator"
>
and
</span>
<gl-link-stub
href="path/to/@johnDoe"
target="_blank"
title="About this feature"
>
John Doe
</gl-link-stub>
</div>
</div>
`;

View File

@ -2,7 +2,7 @@
exports[`Repository last commit component renders commit widget 1`] = `
<div
class="info-well d-none d-sm-flex project-last-commit commit p-3"
class="well-segment commit gl-p-5 gl-w-full"
>
<user-avatar-link-stub
class="avatar-cell"
@ -108,7 +108,7 @@ exports[`Repository last commit component renders commit widget 1`] = `
exports[`Repository last commit component renders the signature HTML as returned by the backend 1`] = `
<div
class="info-well d-none d-sm-flex project-last-commit commit p-3"
class="well-segment commit gl-p-5 gl-w-full"
>
<user-avatar-link-stub
class="avatar-cell"

View File

@ -0,0 +1,89 @@
import { GlLink } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import CodeOwners from '~/repository/components/code_owners.vue';
import codeOwnersInfoQuery from '~/repository/queries/code_owners_info.query.graphql';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { codeOwnerMock, codeOwnersDataMock, refMock } from '../mock_data';
let wrapper;
let mockResolver;
const localVue = createLocalVue();
const createComponent = async (codeOwners = [codeOwnerMock]) => {
localVue.use(VueApollo);
const project = {
...codeOwnersDataMock,
repository: {
blobs: {
nodes: [{ codeOwners }],
},
},
};
mockResolver = jest.fn().mockResolvedValue({ data: { project } });
wrapper = extendedWrapper(
shallowMount(CodeOwners, {
localVue,
apolloProvider: createMockApollo([[codeOwnersInfoQuery, mockResolver]]),
propsData: { projectPath: 'some/project', filePath: 'some/file' },
mixins: [{ data: () => ({ ref: refMock }) }],
}),
);
wrapper.setData({ isFetching: false });
await waitForPromises();
};
describe('Code owners component', () => {
const findHelpIcon = () => wrapper.findByTestId('help-icon');
const findUsersIcon = () => wrapper.findByTestId('users-icon');
const findCodeOwners = () => wrapper.findAllByTestId('code-owners');
const findCommaSeparators = () => wrapper.findAllByTestId('comma-separator');
const findAndSeparator = () => wrapper.findAllByTestId('and-separator');
const findLink = () => wrapper.findComponent(GlLink);
beforeEach(() => createComponent());
afterEach(() => wrapper.destroy());
describe('help link', () => {
it('renders a GlLink component', () => {
expect(findLink().exists()).toBe(true);
expect(findLink().attributes('href')).toBe('/help/user/project/code_owners');
expect(findLink().attributes('target')).toBe('_blank');
expect(findLink().attributes('title')).toBe('About this feature');
});
it('renders a Help icon', () => {
expect(findHelpIcon().exists()).toBe(true);
expect(findHelpIcon().props('name')).toBe('question-o');
});
});
it('renders a Users icon', () => {
expect(findUsersIcon().exists()).toBe(true);
expect(findUsersIcon().props('name')).toBe('users');
});
it.each`
codeOwners | commaSeparators | hasAndSeparator
${[]} | ${0} | ${false}
${[codeOwnerMock]} | ${0} | ${false}
${[codeOwnerMock, codeOwnerMock]} | ${0} | ${true}
${[codeOwnerMock, codeOwnerMock, codeOwnerMock]} | ${2} | ${true}
`('matches the snapshot', async ({ codeOwners, commaSeparators, hasAndSeparator }) => {
await createComponent(codeOwners);
expect(findCommaSeparators().length).toBe(commaSeparators);
expect(findAndSeparator().exists()).toBe(hasAndSeparator);
expect(findCodeOwners().length).toBe(codeOwners.length);
expect(wrapper.element).toMatchSnapshot();
});
});

View File

@ -55,3 +55,18 @@ export const projectMock = {
export const propsMock = { path: 'some_file.js', projectPath: 'some/path' };
export const refMock = 'default-ref';
export const codeOwnerMock = { name: 'John Doe', webPath: 'path/to/@johnDoe' };
export const codeOwnersDataMock = {
id: '1234',
repository: {
blobs: {
nodes: [
{
codeOwners: [],
},
],
},
},
};

View File

@ -24,6 +24,7 @@ RSpec.describe Types::Repository::BlobType do
:raw_path,
:replace_path,
:pipeline_editor_path,
:code_owners,
:simple_viewer,
:rich_viewer,
:plain_data,

View File

@ -61,9 +61,9 @@ RSpec.describe Packages::Debian::UpdateDistributionService do
let_it_be(:architecture0) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'all') }
let_it_be(:architecture1) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'architecture1') }
let_it_be(:architecture2) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'architecture2') }
let_it_be(:component_file1) { create("debian_#{container_type}_component_file", :source, component: component1) }
let_it_be(:component_file1) { create("debian_#{container_type}_component_file", :sources, component: component1) }
let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component1, architecture: architecture1) }
let_it_be(:component_file3) { create("debian_#{container_type}_component_file", :source, component: component2) }
let_it_be(:component_file3) { create("debian_#{container_type}_component_file", :sources, component: component2) }
let_it_be(:component_file4) { create("debian_#{container_type}_component_file", component: component2, architecture: architecture2) }
let(:original_params) do

View File

@ -23,7 +23,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
let_it_be(:component_file_other_file_md5, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, file_md5: 'other_md5') }
let_it_be(:component_file_other_file_sha256, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, file_sha256: 'other_sha256') }
let_it_be(:component_file_other_container, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component2_1, architecture: architecture2_1) }
let_it_be_with_refind(:component_file_with_file_type_source) { create("debian_#{container_type}_component_file", :source, component: component1_1) }
let_it_be_with_refind(:component_file_with_file_type_sources) { create("debian_#{container_type}_component_file", :sources, component: component1_1) }
let_it_be(:component_file_with_file_type_di_packages, freeze: can_freeze) { create("debian_#{container_type}_component_file", :di_packages, component: component1_1, architecture: architecture1_1) }
subject { component_file_with_architecture }
@ -43,8 +43,8 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
it { is_expected.to belong_to(:architecture).class_name("Packages::Debian::#{container_type.capitalize}Architecture").inverse_of(:files) }
end
context 'with :source file_type' do
subject { component_file_with_file_type_source }
context 'with :sources file_type' do
subject { component_file_with_file_type_sources }
it { is_expected.to belong_to(:architecture).class_name("Packages::Debian::#{container_type.capitalize}Architecture").inverse_of(:files).optional }
end
@ -66,8 +66,8 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
it { is_expected.to validate_presence_of(:architecture) }
end
context 'with :source file_type' do
subject { component_file_with_file_type_source }
context 'with :sources file_type' do
subject { component_file_with_file_type_sources }
it { is_expected.to validate_absence_of(:architecture) }
end
@ -135,10 +135,10 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
end
describe '.with_file_type' do
subject { described_class.with_file_type(:source) }
subject { described_class.with_file_type(:sources) }
it do
expect(subject.to_a).to contain_exactly(component_file_with_file_type_source)
expect(subject.to_a).to contain_exactly(component_file_with_file_type_sources)
end
end
@ -214,9 +214,9 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
end
context 'with a Source file_type' do
subject { component_file_with_file_type_source.relative_path }
subject { component_file_with_file_type_sources.relative_path }
it { is_expected.to eq("#{component1_1.name}/source/Source") }
it { is_expected.to eq("#{component1_1.name}/source/Sources") }
end
context 'with a DI Packages file_type' do

View File

@ -126,7 +126,7 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
SHA256: #{package_files[4].file_sha256}
EOF
expected_main_source_content = <<~EOF
expected_main_sources_content = <<~EOF
Package: #{package.name}
Binary: sample-dev, libsample0, sample-udeb
Version: #{package.version}
@ -158,7 +158,7 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
check_component_file(current_time.round, 'main', :di_packages, 'amd64', expected_main_amd64_di_content)
check_component_file(current_time.round, 'main', :di_packages, 'arm64', nil)
check_component_file(current_time.round, 'main', :source, nil, expected_main_source_content)
check_component_file(current_time.round, 'main', :sources, nil, expected_main_sources_content)
check_component_file(current_time.round, 'contrib', :packages, 'all', nil)
check_component_file(current_time.round, 'contrib', :packages, 'amd64', nil)
@ -168,7 +168,7 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
check_component_file(current_time.round, 'contrib', :di_packages, 'amd64', nil)
check_component_file(current_time.round, 'contrib', :di_packages, 'arm64', nil)
check_component_file(current_time.round, 'contrib', :source, nil, nil)
check_component_file(current_time.round, 'contrib', :sources, nil, nil)
main_amd64_size = expected_main_amd64_content.length
main_amd64_md5sum = Digest::MD5.hexdigest(expected_main_amd64_content)
@ -182,9 +182,9 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
main_amd64_di_md5sum = Digest::MD5.hexdigest(expected_main_amd64_di_content)
main_amd64_di_sha256 = Digest::SHA256.hexdigest(expected_main_amd64_di_content)
main_source_size = expected_main_source_content.length
main_source_md5sum = Digest::MD5.hexdigest(expected_main_source_content)
main_source_sha256 = Digest::SHA256.hexdigest(expected_main_source_content)
main_sources_size = expected_main_sources_content.length
main_sources_md5sum = Digest::MD5.hexdigest(expected_main_sources_content)
main_sources_sha256 = Digest::SHA256.hexdigest(expected_main_sources_content)
expected_release_content = <<~EOF
Codename: unstable
@ -199,14 +199,14 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-amd64/Packages
d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-arm64/Packages
d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-arm64/Packages
d41d8cd98f00b204e9800998ecf8427e 0 contrib/source/Source
d41d8cd98f00b204e9800998ecf8427e 0 contrib/source/Sources
d41d8cd98f00b204e9800998ecf8427e 0 main/binary-all/Packages
d41d8cd98f00b204e9800998ecf8427e 0 main/debian-installer/binary-all/Packages
#{main_amd64_md5sum} #{main_amd64_size} main/binary-amd64/Packages
#{main_amd64_di_md5sum} #{main_amd64_di_size} main/debian-installer/binary-amd64/Packages
d41d8cd98f00b204e9800998ecf8427e 0 main/binary-arm64/Packages
d41d8cd98f00b204e9800998ecf8427e 0 main/debian-installer/binary-arm64/Packages
#{main_source_md5sum} #{main_source_size} main/source/Source
#{main_sources_md5sum} #{main_sources_size} main/source/Sources
SHA256:
#{contrib_all_sha256} #{contrib_all_size} contrib/binary-all/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-all/Packages
@ -214,14 +214,14 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-amd64/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-arm64/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-arm64/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/source/Source
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/source/Sources
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-all/Packages
#{main_amd64_sha256} #{main_amd64_size} main/binary-amd64/Packages
#{main_amd64_di_sha256} #{main_amd64_di_size} main/debian-installer/binary-amd64/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-arm64/Packages
#{main_source_sha256} #{main_source_size} main/source/Source
#{main_sources_sha256} #{main_sources_size} main/source/Sources
EOF
check_release_files(expected_release_content)

View File

@ -6,6 +6,7 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
@ -28,6 +29,14 @@ type testCase struct {
expectedResponse string
}
func TestMain(m *testing.M) {
// Secret should be configured before any Geo API poll happens to prevent
// race conditions where the first API call happens without a secret path
testhelper.ConfigureSecret()
os.Exit(m.Run())
}
func TestRouting(t *testing.T) {
handle := func(u *upstream, regex string) routeEntry {
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
@ -288,11 +297,6 @@ func startWorkhorseServer(railsServerURL string, enableGeoProxyFeature bool) (*h
}
cfg := newUpstreamConfig(railsServerURL)
upstreamHandler := newUpstream(*cfg, logrus.StandardLogger(), myConfigureRoutes)
// Secret should be configured before the first Geo API poll happens on server start
// to prevent race conditions where the first API call happens without a secret path
testhelper.ConfigureSecret()
ws := httptest.NewServer(upstreamHandler)
waitForNextApiPoll := func() {}