Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									830a1f59e2
								
							
						
					
					
						commit
						75d101a1c2
					
				|  | @ -1 +1 @@ | ||||||
| 684af7592d733edf7a16a817cd7900f03755cfb7 | d47724f6e9e18fd7c7c73ec68d89ed874c841502 | ||||||
|  |  | ||||||
|  | @ -2,8 +2,9 @@ | ||||||
| import { GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; | import { GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; | ||||||
| import { helpPagePath } from '~/helpers/help_page_helper'; | import { helpPagePath } from '~/helpers/help_page_helper'; | ||||||
| import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue'; | 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 { i18n } from '../constants'; | ||||||
| import NewIssueDropdown from './new_issue_dropdown.vue'; | import { hasNewIssueDropdown } from '../has_new_issue_dropdown_mixin'; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|   i18n, |   i18n, | ||||||
|  | @ -16,6 +17,7 @@ export default { | ||||||
|     GlSprintf, |     GlSprintf, | ||||||
|     NewIssueDropdown, |     NewIssueDropdown, | ||||||
|   }, |   }, | ||||||
|  |   mixins: [hasNewIssueDropdown()], | ||||||
|   inject: [ |   inject: [ | ||||||
|     'canCreateProjects', |     'canCreateProjects', | ||||||
|     'emptyStateSvgPath', |     'emptyStateSvgPath', | ||||||
|  | @ -75,7 +77,13 @@ export default { | ||||||
|           :export-csv-path="exportCsvPathWithQuery" |           :export-csv-path="exportCsvPathWithQuery" | ||||||
|           :issuable-count="currentTabCount" |           :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> |       </template> | ||||||
|     </gl-empty-state> |     </gl-empty-state> | ||||||
|     <hr /> |     <hr /> | ||||||
|  |  | ||||||
|  | @ -48,6 +48,7 @@ import { | ||||||
| import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue'; | import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue'; | ||||||
| import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/constants'; | import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/constants'; | ||||||
| import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; | import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; | ||||||
|  | import NewIssueDropdown from '~/vue_shared/components/new_issue_dropdown/new_issue_dropdown.vue'; | ||||||
| import { | import { | ||||||
|   CREATED_DESC, |   CREATED_DESC, | ||||||
|   defaultTypeTokenOptions, |   defaultTypeTokenOptions, | ||||||
|  | @ -82,9 +83,9 @@ import { | ||||||
|   getSortOptions, |   getSortOptions, | ||||||
|   isSortKey, |   isSortKey, | ||||||
| } from '../utils'; | } from '../utils'; | ||||||
|  | import { hasNewIssueDropdown } from '../has_new_issue_dropdown_mixin'; | ||||||
| import EmptyStateWithAnyIssues from './empty_state_with_any_issues.vue'; | import EmptyStateWithAnyIssues from './empty_state_with_any_issues.vue'; | ||||||
| import EmptyStateWithoutAnyIssues from './empty_state_without_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 UserToken = () => import('~/vue_shared/components/filtered_search_bar/tokens/user_token.vue'); | ||||||
| const EmojiToken = () => | const EmojiToken = () => | ||||||
|  | @ -117,7 +118,7 @@ export default { | ||||||
|   directives: { |   directives: { | ||||||
|     GlTooltip: GlTooltipDirective, |     GlTooltip: GlTooltipDirective, | ||||||
|   }, |   }, | ||||||
|   mixins: [glFeatureFlagMixin()], |   mixins: [glFeatureFlagMixin(), hasNewIssueDropdown()], | ||||||
|   inject: [ |   inject: [ | ||||||
|     'autocompleteAwardEmojisPath', |     'autocompleteAwardEmojisPath', | ||||||
|     'calendarPath', |     'calendarPath', | ||||||
|  | @ -831,7 +832,12 @@ export default { | ||||||
|           {{ $options.i18n.newIssueLabel }} |           {{ $options.i18n.newIssueLabel }} | ||||||
|         </gl-button> |         </gl-button> | ||||||
|         <slot name="new-objective-button"></slot> |         <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> | ||||||
| 
 | 
 | ||||||
|       <template #timeframe="{ issuable = {} }"> |       <template #timeframe="{ issuable = {} }"> | ||||||
|  |  | ||||||
|  | @ -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, | ||||||
|  | }); | ||||||
|  | @ -9,16 +9,17 @@ export const HTTP_STATUS_PARTIAL_CONTENT = 206; | ||||||
| export const HTTP_STATUS_MULTI_STATUS = 207; | export const HTTP_STATUS_MULTI_STATUS = 207; | ||||||
| export const HTTP_STATUS_ALREADY_REPORTED = 208; | export const HTTP_STATUS_ALREADY_REPORTED = 208; | ||||||
| export const HTTP_STATUS_IM_USED = 226; | 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_BAD_REQUEST = 400; | ||||||
| export const HTTP_STATUS_UNAUTHORIZED = 401; | export const HTTP_STATUS_UNAUTHORIZED = 401; | ||||||
| export const HTTP_STATUS_FORBIDDEN = 403; | export const HTTP_STATUS_FORBIDDEN = 403; | ||||||
| export const HTTP_STATUS_NOT_FOUND = 404; | 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_INTERNAL_SERVER_ERROR = 500; | ||||||
| export const HTTP_STATUS_SERVICE_UNAVAILABLE = 503; | export const HTTP_STATUS_SERVICE_UNAVAILABLE = 503; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,11 @@ | ||||||
|  | query searchUserProjects($search: String) { | ||||||
|  |   projects(search: $search, membership: true, sort: "latest_activity_desc") { | ||||||
|  |     nodes { | ||||||
|  |       id | ||||||
|  |       issuesEnabled | ||||||
|  |       name | ||||||
|  |       nameWithNamespace | ||||||
|  |       webUrl | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -10,7 +10,7 @@ import { createAlert } from '~/flash'; | ||||||
| import { DASH_SCOPE, joinPaths } from '~/lib/utils/url_utility'; | import { DASH_SCOPE, joinPaths } from '~/lib/utils/url_utility'; | ||||||
| import { __, sprintf } from '~/locale'; | import { __, sprintf } from '~/locale'; | ||||||
| import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants'; | 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 { | export default { | ||||||
|   i18n: { |   i18n: { | ||||||
|  | @ -25,7 +25,23 @@ export default { | ||||||
|     GlLoadingIcon, |     GlLoadingIcon, | ||||||
|     GlSearchBoxByType, |     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() { |   data() { | ||||||
|     return { |     return { | ||||||
|       projects: [], |       projects: [], | ||||||
|  | @ -36,14 +52,18 @@ export default { | ||||||
|   }, |   }, | ||||||
|   apollo: { |   apollo: { | ||||||
|     projects: { |     projects: { | ||||||
|       query: searchProjectsQuery, |       query() { | ||||||
|  |         return this.query; | ||||||
|  |       }, | ||||||
|       variables() { |       variables() { | ||||||
|         return { |         return { | ||||||
|           fullPath: this.fullPath, |  | ||||||
|           search: this.search, |           search: this.search, | ||||||
|  |           ...this.queryVariables, | ||||||
|         }; |         }; | ||||||
|       }, |       }, | ||||||
|       update: ({ group }) => group.projects.nodes ?? [], |       update(data) { | ||||||
|  |         return this.extractProjects(data) || []; | ||||||
|  |       }, | ||||||
|       error(error) { |       error(error) { | ||||||
|         createAlert({ |         createAlert({ | ||||||
|           message: __('An error occurred while loading projects.'), |           message: __('An error occurred while loading projects.'), | ||||||
|  | @ -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 |  | ||||||
|  | @ -5,7 +5,6 @@ class Projects::PipelinesController < Projects::ApplicationController | ||||||
|   include RedisTracking |   include RedisTracking | ||||||
|   include ProductAnalyticsTracking |   include ProductAnalyticsTracking | ||||||
|   include ProjectStatsRefreshConflictsGuard |   include ProjectStatsRefreshConflictsGuard | ||||||
|   include ZuoraCSP |  | ||||||
| 
 | 
 | ||||||
|   urgency :low, [ |   urgency :low, [ | ||||||
|     :index, :new, :builds, :show, :failures, :create, |     :index, :new, :builds, :show, :failures, :create, | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ module Projects | ||||||
|   module Settings |   module Settings | ||||||
|     class CiCdController < Projects::ApplicationController |     class CiCdController < Projects::ApplicationController | ||||||
|       include RunnerSetupScripts |       include RunnerSetupScripts | ||||||
|       include ZuoraCSP |  | ||||||
| 
 | 
 | ||||||
|       NUMBER_OF_RUNNERS_PER_PAGE = 20 |       NUMBER_OF_RUNNERS_PER_PAGE = 20 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -394,6 +394,7 @@ class MergeRequest < ApplicationRecord | ||||||
|   scope :order_closed_at_desc, -> { order_by_metric(:latest_closed_at, 'DESC') } |   scope :order_closed_at_desc, -> { order_by_metric(:latest_closed_at, 'DESC') } | ||||||
|   scope :preload_source_project, -> { preload(:source_project) } |   scope :preload_source_project, -> { preload(:source_project) } | ||||||
|   scope :preload_target_project, -> { preload(:target_project) } |   scope :preload_target_project, -> { preload(:target_project) } | ||||||
|  |   scope :preload_target_project_with_namespace, -> { preload(target_project: [:namespace]) } | ||||||
|   scope :preload_routables, -> do |   scope :preload_routables, -> do | ||||||
|     preload(target_project: [:route, { namespace: :route }], |     preload(target_project: [:route, { namespace: :route }], | ||||||
|             source_project: [:route, { namespace: :route }]) |             source_project: [:route, { namespace: :route }]) | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | 16e7446f8fba7fe0b76559432ac6ecc30261a5775b9f914c77425ceab3b92315 | ||||||
|  | @ -18479,7 +18479,7 @@ CREATE TABLE oauth_access_tokens ( | ||||||
|     application_id integer, |     application_id integer, | ||||||
|     token character varying NOT NULL, |     token character varying NOT NULL, | ||||||
|     refresh_token character varying, |     refresh_token character varying, | ||||||
|     expires_in integer, |     expires_in integer DEFAULT 7200, | ||||||
|     revoked_at timestamp without time zone, |     revoked_at timestamp without time zone, | ||||||
|     created_at timestamp without time zone NOT NULL, |     created_at timestamp without time zone NOT NULL, | ||||||
|     scopes character varying |     scopes character varying | ||||||
|  | @ -25632,6 +25632,9 @@ ALTER TABLE ONLY chat_teams | ||||||
| ALTER TABLE vulnerability_scanners | ALTER TABLE vulnerability_scanners | ||||||
|     ADD CONSTRAINT check_37608c9db5 CHECK ((char_length(vendor) <= 255)) NOT VALID; |     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 | ALTER TABLE sprints | ||||||
|     ADD CONSTRAINT check_ccd8a1eae0 CHECK ((start_date IS NOT NULL)) NOT VALID; |     ADD CONSTRAINT check_ccd8a1eae0 CHECK ((start_date IS NOT NULL)) NOT VALID; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -397,14 +397,15 @@ Example response: | ||||||
| Download a release asset file by making a request with the following format: | Download a release asset file by making a request with the following format: | ||||||
| 
 | 
 | ||||||
| ```plaintext | ```plaintext | ||||||
| GET /projects/:id/releases/:tag_name/downloads/:filepath | GET /projects/:id/releases/:tag_name/downloads/:direct_asset_path | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| | Attribute                  | Type           | Required | Description                                                                         | | | Attribute                  | Type           | Required | Description                                                                         | | ||||||
| |----------------------------| -------------- | -------- | ----------------------------------------------------------------------------------- | | |----------------------------| -------------- | -------- | ----------------------------------------------------------------------------------- | | ||||||
| | `id`                       | integer/string | yes      | The ID or [URL-encoded path of the project](../rest/index.md#namespaced-path-encoding).  | | | `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.                                         | | | `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: | Example request: | ||||||
| 
 | 
 | ||||||
|  | @ -463,15 +464,16 @@ POST /projects/:id/releases | ||||||
| | `assets:links`     | array of hash   | no                          | An array of assets links.                                                                                                        | | | `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: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: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:filepath` | string     | no | Deprecated: Use `direct_asset_path` instead. | | ||||||
| | `assets:links:link_type` | string     | no | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`. | | `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.  | | | `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: | Example request: | ||||||
| 
 | 
 | ||||||
| ```shell | ```shell | ||||||
| curl --header 'Content-Type: application/json' --header "PRIVATE-TOKEN: <your_access_token>" \ | 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" |      --request POST "https://gitlab.example.com/api/v4/projects/24/releases" | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -93,14 +93,15 @@ Creates an asset as a link from a release. | ||||||
| POST /projects/:id/releases/:tag_name/assets/links | POST /projects/:id/releases/:tag_name/assets/links | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| | Attribute   | Type           | Required | Description                                                                                                               | | | Attribute            | Type           | Required | Description                                                                                                               | | ||||||
| |-------------|----------------|----------|---------------------------------------------------------------------------------------------------------------------------| | |----------------------|----------------|----------|---------------------------------------------------------------------------------------------------------------------------| | ||||||
| | `id`        | integer/string | yes      | The ID or [URL-encoded path of the project](../rest/index.md#namespaced-path-encoding).                                        | | | `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.                                                                                      | | | `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.                                                           | | | `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.                                                             | | | `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). | | | `filepath`           | string         | no       | Deprecated: Use `direct_asset_path` instead.                                                                              | | ||||||
| | `link_type` | string         | no       | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`.                                        | | | `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: | Example request: | ||||||
| 
 | 
 | ||||||
|  | @ -109,7 +110,7 @@ curl --request POST \ | ||||||
|     --header "PRIVATE-TOKEN: <your_access_token>" \ |     --header "PRIVATE-TOKEN: <your_access_token>" \ | ||||||
|     --data name="hellodarwin-amd64" \ |     --data name="hellodarwin-amd64" \ | ||||||
|     --data url="https://gitlab.example.com/mynamespace/hello/-/jobs/688/artifacts/raw/bin/hello-darwin-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" |     "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 | PUT /projects/:id/releases/:tag_name/assets/links/:link_id | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| | Attribute     | Type           | Required | Description                             | | | Attribute            | Type           | Required | Description                                                                                                               | | ||||||
| | ------------- | -------------- | -------- | --------------------------------------- | | | -------------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- | | ||||||
| | `id`          | integer/string | yes      | The ID or [URL-encoded path of the project](../rest/index.md#namespaced-path-encoding). | | | `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. | | | `tag_name`           | string         | yes      | The tag associated with the Release. | | ||||||
| | `link_id`     | integer         | yes      | The ID of the link. | | | `link_id`            | integer        | yes      | The ID of the link. | | ||||||
| | `name`        | string         | no | The name of the link. | | | `name`               | string         | no       | The name of the link. | | ||||||
| | `url`         | string         | no | The URL 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). | | `filepath`           | string         | no       | Deprecated: Use `direct_asset_path` instead. | | ||||||
| | `link_type`        | string         | no       | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`. | | | `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: | NOTE: | ||||||
| You have to specify at least one of `name` or `url` | You have to specify at least one of `name` or `url` | ||||||
|  |  | ||||||
|  | @ -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> | 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 | ### Tooling feature category | ||||||
| 
 | 
 | ||||||
| For Engineering Productivity internal tooling we use `feature_category: :tooling`. | For Engineering Productivity internal tooling we use `feature_category: :tooling`. | ||||||
|  |  | ||||||
|  | @ -61,6 +61,38 @@ To enable Arkose Protect: | ||||||
|    Feature.enable(:arkose_labs_prevent_login) |    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 | ## QA tests caveat | ||||||
| 
 | 
 | ||||||
| Several GitLab QA test suites need to sign in to the app to test its features. This can conflict | Several GitLab QA test suites need to sign in to the app to test its features. This can conflict | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
|  | @ -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. | > - [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. | > - 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. | > - [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 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, | 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. | 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. | 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: | To generate the Chain of Custody report: | ||||||
| 
 | 
 | ||||||
| 1. On the top bar, select **Main menu > Groups** and find your group. | 1. On the top bar, select **Main menu > Groups** and find your group. | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ module Gitlab | ||||||
|           'manifest_src' => "'self'", |           'manifest_src' => "'self'", | ||||||
|           'media_src' => "'self' data: http: https:", |           'media_src' => "'self' data: http: https:", | ||||||
|           'script_src' => ContentSecurityPolicy::Directives.script_src, |           '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:", |           'worker_src' => "#{Gitlab::Utils.append_path(Gitlab.config.gitlab.url, 'assets/')} blob: data:", | ||||||
|           'object_src' => "'none'", |           'object_src' => "'none'", | ||||||
|           'report_uri' => nil |           'report_uri' => nil | ||||||
|  | @ -43,6 +43,7 @@ module Gitlab | ||||||
| 
 | 
 | ||||||
|         allow_websocket_connections(directives) |         allow_websocket_connections(directives) | ||||||
|         allow_cdn(directives, Settings.gitlab.cdn_host) if Settings.gitlab.cdn_host.present? |         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 |         # Support for Sentry setup via configuration files will be removed in 16.0 | ||||||
|         # in favor of Gitlab::CurrentSettings. |         # in favor of Gitlab::CurrentSettings. | ||||||
|         allow_legacy_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn |         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) |         append_to_directive(directives, 'frame_src', cdn_host) | ||||||
|       end |       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) |       def self.append_to_directive(directives, directive, text) | ||||||
|         directives[directive] = "#{directives[directive]} #{text}".strip |         directives[directive] = "#{directives[directive]} #{text}".strip | ||||||
|       end |       end | ||||||
|  |  | ||||||
|  | @ -18,6 +18,10 @@ module Gitlab | ||||||
|       def self.script_src |       def self.script_src | ||||||
|         "'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com" |         "'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com" | ||||||
|       end |       end | ||||||
|  | 
 | ||||||
|  |       def self.style_src | ||||||
|  |         "'self' 'unsafe-inline'" | ||||||
|  |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -25137,6 +25137,9 @@ msgstr "" | ||||||
| msgid "List available repositories" | msgid "List available repositories" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "List of all commits" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "List of all merge commits" | msgid "List of all merge commits" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -32,8 +32,7 @@ module Gitlab | ||||||
|           div :project |           div :project | ||||||
|           div :storage_type_legend |           div :storage_type_legend | ||||||
|           span :container_registry_size |           span :container_registry_size | ||||||
|           div :purchased_usage_total_free # Different UI for free namespace |           div :purchased_usage_total | ||||||
|           span :purchased_usage_total |  | ||||||
|           div :storage_purchase_successful_alert, text: /You have successfully purchased a storage/ |           div :storage_purchase_successful_alert, text: /You have successfully purchased a storage/ | ||||||
|           div :additional_storage_alert, text: /purchase additional 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 |           # Returns total purchased storage value once it's ready on page | ||||||
|           # |           # | ||||||
|           # @return [Float] Total purchased storage value in GiB |           # @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?) |             additional_storage_alert_element.wait_until(&:present?) | ||||||
| 
 | 
 | ||||||
|             if free_name_space |             purchased_usage_total[/(\d+){2}.\d+/].to_f | ||||||
|               purchased_usage_total_free.split('/').last.match(/\d+\.\d+/)[0].to_f |  | ||||||
|             else |  | ||||||
|               purchased_usage_total.to_f |  | ||||||
|             end |  | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|           def additional_ci_minutes_added? |           def additional_ci_minutes_added? | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| require 'spec_helper' | require 'spec_helper' | ||||||
| 
 | 
 | ||||||
| RSpec.describe DashboardController do | RSpec.describe DashboardController, feature_category: :code_review_workflow do | ||||||
|   context 'signed in' do |   context 'signed in' do | ||||||
|     let_it_be(:user) { create(:user) } |     let_it_be(:user) { create(:user) } | ||||||
|     let_it_be(:project) { create(:project) } |     let_it_be(:project) { create(:project) } | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category: | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   before do |   before do | ||||||
|  |     stub_feature_flags(refactor_security_extension: false) | ||||||
|     project.add_maintainer(user) |     project.add_maintainer(user) | ||||||
|     project_only_mwps.add_maintainer(user) |     project_only_mwps.add_maintainer(user) | ||||||
|     sign_in(user) |     sign_in(user) | ||||||
|  |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -8,6 +8,7 @@ import statisticsLabels from '~/admin/statistics_panel/constants'; | ||||||
| import createStore from '~/admin/statistics_panel/store'; | import createStore from '~/admin/statistics_panel/store'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
| import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; | import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; | ||||||
|  | import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
| import mockStatistics from '../mock_data'; | import mockStatistics from '../mock_data'; | ||||||
| 
 | 
 | ||||||
| Vue.use(Vuex); | Vue.use(Vuex); | ||||||
|  | @ -25,7 +26,7 @@ describe('Admin statistics app', () => { | ||||||
| 
 | 
 | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     axiosMock = new AxiosMockAdapter(axios); |     axiosMock = new AxiosMockAdapter(axios); | ||||||
|     axiosMock.onGet(/api\/(.*)\/application\/statistics/).reply(200); |     axiosMock.onGet(/api\/(.*)\/application\/statistics/).reply(HTTP_STATUS_OK); | ||||||
|     store = createStore(); |     store = createStore(); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import testAction from 'helpers/vuex_action_helper'; | ||||||
| import service from '~/batch_comments/services/drafts_service'; | import service from '~/batch_comments/services/drafts_service'; | ||||||
| import * as actions from '~/batch_comments/stores/modules/batch_comments/actions'; | import * as actions from '~/batch_comments/stores/modules/batch_comments/actions'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
|  | import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
| 
 | 
 | ||||||
| describe('Batch comments store actions', () => { | describe('Batch comments store actions', () => { | ||||||
|   let res = {}; |   let res = {}; | ||||||
|  | @ -31,7 +32,7 @@ describe('Batch comments store actions', () => { | ||||||
|   describe('addDraftToDiscussion', () => { |   describe('addDraftToDiscussion', () => { | ||||||
|     it('commits ADD_NEW_DRAFT if no errors returned', () => { |     it('commits ADD_NEW_DRAFT if no errors returned', () => { | ||||||
|       res = { id: 1 }; |       res = { id: 1 }; | ||||||
|       mock.onAny().reply(200, res); |       mock.onAny().reply(HTTP_STATUS_OK, res); | ||||||
| 
 | 
 | ||||||
|       return testAction( |       return testAction( | ||||||
|         actions.addDraftToDiscussion, |         actions.addDraftToDiscussion, | ||||||
|  | @ -58,7 +59,7 @@ describe('Batch comments store actions', () => { | ||||||
|   describe('createNewDraft', () => { |   describe('createNewDraft', () => { | ||||||
|     it('commits ADD_NEW_DRAFT if no errors returned', () => { |     it('commits ADD_NEW_DRAFT if no errors returned', () => { | ||||||
|       res = { id: 1 }; |       res = { id: 1 }; | ||||||
|       mock.onAny().reply(200, res); |       mock.onAny().reply(HTTP_STATUS_OK, res); | ||||||
| 
 | 
 | ||||||
|       return testAction( |       return testAction( | ||||||
|         actions.createNewDraft, |         actions.createNewDraft, | ||||||
|  | @ -100,7 +101,7 @@ describe('Batch comments store actions', () => { | ||||||
|         commit, |         commit, | ||||||
|       }; |       }; | ||||||
|       res = { id: 1 }; |       res = { id: 1 }; | ||||||
|       mock.onAny().reply(200); |       mock.onAny().reply(HTTP_STATUS_OK); | ||||||
| 
 | 
 | ||||||
|       return actions.deleteDraft(context, { id: 1 }).then(() => { |       return actions.deleteDraft(context, { id: 1 }).then(() => { | ||||||
|         expect(commit).toHaveBeenCalledWith('DELETE_DRAFT', 1); |         expect(commit).toHaveBeenCalledWith('DELETE_DRAFT', 1); | ||||||
|  | @ -144,7 +145,7 @@ describe('Batch comments store actions', () => { | ||||||
|         }, |         }, | ||||||
|       }; |       }; | ||||||
|       res = { id: 1 }; |       res = { id: 1 }; | ||||||
|       mock.onAny().reply(200, res); |       mock.onAny().reply(HTTP_STATUS_OK, res); | ||||||
| 
 | 
 | ||||||
|       return actions.fetchDrafts(context).then(() => { |       return actions.fetchDrafts(context).then(() => { | ||||||
|         expect(commit).toHaveBeenCalledWith('SET_BATCH_COMMENTS_DRAFTS', { id: 1 }); |         expect(commit).toHaveBeenCalledWith('SET_BATCH_COMMENTS_DRAFTS', { id: 1 }); | ||||||
|  | @ -169,7 +170,7 @@ describe('Batch comments store actions', () => { | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('dispatches actions & commits', () => { |     it('dispatches actions & commits', () => { | ||||||
|       mock.onAny().reply(200); |       mock.onAny().reply(HTTP_STATUS_OK); | ||||||
| 
 | 
 | ||||||
|       return actions.publishReview({ dispatch, commit, getters, rootGetters }).then(() => { |       return actions.publishReview({ dispatch, commit, getters, rootGetters }).then(() => { | ||||||
|         expect(commit.mock.calls[0]).toEqual(['REQUEST_PUBLISH_REVIEW']); |         expect(commit.mock.calls[0]).toEqual(['REQUEST_PUBLISH_REVIEW']); | ||||||
|  | @ -180,7 +181,7 @@ describe('Batch comments store actions', () => { | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('calls service with notes data', () => { |     it('calls service with notes data', () => { | ||||||
|       mock.onAny().reply(200); |       mock.onAny().reply(HTTP_STATUS_OK); | ||||||
|       jest.spyOn(axios, 'post'); |       jest.spyOn(axios, 'post'); | ||||||
| 
 | 
 | ||||||
|       return actions |       return actions | ||||||
|  | @ -221,7 +222,7 @@ describe('Batch comments store actions', () => { | ||||||
|         commit, |         commit, | ||||||
|       }; |       }; | ||||||
|       res = { id: 1 }; |       res = { id: 1 }; | ||||||
|       mock.onAny().reply(200, res); |       mock.onAny().reply(HTTP_STATUS_OK, res); | ||||||
|       params = { note: { id: 1 }, noteText: 'test' }; |       params = { note: { id: 1 }, noteText: 'test' }; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ import axios from 'axios'; | ||||||
| import MockAdapter from 'axios-mock-adapter'; | import MockAdapter from 'axios-mock-adapter'; | ||||||
| import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | ||||||
| import renderOpenApi from '~/blob/openapi'; | import renderOpenApi from '~/blob/openapi'; | ||||||
|  | import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
| 
 | 
 | ||||||
| describe('OpenAPI blob viewer', () => { | describe('OpenAPI blob viewer', () => { | ||||||
|   const id = 'js-openapi-viewer'; |   const id = 'js-openapi-viewer'; | ||||||
|  | @ -10,7 +11,7 @@ describe('OpenAPI blob viewer', () => { | ||||||
| 
 | 
 | ||||||
|   beforeEach(async () => { |   beforeEach(async () => { | ||||||
|     setHTMLFixture(`<div id="${id}" data-endpoint="${mockEndpoint}"></div>`); |     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(); |     await renderOpenApi(); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | ||||||
| import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; | import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; | ||||||
| import Clusters from '~/clusters/clusters_bundle'; | import Clusters from '~/clusters/clusters_bundle'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
|  | import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
| import initProjectSelectDropdown from '~/project_select'; | import initProjectSelectDropdown from '~/project_select'; | ||||||
| 
 | 
 | ||||||
| jest.mock('~/lib/utils/poll'); | jest.mock('~/lib/utils/poll'); | ||||||
|  | @ -19,7 +20,7 @@ describe('Clusters', () => { | ||||||
| 
 | 
 | ||||||
|     mock = new MockAdapter(axios); |     mock = new MockAdapter(axios); | ||||||
| 
 | 
 | ||||||
|     mock.onGet(statusPath).reply(200); |     mock.onGet(statusPath).reply(HTTP_STATUS_OK); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter'; | ||||||
| import testAction from 'helpers/vuex_action_helper'; | import testAction from 'helpers/vuex_action_helper'; | ||||||
| import * as actions from '~/emoji/awards_app/store/actions'; | import * as actions from '~/emoji/awards_app/store/actions'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
|  | import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
| 
 | 
 | ||||||
| jest.mock('@sentry/browser'); | jest.mock('@sentry/browser'); | ||||||
| jest.mock('~/vue_shared/plugins/global_toast'); | jest.mock('~/vue_shared/plugins/global_toast'); | ||||||
|  | @ -152,7 +153,7 @@ describe('Awards app actions', () => { | ||||||
| 
 | 
 | ||||||
|         describe('success', () => { |         describe('success', () => { | ||||||
|           beforeEach(() => { |           beforeEach(() => { | ||||||
|             mock.onDelete(`${relativeRootUrl || ''}/awards/1`).reply(200); |             mock.onDelete(`${relativeRootUrl || ''}/awards/1`).reply(HTTP_STATUS_OK); | ||||||
|           }); |           }); | ||||||
| 
 | 
 | ||||||
|           it('commits REMOVE_AWARD', async () => { |           it('commits REMOVE_AWARD', async () => { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import MockAdapter from 'axios-mock-adapter'; | import MockAdapter from 'axios-mock-adapter'; | ||||||
| import { s__ } from '~/locale'; | import { s__ } from '~/locale'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
|  | import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
| import { resolvers } from '~/environments/graphql/resolvers'; | import { resolvers } from '~/environments/graphql/resolvers'; | ||||||
| import environmentToRollback from '~/environments/graphql/queries/environment_to_rollback.query.graphql'; | import environmentToRollback from '~/environments/graphql/queries/environment_to_rollback.query.graphql'; | ||||||
| import environmentToDelete from '~/environments/graphql/queries/environment_to_delete.query.graphql'; | import environmentToDelete from '~/environments/graphql/queries/environment_to_delete.query.graphql'; | ||||||
|  | @ -44,7 +45,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { | ||||||
|       const search = ''; |       const search = ''; | ||||||
|       mock |       mock | ||||||
|         .onGet(ENDPOINT, { params: { nested: true, scope, page: 1, search } }) |         .onGet(ENDPOINT, { params: { nested: true, scope, page: 1, search } }) | ||||||
|         .reply(200, environmentsApp, {}); |         .reply(HTTP_STATUS_OK, environmentsApp, {}); | ||||||
| 
 | 
 | ||||||
|       const app = await mockResolvers.Query.environmentApp( |       const app = await mockResolvers.Query.environmentApp( | ||||||
|         null, |         null, | ||||||
|  | @ -63,7 +64,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { | ||||||
|       const interval = 3000; |       const interval = 3000; | ||||||
|       mock |       mock | ||||||
|         .onGet(ENDPOINT, { params: { nested: true, scope, page: 1, search: '' } }) |         .onGet(ENDPOINT, { params: { nested: true, scope, page: 1, search: '' } }) | ||||||
|         .reply(200, environmentsApp, { |         .reply(HTTP_STATUS_OK, environmentsApp, { | ||||||
|           'poll-interval': interval, |           'poll-interval': interval, | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  | @ -78,7 +79,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { | ||||||
|       const scope = 'stopped'; |       const scope = 'stopped'; | ||||||
|       mock |       mock | ||||||
|         .onGet(ENDPOINT, { params: { nested: true, scope, page: 1, search: '' } }) |         .onGet(ENDPOINT, { params: { nested: true, scope, page: 1, search: '' } }) | ||||||
|         .reply(200, environmentsApp, { |         .reply(HTTP_STATUS_OK, environmentsApp, { | ||||||
|           'x-next-page': '2', |           'x-next-page': '2', | ||||||
|           'x-page': '1', |           'x-page': '1', | ||||||
|           'X-Per-Page': '2', |           'X-Per-Page': '2', | ||||||
|  | @ -108,7 +109,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { | ||||||
|       const scope = 'stopped'; |       const scope = 'stopped'; | ||||||
|       mock |       mock | ||||||
|         .onGet(ENDPOINT, { params: { nested: true, scope, page: 1, search: '' } }) |         .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 }); |       await mockResolvers.Query.environmentApp(null, { scope, page: 1, search: '' }, { cache }); | ||||||
|       expect(cache.writeQuery).toHaveBeenCalledWith({ |       expect(cache.writeQuery).toHaveBeenCalledWith({ | ||||||
|  | @ -131,7 +132,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { | ||||||
|     it('should fetch the folder url passed to it', async () => { |     it('should fetch the folder url passed to it', async () => { | ||||||
|       mock |       mock | ||||||
|         .onGet(ENDPOINT, { params: { per_page: 3, scope: 'available', search: '' } }) |         .onGet(ENDPOINT, { params: { per_page: 3, scope: 'available', search: '' } }) | ||||||
|         .reply(200, folder); |         .reply(HTTP_STATUS_OK, folder); | ||||||
| 
 | 
 | ||||||
|       const environmentFolder = await mockResolvers.Query.folder(null, { |       const environmentFolder = await mockResolvers.Query.folder(null, { | ||||||
|         environment: { folderPath: ENDPOINT }, |         environment: { folderPath: ENDPOINT }, | ||||||
|  | @ -144,7 +145,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { | ||||||
|   }); |   }); | ||||||
|   describe('stopEnvironment', () => { |   describe('stopEnvironment', () => { | ||||||
|     it('should post to the stop environment path', async () => { |     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 client = { writeQuery: jest.fn() }; | ||||||
|       const environment = { stopPath: ENDPOINT }; |       const environment = { stopPath: ENDPOINT }; | ||||||
|  | @ -180,7 +181,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { | ||||||
|   }); |   }); | ||||||
|   describe('rollbackEnvironment', () => { |   describe('rollbackEnvironment', () => { | ||||||
|     it('should post to the retry environment path', async () => { |     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, { |       await mockResolvers.Mutation.rollbackEnvironment(null, { | ||||||
|         environment: { retryUrl: ENDPOINT }, |         environment: { retryUrl: ENDPOINT }, | ||||||
|  | @ -193,7 +194,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { | ||||||
|   }); |   }); | ||||||
|   describe('deleteEnvironment', () => { |   describe('deleteEnvironment', () => { | ||||||
|     it('should DELETE to the delete environment path', async () => { |     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, { |       await mockResolvers.Mutation.deleteEnvironment(null, { | ||||||
|         environment: { deletePath: ENDPOINT }, |         environment: { deletePath: ENDPOINT }, | ||||||
|  | @ -206,7 +207,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { | ||||||
|   }); |   }); | ||||||
|   describe('cancelAutoStop', () => { |   describe('cancelAutoStop', () => { | ||||||
|     it('should post to the auto stop path', async () => { |     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 }); |       await mockResolvers.Mutation.cancelAutoStop(null, { autoStopUrl: ENDPOINT }); | ||||||
| 
 | 
 | ||||||
|  | @ -262,7 +263,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { | ||||||
|   }); |   }); | ||||||
|   describe('action', () => { |   describe('action', () => { | ||||||
|     it('should POST to the given path', async () => { |     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 } }); |       const errors = await mockResolvers.Mutation.action(null, { action: { playPath: ENDPOINT } }); | ||||||
| 
 | 
 | ||||||
|       expect(errors).toEqual({ __typename: 'LocalEnvironmentErrors', errors: [] }); |       expect(errors).toEqual({ __typename: 'LocalEnvironmentErrors', errors: [] }); | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import * as types from '~/error_tracking_settings/store/mutation_types'; | ||||||
| import defaultState from '~/error_tracking_settings/store/state'; | import defaultState from '~/error_tracking_settings/store/state'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
| import { convertObjectPropsToCamelCase } from '~/lib/utils/common_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 { refreshCurrentPage } from '~/lib/utils/url_utility'; | ||||||
| import { projectList } from '../mock'; | import { projectList } from '../mock'; | ||||||
| 
 | 
 | ||||||
|  | @ -118,7 +119,7 @@ describe('error tracking settings actions', () => { | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should save the page', async () => { |     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' }]); |       await testAction(actions.updateSettings, null, state, [], [{ type: 'requestSettings' }]); | ||||||
|       expect(mock.history.patch.length).toBe(1); |       expect(mock.history.patch.length).toBe(1); | ||||||
|       expect(refreshCurrentPage).toHaveBeenCalled(); |       expect(refreshCurrentPage).toHaveBeenCalled(); | ||||||
|  |  | ||||||
|  | @ -2,13 +2,14 @@ import axios from 'axios'; | ||||||
| import MockAdapter from 'axios-mock-adapter'; | import MockAdapter from 'axios-mock-adapter'; | ||||||
| import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | ||||||
| import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager'; | import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager'; | ||||||
|  | import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
| 
 | 
 | ||||||
| describe('Filtered Search Dropdown Manager', () => { | describe('Filtered Search Dropdown Manager', () => { | ||||||
|   let mock; |   let mock; | ||||||
| 
 | 
 | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     mock = new MockAdapter(axios); |     mock = new MockAdapter(axios); | ||||||
|     mock.onGet().reply(200); |     mock.onGet().reply(HTTP_STATUS_OK); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   describe('addWordToInput', () => { |   describe('addWordToInput', () => { | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | ||||||
| import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper'; | import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper'; | ||||||
| import waitForPromises from 'helpers/wait_for_promises'; | import waitForPromises from 'helpers/wait_for_promises'; | ||||||
| import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens'; | 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'; | import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants'; | ||||||
| 
 | 
 | ||||||
| describe('Filtered Search Visual Tokens', () => { | describe('Filtered Search Visual Tokens', () => { | ||||||
|  | @ -24,7 +25,7 @@ describe('Filtered Search Visual Tokens', () => { | ||||||
| 
 | 
 | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     mock = new MockAdapter(axios); |     mock = new MockAdapter(axios); | ||||||
|     mock.onGet().reply(200); |     mock.onGet().reply(HTTP_STATUS_OK); | ||||||
| 
 | 
 | ||||||
|     setHTMLFixture(` |     setHTMLFixture(` | ||||||
|       <ul class="tokens-container"> |       <ul class="tokens-container"> | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import Api from '~/api'; | ||||||
| import dismissUserCallout from '~/graphql_shared/mutations/dismiss_user_callout.mutation.graphql'; | import dismissUserCallout from '~/graphql_shared/mutations/dismiss_user_callout.mutation.graphql'; | ||||||
| import services from '~/ide/services'; | import services from '~/ide/services'; | ||||||
| import { query, mutate } from '~/ide/services/gql'; | import { query, mutate } from '~/ide/services/gql'; | ||||||
|  | import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
| import { escapeFileUrl } from '~/lib/utils/url_utility'; | import { escapeFileUrl } from '~/lib/utils/url_utility'; | ||||||
| import ciConfig from '~/ci/pipeline_editor/graphql/queries/ci_config.query.graphql'; | import ciConfig from '~/ci/pipeline_editor/graphql/queries/ci_config.query.graphql'; | ||||||
| import { projectData } from '../mock_data'; | import { projectData } from '../mock_data'; | ||||||
|  | @ -271,7 +272,7 @@ describe('IDE services', () => { | ||||||
|       const TEST_PROJECT_PATH = 'foo/bar'; |       const TEST_PROJECT_PATH = 'foo/bar'; | ||||||
|       const axiosURL = `${TEST_RELATIVE_URL_ROOT}/${TEST_PROJECT_PATH}/service_ping/web_ide_pipelines_count`; |       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(() => { |       return services.pingUsage(TEST_PROJECT_PATH).then(() => { | ||||||
|         expect(axios.post).toHaveBeenCalledWith(axiosURL); |         expect(axios.post).toHaveBeenCalledWith(axiosURL); | ||||||
|  |  | ||||||
|  | @ -23,6 +23,7 @@ import { | ||||||
| } from '~/ide/stores/actions'; | } from '~/ide/stores/actions'; | ||||||
| import * as types from '~/ide/stores/mutation_types'; | import * as types from '~/ide/stores/mutation_types'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | 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 { visitUrl } from '~/lib/utils/url_utility'; | ||||||
| import { file, createTriggerRenameAction, createTriggerChangeAction } from '../helpers'; | 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 () => { |       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( |         await expect(getBranchData(...callParams)).rejects.toEqual( | ||||||
|           new Error('Branch not loaded - <strong>abc/def/main-testing</strong>'), |           new Error('Branch not loaded - <strong>abc/def/main-testing</strong>'), | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ import createMockApollo from 'helpers/mock_apollo_helper'; | ||||||
| import waitForPromises from 'helpers/wait_for_promises'; | import waitForPromises from 'helpers/wait_for_promises'; | ||||||
| import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; | import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; | ||||||
| import { createAlert } from '~/flash'; | 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 axios from '~/lib/utils/axios_utils'; | ||||||
| import { STATUSES } from '~/import_entities/constants'; | import { STATUSES } from '~/import_entities/constants'; | ||||||
| import { i18n, ROOT_NAMESPACE } from '~/import_entities/import_groups/constants'; | import { i18n, ROOT_NAMESPACE } from '~/import_entities/import_groups/constants'; | ||||||
|  | @ -113,7 +113,7 @@ describe('import table', () => { | ||||||
| 
 | 
 | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     axiosMock = new MockAdapter(axios); |     axiosMock = new MockAdapter(axios); | ||||||
|     axiosMock.onGet(/.*\/exists$/, () => []).reply(200); |     axiosMock.onGet(/.*\/exists$/, () => []).reply(HTTP_STATUS_OK); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   afterEach(() => { |   afterEach(() => { | ||||||
|  |  | ||||||
|  | @ -21,6 +21,7 @@ import { | ||||||
| import state from '~/import_entities/import_projects/store/state'; | import state from '~/import_entities/import_projects/store/state'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
| import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; | import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; | ||||||
|  | import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
| 
 | 
 | ||||||
| jest.mock('~/flash'); | jest.mock('~/flash'); | ||||||
| 
 | 
 | ||||||
|  | @ -433,7 +434,7 @@ describe('import_projects store actions', () => { | ||||||
|     afterEach(() => mock.restore()); |     afterEach(() => mock.restore()); | ||||||
| 
 | 
 | ||||||
|     it('commits CANCEL_IMPORT_SUCCESS on success', async () => { |     it('commits CANCEL_IMPORT_SUCCESS on success', async () => { | ||||||
|       mock.onPost(MOCK_ENDPOINT).reply(200); |       mock.onPost(MOCK_ENDPOINT).reply(HTTP_STATUS_OK); | ||||||
| 
 | 
 | ||||||
|       await testAction( |       await testAction( | ||||||
|         cancelImport, |         cancelImport, | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { GlEmptyState, GlLink } from '@gitlab/ui'; | ||||||
| import { mountExtended } from 'helpers/vue_test_utils_helper'; | import { mountExtended } from 'helpers/vue_test_utils_helper'; | ||||||
| import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue'; | import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue'; | ||||||
| import EmptyStateWithoutAnyIssues from '~/issues/list/components/empty_state_without_any_issues.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'; | import { i18n } from '~/issues/list/constants'; | ||||||
| 
 | 
 | ||||||
| describe('EmptyStateWithoutAnyIssues component', () => { | describe('EmptyStateWithoutAnyIssues component', () => { | ||||||
|  |  | ||||||
|  | @ -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 EmptyStateWithAnyIssues from '~/issues/list/components/empty_state_with_any_issues.vue'; | ||||||
| import EmptyStateWithoutAnyIssues from '~/issues/list/components/empty_state_without_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 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 { | import { | ||||||
|   CREATED_DESC, |   CREATED_DESC, | ||||||
|   RELATIVE_POSITION, |   RELATIVE_POSITION, | ||||||
|  |  | ||||||
|  | @ -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}`); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| }); |  | ||||||
|  | @ -343,49 +343,3 @@ export const urlParamsWithSpecialValues = { | ||||||
|   weight: 'None', |   weight: 'None', | ||||||
|   health_status: '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: [], |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
|  | @ -3,11 +3,12 @@ import waitForPromises from 'helpers/wait_for_promises'; | ||||||
| import { initIssueApp } from '~/issues/show'; | import { initIssueApp } from '~/issues/show'; | ||||||
| import * as parseData from '~/issues/show/utils/parse_data'; | import * as parseData from '~/issues/show/utils/parse_data'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
|  | import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
| import createStore from '~/notes/stores'; | import createStore from '~/notes/stores'; | ||||||
| import { appProps } from './mock_data/mock_data'; | import { appProps } from './mock_data/mock_data'; | ||||||
| 
 | 
 | ||||||
| const mock = new MockAdapter(axios); | const mock = new MockAdapter(axios); | ||||||
| mock.onGet().reply(200); | mock.onGet().reply(HTTP_STATUS_OK); | ||||||
| 
 | 
 | ||||||
| jest.mock('~/lib/utils/poll'); | jest.mock('~/lib/utils/poll'); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,14 +3,15 @@ | ||||||
| import AxiosMockAdapter from 'axios-mock-adapter'; | import AxiosMockAdapter from 'axios-mock-adapter'; | ||||||
| 
 | 
 | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
|  | import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
| 
 | 
 | ||||||
| describe('axios_utils', () => { | describe('axios_utils', () => { | ||||||
|   let mock; |   let mock; | ||||||
| 
 | 
 | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     mock = new AxiosMockAdapter(axios); |     mock = new AxiosMockAdapter(axios); | ||||||
|     mock.onAny('/ok').reply(200); |     mock.onAny('/ok').reply(HTTP_STATUS_OK); | ||||||
|     mock.onAny('/err').reply(500); |     mock.onAny('/err').reply(HTTP_STATUS_INTERNAL_SERVER_ERROR); | ||||||
|     // eslint-disable-next-line jest/no-standalone-expect
 |     // eslint-disable-next-line jest/no-standalone-expect
 | ||||||
|     expect(axios.countActiveRequests()).toBe(0); |     expect(axios.countActiveRequests()).toBe(0); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import { TEST_HOST } from 'helpers/test_constants'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
| import DeleteMilestoneModal from '~/milestones/components/delete_milestone_modal.vue'; | import DeleteMilestoneModal from '~/milestones/components/delete_milestone_modal.vue'; | ||||||
| import eventHub from '~/milestones/event_hub'; | 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 { redirectTo } from '~/lib/utils/url_utility'; | ||||||
| import { createAlert } from '~/flash'; | import { createAlert } from '~/flash'; | ||||||
| 
 | 
 | ||||||
|  | @ -71,9 +72,9 @@ describe('Delete milestone modal', () => { | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it.each` |     it.each` | ||||||
|       statusCode | alertMessage |       statusCode                 | alertMessage | ||||||
|       ${418}     | ${`Failed to delete milestone ${mockProps.milestoneTitle}`} |       ${HTTP_STATUS_IM_A_TEAPOT} | ${`Failed to delete milestone ${mockProps.milestoneTitle}`} | ||||||
|       ${404}     | ${`Milestone ${mockProps.milestoneTitle} was not found`} |       ${HTTP_STATUS_NOT_FOUND}   | ${`Milestone ${mockProps.milestoneTitle} was not found`} | ||||||
|     `(
 |     `(
 | ||||||
|       'displays error if deleting milestone failed with code $statusCode', |       'displays error if deleting milestone failed with code $statusCode', | ||||||
|       async ({ statusCode, alertMessage }) => { |       async ({ statusCode, alertMessage }) => { | ||||||
|  |  | ||||||
|  | @ -918,7 +918,7 @@ describe('Monitoring store actions', () => { | ||||||
| 
 | 
 | ||||||
|     it('stars dashboard if it is not starred', () => { |     it('stars dashboard if it is not starred', () => { | ||||||
|       state.selectedDashboard = unstarredDashboard; |       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, [ |       return testAction(toggleStarredValue, null, state, [ | ||||||
|         { type: types.REQUEST_DASHBOARD_STARRING }, |         { type: types.REQUEST_DASHBOARD_STARRING }, | ||||||
|  | @ -934,7 +934,7 @@ describe('Monitoring store actions', () => { | ||||||
| 
 | 
 | ||||||
|     it('unstars dashboard if it is starred', () => { |     it('unstars dashboard if it is starred', () => { | ||||||
|       state.selectedDashboard = starredDashboard; |       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, [ |       return testAction(toggleStarredValue, null, state, [ | ||||||
|         { type: types.REQUEST_DASHBOARD_STARRING }, |         { type: types.REQUEST_DASHBOARD_STARRING }, | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter'; | ||||||
| import { getByText as getByTextHelper } from '@testing-library/dom'; | import { getByText as getByTextHelper } from '@testing-library/dom'; | ||||||
| import { GlToggle } from '@gitlab/ui'; | import { GlToggle } from '@gitlab/ui'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | 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 { useMockLocationHelper } from 'helpers/mock_window_location_helper'; | ||||||
| import NewNavToggle from '~/nav/components/new_nav_toggle.vue'; | import NewNavToggle from '~/nav/components/new_nav_toggle.vue'; | ||||||
| import waitForPromises from 'helpers/wait_for_promises'; | import waitForPromises from 'helpers/wait_for_promises'; | ||||||
|  | @ -74,7 +75,7 @@ describe('NewNavToggle', () => { | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('reloads the page on success', async () => { |     it('reloads the page on success', async () => { | ||||||
|       mock.onPut(TEST_ENDPONT).reply(200); |       mock.onPut(TEST_ENDPONT).reply(HTTP_STATUS_OK); | ||||||
| 
 | 
 | ||||||
|       actFn(); |       actFn(); | ||||||
|       await waitForPromises(); |       await waitForPromises(); | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import { mount } from '@vue/test-utils'; | ||||||
| import MockAdapter from 'axios-mock-adapter'; | import MockAdapter from 'axios-mock-adapter'; | ||||||
| import CiIcon from '~/vue_shared/components/ci_icon.vue'; | import CiIcon from '~/vue_shared/components/ci_icon.vue'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | 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 PipelineStage from '~/pipelines/components/pipeline_mini_graph/pipeline_stage.vue'; | ||||||
| import eventHub from '~/pipelines/event_hub'; | import eventHub from '~/pipelines/event_hub'; | ||||||
| import waitForPromises from 'helpers/wait_for_promises'; | import waitForPromises from 'helpers/wait_for_promises'; | ||||||
|  | @ -188,8 +189,8 @@ describe('Pipelines stage component', () => { | ||||||
| 
 | 
 | ||||||
|   describe('job update in dropdown', () => { |   describe('job update in dropdown', () => { | ||||||
|     beforeEach(async () => { |     beforeEach(async () => { | ||||||
|       mock.onGet(dropdownPath).reply(200, stageReply); |       mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply); | ||||||
|       mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200); |       mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(HTTP_STATUS_OK); | ||||||
| 
 | 
 | ||||||
|       createComponent(); |       createComponent(); | ||||||
|       await waitForPromises(); |       await waitForPromises(); | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import MockAdapter from 'axios-mock-adapter'; | ||||||
| import { nextTick } from 'vue'; | import { nextTick } from 'vue'; | ||||||
| import waitForPromises from 'helpers/wait_for_promises'; | import waitForPromises from 'helpers/wait_for_promises'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | 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'; | import ActionComponent from '~/pipelines/components/jobs_shared/action_component.vue'; | ||||||
| 
 | 
 | ||||||
| describe('pipeline graph action component', () => { | describe('pipeline graph action component', () => { | ||||||
|  | @ -15,7 +16,7 @@ describe('pipeline graph action component', () => { | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     mock = new MockAdapter(axios); |     mock = new MockAdapter(axios); | ||||||
| 
 | 
 | ||||||
|     mock.onPost('foo.json').reply(200); |     mock.onPost('foo.json').reply(HTTP_STATUS_OK); | ||||||
| 
 | 
 | ||||||
|     wrapper = mount(ActionComponent, { |     wrapper = mount(ActionComponent, { | ||||||
|       propsData: { |       propsData: { | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import waitForPromises from 'helpers/wait_for_promises'; | ||||||
| import { TEST_HOST } from 'spec/test_constants'; | import { TEST_HOST } from 'spec/test_constants'; | ||||||
| import { createAlert } from '~/flash'; | import { createAlert } from '~/flash'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | 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 { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; | ||||||
| import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipelines_manual_actions.vue'; | import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipelines_manual_actions.vue'; | ||||||
| import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; | import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; | ||||||
|  | @ -70,7 +71,7 @@ describe('Pipelines Actions dropdown', () => { | ||||||
| 
 | 
 | ||||||
|     describe('on click', () => { |     describe('on click', () => { | ||||||
|       it('makes a request and toggles the loading state', async () => { |       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'); |         findAllDropdownItems().at(0).vm.$emit('click'); | ||||||
| 
 | 
 | ||||||
|  | @ -132,7 +133,7 @@ describe('Pipelines Actions dropdown', () => { | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('makes post request after confirming', async () => { |     it('makes post request after confirming', async () => { | ||||||
|       mock.onPost(scheduledJobAction.path).reply(200); |       mock.onPost(scheduledJobAction.path).reply(HTTP_STATUS_OK); | ||||||
|       confirmAction.mockResolvedValueOnce(true); |       confirmAction.mockResolvedValueOnce(true); | ||||||
| 
 | 
 | ||||||
|       findAllDropdownItems().at(0).vm.$emit('click'); |       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 () => { |     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); |       confirmAction.mockResolvedValueOnce(false); | ||||||
| 
 | 
 | ||||||
|       findAllDropdownItems().at(0).vm.$emit('click'); |       findAllDropdownItems().at(0).vm.$emit('click'); | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import MockAxiosAdapter from 'axios-mock-adapter'; | ||||||
| import { nextTick } from 'vue'; | import { nextTick } from 'vue'; | ||||||
| import waitForPromises from 'helpers/wait_for_promises'; | import waitForPromises from 'helpers/wait_for_promises'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | 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'; | import SharedRunnersToggleComponent from '~/projects/settings/components/shared_runners_toggle.vue'; | ||||||
| 
 | 
 | ||||||
| const TEST_UPDATE_PATH = '/test/update_shared_runners'; | const TEST_UPDATE_PATH = '/test/update_shared_runners'; | ||||||
|  | @ -36,7 +37,7 @@ describe('projects/settings/components/shared_runners', () => { | ||||||
| 
 | 
 | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     mockAxios = new MockAxiosAdapter(axios); |     mockAxios = new MockAxiosAdapter(axios); | ||||||
|     mockAxios.onPost(TEST_UPDATE_PATH).reply(200); |     mockAxios.onPost(TEST_UPDATE_PATH).reply(HTTP_STATUS_OK); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   afterEach(() => { |   afterEach(() => { | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ import axios from 'axios'; | ||||||
| import MockAdapter from 'axios-mock-adapter'; | import MockAdapter from 'axios-mock-adapter'; | ||||||
| import Vue, { nextTick } from 'vue'; | import Vue, { nextTick } from 'vue'; | ||||||
| import waitForPromises from 'helpers/wait_for_promises'; | 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 MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue'; | ||||||
| import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; | import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; | ||||||
| 
 | 
 | ||||||
|  | @ -66,7 +67,7 @@ describe('MemoryUsage', () => { | ||||||
| 
 | 
 | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     mock = new MockAdapter(axios); |     mock = new MockAdapter(axios); | ||||||
|     mock.onGet(`${url}.json`).reply(200); |     mock.onGet(`${url}.json`).reply(HTTP_STATUS_OK); | ||||||
| 
 | 
 | ||||||
|     vm = createComponent(); |     vm = createComponent(); | ||||||
|     el = vm.$el; |     el = vm.$el; | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import waitForPromises from 'helpers/wait_for_promises'; | ||||||
| import { securityReportMergeRequestDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data'; | import { securityReportMergeRequestDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data'; | ||||||
| import api from '~/api'; | import api from '~/api'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
|  | import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
| import Poll from '~/lib/utils/poll'; | import Poll from '~/lib/utils/poll'; | ||||||
| import { setFaviconOverlay } from '~/lib/utils/favicon'; | import { setFaviconOverlay } from '~/lib/utils/favicon'; | ||||||
| import notify from '~/lib/utils/notify'; | import notify from '~/lib/utils/notify'; | ||||||
|  | @ -837,7 +838,7 @@ describe('MrWidgetOptions', () => { | ||||||
| 
 | 
 | ||||||
|   describe('suggestPipeline', () => { |   describe('suggestPipeline', () => { | ||||||
|     beforeEach(() => { |     beforeEach(() => { | ||||||
|       mock.onAny().reply(200); |       mock.onAny().reply(HTTP_STATUS_OK); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     describe('given feature flag is enabled', () => { |     describe('given feature flag is enabled', () => { | ||||||
|  |  | ||||||
|  | @ -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, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | @ -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}`); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -6,6 +6,7 @@ import Mousetrap from 'mousetrap'; | ||||||
| import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | ||||||
| import GLForm from '~/gl_form'; | import GLForm from '~/gl_form'; | ||||||
| import * as utils from '~/lib/utils/common_utils'; | import * as utils from '~/lib/utils/common_utils'; | ||||||
|  | import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
| import ZenMode from '~/zen_mode'; | import ZenMode from '~/zen_mode'; | ||||||
| 
 | 
 | ||||||
| describe('ZenMode', () => { | describe('ZenMode', () => { | ||||||
|  | @ -32,7 +33,7 @@ describe('ZenMode', () => { | ||||||
| 
 | 
 | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     mock = new MockAdapter(axios); |     mock = new MockAdapter(axios); | ||||||
|     mock.onGet().reply(200); |     mock.onGet().reply(HTTP_STATUS_OK); | ||||||
| 
 | 
 | ||||||
|     loadHTMLFixture(fixtureName); |     loadHTMLFixture(fixtureName); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -94,13 +94,31 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do | ||||||
| 
 | 
 | ||||||
|       it 'adds CDN host to CSP' 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['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['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['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/") |         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 | ||||||
|     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 |     context 'when sentry is configured' do | ||||||
|       let(:legacy_dsn) { 'dummy://abc@legacy-sentry.example.com/1' } |       let(:legacy_dsn) { 'dummy://abc@legacy-sentry.example.com/1' } | ||||||
|       let(:dsn) { 'dummy://def@sentry.example.com/2' } |       let(:dsn) { 'dummy://def@sentry.example.com/2' } | ||||||
|  |  | ||||||
|  | @ -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_request3) { create(:merge_request, :unique_branches, reviewers: []) } | ||||||
|     let_it_be(:merge_request4) { create(:merge_request, :draft_merge_request) } |     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 |     describe '.review_requested' do | ||||||
|       it 'returns MRs that have any review requests' do |       it 'returns MRs that have any review requests' do | ||||||
|         expect(described_class.review_requested).to eq([merge_request1, merge_request2]) |         expect(described_class.review_requested).to eq([merge_request1, merge_request2]) | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ module gitlab.com/gitlab-org/gitlab/workhorse | ||||||
| go 1.18 | go 1.18 | ||||||
| 
 | 
 | ||||||
| require ( | 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/BurntSushi/toml v1.2.1 | ||||||
| 	github.com/FZambia/sentinel v1.1.1 | 	github.com/FZambia/sentinel v1.1.1 | ||||||
| 	github.com/alecthomas/chroma/v2 v2.4.0 | 	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/gitaly/v15 v15.7.0 | ||||||
| 	gitlab.com/gitlab-org/golang-archive-zip v0.1.1 | 	gitlab.com/gitlab-org/golang-archive-zip v0.1.1 | ||||||
| 	gitlab.com/gitlab-org/labkit v1.17.0 | 	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/image v0.0.0-20220722155232-062f8c9fd539 | ||||||
| 	golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 | 	golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 | ||||||
| 	golang.org/x/net v0.1.0 | 	golang.org/x/net v0.4.0 | ||||||
| 	golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c | 	golang.org/x/oauth2 v0.2.0 | ||||||
| 	golang.org/x/tools v0.1.12 | 	golang.org/x/tools v0.2.0 | ||||||
| 	google.golang.org/grpc v1.51.0 | 	google.golang.org/grpc v1.51.0 | ||||||
| 	google.golang.org/protobuf v1.28.1 | 	google.golang.org/protobuf v1.28.1 | ||||||
| 	honnef.co/go/tools v0.3.3 | 	honnef.co/go/tools v0.3.3 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
| 	cloud.google.com/go v0.103.0 // indirect | 	cloud.google.com/go v0.107.0 // indirect | ||||||
| 	cloud.google.com/go/compute v1.7.0 // indirect | 	cloud.google.com/go/compute v1.13.0 // indirect | ||||||
| 	cloud.google.com/go/iam v0.3.0 // indirect | 	cloud.google.com/go/compute/metadata v0.2.2 // indirect | ||||||
| 	cloud.google.com/go/monitoring v1.5.0 // 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/profiler v0.1.0 // indirect | ||||||
| 	cloud.google.com/go/storage v1.24.0 // indirect | 	cloud.google.com/go/storage v1.28.0 // indirect | ||||||
| 	cloud.google.com/go/trace v1.2.0 // indirect | 	cloud.google.com/go/trace v1.4.0 // indirect | ||||||
| 	contrib.go.opencensus.io/exporter/stackdriver v0.13.13 // indirect | 	contrib.go.opencensus.io/exporter/stackdriver v0.13.14 // indirect | ||||||
| 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 // indirect | 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.2.0 // indirect | ||||||
| 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.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.0.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 v14.2.0+incompatible // indirect | ||||||
| 	github.com/Azure/go-autorest/autorest/to v0.4.0 // 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/datadog-go v4.4.0+incompatible // indirect | ||||||
| 	github.com/DataDog/sketches-go v1.0.0 // indirect | 	github.com/DataDog/sketches-go v1.0.0 // indirect | ||||||
| 	github.com/Microsoft/go-winio v0.5.1 // indirect | 	github.com/Microsoft/go-winio v0.5.1 // indirect | ||||||
| 	github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect | 	github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect | ||||||
| 	github.com/beevik/ntp v0.3.0 // indirect | 	github.com/beevik/ntp v0.3.0 // indirect | ||||||
| 	github.com/beorn7/perks v1.0.1 // 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/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect | ||||||
| 	github.com/cespare/xxhash/v2 v2.1.2 // indirect | 	github.com/cespare/xxhash/v2 v2.1.2 // indirect | ||||||
| 	github.com/client9/reopen v1.0.0 // indirect | 	github.com/client9/reopen v1.0.0 // indirect | ||||||
|  | @ -69,14 +70,13 @@ require ( | ||||||
| 	github.com/dlclark/regexp2 v1.4.0 // indirect | 	github.com/dlclark/regexp2 v1.4.0 // indirect | ||||||
| 	github.com/go-ole/go-ole v1.2.4 // indirect | 	github.com/go-ole/go-ole v1.2.4 // indirect | ||||||
| 	github.com/gogo/protobuf v1.3.2 // 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/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | ||||||
| 	github.com/google/go-cmp v0.5.9 // 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/uuid v1.3.0 // indirect | ||||||
| 	github.com/google/wire v0.5.0 // indirect | 	github.com/google/wire v0.5.0 // indirect | ||||||
| 	github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect | 	github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect | ||||||
| 	github.com/googleapis/gax-go/v2 v2.4.0 // indirect | 	github.com/googleapis/gax-go/v2 v2.7.0 // indirect | ||||||
| 	github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect | 	github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect | ||||||
| 	github.com/hashicorp/yamux v0.1.1 // indirect | 	github.com/hashicorp/yamux v0.1.1 // indirect | ||||||
| 	github.com/jmespath/go-jmespath v0.4.0 // 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/oklog/ulid/v2 v2.0.2 // indirect | ||||||
| 	github.com/opentracing/opentracing-go v1.2.0 // indirect | 	github.com/opentracing/opentracing-go v1.2.0 // indirect | ||||||
| 	github.com/philhofer/fwd v1.1.1 // 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/pkg/errors v0.9.1 // indirect | ||||||
| 	github.com/pmezard/go-difflib v1.0.0 // indirect | 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||||
| 	github.com/prometheus/client_model v0.3.0 // indirect | 	github.com/prometheus/client_model v0.3.0 // indirect | ||||||
| 	github.com/prometheus/common v0.37.0 // indirect | 	github.com/prometheus/common v0.37.0 // indirect | ||||||
| 	github.com/prometheus/procfs v0.8.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/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect | ||||||
| 	github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 // indirect | 	github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 // indirect | ||||||
| 	github.com/shirou/gopsutil/v3 v3.21.2 // 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/tklauser/numcpus v0.2.1 // indirect | ||||||
| 	github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect | 	github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect | ||||||
| 	github.com/uber/jaeger-lib v2.4.1+incompatible // indirect | 	github.com/uber/jaeger-lib v2.4.1+incompatible // indirect | ||||||
| 	go.opencensus.io v0.23.0 // indirect | 	go.opencensus.io v0.24.0 // indirect | ||||||
| 	go.uber.org/atomic v1.9.0 // indirect | 	go.uber.org/atomic v1.10.0 // indirect | ||||||
| 	golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect | 	golang.org/x/crypto v0.3.0 // indirect | ||||||
| 	golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // 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/sync v0.1.0 // indirect | ||||||
| 	golang.org/x/sys v0.2.0 // indirect | 	golang.org/x/sys v0.3.0 // indirect | ||||||
| 	golang.org/x/text v0.4.0 // indirect | 	golang.org/x/text v0.5.0 // indirect | ||||||
| 	golang.org/x/time v0.2.0 // indirect | 	golang.org/x/time v0.2.0 // indirect | ||||||
| 	golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect | 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect | ||||||
| 	google.golang.org/api v0.91.0 // indirect | 	google.golang.org/api v0.103.0 // indirect | ||||||
| 	google.golang.org/appengine v1.6.7 // 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/DataDog/dd-trace-go.v1 v1.32.0 // indirect | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
| ) | ) | ||||||
|  |  | ||||||
							
								
								
									
										756
									
								
								workhorse/go.sum
								
								
								
								
							
							
						
						
									
										756
									
								
								workhorse/go.sum
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -11,6 +11,7 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" | 	"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" | 	"github.com/BurntSushi/toml" | ||||||
| 	"gocloud.dev/blob" | 	"gocloud.dev/blob" | ||||||
| 	"gocloud.dev/blob/azureblob" | 	"gocloud.dev/blob/azureblob" | ||||||
|  | @ -178,12 +179,13 @@ func (creds *AzureCredentials) getURLOpener() (*azureblob.URLOpener, error) { | ||||||
| 		AccountName: creds.AccountName, | 		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) | 		sharedKeyCred, err := azblob.NewSharedKeyCredential(creds.AccountName, creds.AccountKey) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, fmt.Errorf("error creating Azure credentials: %w", err) | 			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{ | 	return &azureblob.URLOpener{ | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue