Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-01-18 21:10:01 +00:00
parent 830a1f59e2
commit 75d101a1c2
63 changed files with 1174 additions and 537 deletions

View File

@ -1 +1 @@
684af7592d733edf7a16a817cd7900f03755cfb7
d47724f6e9e18fd7c7c73ec68d89ed874c841502

View File

@ -2,8 +2,9 @@
import { GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import NewIssueDropdown from '~/vue_shared/components/new_issue_dropdown/new_issue_dropdown.vue';
import { i18n } from '../constants';
import NewIssueDropdown from './new_issue_dropdown.vue';
import { hasNewIssueDropdown } from '../has_new_issue_dropdown_mixin';
export default {
i18n,
@ -16,6 +17,7 @@ export default {
GlSprintf,
NewIssueDropdown,
},
mixins: [hasNewIssueDropdown()],
inject: [
'canCreateProjects',
'emptyStateSvgPath',
@ -75,7 +77,13 @@ export default {
:export-csv-path="exportCsvPathWithQuery"
:issuable-count="currentTabCount"
/>
<new-issue-dropdown v-if="showNewIssueDropdown" class="gl-align-self-center" />
<new-issue-dropdown
v-if="showNewIssueDropdown"
class="gl-align-self-center"
:query="$options.searchProjectsQuery"
:query-variables="newIssueDropdownQueryVariables"
:extract-projects="extractProjects"
/>
</template>
</gl-empty-state>
<hr />

View File

@ -48,6 +48,7 @@ import {
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import NewIssueDropdown from '~/vue_shared/components/new_issue_dropdown/new_issue_dropdown.vue';
import {
CREATED_DESC,
defaultTypeTokenOptions,
@ -82,9 +83,9 @@ import {
getSortOptions,
isSortKey,
} from '../utils';
import { hasNewIssueDropdown } from '../has_new_issue_dropdown_mixin';
import EmptyStateWithAnyIssues from './empty_state_with_any_issues.vue';
import EmptyStateWithoutAnyIssues from './empty_state_without_any_issues.vue';
import NewIssueDropdown from './new_issue_dropdown.vue';
const UserToken = () => import('~/vue_shared/components/filtered_search_bar/tokens/user_token.vue');
const EmojiToken = () =>
@ -117,7 +118,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagMixin()],
mixins: [glFeatureFlagMixin(), hasNewIssueDropdown()],
inject: [
'autocompleteAwardEmojisPath',
'calendarPath',
@ -831,7 +832,12 @@ export default {
{{ $options.i18n.newIssueLabel }}
</gl-button>
<slot name="new-objective-button"></slot>
<new-issue-dropdown v-if="showNewIssueDropdown" />
<new-issue-dropdown
v-if="showNewIssueDropdown"
:query="$options.searchProjectsQuery"
:query-variables="newIssueDropdownQueryVariables"
:extract-projects="extractProjects"
/>
</template>
<template #timeframe="{ issuable = {} }">

View File

@ -0,0 +1,18 @@
import searchProjectsQuery from './queries/search_projects.query.graphql';
export const hasNewIssueDropdown = () => ({
inject: ['fullPath'],
computed: {
newIssueDropdownQueryVariables() {
return {
fullPath: this.fullPath,
};
},
},
methods: {
extractProjects(data) {
return data?.group?.projects?.nodes;
},
},
searchProjectsQuery,
});

View File

@ -9,16 +9,17 @@ export const HTTP_STATUS_PARTIAL_CONTENT = 206;
export const HTTP_STATUS_MULTI_STATUS = 207;
export const HTTP_STATUS_ALREADY_REPORTED = 208;
export const HTTP_STATUS_IM_USED = 226;
export const HTTP_STATUS_METHOD_NOT_ALLOWED = 405;
export const HTTP_STATUS_CONFLICT = 409;
export const HTTP_STATUS_GONE = 410;
export const HTTP_STATUS_PAYLOAD_TOO_LARGE = 413;
export const HTTP_STATUS_UNPROCESSABLE_ENTITY = 422;
export const HTTP_STATUS_TOO_MANY_REQUESTS = 429;
export const HTTP_STATUS_BAD_REQUEST = 400;
export const HTTP_STATUS_UNAUTHORIZED = 401;
export const HTTP_STATUS_FORBIDDEN = 403;
export const HTTP_STATUS_NOT_FOUND = 404;
export const HTTP_STATUS_METHOD_NOT_ALLOWED = 405;
export const HTTP_STATUS_CONFLICT = 409;
export const HTTP_STATUS_GONE = 410;
export const HTTP_STATUS_PAYLOAD_TOO_LARGE = 413;
export const HTTP_STATUS_IM_A_TEAPOT = 418;
export const HTTP_STATUS_UNPROCESSABLE_ENTITY = 422;
export const HTTP_STATUS_TOO_MANY_REQUESTS = 429;
export const HTTP_STATUS_INTERNAL_SERVER_ERROR = 500;
export const HTTP_STATUS_SERVICE_UNAVAILABLE = 503;

View File

@ -0,0 +1,11 @@
query searchUserProjects($search: String) {
projects(search: $search, membership: true, sort: "latest_activity_desc") {
nodes {
id
issuesEnabled
name
nameWithNamespace
webUrl
}
}
}

View File

@ -10,7 +10,7 @@ import { createAlert } from '~/flash';
import { DASH_SCOPE, joinPaths } from '~/lib/utils/url_utility';
import { __, sprintf } from '~/locale';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
import searchProjectsQuery from '../queries/search_projects.query.graphql';
import searchUserProjects from './graphql/search_user_projects.query.graphql';
export default {
i18n: {
@ -25,7 +25,23 @@ export default {
GlLoadingIcon,
GlSearchBoxByType,
},
inject: ['fullPath'],
props: {
query: {
type: Object,
required: false,
default: () => searchUserProjects,
},
queryVariables: {
type: Object,
required: false,
default: () => ({}),
},
extractProjects: {
type: Function,
required: false,
default: (data) => data?.projects?.nodes,
},
},
data() {
return {
projects: [],
@ -36,14 +52,18 @@ export default {
},
apollo: {
projects: {
query: searchProjectsQuery,
query() {
return this.query;
},
variables() {
return {
fullPath: this.fullPath,
search: this.search,
...this.queryVariables,
};
},
update: ({ group }) => group.projects.nodes ?? [],
update(data) {
return this.extractProjects(data) || [];
},
error(error) {
createAlert({
message: __('An error occurred while loading projects.'),

View File

@ -1,26 +0,0 @@
# frozen_string_literal: true
module ZuoraCSP
extend ActiveSupport::Concern
ZUORA_URL = 'https://*.zuora.com'
included do
content_security_policy do |policy|
next if policy.directives.blank?
default_script_src = policy.directives['script-src'] || policy.directives['default-src']
script_src_values = Array.wrap(default_script_src) | ["'self'", "'unsafe-eval'", ZUORA_URL]
default_frame_src = policy.directives['frame-src'] || policy.directives['default-src']
frame_src_values = Array.wrap(default_frame_src) | ["'self'", ZUORA_URL]
default_child_src = policy.directives['child-src'] || policy.directives['default-src']
child_src_values = Array.wrap(default_child_src) | ["'self'", ZUORA_URL]
policy.script_src(*script_src_values)
policy.frame_src(*frame_src_values)
policy.child_src(*child_src_values)
end
end
end

View File

@ -5,7 +5,6 @@ class Projects::PipelinesController < Projects::ApplicationController
include RedisTracking
include ProductAnalyticsTracking
include ProjectStatsRefreshConflictsGuard
include ZuoraCSP
urgency :low, [
:index, :new, :builds, :show, :failures, :create,

View File

@ -4,7 +4,6 @@ module Projects
module Settings
class CiCdController < Projects::ApplicationController
include RunnerSetupScripts
include ZuoraCSP
NUMBER_OF_RUNNERS_PER_PAGE = 20

View File

@ -394,6 +394,7 @@ class MergeRequest < ApplicationRecord
scope :order_closed_at_desc, -> { order_by_metric(:latest_closed_at, 'DESC') }
scope :preload_source_project, -> { preload(:source_project) }
scope :preload_target_project, -> { preload(:target_project) }
scope :preload_target_project_with_namespace, -> { preload(target_project: [:namespace]) }
scope :preload_routables, -> do
preload(target_project: [:route, { namespace: :route }],
source_project: [:route, { namespace: :route }])

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddNotNullConstraintToOAuthAccessTokensExpiresIn < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
# validate: false ensures that existing records are not affected
# https://docs.gitlab.com/ee/development/database/not_null_constraints.html#prevent-new-invalid-records-current-release
add_not_null_constraint :oauth_access_tokens, :expires_in, validate: false
change_column_default :oauth_access_tokens, :expires_in, 7200
end
def down
remove_not_null_constraint :oauth_access_tokens, :expires_in
change_column_default :oauth_access_tokens, :expires_in, nil
end
end

View File

@ -0,0 +1 @@
16e7446f8fba7fe0b76559432ac6ecc30261a5775b9f914c77425ceab3b92315

View File

@ -18479,7 +18479,7 @@ CREATE TABLE oauth_access_tokens (
application_id integer,
token character varying NOT NULL,
refresh_token character varying,
expires_in integer,
expires_in integer DEFAULT 7200,
revoked_at timestamp without time zone,
created_at timestamp without time zone NOT NULL,
scopes character varying
@ -25632,6 +25632,9 @@ ALTER TABLE ONLY chat_teams
ALTER TABLE vulnerability_scanners
ADD CONSTRAINT check_37608c9db5 CHECK ((char_length(vendor) <= 255)) NOT VALID;
ALTER TABLE oauth_access_tokens
ADD CONSTRAINT check_70f294ef54 CHECK ((expires_in IS NOT NULL)) NOT VALID;
ALTER TABLE sprints
ADD CONSTRAINT check_ccd8a1eae0 CHECK ((start_date IS NOT NULL)) NOT VALID;

View File

@ -397,14 +397,15 @@ Example response:
Download a release asset file by making a request with the following format:
```plaintext
GET /projects/:id/releases/:tag_name/downloads/:filepath
GET /projects/:id/releases/:tag_name/downloads/:direct_asset_path
```
| Attribute | Type | Required | Description |
|----------------------------| -------------- | -------- | ----------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../rest/index.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The Git tag the release is associated with. |
| `filepath` | string | yes | Path to the release asset file as specified when [creating](links.md#create-a-release-link) or [updating](links.md#update-a-release-link) its link. |
| `filepath` | string | yes | Deprecated: Use `direct_asset_path` instead. |
| `direct_asset_path` | string | yes | Path to the release asset file as specified when [creating](links.md#create-a-release-link) or [updating](links.md#update-a-release-link) its link. |
Example request:
@ -463,15 +464,16 @@ POST /projects/:id/releases
| `assets:links` | array of hash | no | An array of assets links. |
| `assets:links:name`| string | required by: `assets:links` | The name of the link. Link names must be unique within the release. |
| `assets:links:url` | string | required by: `assets:links` | The URL of the link. Link URLs must be unique within the release. |
| `assets:links:filepath` | string | no | Optional path for a [Direct Asset link](../../user/project/releases/release_fields.md#permanent-links-to-release-assets).
| `assets:links:link_type` | string | no | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`.
| `assets:links:filepath` | string | no | Deprecated: Use `direct_asset_path` instead. |
| `assets:links:direct_asset_path` | string | no | Optional path for a [direct asset link](../../user/project/releases/release_fields.md#permanent-links-to-release-assets). |
| `assets:links:link_type` | string | no | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`. |
| `released_at` | datetime | no | Date and time for the release. Defaults to the current time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). Only provide this field if creating an [upcoming](../../user/project/releases/index.md#upcoming-releases) or [historical](../../user/project/releases/index.md#historical-releases) release. |
Example request:
```shell
curl --header 'Content-Type: application/json' --header "PRIVATE-TOKEN: <your_access_token>" \
--data '{ "name": "New release", "tag_name": "v0.3", "description": "Super nice release", "milestones": ["v1.0", "v1.0-rc"], "assets": { "links": [{ "name": "hoge", "url": "https://google.com", "filepath": "/binaries/linux-amd64", "link_type":"other" }] } }' \
--data '{ "name": "New release", "tag_name": "v0.3", "description": "Super nice release", "milestones": ["v1.0", "v1.0-rc"], "assets": { "links": [{ "name": "hoge", "url": "https://google.com", "direct_asset_path": "/binaries/linux-amd64", "link_type":"other" }] } }' \
--request POST "https://gitlab.example.com/api/v4/projects/24/releases"
```

View File

@ -93,14 +93,15 @@ Creates an asset as a link from a release.
POST /projects/:id/releases/:tag_name/assets/links
```
| Attribute | Type | Required | Description |
|-------------|----------------|----------|---------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../rest/index.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
| `name` | string | yes | The name of the link. Link names must be unique in the release. |
| `url` | string | yes | The URL of the link. Link URLs must be unique in the release. |
| `filepath` | string | no | Optional path for a [Direct Asset link](../../user/project/releases/release_fields.md#permanent-links-to-release-assets). |
| `link_type` | string | no | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`. |
| Attribute | Type | Required | Description |
|----------------------|----------------|----------|---------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../rest/index.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
| `name` | string | yes | The name of the link. Link names must be unique in the release. |
| `url` | string | yes | The URL of the link. Link URLs must be unique in the release. |
| `filepath` | string | no | Deprecated: Use `direct_asset_path` instead. |
| `direct_asset_path` | string | no | Optional path for a [direct asset link](../../user/project/releases/release_fields.md#permanent-links-to-release-assets). |
| `link_type` | string | no | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`. |
Example request:
@ -109,7 +110,7 @@ curl --request POST \
--header "PRIVATE-TOKEN: <your_access_token>" \
--data name="hellodarwin-amd64" \
--data url="https://gitlab.example.com/mynamespace/hello/-/jobs/688/artifacts/raw/bin/hello-darwin-amd64" \
--data filepath="/bin/hellodarwin-amd64" \
--data direct_asset_path="/bin/hellodarwin-amd64" \
"https://gitlab.example.com/api/v4/projects/20/releases/v1.7.0/assets/links"
```
@ -134,15 +135,16 @@ Updates an asset as a link from a release.
PUT /projects/:id/releases/:tag_name/assets/links/:link_id
```
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../rest/index.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
| `link_id` | integer | yes | The ID of the link. |
| `name` | string | no | The name of the link. |
| `url` | string | no | The URL of the link. |
| `filepath` | string | no | Optional path for a [Direct Asset link](../../user/project/releases/release_fields.md#permanent-links-to-release-assets).
| `link_type` | string | no | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`. |
| Attribute | Type | Required | Description |
| -------------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../rest/index.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
| `link_id` | integer | yes | The ID of the link. |
| `name` | string | no | The name of the link. |
| `url` | string | no | The URL of the link. |
| `filepath` | string | no | Deprecated: Use `direct_asset_path` instead. |
| `direct_asset_path` | string | no | Optional path for a [direct asset link](../../user/project/releases/release_fields.md#permanent-links-to-release-assets). |
| `link_type` | string | no | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`. |
NOTE:
You have to specify at least one of `name` or `url`

View File

@ -214,15 +214,6 @@ In order to disable the warning use `RSPEC_WARN_MISSING_FEATURE_CATEGORY=false`
RSPEC_WARN_MISSING_FEATURE_CATEGORY=false bin/rspec spec/<test_file>
```
### Excluding specs from feature categorization
In the rare case an action cannot be tied to a feature category this
can be done using the `not_owned` feature category.
```ruby
RSpec.describe Utils, feature_category: :not_owned do
```
### Tooling feature category
For Engineering Productivity internal tooling we use `feature_category: :tooling`.

View File

@ -61,6 +61,38 @@ To enable Arkose Protect:
Feature.enable(:arkose_labs_prevent_login)
```
## Triage and debug ArkoseLabs issues
You can triage and debug issues raised by ArkoseLabs with:
- The [GitLab production logs](https://log.gprd.gitlab.net).
- The [Arkose logging service](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/arkose/logger.rb).
### View ArkoseLabs Verify API response for a user session
To view an ArkoseLabs Verify API response for a user, [query the GitLab production logs](https://log.gprd.gitlab.net/goto/54b82f50-935a-11ed-9f43-e3784d7fe3ca) with the following KQL:
```plaintext
KQL: json.message:"Arkose verify response" AND json.username:replace_username_here
```
If the query is valid, the result contains debug information about the user's session:
| Response | Description |
|---------|-------------|
| `json.response.session_details.suppressed` | Value is `true` if the challenge was not shown to the user. Always `true` if the user is allowlisted. |
| `json.arkose.risk_band` | Can be `low`, `medium`, or `high`. Ignored on sign in. Use to debug identity verification issues. |
| `json.response.session_details.solved` | Indicates whether the user solved the challenge. Always `true` if the user is allowlisted. |
| `json.response.session_details.previously_verified` | Indicates whether the token has been reused. Default is `false`. If `true`, it might indicate malicious activity. |
### Check if a user failed an ArkoseLabs challenge
To check if a user failed to sign in because the ArkoseLabs challenge was not solved, [query the GitLab production logs](https://log.gprd.gitlab.net/goto/b97c8a80-935a-11ed-85ed-e7557b0a598c) with the following KQL:
```plaintext
KQL: json.message:"Challenge was not solved" AND json.username:replace_username_here`
```
## QA tests caveat
Several GitLab QA test suites need to sign in to the app to test its features. This can conflict

View File

@ -0,0 +1,32 @@
---
type: reference, howto
stage: Manage
group: Authentication and Authorization
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Configure SCIM for self-managed GitLab instances **(PREMIUM SELF)**
You can use the open standard System for Cross-domain Identity Management (SCIM) to automatically:
- Create users.
- Remove users (deactivate SCIM identity).
The [internal GitLab SCIM API](../../../development/internal_api/index.md#instance-scim-api) implements part of [the RFC7644 protocol](https://www.rfc-editor.org/rfc/rfc7644).
If you are a GitLab.com user, see [configuring SCIM for GitLab.com groups](../../../user/group/saml_sso/scim_setup.md).
## Configure GitLab
Prerequisites:
- Configure [SAML single sign-on](../../../integration/saml.md).
To configure GitLab SCIM:
1. On the top bar, select **Main menu > Admin area**.
1. On the left sidebar, select **Settings > General**.
1. Expand the **SCIM Token** section and select **Generate a SCIM token**.
1. For configuration of your identity provider, save the:
- Token from the **Your SCIM token** field.
- URL from the **SCIM API endpoint URL** field.

View File

@ -95,12 +95,35 @@ Our criteria for the separation of duties is as follows:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213364) in GitLab 13.3.
> - Chain of Custody reports sent using email [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/342594) in GitLab 15.3 with a flag named `async_chain_of_custody_report`. Disabled by default.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/370100) in GitLab 15.5. Feature flag `async_chain_of_custody_report` removed.
> - Chain of Custody report includes all commits (instead of just merge commits) [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267601) in GitLab 15.8 with a flag named `all_commits_compliance_report`. Disabled by default.
FLAG:
On self-managed GitLab, by default the Chain of Custody report only contains information on merge commits. To make the report contain information on all commits to projects within a group, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `all_commits_compliance_report`. On GitLab.com, this feature is not available.
The Chain of Custody report allows customers to export a list of merge commits within the group.
The data provides a comprehensive view with respect to merge commits. It includes the merge commit SHA,
merge request author, merge request ID, merge user, date merged, pipeline ID, group name, project name, and merge request approvers.
Depending on the merge strategy, the merge commit SHA can be a merge commit, squash commit, or a diff head commit.
With the `all_commits_compliance_report` flag enabled, the Chain of Custody report provides a 1 month trailing window of any commit into a project under the group.
To generate the report for all commits, GitLab:
1. Fetches all projects under the group.
1. For each project, fetches the last 1 month of commits. Each project is capped at 1024 commits. If there are more than 1024 commits in the 1-month window, they
are truncated.
1. Writes the commits to a CSV file. The file is truncated at 15 MB because the report is emailed as an attachment.
The expanded report includes the commit SHA, commit author, committer, date committed, the group, and the project.
If the commit has a related merge commit, then the following are also included:
- Merge commit SHA.
- Merge request ID.
- User who merged the merge request.
- Merge date.
- Pipeline ID.
- Merge request approvers.
To generate the Chain of Custody report:
1. On the top bar, select **Main menu > Groups** and find your group.

View File

@ -26,7 +26,7 @@ module Gitlab
'manifest_src' => "'self'",
'media_src' => "'self' data: http: https:",
'script_src' => ContentSecurityPolicy::Directives.script_src,
'style_src' => "'self' 'unsafe-inline'",
'style_src' => ContentSecurityPolicy::Directives.style_src,
'worker_src' => "#{Gitlab::Utils.append_path(Gitlab.config.gitlab.url, 'assets/')} blob: data:",
'object_src' => "'none'",
'report_uri' => nil
@ -43,6 +43,7 @@ module Gitlab
allow_websocket_connections(directives)
allow_cdn(directives, Settings.gitlab.cdn_host) if Settings.gitlab.cdn_host.present?
allow_zuora(directives) if Gitlab.com?
# Support for Sentry setup via configuration files will be removed in 16.0
# in favor of Gitlab::CurrentSettings.
allow_legacy_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn
@ -128,6 +129,14 @@ module Gitlab
append_to_directive(directives, 'frame_src', cdn_host)
end
def self.zuora_host
"https://*.zuora.com/apps/PublicHostedPageLite.do"
end
def self.allow_zuora(directives)
append_to_directive(directives, 'frame_src', zuora_host)
end
def self.append_to_directive(directives, directive, text)
directives[directive] = "#{directives[directive]} #{text}".strip
end

View File

@ -18,6 +18,10 @@ module Gitlab
def self.script_src
"'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com"
end
def self.style_src
"'self' 'unsafe-inline'"
end
end
end
end

View File

@ -25137,6 +25137,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
msgid "List of all commits"
msgstr ""
msgid "List of all merge commits"
msgstr ""

View File

@ -32,8 +32,7 @@ module Gitlab
div :project
div :storage_type_legend
span :container_registry_size
div :purchased_usage_total_free # Different UI for free namespace
span :purchased_usage_total
div :purchased_usage_total
div :storage_purchase_successful_alert, text: /You have successfully purchased a storage/
div :additional_storage_alert, text: /purchase additional storage/
@ -66,14 +65,10 @@ module Gitlab
# Returns total purchased storage value once it's ready on page
#
# @return [Float] Total purchased storage value in GiB
def total_purchased_storage(free_name_space = true)
def total_purchased_storage
additional_storage_alert_element.wait_until(&:present?)
if free_name_space
purchased_usage_total_free.split('/').last.match(/\d+\.\d+/)[0].to_f
else
purchased_usage_total.to_f
end
purchased_usage_total[/(\d+){2}.\d+/].to_f
end
def additional_ci_minutes_added?

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe DashboardController do
RSpec.describe DashboardController, feature_category: :code_review_workflow do
context 'signed in' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }

View File

@ -18,6 +18,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category:
end
before do
stub_feature_flags(refactor_security_extension: false)
project.add_maintainer(user)
project_only_mwps.add_maintainer(user)
sign_in(user)

View File

@ -1,20 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Zuora content security policy', feature_category: :purchase do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
before do
project.add_developer(user)
sign_in(user)
end
it 'has proper Content Security Policy headers' do
visit pipeline_path(pipeline)
expect(response_headers['Content-Security-Policy']).to include('https://*.zuora.com')
end
end

View File

@ -8,6 +8,7 @@ import statisticsLabels from '~/admin/statistics_panel/constants';
import createStore from '~/admin/statistics_panel/store';
import axios from '~/lib/utils/axios_utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import mockStatistics from '../mock_data';
Vue.use(Vuex);
@ -25,7 +26,7 @@ describe('Admin statistics app', () => {
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
axiosMock.onGet(/api\/(.*)\/application\/statistics/).reply(200);
axiosMock.onGet(/api\/(.*)\/application\/statistics/).reply(HTTP_STATUS_OK);
store = createStore();
});

View File

@ -4,6 +4,7 @@ import testAction from 'helpers/vuex_action_helper';
import service from '~/batch_comments/services/drafts_service';
import * as actions from '~/batch_comments/stores/modules/batch_comments/actions';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
describe('Batch comments store actions', () => {
let res = {};
@ -31,7 +32,7 @@ describe('Batch comments store actions', () => {
describe('addDraftToDiscussion', () => {
it('commits ADD_NEW_DRAFT if no errors returned', () => {
res = { id: 1 };
mock.onAny().reply(200, res);
mock.onAny().reply(HTTP_STATUS_OK, res);
return testAction(
actions.addDraftToDiscussion,
@ -58,7 +59,7 @@ describe('Batch comments store actions', () => {
describe('createNewDraft', () => {
it('commits ADD_NEW_DRAFT if no errors returned', () => {
res = { id: 1 };
mock.onAny().reply(200, res);
mock.onAny().reply(HTTP_STATUS_OK, res);
return testAction(
actions.createNewDraft,
@ -100,7 +101,7 @@ describe('Batch comments store actions', () => {
commit,
};
res = { id: 1 };
mock.onAny().reply(200);
mock.onAny().reply(HTTP_STATUS_OK);
return actions.deleteDraft(context, { id: 1 }).then(() => {
expect(commit).toHaveBeenCalledWith('DELETE_DRAFT', 1);
@ -144,7 +145,7 @@ describe('Batch comments store actions', () => {
},
};
res = { id: 1 };
mock.onAny().reply(200, res);
mock.onAny().reply(HTTP_STATUS_OK, res);
return actions.fetchDrafts(context).then(() => {
expect(commit).toHaveBeenCalledWith('SET_BATCH_COMMENTS_DRAFTS', { id: 1 });
@ -169,7 +170,7 @@ describe('Batch comments store actions', () => {
});
it('dispatches actions & commits', () => {
mock.onAny().reply(200);
mock.onAny().reply(HTTP_STATUS_OK);
return actions.publishReview({ dispatch, commit, getters, rootGetters }).then(() => {
expect(commit.mock.calls[0]).toEqual(['REQUEST_PUBLISH_REVIEW']);
@ -180,7 +181,7 @@ describe('Batch comments store actions', () => {
});
it('calls service with notes data', () => {
mock.onAny().reply(200);
mock.onAny().reply(HTTP_STATUS_OK);
jest.spyOn(axios, 'post');
return actions
@ -221,7 +222,7 @@ describe('Batch comments store actions', () => {
commit,
};
res = { id: 1 };
mock.onAny().reply(200, res);
mock.onAny().reply(HTTP_STATUS_OK, res);
params = { note: { id: 1 }, noteText: 'test' };
});

View File

@ -2,6 +2,7 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import renderOpenApi from '~/blob/openapi';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
describe('OpenAPI blob viewer', () => {
const id = 'js-openapi-viewer';
@ -10,7 +11,7 @@ describe('OpenAPI blob viewer', () => {
beforeEach(async () => {
setHTMLFixture(`<div id="${id}" data-endpoint="${mockEndpoint}"></div>`);
mock = new MockAdapter(axios).onGet().reply(200);
mock = new MockAdapter(axios).onGet().reply(HTTP_STATUS_OK);
await renderOpenApi();
});

View File

@ -3,6 +3,7 @@ import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import Clusters from '~/clusters/clusters_bundle';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import initProjectSelectDropdown from '~/project_select';
jest.mock('~/lib/utils/poll');
@ -19,7 +20,7 @@ describe('Clusters', () => {
mock = new MockAdapter(axios);
mock.onGet(statusPath).reply(200);
mock.onGet(statusPath).reply(HTTP_STATUS_OK);
};
beforeEach(() => {

View File

@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/emoji/awards_app/store/actions';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
jest.mock('@sentry/browser');
jest.mock('~/vue_shared/plugins/global_toast');
@ -152,7 +153,7 @@ describe('Awards app actions', () => {
describe('success', () => {
beforeEach(() => {
mock.onDelete(`${relativeRootUrl || ''}/awards/1`).reply(200);
mock.onDelete(`${relativeRootUrl || ''}/awards/1`).reply(HTTP_STATUS_OK);
});
it('commits REMOVE_AWARD', async () => {

View File

@ -1,6 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import { s__ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { resolvers } from '~/environments/graphql/resolvers';
import environmentToRollback from '~/environments/graphql/queries/environment_to_rollback.query.graphql';
import environmentToDelete from '~/environments/graphql/queries/environment_to_delete.query.graphql';
@ -44,7 +45,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
const search = '';
mock
.onGet(ENDPOINT, { params: { nested: true, scope, page: 1, search } })
.reply(200, environmentsApp, {});
.reply(HTTP_STATUS_OK, environmentsApp, {});
const app = await mockResolvers.Query.environmentApp(
null,
@ -63,7 +64,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
const interval = 3000;
mock
.onGet(ENDPOINT, { params: { nested: true, scope, page: 1, search: '' } })
.reply(200, environmentsApp, {
.reply(HTTP_STATUS_OK, environmentsApp, {
'poll-interval': interval,
});
@ -78,7 +79,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
const scope = 'stopped';
mock
.onGet(ENDPOINT, { params: { nested: true, scope, page: 1, search: '' } })
.reply(200, environmentsApp, {
.reply(HTTP_STATUS_OK, environmentsApp, {
'x-next-page': '2',
'x-page': '1',
'X-Per-Page': '2',
@ -108,7 +109,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
const scope = 'stopped';
mock
.onGet(ENDPOINT, { params: { nested: true, scope, page: 1, search: '' } })
.reply(200, environmentsApp, {});
.reply(HTTP_STATUS_OK, environmentsApp, {});
await mockResolvers.Query.environmentApp(null, { scope, page: 1, search: '' }, { cache });
expect(cache.writeQuery).toHaveBeenCalledWith({
@ -131,7 +132,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
it('should fetch the folder url passed to it', async () => {
mock
.onGet(ENDPOINT, { params: { per_page: 3, scope: 'available', search: '' } })
.reply(200, folder);
.reply(HTTP_STATUS_OK, folder);
const environmentFolder = await mockResolvers.Query.folder(null, {
environment: { folderPath: ENDPOINT },
@ -144,7 +145,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
});
describe('stopEnvironment', () => {
it('should post to the stop environment path', async () => {
mock.onPost(ENDPOINT).reply(200);
mock.onPost(ENDPOINT).reply(HTTP_STATUS_OK);
const client = { writeQuery: jest.fn() };
const environment = { stopPath: ENDPOINT };
@ -180,7 +181,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
});
describe('rollbackEnvironment', () => {
it('should post to the retry environment path', async () => {
mock.onPost(ENDPOINT).reply(200);
mock.onPost(ENDPOINT).reply(HTTP_STATUS_OK);
await mockResolvers.Mutation.rollbackEnvironment(null, {
environment: { retryUrl: ENDPOINT },
@ -193,7 +194,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
});
describe('deleteEnvironment', () => {
it('should DELETE to the delete environment path', async () => {
mock.onDelete(ENDPOINT).reply(200);
mock.onDelete(ENDPOINT).reply(HTTP_STATUS_OK);
await mockResolvers.Mutation.deleteEnvironment(null, {
environment: { deletePath: ENDPOINT },
@ -206,7 +207,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
});
describe('cancelAutoStop', () => {
it('should post to the auto stop path', async () => {
mock.onPost(ENDPOINT).reply(200);
mock.onPost(ENDPOINT).reply(HTTP_STATUS_OK);
await mockResolvers.Mutation.cancelAutoStop(null, { autoStopUrl: ENDPOINT });
@ -262,7 +263,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
});
describe('action', () => {
it('should POST to the given path', async () => {
mock.onPost(ENDPOINT).reply(200);
mock.onPost(ENDPOINT).reply(HTTP_STATUS_OK);
const errors = await mockResolvers.Mutation.action(null, { action: { playPath: ENDPOINT } });
expect(errors).toEqual({ __typename: 'LocalEnvironmentErrors', errors: [] });

View File

@ -6,6 +6,7 @@ import * as types from '~/error_tracking_settings/store/mutation_types';
import defaultState from '~/error_tracking_settings/store/state';
import axios from '~/lib/utils/axios_utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import { projectList } from '../mock';
@ -118,7 +119,7 @@ describe('error tracking settings actions', () => {
});
it('should save the page', async () => {
mock.onPatch(TEST_HOST).reply(200);
mock.onPatch(TEST_HOST).reply(HTTP_STATUS_OK);
await testAction(actions.updateSettings, null, state, [], [{ type: 'requestSettings' }]);
expect(mock.history.patch.length).toBe(1);
expect(refreshCurrentPage).toHaveBeenCalled();

View File

@ -2,13 +2,14 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
describe('Filtered Search Dropdown Manager', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet().reply(200);
mock.onGet().reply(HTTP_STATUS_OK);
});
describe('addWordToInput', () => {

View File

@ -4,6 +4,7 @@ import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper';
import waitForPromises from 'helpers/wait_for_promises';
import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
describe('Filtered Search Visual Tokens', () => {
@ -24,7 +25,7 @@ describe('Filtered Search Visual Tokens', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet().reply(200);
mock.onGet().reply(HTTP_STATUS_OK);
setHTMLFixture(`
<ul class="tokens-container">

View File

@ -5,6 +5,7 @@ import Api from '~/api';
import dismissUserCallout from '~/graphql_shared/mutations/dismiss_user_callout.mutation.graphql';
import services from '~/ide/services';
import { query, mutate } from '~/ide/services/gql';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { escapeFileUrl } from '~/lib/utils/url_utility';
import ciConfig from '~/ci/pipeline_editor/graphql/queries/ci_config.query.graphql';
import { projectData } from '../mock_data';
@ -271,7 +272,7 @@ describe('IDE services', () => {
const TEST_PROJECT_PATH = 'foo/bar';
const axiosURL = `${TEST_RELATIVE_URL_ROOT}/${TEST_PROJECT_PATH}/service_ping/web_ide_pipelines_count`;
mock.onPost(axiosURL).reply(200);
mock.onPost(axiosURL).reply(HTTP_STATUS_OK);
return services.pingUsage(TEST_PROJECT_PATH).then(() => {
expect(axios.post).toHaveBeenCalledWith(axiosURL);

View File

@ -23,6 +23,7 @@ import {
} from '~/ide/stores/actions';
import * as types from '~/ide/stores/mutation_types';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_IM_A_TEAPOT } from '~/lib/utils/http_status';
import { visitUrl } from '~/lib/utils/url_utility';
import { file, createTriggerRenameAction, createTriggerChangeAction } from '../helpers';
@ -927,7 +928,7 @@ describe('Multi-file store actions', () => {
});
it('does not pass the error further and flashes an alert if error is not 404', async () => {
mock.onGet(/(.*)/).replyOnce(418);
mock.onGet(/(.*)/).replyOnce(HTTP_STATUS_IM_A_TEAPOT);
await expect(getBranchData(...callParams)).rejects.toEqual(
new Error('Branch not loaded - <strong>abc/def/main-testing</strong>'),

View File

@ -7,7 +7,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { createAlert } from '~/flash';
import { HTTP_STATUS_BAD_REQUEST } from '~/lib/utils/http_status';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils';
import { STATUSES } from '~/import_entities/constants';
import { i18n, ROOT_NAMESPACE } from '~/import_entities/import_groups/constants';
@ -113,7 +113,7 @@ describe('import table', () => {
beforeEach(() => {
axiosMock = new MockAdapter(axios);
axiosMock.onGet(/.*\/exists$/, () => []).reply(200);
axiosMock.onGet(/.*\/exists$/, () => []).reply(HTTP_STATUS_OK);
});
afterEach(() => {

View File

@ -21,6 +21,7 @@ import {
import state from '~/import_entities/import_projects/store/state';
import axios from '~/lib/utils/axios_utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
jest.mock('~/flash');
@ -433,7 +434,7 @@ describe('import_projects store actions', () => {
afterEach(() => mock.restore());
it('commits CANCEL_IMPORT_SUCCESS on success', async () => {
mock.onPost(MOCK_ENDPOINT).reply(200);
mock.onPost(MOCK_ENDPOINT).reply(HTTP_STATUS_OK);
await testAction(
cancelImport,

View File

@ -2,7 +2,7 @@ import { GlEmptyState, GlLink } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import EmptyStateWithoutAnyIssues from '~/issues/list/components/empty_state_without_any_issues.vue';
import NewIssueDropdown from '~/issues/list/components/new_issue_dropdown.vue';
import NewIssueDropdown from '~/vue_shared/components/new_issue_dropdown/new_issue_dropdown.vue';
import { i18n } from '~/issues/list/constants';
describe('EmptyStateWithoutAnyIssues component', () => {

View File

@ -30,7 +30,7 @@ import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/con
import EmptyStateWithAnyIssues from '~/issues/list/components/empty_state_with_any_issues.vue';
import EmptyStateWithoutAnyIssues from '~/issues/list/components/empty_state_without_any_issues.vue';
import IssuesListApp from '~/issues/list/components/issues_list_app.vue';
import NewIssueDropdown from '~/issues/list/components/new_issue_dropdown.vue';
import NewIssueDropdown from '~/vue_shared/components/new_issue_dropdown/new_issue_dropdown.vue';
import {
CREATED_DESC,
RELATIVE_POSITION,

View File

@ -1,133 +0,0 @@
import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import NewIssueDropdown from '~/issues/list/components/new_issue_dropdown.vue';
import searchProjectsQuery from '~/issues/list/queries/search_projects.query.graphql';
import { DASH_SCOPE, joinPaths } from '~/lib/utils/url_utility';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
import {
emptySearchProjectsQueryResponse,
project1,
project3,
searchProjectsQueryResponse,
} from '../mock_data';
describe('NewIssueDropdown component', () => {
let wrapper;
Vue.use(VueApollo);
const mountComponent = ({
search = '',
queryResponse = searchProjectsQueryResponse,
mountFn = shallowMount,
} = {}) => {
const requestHandlers = [[searchProjectsQuery, jest.fn().mockResolvedValue(queryResponse)]];
const apolloProvider = createMockApollo(requestHandlers);
return mountFn(NewIssueDropdown, {
apolloProvider,
provide: {
fullPath: 'mushroom-kingdom',
},
data() {
return { search };
},
});
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findInput = () => wrapper.findComponent(GlSearchBoxByType);
const showDropdown = async () => {
findDropdown().vm.$emit('shown');
await waitForPromises();
jest.advanceTimersByTime(DEBOUNCE_DELAY);
await waitForPromises();
};
afterEach(() => {
wrapper.destroy();
});
it('renders a split dropdown', () => {
wrapper = mountComponent();
expect(findDropdown().props('split')).toBe(true);
});
it('renders a label for the dropdown toggle button', () => {
wrapper = mountComponent();
expect(findDropdown().attributes('toggle-text')).toBe(NewIssueDropdown.i18n.toggleButtonLabel);
});
it('focuses on input when dropdown is shown', async () => {
wrapper = mountComponent({ mountFn: mount });
const inputSpy = jest.spyOn(findInput().vm, 'focusInput');
await showDropdown();
expect(inputSpy).toHaveBeenCalledTimes(1);
});
it('renders projects with issues enabled', async () => {
wrapper = mountComponent({ mountFn: mount });
await showDropdown();
const listItems = wrapper.findAll('li');
expect(listItems.at(0).text()).toBe(project1.nameWithNamespace);
expect(listItems.at(1).text()).toBe(project3.nameWithNamespace);
});
it('renders `No matches found` when there are no matches', async () => {
wrapper = mountComponent({
search: 'no matches',
queryResponse: emptySearchProjectsQueryResponse,
mountFn: mount,
});
await showDropdown();
expect(wrapper.find('li').text()).toBe(NewIssueDropdown.i18n.noMatchesFound);
});
describe('when no project is selected', () => {
beforeEach(() => {
wrapper = mountComponent();
});
it('dropdown button is not a link', () => {
expect(findDropdown().attributes('split-href')).toBeUndefined();
});
it('displays default text on the dropdown button', () => {
expect(findDropdown().props('text')).toBe(NewIssueDropdown.i18n.defaultDropdownText);
});
});
describe('when a project is selected', () => {
beforeEach(async () => {
wrapper = mountComponent({ mountFn: mount });
await waitForPromises();
await showDropdown();
wrapper.findComponent(GlDropdownItem).vm.$emit('click', project1);
await waitForPromises();
});
it('dropdown button is a link', () => {
const href = joinPaths(project1.webUrl, DASH_SCOPE, 'issues/new');
expect(findDropdown().attributes('split-href')).toBe(href);
});
it('displays project name on the dropdown button', () => {
expect(findDropdown().props('text')).toBe(`New issue in ${project1.name}`);
});
});
});

View File

@ -343,49 +343,3 @@ export const urlParamsWithSpecialValues = {
weight: 'None',
health_status: 'None',
};
export const project1 = {
id: 'gid://gitlab/Group/26',
issuesEnabled: true,
name: 'Super Mario Project',
nameWithNamespace: 'Mushroom Kingdom / Super Mario Project',
webUrl: 'https://127.0.0.1:3000/mushroom-kingdom/super-mario-project',
};
export const project2 = {
id: 'gid://gitlab/Group/59',
issuesEnabled: false,
name: 'Mario Kart Project',
nameWithNamespace: 'Mushroom Kingdom / Mario Kart Project',
webUrl: 'https://127.0.0.1:3000/mushroom-kingdom/mario-kart-project',
};
export const project3 = {
id: 'gid://gitlab/Group/103',
issuesEnabled: true,
name: 'Mario Party Project',
nameWithNamespace: 'Mushroom Kingdom / Mario Party Project',
webUrl: 'https://127.0.0.1:3000/mushroom-kingdom/mario-party-project',
};
export const searchProjectsQueryResponse = {
data: {
group: {
id: '1',
projects: {
nodes: [project1, project2, project3],
},
},
},
};
export const emptySearchProjectsQueryResponse = {
data: {
group: {
id: '1',
projects: {
nodes: [],
},
},
},
};

View File

@ -3,11 +3,12 @@ import waitForPromises from 'helpers/wait_for_promises';
import { initIssueApp } from '~/issues/show';
import * as parseData from '~/issues/show/utils/parse_data';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import createStore from '~/notes/stores';
import { appProps } from './mock_data/mock_data';
const mock = new MockAdapter(axios);
mock.onGet().reply(200);
mock.onGet().reply(HTTP_STATUS_OK);
jest.mock('~/lib/utils/poll');

View File

@ -3,14 +3,15 @@
import AxiosMockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
describe('axios_utils', () => {
let mock;
beforeEach(() => {
mock = new AxiosMockAdapter(axios);
mock.onAny('/ok').reply(200);
mock.onAny('/err').reply(500);
mock.onAny('/ok').reply(HTTP_STATUS_OK);
mock.onAny('/err').reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
// eslint-disable-next-line jest/no-standalone-expect
expect(axios.countActiveRequests()).toBe(0);
});

View File

@ -4,6 +4,7 @@ import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
import DeleteMilestoneModal from '~/milestones/components/delete_milestone_modal.vue';
import eventHub from '~/milestones/event_hub';
import { HTTP_STATUS_IM_A_TEAPOT, HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status';
import { redirectTo } from '~/lib/utils/url_utility';
import { createAlert } from '~/flash';
@ -71,9 +72,9 @@ describe('Delete milestone modal', () => {
});
it.each`
statusCode | alertMessage
${418} | ${`Failed to delete milestone ${mockProps.milestoneTitle}`}
${404} | ${`Milestone ${mockProps.milestoneTitle} was not found`}
statusCode | alertMessage
${HTTP_STATUS_IM_A_TEAPOT} | ${`Failed to delete milestone ${mockProps.milestoneTitle}`}
${HTTP_STATUS_NOT_FOUND} | ${`Milestone ${mockProps.milestoneTitle} was not found`}
`(
'displays error if deleting milestone failed with code $statusCode',
async ({ statusCode, alertMessage }) => {

View File

@ -918,7 +918,7 @@ describe('Monitoring store actions', () => {
it('stars dashboard if it is not starred', () => {
state.selectedDashboard = unstarredDashboard;
mock.onPost(unstarredDashboard.user_starred_path).reply(200);
mock.onPost(unstarredDashboard.user_starred_path).reply(HTTP_STATUS_OK);
return testAction(toggleStarredValue, null, state, [
{ type: types.REQUEST_DASHBOARD_STARRING },
@ -934,7 +934,7 @@ describe('Monitoring store actions', () => {
it('unstars dashboard if it is starred', () => {
state.selectedDashboard = starredDashboard;
mock.onPost(starredDashboard.user_starred_path).reply(200);
mock.onPost(starredDashboard.user_starred_path).reply(HTTP_STATUS_OK);
return testAction(toggleStarredValue, null, state, [
{ type: types.REQUEST_DASHBOARD_STARRING },

View File

@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import { getByText as getByTextHelper } from '@testing-library/dom';
import { GlToggle } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import NewNavToggle from '~/nav/components/new_nav_toggle.vue';
import waitForPromises from 'helpers/wait_for_promises';
@ -74,7 +75,7 @@ describe('NewNavToggle', () => {
});
it('reloads the page on success', async () => {
mock.onPut(TEST_ENDPONT).reply(200);
mock.onPut(TEST_ENDPONT).reply(HTTP_STATUS_OK);
actFn();
await waitForPromises();

View File

@ -4,6 +4,7 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import PipelineStage from '~/pipelines/components/pipeline_mini_graph/pipeline_stage.vue';
import eventHub from '~/pipelines/event_hub';
import waitForPromises from 'helpers/wait_for_promises';
@ -188,8 +189,8 @@ describe('Pipelines stage component', () => {
describe('job update in dropdown', () => {
beforeEach(async () => {
mock.onGet(dropdownPath).reply(200, stageReply);
mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(HTTP_STATUS_OK);
createComponent();
await waitForPromises();

View File

@ -4,6 +4,7 @@ import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import ActionComponent from '~/pipelines/components/jobs_shared/action_component.vue';
describe('pipeline graph action component', () => {
@ -15,7 +16,7 @@ describe('pipeline graph action component', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onPost('foo.json').reply(200);
mock.onPost('foo.json').reply(HTTP_STATUS_OK);
wrapper = mount(ActionComponent, {
propsData: {

View File

@ -7,6 +7,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'spec/test_constants';
import { createAlert } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipelines_manual_actions.vue';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
@ -70,7 +71,7 @@ describe('Pipelines Actions dropdown', () => {
describe('on click', () => {
it('makes a request and toggles the loading state', async () => {
mock.onPost(mockActions.path).reply(200);
mock.onPost(mockActions.path).reply(HTTP_STATUS_OK);
findAllDropdownItems().at(0).vm.$emit('click');
@ -132,7 +133,7 @@ describe('Pipelines Actions dropdown', () => {
});
it('makes post request after confirming', async () => {
mock.onPost(scheduledJobAction.path).reply(200);
mock.onPost(scheduledJobAction.path).reply(HTTP_STATUS_OK);
confirmAction.mockResolvedValueOnce(true);
findAllDropdownItems().at(0).vm.$emit('click');
@ -145,7 +146,7 @@ describe('Pipelines Actions dropdown', () => {
});
it('does not make post request if confirmation is cancelled', async () => {
mock.onPost(scheduledJobAction.path).reply(200);
mock.onPost(scheduledJobAction.path).reply(HTTP_STATUS_OK);
confirmAction.mockResolvedValueOnce(false);
findAllDropdownItems().at(0).vm.$emit('click');

View File

@ -4,6 +4,7 @@ import MockAxiosAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import SharedRunnersToggleComponent from '~/projects/settings/components/shared_runners_toggle.vue';
const TEST_UPDATE_PATH = '/test/update_shared_runners';
@ -36,7 +37,7 @@ describe('projects/settings/components/shared_runners', () => {
beforeEach(() => {
mockAxios = new MockAxiosAdapter(axios);
mockAxios.onPost(TEST_UPDATE_PATH).reply(200);
mockAxios.onPost(TEST_UPDATE_PATH).reply(HTTP_STATUS_OK);
});
afterEach(() => {

View File

@ -2,6 +2,7 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
@ -66,7 +67,7 @@ describe('MemoryUsage', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet(`${url}.json`).reply(200);
mock.onGet(`${url}.json`).reply(HTTP_STATUS_OK);
vm = createComponent();
el = vm.$el;

View File

@ -11,6 +11,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { securityReportMergeRequestDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data';
import api from '~/api';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import Poll from '~/lib/utils/poll';
import { setFaviconOverlay } from '~/lib/utils/favicon';
import notify from '~/lib/utils/notify';
@ -837,7 +838,7 @@ describe('MrWidgetOptions', () => {
describe('suggestPipeline', () => {
beforeEach(() => {
mock.onAny().reply(200);
mock.onAny().reply(HTTP_STATUS_OK);
});
describe('given feature flag is enabled', () => {

View File

@ -0,0 +1,57 @@
export const emptySearchProjectsQueryResponse = {
data: {
projects: {
nodes: [],
},
},
};
export const emptySearchProjectsWithinGroupQueryResponse = {
data: {
group: {
id: '1',
projects: emptySearchProjectsQueryResponse.data.projects,
},
},
};
export const project1 = {
id: 'gid://gitlab/Group/26',
issuesEnabled: true,
name: 'Super Mario Project',
nameWithNamespace: 'Mushroom Kingdom / Super Mario Project',
webUrl: 'https://127.0.0.1:3000/mushroom-kingdom/super-mario-project',
};
export const project2 = {
id: 'gid://gitlab/Group/59',
issuesEnabled: false,
name: 'Mario Kart Project',
nameWithNamespace: 'Mushroom Kingdom / Mario Kart Project',
webUrl: 'https://127.0.0.1:3000/mushroom-kingdom/mario-kart-project',
};
export const project3 = {
id: 'gid://gitlab/Group/103',
issuesEnabled: true,
name: 'Mario Party Project',
nameWithNamespace: 'Mushroom Kingdom / Mario Party Project',
webUrl: 'https://127.0.0.1:3000/mushroom-kingdom/mario-party-project',
};
export const searchProjectsQueryResponse = {
data: {
projects: {
nodes: [project1, project2, project3],
},
},
};
export const searchProjectsWithinGroupQueryResponse = {
data: {
group: {
id: '1',
projects: searchProjectsQueryResponse.data.projects,
},
},
};

View File

@ -0,0 +1,149 @@
import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import NewIssueDropdown from '~/vue_shared/components/new_issue_dropdown/new_issue_dropdown.vue';
import searchUserProjectsQuery from '~/vue_shared/components/new_issue_dropdown/graphql/search_user_projects.query.graphql';
import searchProjectsWithinGroupQuery from '~/issues/list/queries/search_projects.query.graphql';
import { DASH_SCOPE, joinPaths } from '~/lib/utils/url_utility';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
import {
emptySearchProjectsQueryResponse,
emptySearchProjectsWithinGroupQueryResponse,
project1,
project3,
searchProjectsQueryResponse,
searchProjectsWithinGroupQueryResponse,
} from './mock_data';
describe('NewIssueDropdown component', () => {
let wrapper;
Vue.use(VueApollo);
// Props
const withinGroupProps = {
query: searchProjectsWithinGroupQuery,
queryVariables: { fullPath: 'mushroom-kingdom' },
extractProjects: (data) => data.group.projects.nodes,
};
const mountComponent = ({
search = '',
query = searchUserProjectsQuery,
queryResponse = searchProjectsQueryResponse,
mountFn = shallowMount,
propsData = {},
} = {}) => {
const requestHandlers = [[query, jest.fn().mockResolvedValue(queryResponse)]];
const apolloProvider = createMockApollo(requestHandlers);
return mountFn(NewIssueDropdown, {
apolloProvider,
propsData,
data() {
return { search };
},
});
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findInput = () => wrapper.findComponent(GlSearchBoxByType);
const showDropdown = async () => {
findDropdown().vm.$emit('shown');
await waitForPromises();
jest.advanceTimersByTime(DEBOUNCE_DELAY);
await waitForPromises();
};
afterEach(() => {
wrapper.destroy();
});
it('renders a split dropdown', () => {
wrapper = mountComponent();
expect(findDropdown().props('split')).toBe(true);
});
it('renders a label for the dropdown toggle button', () => {
wrapper = mountComponent();
expect(findDropdown().attributes('toggle-text')).toBe(NewIssueDropdown.i18n.toggleButtonLabel);
});
it('focuses on input when dropdown is shown', async () => {
wrapper = mountComponent({ mountFn: mount });
const inputSpy = jest.spyOn(findInput().vm, 'focusInput');
await showDropdown();
expect(inputSpy).toHaveBeenCalledTimes(1);
});
describe.each`
description | propsData | query | queryResponse | emptyResponse
${'by default'} | ${undefined} | ${searchUserProjectsQuery} | ${searchProjectsQueryResponse} | ${emptySearchProjectsQueryResponse}
${'within a group'} | ${withinGroupProps} | ${searchProjectsWithinGroupQuery} | ${searchProjectsWithinGroupQueryResponse} | ${emptySearchProjectsWithinGroupQueryResponse}
`('$description', ({ propsData, query, queryResponse, emptyResponse }) => {
it('renders projects with issues enabled', async () => {
wrapper = mountComponent({ mountFn: mount, query, queryResponse, propsData });
await showDropdown();
const listItems = wrapper.findAll('li');
expect(listItems.at(0).text()).toBe(project1.nameWithNamespace);
expect(listItems.at(1).text()).toBe(project3.nameWithNamespace);
});
it('renders `No matches found` when there are no matches', async () => {
wrapper = mountComponent({
search: 'no matches',
query,
queryResponse: emptyResponse,
mountFn: mount,
propsData,
});
await showDropdown();
expect(wrapper.find('li').text()).toBe(NewIssueDropdown.i18n.noMatchesFound);
});
describe('when no project is selected', () => {
beforeEach(() => {
wrapper = mountComponent({ query, queryResponse, propsData });
});
it('dropdown button is not a link', () => {
expect(findDropdown().attributes('split-href')).toBeUndefined();
});
it('displays default text on the dropdown button', () => {
expect(findDropdown().props('text')).toBe(NewIssueDropdown.i18n.defaultDropdownText);
});
});
describe('when a project is selected', () => {
beforeEach(async () => {
wrapper = mountComponent({ mountFn: mount, query, queryResponse, propsData });
await showDropdown();
wrapper.findComponent(GlDropdownItem).vm.$emit('click', project1);
});
it('dropdown button is a link', () => {
const href = joinPaths(project1.webUrl, DASH_SCOPE, 'issues/new');
expect(findDropdown().attributes('split-href')).toBe(href);
});
it('displays project name on the dropdown button', () => {
expect(findDropdown().props('text')).toBe(`New issue in ${project1.name}`);
});
});
});
});

View File

@ -6,6 +6,7 @@ import Mousetrap from 'mousetrap';
import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import GLForm from '~/gl_form';
import * as utils from '~/lib/utils/common_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import ZenMode from '~/zen_mode';
describe('ZenMode', () => {
@ -32,7 +33,7 @@ describe('ZenMode', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet().reply(200);
mock.onGet().reply(HTTP_STATUS_OK);
loadHTMLFixture(fixtureName);

View File

@ -94,13 +94,31 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
it 'adds CDN host to CSP' do
expect(directives['script_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.script_src + " https://cdn.example.com")
expect(directives['style_src']).to eq("'self' 'unsafe-inline' https://cdn.example.com")
expect(directives['style_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.style_src + " https://cdn.example.com")
expect(directives['font_src']).to eq("'self' https://cdn.example.com")
expect(directives['worker_src']).to eq('http://localhost/assets/ blob: data: https://cdn.example.com')
expect(directives['frame_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.frame_src + " https://cdn.example.com http://localhost/admin/ http://localhost/assets/ http://localhost/-/speedscope/index.html http://localhost/-/sandbox/")
end
end
describe 'Zuora directives' do
context 'when is Gitlab.com?' do
before do
allow(::Gitlab).to receive(:com?).and_return(true)
end
it 'adds Zuora host to CSP' do
expect(directives['frame_src']).to include('https://*.zuora.com/apps/PublicHostedPageLite.do')
end
end
context 'when is not Gitlab.com?' do
it 'does not add Zuora host to CSP' do
expect(directives['frame_src']).not_to include('https://*.zuora.com/apps/PublicHostedPageLite.do')
end
end
end
context 'when sentry is configured' do
let(:legacy_dsn) { 'dummy://abc@legacy-sentry.example.com/1' }
let(:dsn) { 'dummy://def@sentry.example.com/2' }

View File

@ -135,6 +135,15 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
let_it_be(:merge_request3) { create(:merge_request, :unique_branches, reviewers: []) }
let_it_be(:merge_request4) { create(:merge_request, :draft_merge_request) }
describe '.preload_target_project_with_namespace' do
subject(:mr) { described_class.preload_target_project_with_namespace.first }
it 'returns MR with the target project\'s namespace preloaded' do
expect(mr.association(:target_project)).to be_loaded
expect(mr.target_project.association(:namespace)).to be_loaded
end
end
describe '.review_requested' do
it 'returns MRs that have any review requests' do
expect(described_class.review_requested).to eq([merge_request1, merge_request2])

View File

@ -3,7 +3,7 @@ module gitlab.com/gitlab-org/gitlab/workhorse
go 1.18
require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1
github.com/BurntSushi/toml v1.2.1
github.com/FZambia/sentinel v1.1.1
github.com/alecthomas/chroma/v2 v2.4.0
@ -29,39 +29,40 @@ require (
gitlab.com/gitlab-org/gitaly/v15 v15.7.0
gitlab.com/gitlab-org/golang-archive-zip v0.1.1
gitlab.com/gitlab-org/labkit v1.17.0
gocloud.dev v0.27.0
gocloud.dev v0.28.0
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
golang.org/x/net v0.1.0
golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c
golang.org/x/tools v0.1.12
golang.org/x/net v0.4.0
golang.org/x/oauth2 v0.2.0
golang.org/x/tools v0.2.0
google.golang.org/grpc v1.51.0
google.golang.org/protobuf v1.28.1
honnef.co/go/tools v0.3.3
)
require (
cloud.google.com/go v0.103.0 // indirect
cloud.google.com/go/compute v1.7.0 // indirect
cloud.google.com/go/iam v0.3.0 // indirect
cloud.google.com/go/monitoring v1.5.0 // indirect
cloud.google.com/go v0.107.0 // indirect
cloud.google.com/go/compute v1.13.0 // indirect
cloud.google.com/go/compute/metadata v0.2.2 // indirect
cloud.google.com/go/iam v0.7.0 // indirect
cloud.google.com/go/monitoring v1.9.0 // indirect
cloud.google.com/go/profiler v0.1.0 // indirect
cloud.google.com/go/storage v1.24.0 // indirect
cloud.google.com/go/trace v1.2.0 // indirect
contrib.go.opencensus.io/exporter/stackdriver v0.13.13 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
cloud.google.com/go/storage v1.28.0 // indirect
cloud.google.com/go/trace v1.4.0 // indirect
contrib.go.opencensus.io/exporter/stackdriver v0.13.14 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 // indirect
github.com/DataDog/datadog-go v4.4.0+incompatible // indirect
github.com/DataDog/sketches-go v1.0.0 // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/beevik/ntp v0.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/client9/reopen v1.0.0 // indirect
@ -69,14 +70,13 @@ require (
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20220608213341-c488b8fa1db3 // indirect
github.com/google/pprof v0.0.0-20221102093814-76f304f74e5e // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/google/wire v0.5.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
@ -89,13 +89,13 @@ require (
github.com/oklog/ulid/v2 v2.0.2 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/philhofer/fwd v1.1.1 // indirect
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/prometheus/prometheus v0.37.0 // indirect
github.com/prometheus/prometheus v0.40.5 // indirect
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 // indirect
github.com/shirou/gopsutil/v3 v3.21.2 // indirect
@ -105,19 +105,19 @@ require (
github.com/tklauser/numcpus v0.2.1 // indirect
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
golang.org/x/crypto v0.3.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/text v0.5.0 // indirect
golang.org/x/time v0.2.0 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/api v0.91.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.103.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220802133213-ce4fa296bf78 // indirect
google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3 // indirect
gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ import (
"time"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
"github.com/BurntSushi/toml"
"gocloud.dev/blob"
"gocloud.dev/blob/azureblob"
@ -178,12 +179,13 @@ func (creds *AzureCredentials) getURLOpener() (*azureblob.URLOpener, error) {
AccountName: creds.AccountName,
}
clientFunc := func(svcURL azureblob.ServiceURL) (*azblob.ServiceClient, error) {
clientFunc := func(svcURL azureblob.ServiceURL, containerName azureblob.ContainerName) (*container.Client, error) {
sharedKeyCred, err := azblob.NewSharedKeyCredential(creds.AccountName, creds.AccountKey)
if err != nil {
return nil, fmt.Errorf("error creating Azure credentials: %w", err)
}
return azblob.NewServiceClientWithSharedKey(string(svcURL), sharedKeyCred, &azblob.ClientOptions{})
containerURL := fmt.Sprintf("%s/%s", svcURL, containerName)
return container.NewClientWithSharedKeyCredential(containerURL, sharedKeyCred, &container.ClientOptions{})
}
return &azureblob.URLOpener{