Commit e57e10c6 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 0d3d9680 37f1f209
818f3d85a2c8e6596376f1d2276aa22660203a6c
d513d220b183d83ae7219ec52f49aa3b4f7fc551
......@@ -472,7 +472,7 @@ end
gem 'spamcheck', '~> 0.1.0'
# Gitaly GRPC protocol definitions
gem 'gitaly', '~> 14.1.0.pre.rc4'
gem 'gitaly', '~> 14.2.0.pre.rc2'
# KAS GRPC protocol definitions
gem 'kas-grpc', '~> 0.0.2'
......
......@@ -452,7 +452,7 @@ GEM
rails (>= 3.2.0)
git (1.7.0)
rchardet (~> 1.8)
gitaly (14.1.0.pre.rc4)
gitaly (14.2.0.pre.rc2)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab (4.16.1)
......@@ -1464,7 +1464,7 @@ DEPENDENCIES
gettext (~> 3.3)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly (~> 14.1.0.pre.rc4)
gitaly (~> 14.2.0.pre.rc2)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 2.3.0)
......
......@@ -528,7 +528,7 @@ class Namespace < ApplicationRecord
def write_projects_repository_config
all_projects.find_each do |project|
project.write_repository_config
project.set_full_path
project.track_project_repository
end
end
......
......@@ -1889,11 +1889,11 @@ class Project < ApplicationRecord
.update_all(deployed: deployment.present?, pages_deployment_id: deployment&.id)
end
def write_repository_config(gl_full_path: full_path)
def set_full_path(gl_full_path: full_path)
# We'd need to keep track of project full path otherwise directory tree
# created with hashed storage enabled cannot be usefully imported using
# the import rake task.
repository.raw_repository.write_config(full_path: gl_full_path)
repository.raw_repository.set_full_path(full_path: gl_full_path)
rescue Gitlab::Git::Repository::NoRepository => e
Gitlab::AppLogger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.")
nil
......@@ -1917,7 +1917,7 @@ class Project < ApplicationRecord
after_create_default_branch
join_pool_repository
refresh_markdown_cache!
write_repository_config
set_full_path
end
def update_project_counter_caches
......
......@@ -83,7 +83,7 @@ module Projects
def update_repository_configuration
project.reload_repository!
project.write_repository_config
project.set_full_path
project.track_project_repository
end
......
......@@ -92,7 +92,7 @@ module Projects
# Skip writing the config for project imports/forks because it
# will always fail since the Git directory doesn't exist until
# a background job creates it (see Project#add_import_job).
@project.write_repository_config unless @project.import?
@project.set_full_path unless @project.import?
unless @project.gitlab_project_import?
@project.create_wiki unless skip_wiki?
......
......@@ -14,7 +14,7 @@ module Projects
result = move_repositories
if result
project.write_repository_config
project.set_full_path
project.track_project_repository
else
rollback_folder_move
......
......@@ -14,7 +14,7 @@ module Projects
result = move_repositories
if result
project.write_repository_config
project.set_full_path
project.track_project_repository
else
rollback_folder_move
......
......@@ -135,7 +135,7 @@ module Projects
end
def update_repository_configuration(full_path)
project.write_repository_config(gl_full_path: full_path)
project.set_full_path(gl_full_path: full_path)
project.track_project_repository
end
......
---
name: set_full_path
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66929
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337002
milestone: '14.2'
type: development
group: group::gitaly
default_enabled: false
# frozen_string_literal: true
class AddTypeNewToIntegrations < ActiveRecord::Migration[6.1]
# rubocop:disable Migration/AddLimitToTextColumns
# limit is added in 20210721134707_add_text_limit_to_integrations_type_new
def change
add_column :integrations, :type_new, :text
end
# rubocop:enable Migration/AddLimitToTextColumns
end
# frozen_string_literal: true
class AddTextLimitToIntegrationsTypeNew < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
add_text_limit :integrations, :type_new, 255
end
def down
remove_text_limit :integrations, :type_new
end
end
# frozen_string_literal: true
class AddTriggersToIntegrationsTypeNew < ActiveRecord::Migration[6.1]
include Gitlab::Database::SchemaHelpers
FUNCTION_NAME = 'integrations_set_type_new'
TRIGGER_ON_INSERT_NAME = 'trigger_type_new_on_insert'
def up
create_trigger_function(FUNCTION_NAME, replace: true) do
# This list matches `Gitlab::Integrations::StiType::NAMESPACED_INTEGRATIONS`.
#
# If we add new integrations after this migration we can directly use the
# correct class name in `type`, and don't need to add it to `NAMESPACED_INTEGRATIONS`.
<<~SQL
WITH mapping(old_type, new_type) AS (VALUES
('AsanaService', 'Integrations::Asana'),
('AssemblaService', 'Integrations::Assembla'),
('BambooService', 'Integrations::Bamboo'),
('BugzillaService', 'Integrations::Bugzilla'),
('BuildkiteService', 'Integrations::Buildkite'),
('CampfireService', 'Integrations::Campfire'),
('ConfluenceService', 'Integrations::Confluence'),
('CustomIssueTrackerService', 'Integrations::CustomIssueTracker'),
('DatadogService', 'Integrations::Datadog'),
('DiscordService', 'Integrations::Discord'),
('DroneCiService', 'Integrations::DroneCi'),
('EmailsOnPushService', 'Integrations::EmailsOnPush'),
('EwmService', 'Integrations::Ewm'),
('ExternalWikiService', 'Integrations::ExternalWiki'),
('FlowdockService', 'Integrations::Flowdock'),
('HangoutsChatService', 'Integrations::HangoutsChat'),
('IrkerService', 'Integrations::Irker'),
('JenkinsService', 'Integrations::Jenkins'),
('JiraService', 'Integrations::Jira'),
('MattermostService', 'Integrations::Mattermost'),
('MattermostSlashCommandsService', 'Integrations::MattermostSlashCommands'),
('MicrosoftTeamsService', 'Integrations::MicrosoftTeams'),
('MockCiService', 'Integrations::MockCi'),
('MockMonitoringService', 'Integrations::MockMonitoring'),
('PackagistService', 'Integrations::Packagist'),
('PipelinesEmailService', 'Integrations::PipelinesEmail'),
('PivotaltrackerService', 'Integrations::Pivotaltracker'),
('PrometheusService', 'Integrations::Prometheus'),
('PushoverService', 'Integrations::Pushover'),
('RedmineService', 'Integrations::Redmine'),
('SlackService', 'Integrations::Slack'),
('SlackSlashCommandsService', 'Integrations::SlackSlashCommands'),
('TeamcityService', 'Integrations::Teamcity'),
('UnifyCircuitService', 'Integrations::UnifyCircuit'),
('YoutrackService', 'Integrations::Youtrack'),
('WebexTeamsService', 'Integrations::WebexTeams'),
-- EE-only integrations
('GithubService', 'Integrations::Github'),
('GitlabSlackApplicationService', 'Integrations::GitlabSlackApplication')
)
UPDATE integrations SET type_new = mapping.new_type
FROM mapping
WHERE integrations.id = NEW.id
AND mapping.old_type = NEW.type;
RETURN NULL;
SQL
end
execute(<<~SQL)
CREATE TRIGGER #{TRIGGER_ON_INSERT_NAME}
AFTER INSERT ON integrations
FOR EACH ROW
EXECUTE FUNCTION #{FUNCTION_NAME}();
SQL
end
def down
drop_trigger(:integrations, TRIGGER_ON_INSERT_NAME)
drop_function(FUNCTION_NAME)
end
end
e6c8fd913f591fed24072e9b0032b47dbb1165f2c1cf50ed01cfcd5f7da32cba
\ No newline at end of file
a795dad532a5ed2a645e49e586c6fb73167e9ae38843cf5cbcf37cf8661b765a
\ No newline at end of file
a4219ce93f790ec372991adca4b1cc5c0410d57e92817082344b11758ca5ae93
\ No newline at end of file
......@@ -10,6 +10,62 @@ CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE FUNCTION integrations_set_type_new() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
WITH mapping(old_type, new_type) AS (VALUES
('AsanaService', 'Integrations::Asana'),
('AssemblaService', 'Integrations::Assembla'),
('BambooService', 'Integrations::Bamboo'),
('BugzillaService', 'Integrations::Bugzilla'),
('BuildkiteService', 'Integrations::Buildkite'),
('CampfireService', 'Integrations::Campfire'),
('ConfluenceService', 'Integrations::Confluence'),
('CustomIssueTrackerService', 'Integrations::CustomIssueTracker'),
('DatadogService', 'Integrations::Datadog'),
('DiscordService', 'Integrations::Discord'),
('DroneCiService', 'Integrations::DroneCi'),
('EmailsOnPushService', 'Integrations::EmailsOnPush'),
('EwmService', 'Integrations::Ewm'),
('ExternalWikiService', 'Integrations::ExternalWiki'),
('FlowdockService', 'Integrations::Flowdock'),
('HangoutsChatService', 'Integrations::HangoutsChat'),
('IrkerService', 'Integrations::Irker'),
('JenkinsService', 'Integrations::Jenkins'),
('JiraService', 'Integrations::Jira'),
('MattermostService', 'Integrations::Mattermost'),
('MattermostSlashCommandsService', 'Integrations::MattermostSlashCommands'),
('MicrosoftTeamsService', 'Integrations::MicrosoftTeams'),
('MockCiService', 'Integrations::MockCi'),
('MockMonitoringService', 'Integrations::MockMonitoring'),
('PackagistService', 'Integrations::Packagist'),
('PipelinesEmailService', 'Integrations::PipelinesEmail'),
('PivotaltrackerService', 'Integrations::Pivotaltracker'),
('PrometheusService', 'Integrations::Prometheus'),
('PushoverService', 'Integrations::Pushover'),
('RedmineService', 'Integrations::Redmine'),
('SlackService', 'Integrations::Slack'),
('SlackSlashCommandsService', 'Integrations::SlackSlashCommands'),
('TeamcityService', 'Integrations::Teamcity'),
('UnifyCircuitService', 'Integrations::UnifyCircuit'),
('YoutrackService', 'Integrations::Youtrack'),
('WebexTeamsService', 'Integrations::WebexTeams'),
-- EE-only integrations
('GithubService', 'Integrations::Github'),
('GitlabSlackApplicationService', 'Integrations::GitlabSlackApplication')
)
UPDATE integrations SET type_new = mapping.new_type
FROM mapping
WHERE integrations.id = NEW.id
AND mapping.old_type = NEW.type;
RETURN NULL;
END
$$;
CREATE FUNCTION set_has_external_issue_tracker() RETURNS trigger
LANGUAGE plpgsql
AS $$
......@@ -14085,7 +14141,9 @@ CREATE TABLE integrations (
comment_detail smallint,
inherit_from_id bigint,
alert_events boolean,
group_id bigint
group_id bigint,
type_new text,
CONSTRAINT check_a948a0aa7e CHECK ((char_length(type_new) <= 255))
);
CREATE SEQUENCE integrations_id_seq
......@@ -25833,6 +25891,8 @@ CREATE TRIGGER trigger_has_external_wiki_on_insert AFTER INSERT ON integrations
CREATE TRIGGER trigger_has_external_wiki_on_update AFTER UPDATE ON integrations FOR EACH ROW WHEN ((((new.type)::text = 'ExternalWikiService'::text) AND (old.active <> new.active) AND (new.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_wiki();
CREATE TRIGGER trigger_type_new_on_insert AFTER INSERT ON integrations FOR EACH ROW EXECUTE FUNCTION integrations_set_type_new();
ALTER TABLE ONLY chat_names
ADD CONSTRAINT fk_00797a2bf9 FOREIGN KEY (service_id) REFERENCES integrations(id) ON DELETE CASCADE;
......@@ -142,7 +142,7 @@ to do so. In a Rails console session, run the following to migrate a project:
```ruby
project = Project.find_by_full_path('gitlab-org/gitlab')
project.write_repository_config
project.set_full_path
```
In a Rails console session, run the following to migrate all of a namespace's
......
......@@ -48,18 +48,26 @@ dast:
The browser-based crawler can be configured using CI/CD variables.
| CI/CD variable | Type | Example | Description |
|--------------------------------------| ----------------| --------------------------------- | ------------|
| `DAST_WEBSITE` | URL | `http://www.site.com` | The URL of the website to scan. |
| `DAST_BROWSER_SCAN` | boolean | `true` | Configures DAST to use the browser-based crawler engine. |
| `DAST_BROWSER_ALLOWED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered in scope when crawled. By default the `DAST_WEBSITE` hostname is included in the allowed hosts list. |
| `DAST_BROWSER_EXCLUDED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered excluded and connections are forcibly dropped. |
| `DAST_BROWSER_IGNORED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are accessed but not reported against. |
| `DAST_BROWSER_MAX_ACTIONS` | number | `10000` | The maximum number of actions that the crawler performs. For example, clicking a link, or filling a form. |
| `DAST_BROWSER_MAX_DEPTH` | number | `10` | The maximum number of chained actions that the crawler takes. For example, `Click -> Form Fill -> Click` is a depth of three. |
| `DAST_BROWSER_NUMBER_OF_BROWSERS` | number | `3` | The maximum number of concurrent browser instances to use. For shared runners on GitLab.com we recommended a maximum of three. Private runners with more resources may benefit from a higher number, but will likely produce little benefit after five to seven instances. |
| `DAST_BROWSER_COOKIES` | dictionary | `abtesting_group:3,region:locked` | A cookie name and value to be added to every request. |
| `DAST_BROWSER_LOG` | List of strings | `brows:debug,auth:debug` | A list of modules and their intended log level. |
| CI/CD variable | Type | Example | Description |
|----------------------------------------------| ----------------| --------------------------------- | ------------|
| `DAST_WEBSITE` | URL | `http://www.site.com` | The URL of the website to scan. |
| `DAST_BROWSER_SCAN` | boolean | `true` | Configures DAST to use the browser-based crawler engine. |
| `DAST_BROWSER_ALLOWED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered in scope when crawled. By default the `DAST_WEBSITE` hostname is included in the allowed hosts list. |
| `DAST_BROWSER_EXCLUDED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered excluded and connections are forcibly dropped. |
| `DAST_BROWSER_IGNORED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are accessed but not reported against. |
| `DAST_BROWSER_MAX_ACTIONS` | number | `10000` | The maximum number of actions that the crawler performs. For example, clicking a link, or filling a form. |
| `DAST_BROWSER_MAX_DEPTH` | number | `10` | The maximum number of chained actions that the crawler takes. For example, `Click -> Form Fill -> Click` is a depth of three. |
| `DAST_BROWSER_NUMBER_OF_BROWSERS` | number | `3` | The maximum number of concurrent browser instances to use. For shared runners on GitLab.com we recommended a maximum of three. Private runners with more resources may benefit from a higher number, but will likely produce little benefit after five to seven instances. |
| `DAST_BROWSER_COOKIES` | dictionary | `abtesting_group:3,region:locked` | A cookie name and value to be added to every request. |
| `DAST_BROWSER_LOG` | List of strings | `brows:debug,auth:debug` | A list of modules and their intended log level. |
| `DAST_BROWSER_NAVIGATION_TIMEOUT` | [Duration string](https://golang.org/pkg/time/#ParseDuration) | `15s` | The maximum amount of time to wait for a browser to navigate from one page to another |
| `DAST_BROWSER_ACTION_TIMEOUT` | [Duration string](https://golang.org/pkg/time/#ParseDuration) | `7s` | The maximum amount of time to wait for a browser to complete an action |
| `DAST_BROWSER_STABILITY_TIMEOUT` | [Duration string](https://golang.org/pkg/time/#ParseDuration) | `7s` | The maximum amount of time to wait for a browser to consider a page loaded and ready for analysis |
| `DAST_BROWSER_NAVIGATION_STABILITY_TIMEOUT` | [Duration string](https://golang.org/pkg/time/#ParseDuration) | `7s` | The maximum amount of time to wait for a browser to consider a page loaded and ready for analysis after a navigation completes |
| `DAST_BROWSER_ACTION_STABILITY_TIMEOUT` | [Duration string](https://golang.org/pkg/time/#ParseDuration) | `800ms` | The maximum amount of time to wait for a browser to consider a page loaded and ready for analysis after completing an action |
| `DAST_BROWSER_SEARCH_ELEMENT_TIMEOUT` | [Duration string](https://golang.org/pkg/time/#ParseDuration) | `3s` | The maximum amount of time to allow the browser to search for new elements or navigations |
| `DAST_BROWSER_EXTRACT_ELEMENT_TIMEOUT` | [Duration string](https://golang.org/pkg/time/#ParseDuration) | `5s` | The maximum amount of time to allow the browser to extract newly found elements or navigations |
| `DAST_BROWSER_ELEMENT_TIMEOUT` | [Duration string](https://golang.org/pkg/time/#ParseDuration) | `600ms` | The maximum amount of time to wait for an element before determining it is ready for analysis |
The [DAST variables](index.md#available-cicd-variables) `SECURE_ANALYZERS_PREFIX`, `DAST_FULL_SCAN_ENABLED`, `DAST_AUTO_UPDATE_ADDONS`, `DAST_EXCLUDE_RULES`, `DAST_REQUEST_HEADERS`, `DAST_HTML_REPORT`, `DAST_MARKDOWN_REPORT`, `DAST_XML_REPORT`,
`DAST_AUTH_URL`, `DAST_USERNAME`, `DAST_PASSWORD`, `DAST_USERNAME_FIELD`, `DAST_PASSWORD_FIELD`, `DAST_FIRST_SUBMIT_FIELD`, `DAST_SUBMIT_FIELD`, `DAST_EXCLUDE_URLS`, `DAST_AUTH_VERIFICATION_URL`, `DAST_BROWSER_AUTH_VERIFICATION_SELECTOR`, `DAST_BROWSER_AUTH_VERIFICATION_LOGIN_FORM`, `DAST_BROWSER_AUTH_REPORT`,
......@@ -86,6 +94,46 @@ You can manage the trade-off between coverage and scan time with the following m
- Limit the page depth that the browser-based crawler will check coverage on with the [variable](#available-cicd-variables) `DAST_BROWSER_MAX_DEPTH`. The crawler uses a breadth-first search strategy, so pages with smaller depth are crawled first. The default is `10`.
- Vertically scaling the runner and using a higher number of browsers with [variable](#available-cicd-variables) `DAST_BROWSER_NUMBER_OF_BROWSERS`. The default is `3`.
## Timeouts
Due to poor network conditions or heavy application load, the default timeouts may not be applicable to your application.
Browser-based scans offer the ability to adjust various timeouts to ensure it continues smoothly as it transitions from one page to the next. These values are configured using a [Duration string](https://golang.org/pkg/time/#ParseDuration) which allow you to configure durations with a prefix: `m` for minutes, `s` for seconds, and `ms` for milliseconds.
Navigations, or the act of loading a new page, usually require the most amount of time as they are
loading multiple new resources such as JavaScript or CSS files. Depending on the size of these resources, or the speed at which they are returned, the default `DAST_BROWSER_NAVIGATION_TIMEOUT` may not be sufficient.
Stability timeouts, such as those configurable with `DAST_BROWSER_NAVIGATION_STABILITY_TIMEOUT`, `DAST_BROWSER_STABILITY_TIMEOUT`, and `DAST_BROWSER_ACTION_STABILITY_TIMEOUT` can also be configured. Stability timeouts determine when browser-based scans consider
a page fully loaded. Browser-based scans consider a page loaded when:
1. The [DOMContentLoaded](https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event) event has fired.
1. There are no open or outstanding requests that are deemed important, such as JavaScript and CSS. Media files are usually deemed unimportant.
1. Depending on whether the browser executed a navigation, was forcibly transitioned, or action:
- There are no new Document Object Model (DOM) modification events after the `DAST_BROWSER_NAVIGATION_STABILITY_TIMEOUT`, `DAST_BROWSER_STABILITY_TIMEOUT` or `DAST_BROWSER_ACTION_STABILITY_TIMEOUT` durations
After these events have occurred, browser-based scans consider the page loaded and ready and attempt the next action.
If your application experiences latency or returns many navigation failures, consider adjusting the timeout values such in this example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://my.site.com"
DAST_BROWSER_NAVIGATION_TIMEOUT: "25s"
DAST_BROWSER_ACTION_TIMEOUT: "10s"
DAST_BROWSER_STABILITY_TIMEOUT: "15s"
DAST_BROWSER_NAVIGATION_STABILITY_TIMEOUT: "15s"
DAST_BROWSER_ACTION_TIMEOUT: "10s"
DAST_BROWSER_ACTION_STABILITY_TIMEOUT: "3s"
```
NOTE:
Adjusting these values may impact scan time as they adjust how long each browser waits for various activities to complete.
## Debugging scans using logging
Logging can be used to help you troubleshoot a scan.
......
......@@ -23,10 +23,10 @@ export default {
},
computed: {
...mapState({
isLoading: (state) => state.approvals.isLoading,
isUpdated: (state) => state.approvals.isUpdated,
settings: (state) => state.approvals.settings,
errorMessage: (state) => state.approvals.errorMessage,
isLoading: (state) => state.approvalSettings.isLoading,
isUpdated: (state) => state.approvalSettings.isUpdated,
settings: (state) => state.approvalSettings.settings,
errorMessage: (state) => state.approvalSettings.errorMessage,
}),
...mapComputed(
[
......@@ -37,7 +37,7 @@ export default {
{ key: 'requireUserPassword', updateFn: 'setRequireUserPassword' },
],
undefined,
(state) => state.approvals.settings,
(state) => state.approvalSettings.settings,
),
hasSettings() {
return !isEmpty(this.settings);
......
......@@ -11,7 +11,9 @@ const mountGroupApprovalSettings = (el) => {
}
const { defaultExpanded, approvalSettingsPath } = el.dataset;
const store = createStore(approvalSettingsModule(groupApprovalsMappers));
const store = createStore({
approvalSettings: approvalSettingsModule(groupApprovalsMappers),
});
return new Vue({
el,
......
......@@ -13,15 +13,18 @@ export default function mountApprovalInput(el) {
document.querySelector('#js-target-branch-title')?.textContent ||
document.querySelector('#merge_request_target_branch')?.value;
const store = createStore(mrEditModule(), {
...el.dataset,
prefix: 'mr-edit',
canEdit: parseBoolean(el.dataset.canEdit),
canUpdateApprovers: parseBoolean(el.dataset.canUpdateApprovers),
showCodeOwnerTip: parseBoolean(el.dataset.showCodeOwnerTip),
allowMultiRule: parseBoolean(el.dataset.allowMultiRule),
canOverride: parseBoolean(el.dataset.canOverride),
});
const store = createStore(
{ approvals: mrEditModule() },
{
...el.dataset,
prefix: 'mr-edit',
canEdit: parseBoolean(el.dataset.canEdit),
canUpdateApprovers: parseBoolean(el.dataset.canUpdateApprovers),
showCodeOwnerTip: parseBoolean(el.dataset.showCodeOwnerTip),
allowMultiRule: parseBoolean(el.dataset.allowMultiRule),
canOverride: parseBoolean(el.dataset.canOverride),
},
);
store.dispatch('setTargetBranch', targetBranch);
......
......@@ -15,12 +15,15 @@ export default function mountProjectSettingsApprovals(el) {
coverageCheckHelpPagePath,
} = el.dataset;
const store = createStore(projectSettingsModule(), {
...el.dataset,
prefix: 'project-settings',
allowMultiRule: parseBoolean(el.dataset.allowMultiRule),
canEdit: parseBoolean(el.dataset.canEdit),
});
const store = createStore(
{ approvals: projectSettingsModule() },
{
...el.dataset,
prefix: 'project-settings',
allowMultiRule: parseBoolean(el.dataset.allowMultiRule),
canEdit: parseBoolean(el.dataset.canEdit),
},
);
return new Vue({
el,
......
......@@ -3,10 +3,10 @@ import securityConfigurationModule from 'ee/security_configuration/modules/confi
import modalModule from '~/vuex_shared/modules/modal';
import state from './state';
export const createStoreOptions = (approvalsModule, settings) => ({
export const createStoreOptions = (approvalsModules, settings) => ({
state: state(settings),
modules: {
...(approvalsModule ? { approvals: approvalsModule } : {}),
...approvalsModules,
createModal: modalModule(),
deleteModal: modalModule(),
securityConfiguration: securityConfigurationModule({
......@@ -15,5 +15,5 @@ export const createStoreOptions = (approvalsModule, settings) => ({
},
});
export default (approvalsModule, settings = {}) =>
new Vuex.Store(createStoreOptions(approvalsModule, settings));
export default (approvalModules, settings = {}) =>
new Vuex.Store(createStoreOptions(approvalModules, settings));
......@@ -5,17 +5,18 @@ module EE
module Integrations
module StiType
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
EE_NAMESPACED_INTEGRATIONS = (::Gitlab::Integrations::StiType::NAMESPACED_INTEGRATIONS + %w(
Github GitlabSlackApplication
)).freeze
private
class_methods do
extend ::Gitlab::Utils::Override
override :namespaced_integrations
def namespaced_integrations
EE_NAMESPACED_INTEGRATIONS
override :namespaced_integrations
def namespaced_integrations
EE_NAMESPACED_INTEGRATIONS
end
end
end
end
......
......@@ -38,10 +38,13 @@ describe('EE Approvals App', () => {
rules: `<div class="${TEST_RULES_CLASS}">These are the rules!</div>`,
};
store = createStoreOptions(settingsModule(), {
canEdit: true,
prefix: APP_PREFIX,
});
store = createStoreOptions(
{ approvals: settingsModule() },
{
canEdit: true,
prefix: APP_PREFIX,
},
);
store.modules.approvals.actions = {
fetchRules: jest.fn().mockResolvedValue(),
......
......@@ -30,7 +30,7 @@ describe('ApprovalSettings', () => {
jest.spyOn(actions, 'dismissErrorMessage').mockImplementation();
jest.spyOn(actions, 'dismissSuccessMessage').mockImplementation();
store = createStore(module);
store = createStore({ approvalSettings: module });
};
const createWrapper = () => {
......
......@@ -43,7 +43,7 @@ describe('EE Approvals Group Settings App', () => {
axiosMock = new MockAdapter(axios);
axiosMock.onGet('*');
store = createStoreOptions(approvalSettingsModule(groupApprovalsMappers));
store = createStoreOptions({ approvalSettings: approvalSettingsModule(groupApprovalsMappers) });
});
afterEach(() => {
......
......@@ -32,7 +32,7 @@ describe('EE Approvals MREditApp', () => {
axiosMock = new MockAdapter(axios);
axiosMock.onGet('*');
store = createStoreOptions(MREditModule());
store = createStoreOptions({ approvals: MREditModule() });
store.modules.approvals.state.hasLoaded = true;
});
......
......@@ -34,7 +34,7 @@ describe('EE Approvlas MRRulesHiddenInputs', () => {
};
beforeEach(() => {
store = createStoreOptions(MREditModule());
store = createStoreOptions({ approvals: MREditModule() });
store.modules.approvals.state = {
rules: [],
rulesToDelete: [],
......
......@@ -46,7 +46,7 @@ describe('EE Approvals MRRules', () => {
.fn()
.mockImplementation((args) => new OriginalMutationObserver(args));
store = createStoreOptions(MREditModule());
store = createStoreOptions({ approvals: MREditModule() });
store.modules.approvals.state = {
hasLoaded: true,
rules: [],
......
......@@ -26,7 +26,7 @@ describe('Rule Input', () => {
};
beforeEach(() => {
store = createStoreOptions(MREditModule());
store = createStoreOptions({ approvals: MREditModule() });
store.state.settings.canEdit = true;
store.modules.approvals.actions = {
......
......@@ -41,7 +41,7 @@ describe('Approvals ProjectRules', () => {
};
beforeEach(() => {
store = createStoreOptions(projectSettingsModule());
store = createStoreOptions({ approvals: projectSettingsModule() });
store.modules.approvals.state.rules = TEST_RULES;
});
......
......@@ -33,7 +33,7 @@ describe('EE Approvals RuleControls', () => {
const findRemoveButton = () => findButton('Remove');
beforeEach(() => {
store = createStoreOptions(MREditModule());
store = createStoreOptions({ approvals: MREditModule() });
({ actions } = store.modules.approvals);
['requestEditRule', 'requestDeleteRule'].forEach((actionName) =>
jest.spyOn(actions, actionName),
......
......@@ -104,7 +104,10 @@ describe('EE Approvals RuleForm', () => {
];
beforeEach(() => {
store = createStoreOptions(projectSettingsModule(), { projectId: TEST_PROJECT_ID });
store = createStoreOptions(
{ approvals: projectSettingsModule() },
{ projectId: TEST_PROJECT_ID },
);
['postRule', 'putRule', 'deleteRule', 'putFallbackRule'].forEach((actionName) => {
jest.spyOn(store.modules.approvals.actions, actionName).mockImplementation(() => {});
......
......@@ -31,7 +31,7 @@ describe('UnconfiguredSecurityRules component', () => {
beforeEach(() => {
store = new Vuex.Store(
createStoreOptions(projectSettingsModule(), { projectId: TEST_PROJECT_ID }),
createStoreOptions({ approvals: projectSettingsModule() }, { projectId: TEST_PROJECT_ID }),
);
jest.spyOn(store, 'dispatch');
});
......
......@@ -73,7 +73,7 @@ module Gitlab
if project.persisted? && mv_repositories(project)
log " * Created #{project.name} (#{project_full_path})".color(:green)
project.write_repository_config
project.set_full_path
ProjectCacheWorker.perform_async(project.id)
else
......
......@@ -905,13 +905,17 @@ module Gitlab
end
# rubocop:enable Metrics/ParameterLists
def write_config(full_path:)
def set_full_path(full_path:)
return unless full_path.present?
# This guard avoids Gitaly log/error spam
raise NoRepository, 'repository does not exist' unless exists?
set_config('gitlab.fullpath' => full_path)
if Feature.enabled?(:set_full_path)
gitaly_repository_client.set_full_path(full_path)
else
set_config('gitlab.fullpath' => full_path)
end
end
def set_config(entries)
......
......@@ -263,6 +263,21 @@ module Gitlab
GitalyClient.call(@storage, :repository_service, :write_ref, request, timeout: GitalyClient.fast_timeout)
end
def set_full_path(path)
GitalyClient.call(
@storage,
:repository_service,
:set_full_path,
Gitaly::SetFullPathRequest.new(
repository: @gitaly_repo,
path: path
),
timeout: GitalyClient.fast_timeout
)
nil
end
def set_config(entries)
return if entries.empty?
......
......@@ -10,6 +10,10 @@ module Gitlab
Prometheus Pushover Redmine Slack SlackSlashCommands Teamcity UnifyCircuit Youtrack WebexTeams
)).freeze
def self.namespaced_integrations
NAMESPACED_INTEGRATIONS
end
def cast(value)
new_cast(value) || super
end
......@@ -32,16 +36,12 @@ module Gitlab
private
def namespaced_integrations
NAMESPACED_INTEGRATIONS
end
def new_cast(value)
value = prepare_value(value)
return unless value
stripped_name = value.delete_suffix('Service')
return unless namespaced_integrations.include?(stripped_name)
return unless self.class.namespaced_integrations.include?(stripped_name)
"Integrations::#{stripped_name}"
end
......
......@@ -1729,43 +1729,61 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
end
describe '#write_config' do
before do
repository_rugged.config["gitlab.fullpath"] = repository_path
end
describe '#set_full_path' do
shared_examples '#set_full_path' do
before do
repository_rugged.config["gitlab.fullpath"] = repository_path
end
context 'is given a path' do
it 'writes it to disk' do
repository.write_config(full_path: "not-the/real-path.git")
context 'is given a path' do
it 'writes it to disk' do
repository.set_full_path(full_path: "not-the/real-path.git")
config = File.read(File.join(repository_path, "config"))
config = File.read(File.join(repository_path, "config"))
expect(config).to include("[gitlab]")
expect(config).to include("fullpath = not-the/real-path.git")
expect(config).to include("[gitlab]")
expect(config).to include("fullpath = not-the/real-path.git")
end
end
end
context 'it is given an empty path' do
it 'does not write it to disk' do
repository.write_config(full_path: "")
context 'it is given an empty path' do
it 'does not write it to disk' do
repository.set_full_path(full_path: "")
config = File.read(File.join(repository_path, "config"))
config = File.read(File.join(repository_path, "config"))
expect(config).to include("[gitlab]")
expect(config).to include("fullpath = #{repository_path}")
expect(config).to include("[gitlab]")
expect(config).to include("fullpath = #{repository_path}")
end
end
context 'repository does not exist' do
it 'raises NoRepository and does not call Gitaly WriteConfig' do
repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '', 'group/project')
expect(repository.gitaly_repository_client).not_to receive(:set_full_path)
expect do
repository.set_full_path(full_path: 'foo/bar.git')
end.to raise_error(Gitlab::Git::Repository::NoRepository)
end
end
end
context 'repository does not exist' do
it 'raises NoRepository and does not call Gitaly WriteConfig' do
repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '', 'group/project')
context 'with :set_full_path enabled' do
before do
stub_feature_flags(set_full_path: true)
end
expect(repository.gitaly_repository_client).not_to receive(:write_config)
it_behaves_like '#set_full_path'
end
expect do
repository.write_config(full_path: 'foo/bar.git')
end.to raise_error(Gitlab::Git::Repository::NoRepository)
context 'with :set_full_path disabled' do
before do
stub_feature_flags(set_full_path: false)
end
it_behaves_like '#set_full_path'
end
end
......
......@@ -333,4 +333,17 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
client.replicate(source_repository)
end
end
describe '#set_full_path' do
let(:path) { 'repo/path' }
it 'sends a set_full_path message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:set_full_path)
.with(gitaly_request_with_params(path: path), kind_of(Hash))
.and_return(double)
client.set_full_path(path)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddTriggersToIntegrationsTypeNew do
let(:migration) { described_class.new }
let(:integrations) { table(:integrations) }
describe '#up' do
before do
migrate!
end
describe 'INSERT trigger' do
it 'sets `type_new` to the transformed `type` class name' do
Gitlab::Integrations::StiType.namespaced_integrations.each do |type|
integration = integrations.create!(type: "#{type}Service")
expect(integration.reload).to have_attributes(
type: "#{type}Service",
type_new: "Integrations::#{type}"
)
end
end
it 'ignores types that are not namespaced' do
# We don't actually have any integrations without namespaces,
# but we can abuse one of the integration base classes.
integration = integrations.create!(type: 'BaseIssueTracker')
expect(integration.reload).to have_attributes(
type: 'BaseIssueTracker',
type_new: nil
)
end
it 'ignores types that are unknown' do
integration = integrations.create!(type: 'FooBar')
expect(integration.reload).to have_attributes(
type: 'FooBar',
type_new: nil
)
end
end
end
describe '#down' do
before do
migration.up
migration.down
end
it 'drops the INSERT trigger' do
integration = integrations.create!(type: 'JiraService')
expect(integration.reload).to have_attributes(
type: 'JiraService',
type_new: nil
)
end
end
end
......@@ -5228,7 +5228,7 @@ RSpec.describe Project, factory_default: :keep do
expect(InternalId).to receive(:flush_records!).with(project: project)
expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:repository_size])
expect(DetectRepositoryLanguagesWorker).to receive(:perform_async).with(project.id)
expect(project).to receive(:write_repository_config)
expect(project).to receive(:set_full_path)
project.after_import
end
......@@ -5297,25 +5297,25 @@ RSpec.describe Project, factory_default: :keep do
end
end
describe '#write_repository_config' do
describe '#set_full_path' do
let_it_be(:project) { create(:project, :repository) }
it 'writes full path in .git/config when key is missing' do
project.write_repository_config
project.set_full_path
expect(rugged_config['gitlab.fullpath']).to eq project.full_path
end
it 'updates full path in .git/config when key is present' do
project.write_repository_config(gl_full_path: 'old/path')
project.set_full_path(gl_full_path: 'old/path')
expect { project.write_repository_config }.to change { rugged_config['gitlab.fullpath'] }.from('old/path').to(project.full_path)
expect { project.set_full_path }.to change { rugged_config['gitlab.fullpath'] }.from('old/path').to(project.full_path)
end
it 'does not raise an error with an empty repository' do
project = create(:project_empty_repo)
expect { project.write_repository_config }.not_to raise_error
expect { project.set_full_path }.not_to raise_error
end
end
......
......@@ -21,7 +21,7 @@ RSpec.describe BulkCreateIntegrationService do
described_class.new(integration, batch, association).execute
expect(created_integration.attributes.except(*excluded_attributes))
.to eq(integration.attributes.except(*excluded_attributes))
.to eq(integration.reload.attributes.except(*excluded_attributes))
end
context 'integration with data fields' do
......
......@@ -51,11 +51,11 @@ RSpec.describe BulkUpdateIntegrationService do
context 'with inherited integration' do
it 'updates the integration', :aggregate_failures do
described_class.new(subgroup_integration, batch).execute
described_class.new(subgroup_integration.reload, batch).execute
expect(integration.reload.inherit_from_id).to eq(group_integration.id)
expect(integration.reload.attributes.except(*excluded_attributes))
.to eq(subgroup_integration.attributes.except(*excluded_attributes))
.to eq(subgroup_integration.reload.attributes.except(*excluded_attributes))
expect(excluded_integration.reload.inherit_from_id).not_to eq(group_integration.id)
expect(excluded_integration.reload.attributes.except(*excluded_attributes))
......
......@@ -335,7 +335,7 @@ RSpec.describe Projects::CreateService, '#execute' do
it 'does not write repository config' do
expect_next_instance_of(Project) do |project|
expect(project).not_to receive(:write_repository_config)
expect(project).not_to receive(:set_full_path)
end
imported_project
......
......@@ -96,13 +96,13 @@ RSpec.describe Projects::HashedStorage::MigrateRepositoryService do
end
it 'handles Gitlab::Git::CommandError' do
expect(project).to receive(:write_repository_config).and_raise(Gitlab::Git::CommandError)
expect(project).to receive(:set_full_path).and_raise(Gitlab::Git::CommandError)
expect { service.execute }.not_to raise_exception
end
it 'ensures rollback when Gitlab::Git::CommandError' do
expect(project).to receive(:write_repository_config).and_raise(Gitlab::Git::CommandError)
expect(project).to receive(:set_full_path).and_raise(Gitlab::Git::CommandError)
expect(service).to receive(:rollback_folder_move).and_call_original
service.execute
......
......@@ -96,13 +96,13 @@ RSpec.describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab
end
it 'handles Gitlab::Git::CommandError' do
expect(project).to receive(:write_repository_config).and_raise(Gitlab::Git::CommandError)
expect(project).to receive(:set_full_path).and_raise(Gitlab::Git::CommandError)
expect { service.execute }.not_to raise_exception
end
it 'ensures rollback when Gitlab::Git::CommandError' do
expect(project).to receive(:write_repository_config).and_raise(Gitlab::Git::CommandError)
expect(project).to receive(:set_full_path).and_raise(Gitlab::Git::CommandError)
expect(service).to receive(:rollback_folder_move).and_call_original
service.execute
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment