Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
13ddda5208
commit
25805c1633
|
|
@ -167,7 +167,6 @@ variables:
|
|||
RETRY_FAILED_TESTS_IN_NEW_PROCESS: "true"
|
||||
# Run with decomposed databases by default
|
||||
DECOMPOSED_DB: "true"
|
||||
GITLAB_ALLOW_SEPARATE_CI_DATABASE: "true"
|
||||
|
||||
DOCS_REVIEW_APPS_DOMAIN: "docs.gitlab-review.app"
|
||||
DOCS_GITLAB_REPO_SUFFIX: "ee"
|
||||
|
|
|
|||
|
|
@ -47,10 +47,9 @@ cache-assets:test as-if-foss:
|
|||
- .as-if-foss
|
||||
|
||||
cache-assets:production:
|
||||
extends: .cache-assets-base
|
||||
variables:
|
||||
NODE_ENV: "production"
|
||||
RAILS_ENV: "production"
|
||||
extends:
|
||||
- .cache-assets-base
|
||||
- .production
|
||||
|
||||
packages-cleanup:
|
||||
extends:
|
||||
|
|
|
|||
|
|
@ -33,10 +33,8 @@
|
|||
compile-production-assets:
|
||||
extends:
|
||||
- .compile-assets-base
|
||||
- .production
|
||||
- .frontend:rules:compile-production-assets
|
||||
variables:
|
||||
NODE_ENV: "production"
|
||||
RAILS_ENV: "production"
|
||||
artifacts:
|
||||
name: webpack-report
|
||||
expire_in: 31d
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@
|
|||
- !reference [.default-utils-before_script, before_script]
|
||||
- source scripts/prepare_build.sh
|
||||
|
||||
.production:
|
||||
variables:
|
||||
RAILS_ENV: "production"
|
||||
NODE_ENV: "production"
|
||||
GITLAB_ALLOW_SEPARATE_CI_DATABASE: "true"
|
||||
|
||||
.ruby-gems-cache: &ruby-gems-cache
|
||||
key: "ruby-gems-debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}"
|
||||
paths:
|
||||
|
|
|
|||
|
|
@ -19,12 +19,11 @@
|
|||
memory-on-boot:
|
||||
extends:
|
||||
- .only-code-memory-job-base
|
||||
- .production
|
||||
- .use-pg12
|
||||
stage: test
|
||||
needs: ["setup-test-env", "compile-test-assets"]
|
||||
variables:
|
||||
NODE_ENV: "production"
|
||||
RAILS_ENV: "production"
|
||||
SETUP_DB: "true"
|
||||
MEMORY_ON_BOOT_FILE_PREFIX: "tmp/memory_on_boot_"
|
||||
TEST_COUNT: 5
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -198,7 +198,7 @@ gem 'asciidoctor-kroki', '~> 0.7.0', require: false
|
|||
gem 'rouge', '~> 3.30.0'
|
||||
gem 'truncato', '~> 0.7.12'
|
||||
gem 'bootstrap_form', '~> 4.2.0'
|
||||
gem 'nokogiri', '~> 1.14.0'
|
||||
gem 'nokogiri', '~> 1.14.1'
|
||||
|
||||
# Calendar rendering
|
||||
gem 'icalendar'
|
||||
|
|
|
|||
|
|
@ -364,17 +364,17 @@
|
|||
{"name":"nio4r","version":"2.5.8","platform":"java","checksum":"b2b1800f6bf7ce4b797ca8b639ad278a99c9c904fb087a91d944f38e4bd71401"},
|
||||
{"name":"nio4r","version":"2.5.8","platform":"ruby","checksum":"3becb4ad95ab8ac0a9bd2e1b16466869402be62848082bf6329ae9091f276676"},
|
||||
{"name":"no_proxy_fix","version":"0.1.2","platform":"ruby","checksum":"4e9b4c31bb146de7fcf347dc1087bb13ac2039b56d50aa019e61036256abcd00"},
|
||||
{"name":"nokogiri","version":"1.14.0","platform":"aarch64-linux","checksum":"c87564f5f8fbfb72fbcb7ed9781f6472ceabe2f288ede6b9c37071dc32320ba6"},
|
||||
{"name":"nokogiri","version":"1.14.0","platform":"arm-linux","checksum":"33617e8a94993b8130a50bd59d6141a8d4d2aa4d4053f5c7874c71608e6e6dcc"},
|
||||
{"name":"nokogiri","version":"1.14.0","platform":"arm64-darwin","checksum":"5c0cd4eeb8501526e7e2aaba93b60ebf3dda37bfda665691196d4e9bb87adb1a"},
|
||||
{"name":"nokogiri","version":"1.14.0","platform":"java","checksum":"772936bf635b33b99bc89828de8e7077de47009638fe5ff11795f8b1d578465c"},
|
||||
{"name":"nokogiri","version":"1.14.0","platform":"ruby","checksum":"55ca6e87ae85e944a5901dd5a6cacbb961eaaf8b8dd3901b57475665396914bb"},
|
||||
{"name":"nokogiri","version":"1.14.0","platform":"x64-mingw-ucrt","checksum":"ee11c092b2cf2b137e71f623746162c578b53483dccf4c6209c80f5ba47927fe"},
|
||||
{"name":"nokogiri","version":"1.14.0","platform":"x64-mingw32","checksum":"9b91eede6155eb8891d7d95d8087d514f3007dd19813982104ed77452a2a7ace"},
|
||||
{"name":"nokogiri","version":"1.14.0","platform":"x86-linux","checksum":"649019d961b0ea8aee1bc8aa2573ab8ffb77d3f5e9c333aa2462a79fc56745fc"},
|
||||
{"name":"nokogiri","version":"1.14.0","platform":"x86-mingw32","checksum":"40985fc46315ea3d33ed900a649c0bb77484035ea882b7c9e55aef436b1958a8"},
|
||||
{"name":"nokogiri","version":"1.14.0","platform":"x86_64-darwin","checksum":"5d328c0d0c5f6f37a26c75b0282f9014c9686d4c10578ec8dfbbfcbea7da8b95"},
|
||||
{"name":"nokogiri","version":"1.14.0","platform":"x86_64-linux","checksum":"faa88b2bca46adaa3420c6e27eb8eb71f5b8d9f454ed7488a194a00c5ef52fbe"},
|
||||
{"name":"nokogiri","version":"1.14.1","platform":"aarch64-linux","checksum":"99594e8b94f576644ac640a223d74c79e840218948e963aa635f0254927bff10"},
|
||||
{"name":"nokogiri","version":"1.14.1","platform":"arm-linux","checksum":"1dc9b7821e1fa1f3fda40659662e51a4b3692acc4ee6342ee34a6a537fc1d5d8"},
|
||||
{"name":"nokogiri","version":"1.14.1","platform":"arm64-darwin","checksum":"1a693df86da8c4c97b01d614470f9c3e10b9c755de8803fbfcfffe0f9dff522a"},
|
||||
{"name":"nokogiri","version":"1.14.1","platform":"java","checksum":"c1f87a8f7bc56028deb2aecbb29e9b318405f7c468b29047aede78b41bc735a2"},
|
||||
{"name":"nokogiri","version":"1.14.1","platform":"ruby","checksum":"b2db3af7769c29cd77d5f39cd3d0b65ab10975bdecf04be71d683f9c9abe2663"},
|
||||
{"name":"nokogiri","version":"1.14.1","platform":"x64-mingw-ucrt","checksum":"2463a1ae0be5f06a10f3f3b374c2b743bff6280db993d488511a19bb7bc7cb7c"},
|
||||
{"name":"nokogiri","version":"1.14.1","platform":"x64-mingw32","checksum":"f3a2b0ceedf51d776b39dc759ce191a4df842d7d4f5900c64f33d4753db39877"},
|
||||
{"name":"nokogiri","version":"1.14.1","platform":"x86-linux","checksum":"f395d6c28c822b0877cfb0c71781f05243c034b4823359ab25b3288a73b9fc82"},
|
||||
{"name":"nokogiri","version":"1.14.1","platform":"x86-mingw32","checksum":"be34b32fe74e82bffca5b1f3df8727c8fdc828762b6dddab53a11cd8f8515785"},
|
||||
{"name":"nokogiri","version":"1.14.1","platform":"x86_64-darwin","checksum":"9b14091f77086c4f0f09451ba3acd1b5f7e0076fb34fc536682170fa9f1a5074"},
|
||||
{"name":"nokogiri","version":"1.14.1","platform":"x86_64-linux","checksum":"21d234c51582b292e2e1e02e6c30eea9188894348985d6910aa8e993749c0aff"},
|
||||
{"name":"notiffany","version":"0.1.3","platform":"ruby","checksum":"d37669605b7f8dcb04e004e6373e2a780b98c776f8eb503ac9578557d7808738"},
|
||||
{"name":"numerizer","version":"0.2.0","platform":"ruby","checksum":"e58076d5ee5370417b7e52d9cb25836d62acd1b8d9a194c308707986c1705d7b"},
|
||||
{"name":"oauth","version":"0.5.6","platform":"ruby","checksum":"4085fe28e0c5e2434135e00a6555294fd2a4ff96a98d1bdecdcd619fc6368dff"},
|
||||
|
|
|
|||
|
|
@ -942,7 +942,7 @@ GEM
|
|||
netrc (0.11.0)
|
||||
nio4r (2.5.8)
|
||||
no_proxy_fix (0.1.2)
|
||||
nokogiri (1.14.0)
|
||||
nokogiri (1.14.1)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
notiffany (0.1.3)
|
||||
|
|
@ -1742,7 +1742,7 @@ DEPENDENCIES
|
|||
multi_json (~> 1.14.1)
|
||||
net-ldap (~> 0.17.1)
|
||||
net-ntp
|
||||
nokogiri (~> 1.14.0)
|
||||
nokogiri (~> 1.14.1)
|
||||
oauth2 (~> 2.0)
|
||||
octokit (~> 4.15)
|
||||
ohai (~> 16.10)
|
||||
|
|
|
|||
|
|
@ -262,9 +262,11 @@ module UsersHelper
|
|||
if with_schema_markup
|
||||
job_title = '<span itemprop="jobTitle">'.html_safe + job_title + "</span>".html_safe
|
||||
organization = '<span itemprop="worksFor">'.html_safe + organization + "</span>".html_safe
|
||||
end
|
||||
|
||||
html_escape(s_('Profile|%{job_title} at %{organization}')) % { job_title: job_title, organization: organization }
|
||||
html_escape(s_('Profile|%{job_title} at %{organization}')) % { job_title: job_title, organization: organization }
|
||||
else
|
||||
s_('Profile|%{job_title} at %{organization}') % { job_title: job_title, organization: organization }
|
||||
end
|
||||
end
|
||||
|
||||
def user_table_headers
|
||||
|
|
|
|||
|
|
@ -10878,6 +10878,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="boardepicancestorsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
|
||||
| <a id="boardepicancestorsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
|
||||
| <a id="boardepicancestorsnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
|
||||
| <a id="boardepicancestorsor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. |
|
||||
| <a id="boardepicancestorssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="boardepicancestorssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
|
||||
| <a id="boardepicancestorsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
|
||||
|
|
@ -10916,6 +10917,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="boardepicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
|
||||
| <a id="boardepicchildrenmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
|
||||
| <a id="boardepicchildrennot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
|
||||
| <a id="boardepicchildrenor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. |
|
||||
| <a id="boardepicchildrensearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="boardepicchildrensort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
|
||||
| <a id="boardepicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
|
||||
|
|
@ -12947,6 +12949,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="epicancestorsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
|
||||
| <a id="epicancestorsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
|
||||
| <a id="epicancestorsnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
|
||||
| <a id="epicancestorsor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. |
|
||||
| <a id="epicancestorssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="epicancestorssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
|
||||
| <a id="epicancestorsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
|
||||
|
|
@ -12985,6 +12988,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="epicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
|
||||
| <a id="epicchildrenmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
|
||||
| <a id="epicchildrennot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
|
||||
| <a id="epicchildrenor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. |
|
||||
| <a id="epicchildrensearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="epicchildrensort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
|
||||
| <a id="epicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
|
||||
|
|
@ -14004,6 +14008,7 @@ Returns [`Epic`](#epic).
|
|||
| <a id="groupepicmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
|
||||
| <a id="groupepicmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
|
||||
| <a id="groupepicnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
|
||||
| <a id="groupepicor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. |
|
||||
| <a id="groupepicsearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="groupepicsort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
|
||||
| <a id="groupepicstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
|
||||
|
|
@ -14054,6 +14059,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="groupepicsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
|
||||
| <a id="groupepicsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
|
||||
| <a id="groupepicsnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
|
||||
| <a id="groupepicsor"></a>`or` | [`UnionedEpicFilterInput`](#unionedepicfilterinput) | List of arguments with inclusive OR. |
|
||||
| <a id="groupepicssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="groupepicssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
|
||||
| <a id="groupepicsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
|
||||
|
|
@ -25444,6 +25450,14 @@ A time-frame defined as a closed inclusive range of two dates.
|
|||
| <a id="timeframeend"></a>`end` | [`Date!`](#date) | End of the range. |
|
||||
| <a id="timeframestart"></a>`start` | [`Date!`](#date) | Start of the range. |
|
||||
|
||||
### `UnionedEpicFilterInput`
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="unionedepicfilterinputlabelname"></a>`labelName` | [`[String!]`](#string) | Filters epics that have at least one of the given labels. Ignored unless `or_issuable_queries` flag is enabled. |
|
||||
|
||||
### `UnionedIssueFilterInput`
|
||||
|
||||
#### Arguments
|
||||
|
|
|
|||
|
|
@ -25,8 +25,7 @@ Only three directives are applicable for the `Strict-Transport-Security` header.
|
|||
Note that invalid directives, or the `Strict-Transport-Security` header appearing more than once (if the values are
|
||||
different) is considered invalid.
|
||||
|
||||
Prior to adding to this security configuration to your website, it is recommended you review the hstspreload.org
|
||||
[Deployment Recommendations](https://hstspreload.org/#deployment-recommendations).
|
||||
Prior to adding to this security configuration to your website, it is recommended you review the hstspreload.org [Deployment Recommendations](https://hstspreload.org/#deployment-recommendations).
|
||||
|
||||
## Details
|
||||
|
||||
|
|
|
|||
|
|
@ -8,12 +8,13 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
## Description
|
||||
|
||||
A `Content-Security-Policy` (CSP) was identified on the target site. CSP can aid in hardening
|
||||
a website against various client side attacks such as Cross-Site Scripting (XSS).
|
||||
A missing or invalid `Content-Security-Policy` (CSP) was identified on the target site. CSP can aid in
|
||||
hardening a website against various client side attacks such as Cross-Site Scripting (XSS).
|
||||
|
||||
## Remediation
|
||||
|
||||
Follow the recommendations to determine if any actions are necessary to harden this `Content-Security-Policy`.
|
||||
If the target site is missing a CSP, please investigate the relevant URLs for enabling CSP. Otherwise,
|
||||
follow the recommendations to determine if any actions are necessary.
|
||||
|
||||
## Details
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ The [DAST browser-based crawler](../browser_based.md) provides a number of vulne
|
|||
| [798.94](798.94.md) | Exposure of confidential secret or token Private Key | High | Passive |
|
||||
| [798.95](798.95.md) | Exposure of confidential secret or token Pulumi API token | High | Passive |
|
||||
| [798.96](798.96.md) | Exposure of confidential secret or token PyPI upload token | High | Passive |
|
||||
| [798.97](798.97.md) | Exposure of confidential secret or token RubyGem API token | High | Passive |
|
||||
| [798.97](798.97.md) | Exposure of confidential secret or token RubyGems API token | High | Passive |
|
||||
| [798.98](798.98.md) | Exposure of confidential secret or token RapidAPI Access Token | High | Passive |
|
||||
| [798.99](798.99.md) | Exposure of confidential secret or token Sendbird Access ID | High | Passive |
|
||||
| [798.100](798.100.md) | Exposure of confidential secret or token Sendbird Access Token | High | Passive |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'chemlab/library'
|
||||
|
||||
module Slack
|
||||
include Chemlab::Library
|
||||
|
||||
self.base_url = 'https://slack.com'
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Slack
|
||||
module Mixins
|
||||
module Browser
|
||||
def browser
|
||||
::Chemlab.configuration.browser.session.engine
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Slack
|
||||
module Mixins
|
||||
module GitlabApp
|
||||
# @param [QA::Resource::Project] project
|
||||
# @param [String] channel
|
||||
# @param [String] title
|
||||
# @param [String] description
|
||||
def create_issue(project, channel:, title:, description:)
|
||||
lines = [
|
||||
"/staging-gitlab #{project.path_with_namespace} issue new #{title}",
|
||||
description
|
||||
]
|
||||
|
||||
send_message_to_channel(lines, channel: channel)
|
||||
end
|
||||
|
||||
# @param [QA::Resource::Project] project
|
||||
# @param [QA::Resource::Project] target
|
||||
# @param [String] id
|
||||
# @param [String] channel
|
||||
def move_issue(project, target, id:, channel:)
|
||||
line = "/staging-gitlab #{project.path_with_namespace} issue move #{id} to #{target.path_with_namespace}"
|
||||
send_message_to_channel([line], channel: channel)
|
||||
end
|
||||
|
||||
# @param [QA::Resource::Project] project
|
||||
# @param [String] id
|
||||
# @param [String] channel
|
||||
def show_issue(project, id:, channel:)
|
||||
send_message_to_channel(["/staging-gitlab #{project.path_with_namespace} issue show #{id}"], channel: channel)
|
||||
end
|
||||
|
||||
# @param [QA::Resource::Project] project
|
||||
# @param [String] id
|
||||
# @param [String] channel
|
||||
def close_issue(project, id:, channel:)
|
||||
send_message_to_channel(["/staging-gitlab #{project.path_with_namespace} issue close #{id}"], channel: channel)
|
||||
end
|
||||
|
||||
# @param [QA::Resource::Project] project
|
||||
# @param [String] channel
|
||||
# @param [String] id
|
||||
# @param [String] comment
|
||||
def comment_on_issue(project, channel:, id:, comment:)
|
||||
command = "/staging-gitlab #{project.path_with_namespace} issue comment #{id}"
|
||||
send_message_to_channel([command, comment], channel: channel)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Slack
|
||||
module Page
|
||||
class Chat < Chemlab::Page
|
||||
include Mixins::Browser
|
||||
include Mixins::GitlabApp
|
||||
|
||||
div :message_field, data_qa: 'message_input'
|
||||
button :connect_gitlab_button, visible_text: /Connect your GitLab account/
|
||||
button :skip_download_slack_button, data_qa: 'continue_in_browser'
|
||||
|
||||
def skip_download_screen
|
||||
wait_for_text('download the Slack app')
|
||||
|
||||
skip_download_slack_button_element.click if skip_download_slack_button_element.exists?
|
||||
end
|
||||
|
||||
# @param [Array<String>] lines - messages to send
|
||||
# @param [String] channel to send message to
|
||||
def send_message_to_channel(lines, channel:)
|
||||
go_to_channel(channel)
|
||||
|
||||
message_field_element.focus
|
||||
message_field_element.click
|
||||
|
||||
while line = lines.shift
|
||||
browser.send_keys(line)
|
||||
wait_for_text(line)
|
||||
|
||||
browser.send_keys([:shift, :enter]) unless lines.empty?
|
||||
end
|
||||
|
||||
browser.send_keys(:enter)
|
||||
end
|
||||
|
||||
def wait_for_text(line)
|
||||
QA::Support::Waiter.wait_until(max_duration: 3, raise_on_failure: false) do
|
||||
browser.text.include?(line)
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_channel(channel)
|
||||
menu_item = messages.find do |div|
|
||||
div.text == channel
|
||||
end
|
||||
menu_item.click
|
||||
end
|
||||
|
||||
def click_connect_account_link
|
||||
divs = messages(visible_text: /connect your GitLab account/i)
|
||||
el = divs.last.a(href: /staging-ref/)
|
||||
el.scroll.to(:top)
|
||||
el.click
|
||||
end
|
||||
|
||||
def messages(**opts)
|
||||
browser.divs(data_qa: 'virtual-list-item', **opts)
|
||||
end
|
||||
|
||||
def gitlab_app_home
|
||||
browser.divs(data_qa: 'channel_item_container').find do |el|
|
||||
el.text == 'GitLab'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Slack
|
||||
module Page
|
||||
class Login < Chemlab::Page
|
||||
path '/workspace-signin'
|
||||
|
||||
text_field :workspace_field, data_qa: 'signin_domain_input'
|
||||
button :continue_button, data_qa: 'submit_team_domain_button'
|
||||
|
||||
link :sign_in_with_password_link, data_qa: 'sign_in_password_link'
|
||||
|
||||
text_field :email_address_field, data_qa: 'login_email'
|
||||
text_field :password_field, data_qa: 'login_password', type: 'password'
|
||||
button :sign_in_button, data_qa: 'signin_button'
|
||||
|
||||
def sign_in
|
||||
navigate_to_workspace
|
||||
|
||||
# sign into with password if needed
|
||||
sign_in_with_password_link_element.click if sign_in_with_password_link_element.exists?
|
||||
|
||||
finish_sign_in
|
||||
end
|
||||
|
||||
def navigate_to_workspace
|
||||
self.workspace_field = ::QA::Runtime::Env.slack_workspace
|
||||
continue_button
|
||||
end
|
||||
|
||||
def finish_sign_in
|
||||
return unless email_address_field_element.exists?
|
||||
|
||||
self.email_address_field = ::QA::Runtime::Env.slack_email
|
||||
self.password_field = ::QA::Runtime::Env.slack_password
|
||||
sign_in_button
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Slack
|
||||
module Page
|
||||
class Oauth < Chemlab::Page
|
||||
button :submit_oauth, data_qa: 'oauth_submit_button'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Flow
|
||||
module Integrations
|
||||
module Slack
|
||||
extend self
|
||||
|
||||
# Need to sign in for this method
|
||||
# @param [QA::Resource::Project]
|
||||
def start_slack_install(project)
|
||||
project.visit!
|
||||
|
||||
Page::Project::Menu.perform do |project_menu_page|
|
||||
project_menu_page.click_project
|
||||
project_menu_page.go_to_integrations_settings
|
||||
end
|
||||
|
||||
Page::Project::Settings::Integrations.perform(&:click_slack_application_link)
|
||||
|
||||
EE::Page::Project::Settings::Services::Slack.perform(&:start_slack_install)
|
||||
::Slack::Page::Oauth.perform(&:submit_oauth)
|
||||
end
|
||||
|
||||
# @param [QA::Resource::Project] project
|
||||
# @option [String | Nil] channel
|
||||
# @return [Boolean] is this account already authorized?
|
||||
def start_gitlab_connect(project, channel: nil)
|
||||
::Slack::Page::Chat.perform do |chat_page|
|
||||
# sometimes Slack will present a blocking page
|
||||
# for downloading the app instead of using a browser
|
||||
chat_page.skip_download_screen
|
||||
|
||||
lines = ["/staging-gitlab #{project.path_with_namespace} issue show 1"]
|
||||
chat_page.send_message_to_channel(lines, channel: channel)
|
||||
|
||||
# The only way to know if we are authorized is to send a slash command to the channel.
|
||||
# If the account / chat_name is already authorized, the Slack app will try to look up the issue
|
||||
# and return a 404 because it doesn't exist
|
||||
QA::Support::Waiter.wait_until(max_duration: 4, raise_on_failure: false) do
|
||||
chat_page.messages.last.text =~ /connect your GitLab account|404 not found!/i
|
||||
end
|
||||
|
||||
break(true) if chat_page.messages.last.text =~ /404 not found!/i
|
||||
|
||||
chat_page.click_connect_account_link
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Page
|
||||
module Profile
|
||||
module ChatNames
|
||||
class New < Chemlab::Page
|
||||
button :authorize, value: /Authorize/i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -10,6 +10,7 @@ module QA
|
|||
element :prometheus_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
|
||||
element :jira_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
|
||||
element :pipelines_email_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
|
||||
element :gitlab_slack_application_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
|
||||
end
|
||||
|
||||
def click_on_prometheus_integration
|
||||
|
|
@ -27,6 +28,10 @@ module QA
|
|||
def click_jenkins_ci_link
|
||||
click_element :jenkins_link
|
||||
end
|
||||
|
||||
def click_slack_application_link
|
||||
click_element :gitlab_slack_application_link
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ module QA
|
|||
|
||||
# Specify the user-agent to allow challenges to be bypassed
|
||||
# See https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/11938
|
||||
if QA::Runtime::Env.user_agent
|
||||
unless QA::Runtime::Env.user_agent.blank?
|
||||
capabilities['goog:chromeOptions'][:args] << "user-agent=#{QA::Runtime::Env.user_agent}"
|
||||
end
|
||||
|
||||
|
|
@ -117,6 +117,38 @@ module QA
|
|||
capabilities['goog:chromeOptions'][:args] << 'window-size=1480,2200'
|
||||
end
|
||||
|
||||
# Slack tries to open an external URL handler
|
||||
# The test needs to default the handler to always open Slack
|
||||
# to prevent a blocking popup.
|
||||
if QA::Runtime::Env.slack_workspace
|
||||
slack_default_preference = {
|
||||
'protocol_handler' => {
|
||||
'allowed_origin_protocol_pairs' => {
|
||||
"https://#{QA::Runtime::Env.slack_workspace}.slack.com" => {
|
||||
'slack' => true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default_profile = File.join("#{chrome_profile_location}/Default")
|
||||
FileUtils.mkdir_p(default_profile) unless Dir.exist?(default_profile)
|
||||
preferences = slack_default_preference
|
||||
|
||||
# mutate the preferences if it exists
|
||||
# else write a new file
|
||||
if File.exist?("#{default_profile}/Preferences")
|
||||
begin
|
||||
preferences = JSON.parse(File.read("#{default_profile}/Preferences"))
|
||||
preferences.deep_merge!(slack_default_preference)
|
||||
rescue JSON::ParserError => _
|
||||
end
|
||||
end
|
||||
|
||||
File.write("#{default_profile}/Preferences", preferences.to_json)
|
||||
append_chrome_profile_to_capabilities(capabilities)
|
||||
end
|
||||
|
||||
when :safari
|
||||
if QA::Runtime::Env.remote_mobile_device_name
|
||||
capabilities['platformName'] = 'iOS'
|
||||
|
|
@ -131,10 +163,7 @@ module QA
|
|||
|
||||
# Use the same profile on QA runs if CHROME_REUSE_PROFILE is true.
|
||||
# Useful to speed up local QA.
|
||||
if QA::Runtime::Env.reuse_chrome_profile?
|
||||
qa_profile_dir = ::File.expand_path('../../tmp/qa-profile', __dir__)
|
||||
capabilities['goog:chromeOptions'][:args] << "user-data-dir=#{qa_profile_dir}"
|
||||
end
|
||||
append_chrome_profile_to_capabilities(capabilities) if QA::Runtime::Env.reuse_chrome_profile?
|
||||
|
||||
selenium_options = {
|
||||
browser: QA::Runtime::Env.browser,
|
||||
|
|
@ -193,6 +222,16 @@ module QA
|
|||
end
|
||||
# rubocop: enable Metrics/AbcSize
|
||||
|
||||
def self.append_chrome_profile_to_capabilities(capabilities)
|
||||
return if capabilities['goog:chromeOptions'][:args].include?(chrome_profile_location)
|
||||
|
||||
capabilities['goog:chromeOptions'][:args] << "user-data-dir=#{chrome_profile_location}"
|
||||
end
|
||||
|
||||
def self.chrome_profile_location
|
||||
::File.expand_path('../../tmp/qa-profile', __dir__)
|
||||
end
|
||||
|
||||
class Session
|
||||
include Capybara::DSL
|
||||
|
||||
|
|
|
|||
|
|
@ -293,6 +293,18 @@ module QA
|
|||
ENV['JIRA_HOSTNAME']
|
||||
end
|
||||
|
||||
def slack_workspace
|
||||
ENV['QA_SLACK_WORKSPACE']
|
||||
end
|
||||
|
||||
def slack_email
|
||||
ENV['QA_SLACK_EMAIL']
|
||||
end
|
||||
|
||||
def slack_password
|
||||
ENV['QA_SLACK_PASSWORD']
|
||||
end
|
||||
|
||||
def jenkins_admin_username
|
||||
ENV.fetch('QA_JENKINS_USER', 'administrator')
|
||||
end
|
||||
|
|
@ -502,6 +514,15 @@ module QA
|
|||
ENV['DEFAULT_CHROME_DOWNLOAD_PATH'] || Dir.tmpdir
|
||||
end
|
||||
|
||||
def require_slack_env!
|
||||
missing_env = %i[slack_workspace slack_email slack_password].select do |method|
|
||||
::QA::Runtime::Env.public_send(method).nil?
|
||||
end
|
||||
return unless missing_env.any?
|
||||
|
||||
raise "Missing Slack env: #{missing_env.map(&:upcase).join(', ')}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remote_grid_credentials
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GroupsController, factory_default: :keep do
|
||||
RSpec.describe GroupsController, factory_default: :keep, feature_category: :code_review_workflow do
|
||||
include ExternalAuthorizationServiceHelpers
|
||||
include AdminModeHelper
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue