Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-09-15 21:11:12 +00:00
parent 27d1ed4ddf
commit a7aff3e0e4
38 changed files with 368 additions and 81 deletions

View File

@ -1,5 +1,5 @@
<script> <script>
import { GlButton, GlFormGroup, GlFormCheckbox } from '@gitlab/ui'; import { GlButton, GlFormGroup, GlFormCheckbox, GlFormRadioGroup, GlFormRadio } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import ErrorTrackingForm from './error_tracking_form.vue'; import ErrorTrackingForm from './error_tracking_form.vue';
import ProjectDropdown from './project_dropdown.vue'; import ProjectDropdown from './project_dropdown.vue';
@ -10,6 +10,8 @@ export default {
GlButton, GlButton,
GlFormCheckbox, GlFormCheckbox,
GlFormGroup, GlFormGroup,
GlFormRadioGroup,
GlFormRadio,
ProjectDropdown, ProjectDropdown,
}, },
props: { props: {
@ -22,6 +24,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
initialIntegrated: {
type: String,
required: true,
},
initialProject: { initialProject: {
type: String, type: String,
required: false, required: false,
@ -49,12 +55,20 @@ export default {
'isProjectInvalid', 'isProjectInvalid',
'projectSelectionLabel', 'projectSelectionLabel',
]), ]),
...mapState(['enabled', 'projects', 'selectedProject', 'settingsLoading', 'token']), ...mapState([
'enabled',
'integrated',
'projects',
'selectedProject',
'settingsLoading',
'token',
]),
}, },
created() { created() {
this.setInitialState({ this.setInitialState({
apiHost: this.initialApiHost, apiHost: this.initialApiHost,
enabled: this.initialEnabled, enabled: this.initialEnabled,
integrated: this.initialIntegrated,
project: this.initialProject, project: this.initialProject,
token: this.initialToken, token: this.initialToken,
listProjectsEndpoint: this.listProjectsEndpoint, listProjectsEndpoint: this.listProjectsEndpoint,
@ -62,7 +76,13 @@ export default {
}); });
}, },
methods: { methods: {
...mapActions(['setInitialState', 'updateEnabled', 'updateSelectedProject', 'updateSettings']), ...mapActions([
'setInitialState',
'updateEnabled',
'updateIntegrated',
'updateSelectedProject',
'updateSettings',
]),
handleSubmit() { handleSubmit() {
this.updateSettings(); this.updateSettings();
}, },
@ -76,27 +96,44 @@ export default {
:label="s__('ErrorTracking|Enable error tracking')" :label="s__('ErrorTracking|Enable error tracking')"
label-for="error-tracking-enabled" label-for="error-tracking-enabled"
> >
<gl-form-checkbox <gl-form-checkbox id="error-tracking-enabled" :checked="enabled" @change="updateEnabled">
id="error-tracking-enabled"
:checked="enabled"
@change="updateEnabled($event)"
>
{{ s__('ErrorTracking|Active') }} {{ s__('ErrorTracking|Active') }}
</gl-form-checkbox> </gl-form-checkbox>
</gl-form-group> </gl-form-group>
<error-tracking-form /> <gl-form-group
<div class="form-group"> :label="s__('ErrorTracking|Error tracking backend')"
<project-dropdown data-testid="tracking-backend-settings"
:has-projects="hasProjects" >
:invalid-project-label="invalidProjectLabel" <gl-form-radio-group name="explicit" :checked="integrated" @change="updateIntegrated">
:is-project-invalid="isProjectInvalid" <gl-form-radio name="error-tracking-integrated" :value="false">
:dropdown-label="dropdownLabel" {{ __('Sentry') }}
:project-selection-label="projectSelectionLabel" <template #help>
:projects="projects" {{ __('Requires you to deploy or set up cloud-hosted Sentry.') }}
:selected-project="selectedProject" </template>
:token="token" </gl-form-radio>
@select-project="updateSelectedProject" <gl-form-radio name="error-tracking-integrated" :value="true">
/> {{ __('GitLab') }}
<template #help>
{{ __('Uses GitLab as a lightweight alternative to Sentry.') }}
</template>
</gl-form-radio>
</gl-form-radio-group>
</gl-form-group>
<div v-if="!integrated" class="js-sentry-setting-form" data-testid="sentry-setting-form">
<error-tracking-form />
<div class="form-group">
<project-dropdown
:has-projects="hasProjects"
:invalid-project-label="invalidProjectLabel"
:is-project-invalid="isProjectInvalid"
:dropdown-label="dropdownLabel"
:project-selection-label="projectSelectionLabel"
:projects="projects"
:selected-project="selectedProject"
:token="token"
@select-project="updateSelectedProject"
/>
</div>
</div> </div>
<gl-button <gl-button
:disabled="settingsLoading" :disabled="settingsLoading"

View File

@ -5,7 +5,15 @@ import createStore from './store';
export default () => { export default () => {
const formContainerEl = document.querySelector('.js-error-tracking-form'); const formContainerEl = document.querySelector('.js-error-tracking-form');
const { const {
dataset: { apiHost, enabled, project, token, listProjectsEndpoint, operationsSettingsEndpoint }, dataset: {
apiHost,
enabled,
integrated,
project,
token,
listProjectsEndpoint,
operationsSettingsEndpoint,
},
} = formContainerEl; } = formContainerEl;
return new Vue({ return new Vue({
@ -16,6 +24,7 @@ export default () => {
props: { props: {
initialApiHost: apiHost, initialApiHost: apiHost,
initialEnabled: enabled, initialEnabled: enabled,
initialIntegrated: integrated,
initialProject: project, initialProject: project,
initialToken: token, initialToken: token,
listProjectsEndpoint, listProjectsEndpoint,

View File

@ -79,6 +79,10 @@ export const updateEnabled = ({ commit }, enabled) => {
commit(types.UPDATE_ENABLED, enabled); commit(types.UPDATE_ENABLED, enabled);
}; };
export const updateIntegrated = ({ commit }, integrated) => {
commit(types.UPDATE_INTEGRATED, integrated);
};
export const updateToken = ({ commit }, token) => { export const updateToken = ({ commit }, token) => {
commit(types.UPDATE_TOKEN, token); commit(types.UPDATE_TOKEN, token);
commit(types.RESET_CONNECT); commit(types.RESET_CONNECT);

View File

@ -6,6 +6,7 @@ export const UPDATE_API_HOST = 'UPDATE_API_HOST';
export const UPDATE_CONNECT_ERROR = 'UPDATE_CONNECT_ERROR'; export const UPDATE_CONNECT_ERROR = 'UPDATE_CONNECT_ERROR';
export const UPDATE_CONNECT_SUCCESS = 'UPDATE_CONNECT_SUCCESS'; export const UPDATE_CONNECT_SUCCESS = 'UPDATE_CONNECT_SUCCESS';
export const UPDATE_ENABLED = 'UPDATE_ENABLED'; export const UPDATE_ENABLED = 'UPDATE_ENABLED';
export const UPDATE_INTEGRATED = 'UPDATE_INTEGRATED';
export const UPDATE_SELECTED_PROJECT = 'UPDATE_SELECTED_PROJECT'; export const UPDATE_SELECTED_PROJECT = 'UPDATE_SELECTED_PROJECT';
export const UPDATE_SETTINGS_LOADING = 'UPDATE_SETTINGS_LOADING'; export const UPDATE_SETTINGS_LOADING = 'UPDATE_SETTINGS_LOADING';
export const UPDATE_TOKEN = 'UPDATE_TOKEN'; export const UPDATE_TOKEN = 'UPDATE_TOKEN';

View File

@ -20,9 +20,18 @@ export default {
}, },
[types.SET_INITIAL_STATE]( [types.SET_INITIAL_STATE](
state, state,
{ apiHost, enabled, project, token, listProjectsEndpoint, operationsSettingsEndpoint }, {
apiHost,
enabled,
integrated,
project,
token,
listProjectsEndpoint,
operationsSettingsEndpoint,
},
) { ) {
state.enabled = parseBoolean(enabled); state.enabled = parseBoolean(enabled);
state.integrated = parseBoolean(integrated);
state.apiHost = apiHost; state.apiHost = apiHost;
state.token = token; state.token = token;
state.listProjectsEndpoint = listProjectsEndpoint; state.listProjectsEndpoint = listProjectsEndpoint;
@ -38,6 +47,9 @@ export default {
[types.UPDATE_ENABLED](state, enabled) { [types.UPDATE_ENABLED](state, enabled) {
state.enabled = enabled; state.enabled = enabled;
}, },
[types.UPDATE_INTEGRATED](state, integrated) {
state.integrated = integrated;
},
[types.UPDATE_TOKEN](state, token) { [types.UPDATE_TOKEN](state, token) {
state.token = token; state.token = token;
}, },

View File

@ -1,6 +1,7 @@
export default () => ({ export default () => ({
apiHost: '', apiHost: '',
enabled: false, enabled: false,
integrated: false,
token: '', token: '',
projects: [], projects: [],
isLoadingProjects: false, isLoadingProjects: false,

View File

@ -1,6 +1,12 @@
export const projectKeys = ['name', 'organizationName', 'organizationSlug', 'slug']; export const projectKeys = ['name', 'organizationName', 'organizationSlug', 'slug'];
export const transformFrontendSettings = ({ apiHost, enabled, token, selectedProject }) => { export const transformFrontendSettings = ({
apiHost,
enabled,
integrated,
token,
selectedProject,
}) => {
const project = selectedProject const project = selectedProject
? { ? {
slug: selectedProject.slug, slug: selectedProject.slug,
@ -10,7 +16,7 @@ export const transformFrontendSettings = ({ apiHost, enabled, token, selectedPro
} }
: null; : null;
return { api_host: apiHost || null, enabled, token: token || null, project }; return { api_host: apiHost || null, enabled, integrated, token: token || null, project };
}; };
export const getDisplayName = (project) => `${project.organizationName} | ${project.slug}`; export const getDisplayName = (project) => `${project.organizationName} | ${project.slug}`;

View File

@ -136,6 +136,7 @@ module Projects
error_tracking_setting_attributes: [ error_tracking_setting_attributes: [
:enabled, :enabled,
:integrated,
:api_host, :api_host,
:token, :token,
project: [:slug, :name, :organization_slug, :organization_name] project: [:slug, :name, :organization_slug, :organization_name]

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class SecurityReportsMrWidgetPromptExperiment < ApplicationExperiment # rubocop:disable Gitlab/NamespacedClass
def publish(_result = nil)
super
publish_to_database
end
# This is a purely client side experiment, and since we don't have a nicer
# way to define variants yet, we define them here.
def candidate_behavior
end
end

View File

@ -94,6 +94,7 @@ module Projects
} }
} }
params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value
params[:error_tracking_setting_attributes][:integrated] = settings[:integrated] unless settings[:integrated].nil?
params params
end end

View File

@ -17,4 +17,5 @@
project: error_tracking_setting_project_json, project: error_tracking_setting_project_json,
api_host: setting.api_host, api_host: setting.api_host,
enabled: setting.enabled.to_json, enabled: setting.enabled.to_json,
integrated: setting.integrated.to_json,
token: setting.token.present? ? '*' * 12 : nil } } token: setting.token.present? ? '*' * 12 : nil } }

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337628
milestone: '14.2' milestone: '14.2'
type: development type: development
group: group::pipeline authoring group: group::pipeline authoring
default_enabled: false default_enabled: true

View File

@ -0,0 +1,8 @@
---
name: security_reports_mr_widget_prompt
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70086
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340436
milestone: '14.3'
type: experiment
group: group::adoption
default_enabled: false

View File

@ -7,7 +7,8 @@ product_stage: configure
product_group: group::configure product_group: group::configure
product_category: infrastructure_as_code product_category: infrastructure_as_code
value_type: number value_type: number
status: broken status: removed
milestone_removed: '14.3'
repair_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332466 repair_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332466
time_frame: 28d time_frame: 28d
data_source: redis_hll data_source: redis_hll

View File

@ -7,7 +7,8 @@ product_stage: configure
product_group: group::configure product_group: group::configure
product_category: infrastructure_as_code product_category: infrastructure_as_code
value_type: number value_type: number
status: broken status: removed
milestone_removed: '14.3'
repair_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332466 repair_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332466
time_frame: 7d time_frame: 7d
data_source: redis_hll data_source: redis_hll

View File

@ -385,7 +385,6 @@ tables:
- public_builds - public_builds
- last_repository_check_failed - last_repository_check_failed
- last_repository_check_at - last_repository_check_at
- container_registry_enabled
- only_allow_merge_if_pipeline_succeeds - only_allow_merge_if_pipeline_succeeds
- has_external_issue_tracker - has_external_issue_tracker
- repository_storage - repository_storage

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
class DropTemporaryColumnsAndTriggersForCiBuildNeeds < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
TABLE = 'ci_build_needs'
TEMPORARY_COLUMN = 'build_id_convert_to_bigint'
MAIN_COLUMN = 'build_id'
# rubocop:disable Migration/WithLockRetriesDisallowedMethod
def up
with_lock_retries do
cleanup_conversion_of_integer_to_bigint(TABLE, MAIN_COLUMN)
end
end
def down
check_trigger_permissions!(TABLE)
with_lock_retries do
add_column(TABLE, TEMPORARY_COLUMN, :int, default: 0, null: false)
install_rename_triggers(TABLE, MAIN_COLUMN, TEMPORARY_COLUMN)
end
end
# rubocop:enable Migration/WithLockRetriesDisallowedMethod
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class RemoveContainerRegistryEnabled < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
with_lock_retries do
remove_column :projects, :container_registry_enabled
end
end
def down
with_lock_retries do
add_column :projects, :container_registry_enabled, :boolean # rubocop:disable Migration/AddColumnsToWideTables
end
end
end

View File

@ -0,0 +1 @@
c99df310082dd6f5faff3cfd90dfca88af26d840889910ebac0e73ba483a09b2

View File

@ -0,0 +1 @@
abed3f9a6c188890d3dcd21f73a09347f8ccec0f6cc448220fadba5cbda17281

View File

@ -64,15 +64,6 @@ RETURN NULL;
END END
$$; $$;
CREATE FUNCTION trigger_21e7a2602957() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
NEW."build_id_convert_to_bigint" := NEW."build_id";
RETURN NEW;
END;
$$;
CREATE FUNCTION trigger_3f6129be01d2() RETURNS trigger CREATE FUNCTION trigger_3f6129be01d2() RETURNS trigger
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
@ -11227,7 +11218,6 @@ ALTER SEQUENCE chat_teams_id_seq OWNED BY chat_teams.id;
CREATE TABLE ci_build_needs ( CREATE TABLE ci_build_needs (
id integer NOT NULL, id integer NOT NULL,
build_id_convert_to_bigint integer DEFAULT 0 NOT NULL,
name text NOT NULL, name text NOT NULL,
artifacts boolean DEFAULT true NOT NULL, artifacts boolean DEFAULT true NOT NULL,
optional boolean DEFAULT false NOT NULL, optional boolean DEFAULT false NOT NULL,
@ -18184,7 +18174,6 @@ CREATE TABLE projects (
public_builds boolean DEFAULT true NOT NULL, public_builds boolean DEFAULT true NOT NULL,
last_repository_check_failed boolean, last_repository_check_failed boolean,
last_repository_check_at timestamp without time zone, last_repository_check_at timestamp without time zone,
container_registry_enabled boolean,
only_allow_merge_if_pipeline_succeeds boolean DEFAULT false NOT NULL, only_allow_merge_if_pipeline_succeeds boolean DEFAULT false NOT NULL,
has_external_issue_tracker boolean, has_external_issue_tracker boolean,
repository_storage character varying DEFAULT 'default'::character varying NOT NULL, repository_storage character varying DEFAULT 'default'::character varying NOT NULL,
@ -27322,8 +27311,6 @@ ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_p
ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_63_pkey; ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_63_pkey;
CREATE TRIGGER trigger_21e7a2602957 BEFORE INSERT OR UPDATE ON ci_build_needs FOR EACH ROW EXECUTE FUNCTION trigger_21e7a2602957();
CREATE TRIGGER trigger_3f6129be01d2 BEFORE INSERT OR UPDATE ON ci_builds FOR EACH ROW EXECUTE FUNCTION trigger_3f6129be01d2(); CREATE TRIGGER trigger_3f6129be01d2 BEFORE INSERT OR UPDATE ON ci_builds FOR EACH ROW EXECUTE FUNCTION trigger_3f6129be01d2();
CREATE TRIGGER trigger_542d6c2ad72e BEFORE INSERT OR UPDATE ON ci_builds_metadata FOR EACH ROW EXECUTE FUNCTION trigger_542d6c2ad72e(); CREATE TRIGGER trigger_542d6c2ad72e BEFORE INSERT OR UPDATE ON ci_builds_metadata FOR EACH ROW EXECUTE FUNCTION trigger_542d6c2ad72e();

View File

@ -265,3 +265,9 @@ by assigning different processes to different parts of the table. The `BATCH`
and `UPDATE_DELAY` parameters allow the speed of the migration to be traded off and `UPDATE_DELAY` parameters allow the speed of the migration to be traded off
against concurrent access to the table. The `ANSI` parameter should be set to against concurrent access to the table. The `ANSI` parameter should be set to
false if your terminal does not support ANSI escape codes. false if your terminal does not support ANSI escape codes.
By default, `sudo` does not preserve existing environment variables. You should append them, rather than prefix them.
```shell
sudo gitlab-rake gitlab:external_diffs:force_object_storage START_ID=59946109 END_ID=59946109 UPDATE_DELAY=5
```

View File

@ -78,7 +78,7 @@ packages on the group level, create a distribution with the same `codename`.
To create a project-level distribution: To create a project-level distribution:
```shell ```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/<project_id>/debian_distributions?codename=unstable curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/<project_id>/debian_distributions?codename=unstable"
``` ```
Example response: Example response:

View File

@ -18,6 +18,24 @@ module Gitlab
KNOWN_EVENTS_PATH = File.expand_path('known_events/*.yml', __dir__) KNOWN_EVENTS_PATH = File.expand_path('known_events/*.yml', __dir__)
ALLOWED_AGGREGATIONS = %i(daily weekly).freeze ALLOWED_AGGREGATIONS = %i(daily weekly).freeze
CATEGORIES_FOR_TOTALS = %w[
analytics
code_review
compliance
deploy_token_packages
ecosystem
epic_boards_usage
epics_usage
ide_edit
incident_management
issues_edit
pipeline_authoring
quickactions
search
testing
user_packages
].freeze
# Track event on entity_id # Track event on entity_id
# Increment a Redis HLL counter for unique event_name and entity_id # Increment a Redis HLL counter for unique event_name and entity_id
# #
@ -90,7 +108,7 @@ module Gitlab
hash["#{event}_monthly"] = unique_events(**monthly_time_range.merge(event_names: [event])) hash["#{event}_monthly"] = unique_events(**monthly_time_range.merge(event_names: [event]))
end end
if eligible_for_totals?(events_names) if eligible_for_totals?(events_names) && CATEGORIES_FOR_TOTALS.include?(category)
event_results["#{category}_total_unique_counts_weekly"] = unique_events(**weekly_time_range.merge(event_names: events_names)) event_results["#{category}_total_unique_counts_weekly"] = unique_events(**weekly_time_range.merge(event_names: events_names))
event_results["#{category}_total_unique_counts_monthly"] = unique_events(**monthly_time_range.merge(event_names: events_names)) event_results["#{category}_total_unique_counts_monthly"] = unique_events(**monthly_time_range.merge(event_names: events_names))
end end

View File

@ -13366,6 +13366,9 @@ msgstr ""
msgid "ErrorTracking|Enable error tracking" msgid "ErrorTracking|Enable error tracking"
msgstr "" msgstr ""
msgid "ErrorTracking|Error tracking backend"
msgstr ""
msgid "ErrorTracking|If you self-host Sentry, enter your Sentry instance's full URL. If you use Sentry's hosted solution, enter https://sentry.io" msgid "ErrorTracking|If you self-host Sentry, enter your Sentry instance's full URL. If you use Sentry's hosted solution, enter https://sentry.io"
msgstr "" msgstr ""
@ -28637,6 +28640,9 @@ msgstr[1] ""
msgid "Requires values to meet regular expression requirements." msgid "Requires values to meet regular expression requirements."
msgstr "" msgstr ""
msgid "Requires you to deploy or set up cloud-hosted Sentry."
msgstr ""
msgid "Requires your primary GitLab email address." msgid "Requires your primary GitLab email address."
msgstr "" msgstr ""
@ -30517,6 +30523,9 @@ msgstr ""
msgid "Send service data" msgid "Send service data"
msgstr "" msgstr ""
msgid "Sentry"
msgstr ""
msgid "Sentry API URL" msgid "Sentry API URL"
msgstr "" msgstr ""
@ -37013,6 +37022,9 @@ msgstr ""
msgid "UsersSelect|Unassigned" msgid "UsersSelect|Unassigned"
msgstr "" msgstr ""
msgid "Uses GitLab as a lightweight alternative to Sentry."
msgstr ""
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}" msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr "" msgstr ""

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe SecurityReportsMrWidgetPromptExperiment do
it "defines a control and candidate" do
expect(subject.behaviors.keys).to match_array(%w[control candidate])
end
it "publishes to the database" do
expect(subject).to receive(:publish_to_database)
subject.publish
end
end

View File

@ -89,7 +89,7 @@ RSpec.describe 'Search bar', :js do
expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: original_size) expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: original_size)
end end
it 'resets the dropdown filters', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/9985' do it 'resets the dropdown filters' do
filtered_search.click filtered_search.click
hint_offset = get_left_style(find('#js-dropdown-hint')['style']) hint_offset = get_left_style(find('#js-dropdown-hint')['style'])
@ -103,7 +103,7 @@ RSpec.describe 'Search bar', :js do
find('.filtered-search-box .clear-search').click find('.filtered-search-box .clear-search').click
filtered_search.click filtered_search.click
expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 6) expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', minimum: 6)
expect(get_left_style(find('#js-dropdown-hint')['style'])).to eq(hint_offset) expect(get_left_style(find('#js-dropdown-hint')['style'])).to eq(hint_offset)
end end
end end

View File

@ -150,6 +150,33 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
assert_text('Connection failed. Check Auth Token and try again.') assert_text('Connection failed. Check Auth Token and try again.')
end end
end end
context 'integrated error tracking backend' do
it 'successfully fills and submits the form' do
visit project_settings_operations_path(project)
wait_for_requests
within '.js-error-tracking-settings' do
click_button('Expand')
end
expect(page).to have_content('Error tracking backend')
within '.js-error-tracking-settings' do
check('Active')
choose('GitLab')
end
expect(page).not_to have_content('Sentry API URL')
click_button('Save changes')
wait_for_requests
assert_text('Your changes have been saved')
end
end
end end
context 'grafana integration settings form' do context 'grafana integration settings form' do

View File

@ -1,6 +1,9 @@
import { GlFormRadioGroup, GlFormRadio } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ErrorTrackingSettings from '~/error_tracking_settings/components/app.vue'; import ErrorTrackingSettings from '~/error_tracking_settings/components/app.vue';
import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue'; import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue';
import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue'; import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue';
@ -14,20 +17,31 @@ describe('error tracking settings app', () => {
let wrapper; let wrapper;
function mountComponent() { function mountComponent() {
wrapper = shallowMount(ErrorTrackingSettings, { wrapper = extendedWrapper(
localVue, shallowMount(ErrorTrackingSettings, {
store, // Override the imported store localVue,
propsData: { store, // Override the imported store
initialEnabled: 'true', propsData: {
initialApiHost: TEST_HOST, initialEnabled: 'true',
initialToken: 'someToken', initialIntegrated: 'false',
initialProject: null, initialApiHost: TEST_HOST,
listProjectsEndpoint: TEST_HOST, initialToken: 'someToken',
operationsSettingsEndpoint: TEST_HOST, initialProject: null,
}, listProjectsEndpoint: TEST_HOST,
}); operationsSettingsEndpoint: TEST_HOST,
},
}),
);
} }
const findBackendSettingsSection = () => wrapper.findByTestId('tracking-backend-settings');
const findBackendSettingsRadioGroup = () =>
findBackendSettingsSection().findComponent(GlFormRadioGroup);
const findBackendSettingsRadioButtons = () =>
findBackendSettingsRadioGroup().findAllComponents(GlFormRadio);
const findElementWithText = (wrappers, text) => wrappers.filter((item) => item.text() === text);
const findSentrySettings = () => wrapper.findByTestId('sentry-setting-form');
beforeEach(() => { beforeEach(() => {
store = createStore(); store = createStore();
@ -62,4 +76,46 @@ describe('error tracking settings app', () => {
}); });
}); });
}); });
describe('tracking-backend settings', () => {
it('contains a form-group with the correct label', () => {
expect(findBackendSettingsSection().attributes('label')).toBe('Error tracking backend');
});
it('contains a radio group', () => {
expect(findBackendSettingsRadioGroup().exists()).toBe(true);
});
it('contains the correct radio buttons', () => {
expect(findBackendSettingsRadioButtons()).toHaveLength(2);
expect(findElementWithText(findBackendSettingsRadioButtons(), 'Sentry')).toHaveLength(1);
expect(findElementWithText(findBackendSettingsRadioButtons(), 'GitLab')).toHaveLength(1);
});
it('toggles the sentry-settings section when sentry is selected as a tracking-backend', async () => {
expect(findSentrySettings().exists()).toBe(true);
// set the "integrated" setting to "true"
findBackendSettingsRadioGroup().vm.$emit('change', true);
await nextTick();
expect(findSentrySettings().exists()).toBe(false);
});
it.each([true, false])(
'calls the `updateIntegrated` action when the setting changes to `%s`',
(integrated) => {
jest.spyOn(store, 'dispatch').mockImplementation();
expect(store.dispatch).toHaveBeenCalledTimes(0);
findBackendSettingsRadioGroup().vm.$emit('change', integrated);
expect(store.dispatch).toHaveBeenCalledTimes(1);
expect(store.dispatch).toHaveBeenCalledWith('updateIntegrated', integrated);
},
);
});
}); });

View File

@ -42,6 +42,7 @@ export const sampleBackendProject = {
export const sampleFrontendSettings = { export const sampleFrontendSettings = {
apiHost: 'apiHost', apiHost: 'apiHost',
enabled: false, enabled: false,
integrated: false,
token: 'token', token: 'token',
selectedProject: { selectedProject: {
slug: normalizedProject.slug, slug: normalizedProject.slug,
@ -54,6 +55,7 @@ export const sampleFrontendSettings = {
export const transformedSettings = { export const transformedSettings = {
api_host: 'apiHost', api_host: 'apiHost',
enabled: false, enabled: false,
integrated: false,
token: 'token', token: 'token',
project: { project: {
slug: normalizedProject.slug, slug: normalizedProject.slug,
@ -71,6 +73,7 @@ export const defaultProps = {
export const initialEmptyState = { export const initialEmptyState = {
apiHost: '', apiHost: '',
enabled: false, enabled: false,
integrated: false,
project: null, project: null,
token: '', token: '',
listProjectsEndpoint: TEST_HOST, listProjectsEndpoint: TEST_HOST,
@ -80,6 +83,7 @@ export const initialEmptyState = {
export const initialPopulatedState = { export const initialPopulatedState = {
apiHost: 'apiHost', apiHost: 'apiHost',
enabled: true, enabled: true,
integrated: true,
project: JSON.stringify(projectList[0]), project: JSON.stringify(projectList[0]),
token: 'token', token: 'token',
listProjectsEndpoint: TEST_HOST, listProjectsEndpoint: TEST_HOST,

View File

@ -202,5 +202,11 @@ describe('error tracking settings actions', () => {
done, done,
); );
}); });
it.each([true, false])('should set the `integrated` flag to `%s`', async (payload) => {
await testAction(actions.updateIntegrated, payload, state, [
{ type: types.UPDATE_INTEGRATED, payload },
]);
});
}); });
}); });

View File

@ -25,6 +25,7 @@ describe('error tracking settings mutations', () => {
expect(state.apiHost).toEqual(''); expect(state.apiHost).toEqual('');
expect(state.enabled).toEqual(false); expect(state.enabled).toEqual(false);
expect(state.integrated).toEqual(false);
expect(state.selectedProject).toEqual(null); expect(state.selectedProject).toEqual(null);
expect(state.token).toEqual(''); expect(state.token).toEqual('');
expect(state.listProjectsEndpoint).toEqual(TEST_HOST); expect(state.listProjectsEndpoint).toEqual(TEST_HOST);
@ -38,6 +39,7 @@ describe('error tracking settings mutations', () => {
expect(state.apiHost).toEqual('apiHost'); expect(state.apiHost).toEqual('apiHost');
expect(state.enabled).toEqual(true); expect(state.enabled).toEqual(true);
expect(state.integrated).toEqual(true);
expect(state.selectedProject).toEqual(projectList[0]); expect(state.selectedProject).toEqual(projectList[0]);
expect(state.token).toEqual('token'); expect(state.token).toEqual('token');
expect(state.listProjectsEndpoint).toEqual(TEST_HOST); expect(state.listProjectsEndpoint).toEqual(TEST_HOST);
@ -78,5 +80,11 @@ describe('error tracking settings mutations', () => {
expect(state.connectSuccessful).toBe(false); expect(state.connectSuccessful).toBe(false);
expect(state.connectError).toBe(false); expect(state.connectError).toBe(false);
}); });
it.each([true, false])('should update `integrated` to `%s`', (integrated) => {
mutations[types.UPDATE_INTEGRATED](state, integrated);
expect(state.integrated).toBe(integrated);
});
}); });
}); });

View File

@ -11,12 +11,14 @@ describe('error tracking settings utils', () => {
const emptyFrontendSettingsObject = { const emptyFrontendSettingsObject = {
apiHost: '', apiHost: '',
enabled: false, enabled: false,
integrated: false,
token: '', token: '',
selectedProject: null, selectedProject: null,
}; };
const transformedEmptySettingsObject = { const transformedEmptySettingsObject = {
api_host: null, api_host: null,
enabled: false, enabled: false,
integrated: false,
token: null, token: null,
project: null, project: null,
}; };

View File

@ -462,6 +462,8 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
allow(described_class).to receive(:known_events).and_return(known_events) allow(described_class).to receive(:known_events).and_return(known_events)
allow(described_class).to receive(:categories).and_return(%w(category1 category2)) allow(described_class).to receive(:categories).and_return(%w(category1 category2))
stub_const('Gitlab::UsageDataCounters::HLLRedisCounter::CATEGORIES_FOR_TOTALS', %w(category1 category2))
described_class.track_event('event1_slot', values: entity1, time: 2.days.ago) described_class.track_event('event1_slot', values: entity1, time: 2.days.ago)
described_class.track_event('event2_slot', values: entity2, time: 2.days.ago) described_class.track_event('event2_slot', values: entity2, time: 2.days.ago)
described_class.track_event('event2_slot', values: entity3, time: 2.weeks.ago) described_class.track_event('event2_slot', values: entity3, time: 2.weeks.ago)

View File

@ -1279,9 +1279,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
subject { described_class.redis_hll_counters } subject { described_class.redis_hll_counters }
let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories } let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories }
let(:ineligible_total_categories) do
%w[source_code ci_secrets_management incident_management_alerts snippets terraform incident_management_oncall secure network_policies]
end
context 'with redis_hll_tracking feature enabled' do context 'with redis_hll_tracking feature enabled' do
it 'has all known_events' do it 'has all known_events' do
@ -1296,7 +1293,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
metrics = keys.map { |key| "#{key}_weekly" } + keys.map { |key| "#{key}_monthly" } metrics = keys.map { |key| "#{key}_weekly" } + keys.map { |key| "#{key}_monthly" }
if ineligible_total_categories.exclude?(category) if ::Gitlab::UsageDataCounters::HLLRedisCounter::CATEGORIES_FOR_TOTALS.include?(category)
metrics.append("#{category}_total_unique_counts_weekly", "#{category}_total_unique_counts_monthly") metrics.append("#{category}_total_unique_counts_weekly", "#{category}_total_unique_counts_monthly")
end end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!('drop_temporary_columns_and_triggers_for_ci_build_needs')
RSpec.describe DropTemporaryColumnsAndTriggersForCiBuildNeeds do
let(:ci_build_needs_table) { table(:ci_build_needs) }
it 'correctly migrates up and down' do
reversible_migration do |migration|
migration.before -> {
expect(ci_build_needs_table.column_names).to include('build_id_convert_to_bigint')
}
migration.after -> {
ci_build_needs_table.reset_column_information
expect(ci_build_needs_table.column_names).not_to include('build_id_convert_to_bigint')
}
end
end
end

View File

@ -153,6 +153,7 @@ RSpec.describe Projects::Operations::UpdateService do
{ {
error_tracking_setting_attributes: { error_tracking_setting_attributes: {
enabled: false, enabled: false,
integrated: true,
api_host: 'http://gitlab.com/', api_host: 'http://gitlab.com/',
token: 'token', token: 'token',
project: { project: {
@ -174,6 +175,7 @@ RSpec.describe Projects::Operations::UpdateService do
project.reload project.reload
expect(project.error_tracking_setting).not_to be_enabled expect(project.error_tracking_setting).not_to be_enabled
expect(project.error_tracking_setting.integrated).to be_truthy
expect(project.error_tracking_setting.api_url).to eq( expect(project.error_tracking_setting.api_url).to eq(
'http://gitlab.com/api/0/projects/org/project/' 'http://gitlab.com/api/0/projects/org/project/'
) )
@ -206,6 +208,7 @@ RSpec.describe Projects::Operations::UpdateService do
{ {
error_tracking_setting_attributes: { error_tracking_setting_attributes: {
enabled: true, enabled: true,
integrated: true,
api_host: 'http://gitlab.com/', api_host: 'http://gitlab.com/',
token: 'token', token: 'token',
project: { project: {
@ -222,6 +225,7 @@ RSpec.describe Projects::Operations::UpdateService do
expect(result[:status]).to eq(:success) expect(result[:status]).to eq(:success)
expect(project.error_tracking_setting).to be_enabled expect(project.error_tracking_setting).to be_enabled
expect(project.error_tracking_setting.integrated).to be_truthy
expect(project.error_tracking_setting.api_url).to eq( expect(project.error_tracking_setting.api_url).to eq(
'http://gitlab.com/api/0/projects/org/project/' 'http://gitlab.com/api/0/projects/org/project/'
) )

View File

@ -17,25 +17,6 @@ class BareRepoOperations
commit_id[0] commit_id[0]
end end
def commit_file(file, dst_path, branch = 'master')
head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || Gitlab::Git::EMPTY_TREE_ID
execute(['read-tree', '--empty'])
execute(['read-tree', head_id])
blob_id = execute(['hash-object', '--stdin', '-w']) do |stdin|
stdin.write(file.read)
end
execute(['update-index', '--add', '--cacheinfo', '100644', blob_id[0], dst_path])
tree_id = execute(['write-tree'])
commit_id = commit_tree(tree_id[0], "Add #{dst_path}", parent: head_id)
execute(['update-ref', "refs/heads/#{branch}", commit_id])
end
private private
def execute(args, allow_failure: false) def execute(args, allow_failure: false)