diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-09 03:08:41 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-09 03:08:41 +0300 |
commit | c02e2a5ef6b928da420e844b54fc6bbce16754c8 (patch) | |
tree | 1f738c4c0c7fc04a3235e8bad807972a740611f2 | |
parent | b0980f5557a8621fb08785b06be950ee46796c18 (diff) |
Add latest changes from gitlab-org/gitlab@master
113 files changed, 977 insertions, 1045 deletions
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml index 0e2f12789db..168f60f0f65 100644 --- a/.gitlab/ci/reports.gitlab-ci.yml +++ b/.gitlab/ci/reports.gitlab-ci.yml @@ -151,3 +151,20 @@ dependency_scanning: reports: dependency_scanning: gl-dependency-scanning-report.json expire_in: 1 week # GitLab-specific + +license_scanning: + extends: + - .default-retry + - .reports:rules:license_scanning + stage: test + image: + name: "registry.gitlab.com/gitlab-org/security-products/analyzers/license-finder:3" + entrypoint: [""] + needs: [] + script: + - /run.sh analyze . + artifacts: + reports: + license_scanning: gl-license-scanning-report.json + expire_in: 1 week # GitLab-specific + dependencies: [] diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index e5fa6082a7b..cb543076c5b 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -719,6 +719,14 @@ - <<: *if-master-schedule-nightly allow_failure: true +.reports:rules:license_scanning: + rules: + - if: '$LICENSE_SCANNING_DISABLED || $GITLAB_FEATURES !~ /\blicense_scanning\b/' + when: never + - <<: *if-default-refs + changes: *code-backstage-qa-patterns + allow_failure: true + ################ # Review rules # ################ @@ -401,7 +401,7 @@ end # Gems required in omnibus-gitlab pipeline group :development, :test, :omnibus do - gem 'license_finder', '~> 5.4', require: false + gem 'license_finder', '~> 6.0', require: false end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 13a5c5832ac..7a24130096b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -643,9 +643,9 @@ GEM actionmailer (>= 3.2) letter_opener (~> 1.0) railties (>= 3.2) - license_finder (5.4.0) + license_finder (6.0.0) bundler - rubyzip + rubyzip (>= 1, < 3) thor toml (= 0.2.0) with_env (= 1.1.0) @@ -1374,7 +1374,7 @@ DEPENDENCIES kramdown (~> 2.3.0) kubeclient (~> 4.6.0) letter_opener_web (~> 1.3.4) - license_finder (~> 5.4) + license_finder (~> 6.0) licensee (~> 8.9) lockbox (~> 0.3.3) lograge (~> 0.5) diff --git a/app/assets/javascripts/editor/constants.js b/app/assets/javascripts/editor/constants.js index 9ee692e953a..b02eb37206a 100644 --- a/app/assets/javascripts/editor/constants.js +++ b/app/assets/javascripts/editor/constants.js @@ -5,3 +5,4 @@ export const EDITOR_LITE_INSTANCE_ERROR_NO_EL = __( ); export const URI_PREFIX = 'gitlab'; +export const CONTENT_UPDATE_DEBOUNCE = 250; diff --git a/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue b/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue index 7c9744da0e8..b4a6286eb55 100644 --- a/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue +++ b/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue @@ -1,17 +1,13 @@ <script> import { GlAlert, GlLoadingIcon, GlToggle } from '@gitlab/ui'; -import { createNamespacedHelpers } from 'vuex'; +import { mapState, mapActions } from 'vuex'; import axios from '~/lib/utils/axios_utils'; import { sprintf, s__ } from '~/locale'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { LEGACY_FLAG, NEW_FLAG_ALERT } from '../constants'; -import store from '../store/index'; import FeatureFlagForm from './form.vue'; -const { mapState, mapActions } = createNamespacedHelpers('edit'); - export default { - store, components: { GlAlert, GlLoadingIcon, @@ -20,14 +16,6 @@ export default { }, mixins: [glFeatureFlagMixin()], props: { - endpoint: { - type: String, - required: true, - }, - path: { - type: String, - required: true, - }, environmentsEndpoint: { type: String, required: true, @@ -71,6 +59,7 @@ export default { }, computed: { ...mapState([ + 'path', 'error', 'name', 'description', @@ -110,17 +99,10 @@ export default { }, }, created() { - this.setPath(this.path); - return this.setEndpoint(this.endpoint).then(() => this.fetchFeatureFlag()); + return this.fetchFeatureFlag(); }, methods: { - ...mapActions([ - 'updateFeatureFlag', - 'setEndpoint', - 'setPath', - 'fetchFeatureFlag', - 'toggleActive', - ]), + ...mapActions(['updateFeatureFlag', 'fetchFeatureFlag', 'toggleActive']), dismissNewVersionFlagAlert() { this.userShouldSeeNewFlagAlert = false; axios.post(this.userCalloutsPath, { diff --git a/app/assets/javascripts/feature_flags/components/feature_flags.vue b/app/assets/javascripts/feature_flags/components/feature_flags.vue index 18008111a18..2ea92ca0d9c 100644 --- a/app/assets/javascripts/feature_flags/components/feature_flags.vue +++ b/app/assets/javascripts/feature_flags/components/feature_flags.vue @@ -1,12 +1,11 @@ <script> -import { createNamespacedHelpers } from 'vuex'; +import { mapState, mapActions } from 'vuex'; import { isEmpty } from 'lodash'; import { GlButton, GlModalDirective, GlTabs } from '@gitlab/ui'; import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../constants'; import FeatureFlagsTab from './feature_flags_tab.vue'; import FeatureFlagsTable from './feature_flags_table.vue'; import UserListsTable from './user_lists_table.vue'; -import store from '../store'; import { s__ } from '~/locale'; import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; import { @@ -17,12 +16,9 @@ import { import ConfigureFeatureFlagsModal from './configure_feature_flags_modal.vue'; -const { mapState, mapActions } = createNamespacedHelpers('index'); - const SCOPES = { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE }; export default { - store, components: { FeatureFlagsTable, UserListsTable, @@ -36,14 +32,6 @@ export default { GlModal: GlModalDirective, }, props: { - endpoint: { - type: String, - required: true, - }, - projectId: { - type: String, - required: true, - }, csrfToken: { type: String, required: true, @@ -56,19 +44,10 @@ export default { type: String, required: true, }, - rotateInstanceIdPath: { - type: String, - required: false, - default: '', - }, unleashApiUrl: { type: String, required: true, }, - unleashApiInstanceId: { - type: String, - required: true, - }, canUserConfigure: { type: Boolean, required: true, @@ -144,23 +123,15 @@ export default { }, }, created() { - this.setFeatureFlagsEndpoint(this.endpoint); this.setFeatureFlagsOptions({ scope: this.scope, page: this.page }); - this.setProjectId(this.projectId); this.fetchFeatureFlags(); this.fetchUserLists(); - this.setInstanceId(this.unleashApiInstanceId); - this.setInstanceIdEndpoint(this.rotateInstanceIdPath); }, methods: { ...mapActions([ - 'setFeatureFlagsEndpoint', 'setFeatureFlagsOptions', 'fetchFeatureFlags', 'fetchUserLists', - 'setInstanceIdEndpoint', - 'setInstanceId', - 'setProjectId', 'rotateInstanceId', 'toggleFeatureFlag', 'deleteUserList', diff --git a/app/assets/javascripts/feature_flags/components/form.vue b/app/assets/javascripts/feature_flags/components/form.vue index 8d065933316..2858f02688f 100644 --- a/app/assets/javascripts/feature_flags/components/form.vue +++ b/app/assets/javascripts/feature_flags/components/form.vue @@ -28,7 +28,7 @@ import { NEW_VERSION_FLAG, LEGACY_FLAG, } from '../constants'; -import { createNewEnvironmentScope } from '../store/modules/helpers'; +import { createNewEnvironmentScope } from '../store/helpers'; export default { components: { diff --git a/app/assets/javascripts/feature_flags/components/new_feature_flag.vue b/app/assets/javascripts/feature_flags/components/new_feature_flag.vue index df19667a3ae..927265b83a1 100644 --- a/app/assets/javascripts/feature_flags/components/new_feature_flag.vue +++ b/app/assets/javascripts/feature_flags/components/new_feature_flag.vue @@ -1,8 +1,7 @@ <script> -import { createNamespacedHelpers } from 'vuex'; +import { mapState, mapActions } from 'vuex'; import { GlAlert } from '@gitlab/ui'; import axios from '~/lib/utils/axios_utils'; -import store from '../store/index'; import FeatureFlagForm from './form.vue'; import { LEGACY_FLAG, @@ -10,28 +9,17 @@ import { NEW_FLAG_ALERT, ROLLOUT_STRATEGY_ALL_USERS, } from '../constants'; -import { createNewEnvironmentScope } from '../store/modules/helpers'; +import { createNewEnvironmentScope } from '../store/helpers'; import featureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -const { mapState, mapActions } = createNamespacedHelpers('new'); - export default { - store, components: { GlAlert, FeatureFlagForm, }, mixins: [featureFlagsMixin()], props: { - endpoint: { - type: String, - required: true, - }, - path: { - type: String, - required: true, - }, environmentsEndpoint: { type: String, required: true, @@ -64,7 +52,7 @@ export default { newFlagAlert: NEW_FLAG_ALERT, }, computed: { - ...mapState(['error']), + ...mapState(['error', 'path']), scopes() { return [ createNewEnvironmentScope( @@ -89,12 +77,8 @@ export default { return [{ name: ROLLOUT_STRATEGY_ALL_USERS, parameters: {}, scopes: [] }]; }, }, - created() { - this.setEndpoint(this.endpoint); - this.setPath(this.path); - }, methods: { - ...mapActions(['createFeatureFlag', 'setEndpoint', 'setPath']), + ...mapActions(['createFeatureFlag']), dismissNewVersionFlagAlert() { this.userShouldSeeNewFlagAlert = false; axios.post(this.userCalloutsPath, { diff --git a/app/assets/javascripts/feature_flags/edit.js b/app/assets/javascripts/feature_flags/edit.js index 390a1f7555d..2e2e2383eb6 100644 --- a/app/assets/javascripts/feature_flags/edit.js +++ b/app/assets/javascripts/feature_flags/edit.js @@ -1,25 +1,30 @@ import Vue from 'vue'; -import EditFeatureFlag from '~/feature_flags/components/edit_feature_flag.vue'; +import Vuex from 'vuex'; import { parseBoolean } from '~/lib/utils/common_utils'; +import createStore from './store/edit'; +import EditFeatureFlag from './components/edit_feature_flag.vue'; + +Vue.use(Vuex); export default () => { const el = document.querySelector('#js-edit-feature-flag'); - const { environmentsScopeDocsPath, strategyTypeDocsPagePath } = el.dataset; + const { + environmentsScopeDocsPath, + strategyTypeDocsPagePath, + endpoint, + featureFlagsPath, + } = el.dataset; return new Vue({ + store: createStore({ endpoint, path: featureFlagsPath }), el, - components: { - EditFeatureFlag, - }, provide: { environmentsScopeDocsPath, strategyTypeDocsPagePath, }, render(createElement) { - return createElement('edit-feature-flag', { + return createElement(EditFeatureFlag, { props: { - endpoint: el.dataset.endpoint, - path: el.dataset.featureFlagsPath, environmentsEndpoint: el.dataset.environmentsEndpoint, projectId: el.dataset.projectId, featureFlagIssuesEndpoint: el.dataset.featureFlagIssuesEndpoint, diff --git a/app/assets/javascripts/feature_flags/index.js b/app/assets/javascripts/feature_flags/index.js index 90857c5f2da..48160fe687c 100644 --- a/app/assets/javascripts/feature_flags/index.js +++ b/app/assets/javascripts/feature_flags/index.js @@ -1,41 +1,47 @@ import Vue from 'vue'; -import FeatureFlagsComponent from '~/feature_flags/components/feature_flags.vue'; +import Vuex from 'vuex'; import csrf from '~/lib/utils/csrf'; +import FeatureFlagsComponent from './components/feature_flags.vue'; +import createStore from './store/index'; -export default () => - new Vue({ - el: '#feature-flags-vue', - components: { - FeatureFlagsComponent, - }, - data() { - return { - dataset: document.querySelector(this.$options.el).dataset, - }; - }, +Vue.use(Vuex); + +export default () => { + const el = document.querySelector('#feature-flags-vue'); + + const { + projectName, + featureFlagsHelpPagePath, + errorStateSvgPath, + endpoint, + projectId, + unleashApiInstanceId, + rotateInstanceIdPath, + } = el.dataset; + + return new Vue({ + el, + store: createStore({ endpoint, projectId, unleashApiInstanceId, rotateInstanceIdPath }), provide() { return { - projectName: this.dataset.projectName, - featureFlagsHelpPagePath: this.dataset.featureFlagsHelpPagePath, - errorStateSvgPath: this.dataset.errorStateSvgPath, + projectName, + featureFlagsHelpPagePath, + errorStateSvgPath, }; }, render(createElement) { - return createElement('feature-flags-component', { + return createElement(FeatureFlagsComponent, { props: { - endpoint: this.dataset.endpoint, - projectId: this.dataset.projectId, - featureFlagsClientLibrariesHelpPagePath: this.dataset - .featureFlagsClientLibrariesHelpPagePath, - featureFlagsClientExampleHelpPagePath: this.dataset.featureFlagsClientExampleHelpPagePath, - unleashApiUrl: this.dataset.unleashApiUrl, - unleashApiInstanceId: this.dataset.unleashApiInstanceId || '', + featureFlagsClientLibrariesHelpPagePath: + el.dataset.featureFlagsClientLibrariesHelpPagePath, + featureFlagsClientExampleHelpPagePath: el.dataset.featureFlagsClientExampleHelpPagePath, + unleashApiUrl: el.dataset.unleashApiUrl, csrfToken: csrf.token, - canUserConfigure: this.dataset.canUserAdminFeatureFlag, - newFeatureFlagPath: this.dataset.newFeatureFlagPath, - rotateInstanceIdPath: this.dataset.rotateInstanceIdPath, - newUserListPath: this.dataset.newUserListPath, + canUserConfigure: el.dataset.canUserAdminFeatureFlag, + newFeatureFlagPath: el.dataset.newFeatureFlagPath, + newUserListPath: el.dataset.newUserListPath, }, }); }, }); +}; diff --git a/app/assets/javascripts/feature_flags/new.js b/app/assets/javascripts/feature_flags/new.js index f14dd151910..8f1436314e0 100644 --- a/app/assets/javascripts/feature_flags/new.js +++ b/app/assets/javascripts/feature_flags/new.js @@ -1,25 +1,30 @@ import Vue from 'vue'; -import NewFeatureFlag from '~/feature_flags/components/new_feature_flag.vue'; +import Vuex from 'vuex'; import { parseBoolean } from '~/lib/utils/common_utils'; +import createStore from './store/new'; +import NewFeatureFlag from './components/new_feature_flag.vue'; + +Vue.use(Vuex); export default () => { const el = document.querySelector('#js-new-feature-flag'); - const { environmentsScopeDocsPath, strategyTypeDocsPagePath } = el.dataset; + const { + environmentsScopeDocsPath, + strategyTypeDocsPagePath, + endpoint, + featureFlagsPath, + } = el.dataset; return new Vue({ el, - components: { - NewFeatureFlag, - }, + store: createStore({ endpoint, path: featureFlagsPath }), provide: { environmentsScopeDocsPath, strategyTypeDocsPagePath, }, render(createElement) { - return createElement('new-feature-flag', { + return createElement(NewFeatureFlag, { props: { - endpoint: el.dataset.endpoint, - path: el.dataset.featureFlagsPath, environmentsEndpoint: el.dataset.environmentsEndpoint, projectId: el.dataset.projectId, userCalloutsPath: el.dataset.userCalloutsPath, diff --git a/app/assets/javascripts/feature_flags/store/modules/edit/actions.js b/app/assets/javascripts/feature_flags/store/edit/actions.js index 351f36d8fa6..3678c2f7788 100644 --- a/app/assets/javascripts/feature_flags/store/modules/edit/actions.js +++ b/app/assets/javascripts/feature_flags/store/edit/actions.js @@ -3,24 +3,10 @@ import axios from '~/lib/utils/axios_utils'; import { visitUrl } from '~/lib/utils/url_utility'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import { __ } from '~/locale'; -import { NEW_VERSION_FLAG } from '../../../constants'; +import { NEW_VERSION_FLAG } from '../../constants'; import { mapFromScopesViewModel, mapStrategiesToRails } from '../helpers'; /** - * Commits mutation to set the main endpoint - * @param {String} endpoint - */ -export const setEndpoint = ({ commit }, endpoint) => commit(types.SET_ENDPOINT, endpoint); - -/** - * Commits mutation to set the feature flag path. - * Used to redirect the user after form submission - * - * @param {String} path - */ -export const setPath = ({ commit }, path) => commit(types.SET_PATH, path); - -/** * Handles the edition of a feature flag. * * Will dispatch `requestUpdateFeatureFlag` diff --git a/app/assets/javascripts/feature_flags/store/edit/index.js b/app/assets/javascripts/feature_flags/store/edit/index.js new file mode 100644 index 00000000000..f737e0517fc --- /dev/null +++ b/app/assets/javascripts/feature_flags/store/edit/index.js @@ -0,0 +1,11 @@ +import Vuex from 'vuex'; +import state from './state'; +import * as actions from './actions'; +import mutations from './mutations'; + +export default data => + new Vuex.Store({ + actions, + mutations, + state: state(data), + }); diff --git a/app/assets/javascripts/feature_flags/store/modules/edit/mutation_types.js b/app/assets/javascripts/feature_flags/store/edit/mutation_types.js index b2715e501f4..c215dad3513 100644 --- a/app/assets/javascripts/feature_flags/store/modules/edit/mutation_types.js +++ b/app/assets/javascripts/feature_flags/store/edit/mutation_types.js @@ -1,6 +1,3 @@ -export const SET_ENDPOINT = 'SET_ENDPOINT'; -export const SET_PATH = 'SET_PATH'; - export const REQUEST_UPDATE_FEATURE_FLAG = 'REQUEST_UPDATE_FEATURE_FLAG'; export const RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS = 'RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS'; export const RECEIVE_UPDATE_FEATURE_FLAG_ERROR = 'RECEIVE_UPDATE_FEATURE_FLAG_ERROR'; diff --git a/app/assets/javascripts/feature_flags/store/modules/edit/mutations.js b/app/assets/javascripts/feature_flags/store/edit/mutations.js index 1d2721e037d..e60dbaf4a34 100644 --- a/app/assets/javascripts/feature_flags/store/modules/edit/mutations.js +++ b/app/assets/javascripts/feature_flags/store/edit/mutations.js @@ -1,14 +1,8 @@ import * as types from './mutation_types'; import { mapToScopesViewModel, mapStrategiesToViewModel } from '../helpers'; -import { LEGACY_FLAG } from '../../../constants'; +import { LEGACY_FLAG } from '../../constants'; export default { - [types.SET_ENDPOINT](state, endpoint) { - state.endpoint = endpoint; - }, - [types.SET_PATH](state, path) { - state.path = path; - }, [types.REQUEST_FEATURE_FLAG](state) { state.isLoading = true; }, diff --git a/app/assets/javascripts/feature_flags/store/modules/edit/state.js b/app/assets/javascripts/feature_flags/store/edit/state.js index 7de05b49482..ec507532d6a 100644 --- a/app/assets/javascripts/feature_flags/store/modules/edit/state.js +++ b/app/assets/javascripts/feature_flags/store/edit/state.js @@ -1,8 +1,8 @@ -import { LEGACY_FLAG } from '../../../constants'; +import { LEGACY_FLAG } from '../../constants'; -export default () => ({ - endpoint: null, - path: null, +export default ({ path, endpoint }) => ({ + endpoint, + path, isSendingRequest: false, error: [], diff --git a/app/assets/javascripts/feature_flags/store/modules/helpers.js b/app/assets/javascripts/feature_flags/store/helpers.js index bbde3cad7cd..db6da815abf 100644 --- a/app/assets/javascripts/feature_flags/store/modules/helpers.js +++ b/app/assets/javascripts/feature_flags/store/helpers.js @@ -10,7 +10,7 @@ import { fetchPercentageParams, fetchUserIdParams, LEGACY_FLAG, -} from '../../constants'; +} from '../constants'; /** * Converts raw scope objects fetched from the API into an array of scope diff --git a/app/assets/javascripts/feature_flags/store/index.js b/app/assets/javascripts/feature_flags/store/index.js deleted file mode 100644 index f4f49c20895..00000000000 --- a/app/assets/javascripts/feature_flags/store/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import Vue from 'vue'; -import Vuex from 'vuex'; -import indexModule from './modules/index'; -import newModule from './modules/new'; -import editModule from './modules/edit'; - -Vue.use(Vuex); - -export const createStore = () => - new Vuex.Store({ - modules: { - index: indexModule, - new: newModule, - edit: editModule, - }, - }); - -export default createStore(); diff --git a/app/assets/javascripts/feature_flags/store/modules/index/actions.js b/app/assets/javascripts/feature_flags/store/index/actions.js index ed41dd34e4d..a8c1a72c016 100644 --- a/app/assets/javascripts/feature_flags/store/modules/index/actions.js +++ b/app/assets/javascripts/feature_flags/store/index/actions.js @@ -2,19 +2,9 @@ import Api from '~/api'; import * as types from './mutation_types'; import axios from '~/lib/utils/axios_utils'; -export const setFeatureFlagsEndpoint = ({ commit }, endpoint) => - commit(types.SET_FEATURE_FLAGS_ENDPOINT, endpoint); - export const setFeatureFlagsOptions = ({ commit }, options) => commit(types.SET_FEATURE_FLAGS_OPTIONS, options); -export const setInstanceIdEndpoint = ({ commit }, endpoint) => - commit(types.SET_INSTANCE_ID_ENDPOINT, endpoint); - -export const setProjectId = ({ commit }, endpoint) => commit(types.SET_PROJECT_ID, endpoint); - -export const setInstanceId = ({ commit }, instanceId) => commit(types.SET_INSTANCE_ID, instanceId); - export const fetchFeatureFlags = ({ state, dispatch }) => { dispatch('requestFeatureFlags'); diff --git a/app/assets/javascripts/feature_flags/store/index/index.js b/app/assets/javascripts/feature_flags/store/index/index.js new file mode 100644 index 00000000000..f737e0517fc --- /dev/null +++ b/app/assets/javascripts/feature_flags/store/index/index.js @@ -0,0 +1,11 @@ +import Vuex from 'vuex'; +import state from './state'; +import * as actions from './actions'; +import mutations from './mutations'; + +export default data => + new Vuex.Store({ + actions, + mutations, + state: state(data), + }); diff --git a/app/assets/javascripts/feature_flags/store/modules/index/mutation_types.js b/app/assets/javascripts/feature_flags/store/index/mutation_types.js index 4a4bd13c945..189c763782e 100644 --- a/app/assets/javascripts/feature_flags/store/modules/index/mutation_types.js +++ b/app/assets/javascripts/feature_flags/store/index/mutation_types.js @@ -1,8 +1,4 @@ -export const SET_FEATURE_FLAGS_ENDPOINT = 'SET_FEATURE_FLAGS_ENDPOINT'; export const SET_FEATURE_FLAGS_OPTIONS = 'SET_FEATURE_FLAGS_OPTIONS'; -export const SET_INSTANCE_ID_ENDPOINT = 'SET_INSTANCE_ID_ENDPOINT'; -export const SET_INSTANCE_ID = 'SET_INSTANCE_ID'; -export const SET_PROJECT_ID = 'SET_PROJECT_ID'; export const REQUEST_FEATURE_FLAGS = 'REQUEST_FEATURE_FLAGS'; export const RECEIVE_FEATURE_FLAGS_SUCCESS = 'RECEIVE_FEATURE_FLAGS_SUCCESS'; diff --git a/app/assets/javascripts/feature_flags/store/modules/index/mutations.js b/app/assets/javascripts/feature_flags/store/index/mutations.js index 948786a3533..bdc23e66214 100644 --- a/app/assets/javascripts/feature_flags/store/modules/index/mutations.js +++ b/app/assets/javascripts/feature_flags/store/index/mutations.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import * as types from './mutation_types'; import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; -import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../../../constants'; +import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../../constants'; import { mapToScopesViewModel } from '../helpers'; const mapFlag = flag => ({ ...flag, scopes: mapToScopesViewModel(flag.scopes || []) }); @@ -23,21 +23,9 @@ const createPaginationInfo = (state, headers) => { }; export default { - [types.SET_FEATURE_FLAGS_ENDPOINT](state, endpoint) { - state.endpoint = endpoint; - }, [types.SET_FEATURE_FLAGS_OPTIONS](state, options = {}) { state.options = options; }, - [types.SET_INSTANCE_ID_ENDPOINT](state, endpoint) { - state.rotateEndpoint = endpoint; - }, - [types.SET_INSTANCE_ID](state, instance) { - state.instanceId = instance; - }, - [types.SET_PROJECT_ID](state, project) { - state.projectId = project; - }, [types.REQUEST_FEATURE_FLAGS](state) { state.isLoading = true; }, diff --git a/app/assets/javascripts/feature_flags/store/index/state.js b/app/assets/javascripts/feature_flags/store/index/state.js new file mode 100644 index 00000000000..f8439b02639 --- /dev/null +++ b/app/assets/javascripts/feature_flags/store/index/state.js @@ -0,0 +1,18 @@ +import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../../constants'; + +export default ({ endpoint, projectId, unleashApiInstanceId, rotateInstanceIdPath }) => ({ + [FEATURE_FLAG_SCOPE]: [], + [USER_LIST_SCOPE]: [], + alerts: [], + count: {}, + pageInfo: { [FEATURE_FLAG_SCOPE]: {}, [USER_LIST_SCOPE]: {} }, + isLoading: true, + hasError: false, + endpoint, + rotateEndpoint: rotateInstanceIdPath, + instanceId: unleashApiInstanceId, + isRotating: false, + hasRotateError: false, + options: {}, + projectId, +}); diff --git a/app/assets/javascripts/feature_flags/store/modules/edit/index.js b/app/assets/javascripts/feature_flags/store/modules/edit/index.js deleted file mode 100644 index 665bb29a17e..00000000000 --- a/app/assets/javascripts/feature_flags/store/modules/edit/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import state from './state'; -import * as actions from './actions'; -import mutations from './mutations'; - -export default { - namespaced: true, - actions, - mutations, - state: state(), -}; diff --git a/app/assets/javascripts/feature_flags/store/modules/index/index.js b/app/assets/javascripts/feature_flags/store/modules/index/index.js deleted file mode 100644 index 665bb29a17e..00000000000 --- a/app/assets/javascripts/feature_flags/store/modules/index/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import state from './state'; -import * as actions from './actions'; -import mutations from './mutations'; - -export default { - namespaced: true, - actions, - mutations, - state: state(), -}; diff --git a/app/assets/javascripts/feature_flags/store/modules/index/state.js b/app/assets/javascripts/feature_flags/store/modules/index/state.js deleted file mode 100644 index 443a12d485d..00000000000 --- a/app/assets/javascripts/feature_flags/store/modules/index/state.js +++ /dev/null @@ -1,18 +0,0 @@ -import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../../../constants'; - -export default () => ({ - [FEATURE_FLAG_SCOPE]: [], - [USER_LIST_SCOPE]: [], - alerts: [], - count: {}, - pageInfo: { [FEATURE_FLAG_SCOPE]: {}, [USER_LIST_SCOPE]: {} }, - isLoading: true, - hasError: false, - endpoint: null, - rotateEndpoint: null, - instanceId: '', - isRotating: false, - hasRotateError: false, - options: {}, - projectId: '', -}); diff --git a/app/assets/javascripts/feature_flags/store/modules/new/index.js b/app/assets/javascripts/feature_flags/store/modules/new/index.js deleted file mode 100644 index 665bb29a17e..00000000000 --- a/app/assets/javascripts/feature_flags/store/modules/new/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import state from './state'; -import * as actions from './actions'; -import mutations from './mutations'; - -export default { - namespaced: true, - actions, - mutations, - state: state(), -}; diff --git a/app/assets/javascripts/feature_flags/store/modules/new/state.js b/app/assets/javascripts/feature_flags/store/modules/new/state.js deleted file mode 100644 index 6f9263dbb2a..00000000000 --- a/app/assets/javascripts/feature_flags/store/modules/new/state.js +++ /dev/null @@ -1,6 +0,0 @@ -export default () => ({ - endpoint: null, - path: null, - isSendingRequest: false, - error: [], -}); diff --git a/app/assets/javascripts/feature_flags/store/modules/new/actions.js b/app/assets/javascripts/feature_flags/store/new/actions.js index d2159d55d53..e21c128cd39 100644 --- a/app/assets/javascripts/feature_flags/store/modules/new/actions.js +++ b/app/assets/javascripts/feature_flags/store/new/actions.js @@ -1,24 +1,10 @@ import * as types from './mutation_types'; import axios from '~/lib/utils/axios_utils'; import { visitUrl } from '~/lib/utils/url_utility'; -import { NEW_VERSION_FLAG } from '../../../constants'; +import { NEW_VERSION_FLAG } from '../../constants'; import { mapFromScopesViewModel, mapStrategiesToRails } from '../helpers'; /** - * Commits mutation to set the main endpoint - * @param {String} endpoint - */ -export const setEndpoint = ({ commit }, endpoint) => commit(types.SET_ENDPOINT, endpoint); - -/** - * Commits mutation to set the feature flag path. - * Used to redirect the user after form submission - * - * @param {String} path - */ -export const setPath = ({ commit }, path) => commit(types.SET_PATH, path); - -/** * Handles the creation of a new feature flag. * * Will dispatch `requestCreateFeatureFlag` diff --git a/app/assets/javascripts/feature_flags/store/new/index.js b/app/assets/javascripts/feature_flags/store/new/index.js new file mode 100644 index 00000000000..f737e0517fc --- /dev/null +++ b/app/assets/javascripts/feature_flags/store/new/index.js @@ -0,0 +1,11 @@ +import Vuex from 'vuex'; +import state from './state'; +import * as actions from './actions'; +import mutations from './mutations'; + +export default data => + new Vuex.Store({ + actions, + mutations, + state: state(data), + }); diff --git a/app/assets/javascripts/feature_flags/store/modules/new/mutation_types.js b/app/assets/javascripts/feature_flags/store/new/mutation_types.js index 317f3689dfd..71cc57c8d2e 100644 --- a/app/assets/javascripts/feature_flags/store/modules/new/mutation_types.js +++ b/app/assets/javascripts/feature_flags/store/new/mutation_types.js @@ -1,6 +1,3 @@ -export const SET_ENDPOINT = 'SET_ENDPOINT'; -export const SET_PATH = 'SET_PATH'; - export const REQUEST_CREATE_FEATURE_FLAG = 'REQUEST_CREATE_FEATURE_FLAG'; export const RECEIVE_CREATE_FEATURE_FLAG_SUCCESS = 'RECEIVE_CREATE_FEATURE_FLAG_SUCCESS'; export const RECEIVE_CREATE_FEATURE_FLAG_ERROR = 'RECEIVE_CREATE_FEATURE_FLAG_ERROR'; diff --git a/app/assets/javascripts/feature_flags/store/modules/new/mutations.js b/app/assets/javascripts/feature_flags/store/new/mutations.js index 06e467c04f1..eeefc8413e8 100644 --- a/app/assets/javascripts/feature_flags/store/modules/new/mutations.js +++ b/app/assets/javascripts/feature_flags/store/new/mutations.js @@ -1,12 +1,6 @@ import * as types from './mutation_types'; export default { - [types.SET_ENDPOINT](state, endpoint) { - state.endpoint = endpoint; - }, - [types.SET_PATH](state, path) { - state.path = path; - }, [types.REQUEST_CREATE_FEATURE_FLAG](state) { state.isSendingRequest = true; state.error = []; diff --git a/app/assets/javascripts/feature_flags/store/new/state.js b/app/assets/javascripts/feature_flags/store/new/state.js new file mode 100644 index 00000000000..56940925aa0 --- /dev/null +++ b/app/assets/javascripts/feature_flags/store/new/state.js @@ -0,0 +1,6 @@ +export default ({ endpoint, path }) => ({ + endpoint, + path, + isSendingRequest: false, + error: [], +}); diff --git a/app/assets/javascripts/pages/projects/feature_flags/edit/index.js b/app/assets/javascripts/pages/projects/feature_flags/edit/index.js index 62c85ada63b..36b1d800103 100644 --- a/app/assets/javascripts/pages/projects/feature_flags/edit/index.js +++ b/app/assets/javascripts/pages/projects/feature_flags/edit/index.js @@ -1,3 +1,3 @@ import initEditFeatureFlags from '~/feature_flags/edit'; -document.addEventListener('DOMContentLoaded', initEditFeatureFlags); +initEditFeatureFlags(); diff --git a/app/assets/javascripts/pages/projects/feature_flags/index/index.js b/app/assets/javascripts/pages/projects/feature_flags/index/index.js index 54e8dd73553..c11a5c929ee 100644 --- a/app/assets/javascripts/pages/projects/feature_flags/index/index.js +++ b/app/assets/javascripts/pages/projects/feature_flags/index/index.js @@ -1,3 +1,3 @@ import initFeatureFlags from '~/feature_flags'; -document.addEventListener('DOMContentLoaded', initFeatureFlags); +initFeatureFlags(); diff --git a/app/assets/javascripts/pages/projects/feature_flags/new/index.js b/app/assets/javascripts/pages/projects/feature_flags/new/index.js index c5f29ae08a8..d598f6b31dd 100644 --- a/app/assets/javascripts/pages/projects/feature_flags/new/index.js +++ b/app/assets/javascripts/pages/projects/feature_flags/new/index.js @@ -1,3 +1,3 @@ import initNewFeatureFlags from '~/feature_flags/new'; -document.addEventListener('DOMContentLoaded', initNewFeatureFlags); +initNewFeatureFlags(); diff --git a/app/assets/javascripts/vue_shared/components/editor_lite.vue b/app/assets/javascripts/vue_shared/components/editor_lite.vue new file mode 100644 index 00000000000..98889a0dced --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/editor_lite.vue @@ -0,0 +1,91 @@ +<script> +import { debounce } from 'lodash'; +import Editor from '~/editor/editor_lite'; +import { CONTENT_UPDATE_DEBOUNCE } from '~/editor/constants'; + +function initEditorLite({ el, ...args }) { + const editor = new Editor({ + scrollbar: { + alwaysConsumeMouseWheel: false, + }, + }); + + return editor.createInstance({ + el, + ...args, + }); +} + +export default { + inheritAttrs: false, + props: { + value: { + type: String, + required: false, + default: '', + }, + fileName: { + type: String, + required: false, + default: '', + }, + // This is used to help uniquely create a monaco model + // even if two blob's share a file path. + fileGlobalId: { + type: String, + required: false, + default: '', + }, + extensions: { + type: [String, Array], + required: false, + default: () => null, + }, + editorOptions: { + type: Object, + required: false, + default: () => ({}), + }, + }, + data() { + return { + loading: true, + editor: null, + }; + }, + watch: { + fileName(newVal) { + this.editor.updateModelLanguage(newVal); + }, + }, + mounted() { + this.editor = initEditorLite({ + el: this.$refs.editor, + blobPath: this.fileName, + blobContent: this.value, + blobGlobalId: this.fileGlobalId, + extensions: this.extensions, + ...this.editorOptions, + }); + + this.editor.onDidChangeModelContent( + debounce(this.onFileChange.bind(this), CONTENT_UPDATE_DEBOUNCE), + ); + }, + beforeDestroy() { + this.editor.dispose(); + }, + methods: { + onFileChange() { + this.$emit('input', this.editor.getValue()); + }, + }, +}; +</script> +<template> + <div class="file-content code"> + <div id="editor" ref="editor" data-editor-loading @editor-ready="$emit('editor-ready')"> + <pre class="editor-loading-content">{{ value }}</pre> + </div> + </div> +</template> diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 4b1139d2354..840c21db20f 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -6,6 +6,7 @@ @import '@gitlab/at.js/dist/css/jquery.atwho'; @import 'dropzone/dist/basic'; @import 'select2'; +@import 'cropper/dist/cropper'; // GitLab UI framework @import 'framework'; diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 833350d4a68..40de3f3cf06 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -241,7 +241,8 @@ class GroupsController < Groups::ApplicationController :two_factor_grace_period, :project_creation_level, :subgroup_creation_level, - :default_branch_protection + :default_branch_protection, + :default_branch_name ] end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 6e317a63e47..060c155401f 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -301,7 +301,6 @@ module ProjectsHelper !disabled && !compact_mode end - # overridden in EE def settings_operations_available? can?(current_user, :read_environment, @project) end @@ -755,6 +754,7 @@ module ProjectsHelper logs product_analytics metrics_dashboard + tracings ] end diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb index 53bfa3d979e..40c46fa6e3d 100644 --- a/app/models/namespace_setting.rb +++ b/app/models/namespace_setting.rb @@ -3,7 +3,19 @@ class NamespaceSetting < ApplicationRecord belongs_to :namespace, inverse_of: :namespace_settings + validate :default_branch_name_content + + NAMESPACE_SETTINGS_PARAMS = [:default_branch_name].freeze + self.primary_key = :namespace_id + + def default_branch_name_content + return if default_branch_name.nil? + + if default_branch_name.blank? + errors.add(:default_branch_name, "can not be an empty string") + end + end end NamespaceSetting.prepend_if_ee('EE::NamespaceSetting') diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb index 382a3dbf0f7..08d113bc0f3 100644 --- a/app/services/groups/update_service.rb +++ b/app/services/groups/update_service.rb @@ -23,6 +23,8 @@ module Groups before_assignment_hook(group, params) + handle_namespace_settings + group.assign_attributes(params) begin @@ -40,6 +42,18 @@ module Groups private + def handle_namespace_settings + settings_params = params.slice(*::NamespaceSetting::NAMESPACE_SETTINGS_PARAMS) + + return if settings_params.empty? + + ::NamespaceSetting::NAMESPACE_SETTINGS_PARAMS.each do |nsp| + params.delete(nsp) + end + + ::NamespaceSettings::UpdateService.new(current_user, group, settings_params).execute + end + def valid_path_change_with_npm_packages? return true unless group.packages_feature_enabled? return true if params[:path].blank? diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 18d62e275a0..a068643cdfe 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -236,7 +236,7 @@ = _('Logs') - if project_nav_tab? :environments - = render_if_exists "layouts/nav/sidebar/tracing_link" + = render "layouts/nav/sidebar/tracing_link" - if project_nav_tab?(:error_tracking) = nav_link(controller: :error_tracking) do diff --git a/app/views/layouts/nav/sidebar/_tracing_link.html.haml b/app/views/layouts/nav/sidebar/_tracing_link.html.haml new file mode 100644 index 00000000000..7a31a20f5f0 --- /dev/null +++ b/app/views/layouts/nav/sidebar/_tracing_link.html.haml @@ -0,0 +1,7 @@ +- return unless can?(current_user, :read_environment, @project) + +- if project_nav_tab? :settings + = nav_link(controller: :tracings, action: [:show]) do + = link_to project_tracing_path(@project), title: _('Tracing') do + %span + = _('Tracing') diff --git a/app/views/projects/settings/operations/_tracing.html.haml b/app/views/projects/settings/operations/_tracing.html.haml new file mode 100644 index 00000000000..f654c723e36 --- /dev/null +++ b/app/views/projects/settings/operations/_tracing.html.haml @@ -0,0 +1,33 @@ +- setting = tracing_setting +- has_jaeger_url = setting.external_url.present? + +%section.settings.border-0.no-animate + .settings-header{ :class => "border-top" } + %h3{ :class => "h4" } + = _("Jaeger tracing") + %button.btn.gl-button.js-settings-toggle{ type: 'button' } + = _('Expand') + %p + - if has_jaeger_url + - tracing_link = link_to sanitize(setting.external_url, scrubber: Rails::Html::TextOnlyScrubber.new), target: "_blank", rel: 'noopener noreferrer' do + %span + = _('Tracing') + = sprite_icon('external-link', css_class: 'ml-1 vertical-align-middle') + - else + - tracing_link = link_to project_tracing_path(@project) do + %span + = _('Tracing') + = _("To open Jaeger and easily view tracing from GitLab, link the %{link} page to your server").html_safe % { link: tracing_link } + .settings-content + = form_for @project, url: project_settings_operations_path(@project), method: :patch do |f| + = form_errors(@project) + .form-group + = f.fields_for :tracing_setting_attributes, setting do |form| + = form.label :external_url, _('Jaeger URL'), class: 'label-bold' + = form.url_field :external_url, class: 'form-control', placeholder: 'e.g. https://jaeger.mycompany.com' + %p.form-text.text-muted + - jaeger_help_url = "https://www.jaegertracing.io/docs/1.7/getting-started/" + - link_start_tag = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: jaeger_help_url } + - link_end_tag = "#{sprite_icon('external-link', css_class: 'ml-1 vertical-align-middle')}</a>".html_safe + = _("For more information, please review %{link_start_tag}Jaeger's configuration doc%{link_end_tag}").html_safe % { link_start_tag: link_start_tag, link_end_tag: link_end_tag } + = f.submit _('Save changes'), class: 'btn btn-success' diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml index 103828ee0a0..e5d34ff0fc9 100644 --- a/app/views/projects/settings/operations/show.html.haml +++ b/app/views/projects/settings/operations/show.html.haml @@ -8,5 +8,5 @@ = render 'projects/settings/operations/prometheus', service: prometheus_service if Feature.enabled?(:settings_operations_prometheus_service) = render 'projects/settings/operations/metrics_dashboard' = render 'projects/settings/operations/grafana_integration' -= render_if_exists 'projects/settings/operations/tracing' += render 'projects/settings/operations/tracing' = render_if_exists 'projects/settings/operations/status_page' diff --git a/app/views/shared/issuable/form/_type_selector.html.haml b/app/views/shared/issuable/form/_type_selector.html.haml index 9f818787848..0281c093636 100644 --- a/app/views/shared/issuable/form/_type_selector.html.haml +++ b/app/views/shared/issuable/form/_type_selector.html.haml @@ -25,6 +25,6 @@ = _("Incident") - if issuable.incident? %p.form-text.text-muted - - incident_docs_url = help_page_path('operations/incident_management/incidents.md', anchor: 'create-and-manage-incidents-in-gitlab') + - incident_docs_url = help_page_path('operations/incident_management/incidents.md') - incident_docs_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: incident_docs_url } = _('A %{incident_docs_start}modified issue%{incident_docs_end} to guide the resolution of incidents.').html_safe % { incident_docs_start: incident_docs_start, incident_docs_end: '</a>'.html_safe } diff --git a/changelogs/unreleased/220561-fix-invalid-project-path-response-in-go-middleware.yml b/changelogs/unreleased/220561-fix-invalid-project-path-response-in-go-middleware.yml new file mode 100644 index 00000000000..8c72e60250b --- /dev/null +++ b/changelogs/unreleased/220561-fix-invalid-project-path-response-in-go-middleware.yml @@ -0,0 +1,5 @@ +--- +title: Handle the blacklisted ip error in the Go middleware +merge_request: 44614 +author: +type: changed diff --git a/changelogs/unreleased/232503-editor-lite-vue-component.yml b/changelogs/unreleased/232503-editor-lite-vue-component.yml new file mode 100644 index 00000000000..ccccfe2b427 --- /dev/null +++ b/changelogs/unreleased/232503-editor-lite-vue-component.yml @@ -0,0 +1,5 @@ +--- +title: Added new editor-lite Vue component +merge_request: 44577 +author: +type: added diff --git a/changelogs/unreleased/pl-tracing-core-5-settings-haml.yml b/changelogs/unreleased/pl-tracing-core-5-settings-haml.yml new file mode 100644 index 00000000000..35a0cb50d6f --- /dev/null +++ b/changelogs/unreleased/pl-tracing-core-5-settings-haml.yml @@ -0,0 +1,5 @@ +--- +title: Move Tracing feature to Core +merge_request: 44574 +author: +type: added diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml index 2f0d9066a7a..c12ede63fba 100644 --- a/config/dependency_decisions.yml +++ b/config/dependency_decisions.yml @@ -18,85 +18,85 @@ :why: Bundler is MIT licensed but will sometimes fail in CI. :versions: [] :when: 2016-05-02 06:42:08.045090000 Z -- - :whitelist +- - :permit - MIT - :who: Connor Shea :why: http://choosealicense.com/licenses/mit/ :versions: [] :when: 2016-04-17 21:12:24.558441000 Z -- - :whitelist +- - :permit - Apache 2.0 - :who: Connor Shea :why: http://choosealicense.com/licenses/apache-2.0/ :versions: [] :when: 2016-05-02 05:27:43.762702000 Z -- - :whitelist +- - :permit - ruby - :who: Connor Shea :why: https://github.com/ruby/ruby/blob/ruby_2_1/COPYING :versions: [] :when: 2016-05-02 05:31:54.498490000 Z -- - :whitelist +- - :permit - LGPL - :who: Connor Shea :why: http://www.gnu.org/licenses/license-list.html#LGPLv2.1 :versions: [] :when: 2016-05-02 05:32:48.645841000 Z -- - :whitelist +- - :permit - ISC - :who: Connor Shea :why: http://www.gnu.org/licenses/license-list.html#ISC :versions: [] :when: 2016-05-02 05:42:01.894452000 Z -- - :whitelist +- - :permit - New BSD - :who: Connor Shea :why: https://opensource.org/licenses/BSD-3-Clause :versions: [] :when: 2016-05-02 05:44:38.246021000 Z -- - :whitelist +- - :permit - LGPL-2.1+ - :who: Connor Shea :why: Equivalent to LGPL. :versions: [] :when: 2016-05-02 05:52:56.303239000 Z -- - :whitelist +- - :permit - BSD - :who: Connor Shea :why: https://opensource.org/licenses/BSD-2-Clause :versions: [] :when: 2016-05-02 05:55:09.796363000 Z -- - :whitelist +- - :permit - LGPLv2+ - :who: Stan Hu :why: Equivalent to LGPLv2 :versions: [] :when: 2016-06-07 17:14:10.907682000 Z -- - :whitelist +- - :permit - Artistic 2.0 - :who: Josh Frye :why: Disk/mount information display on Admin pages :versions: [] :when: 2016-06-29 16:32:45.432113000 Z -- - :whitelist +- - :permit - Simplified BSD - :who: Douwe Maan :why: https://opensource.org/licenses/BSD-2-Clause :versions: [] :when: 2016-07-26 21:24:07.248480000 Z -- - :blacklist +- - :restrict - GPLv2 - :who: Connor Shea :why: GPL-licensed libraries cannot be linked to from non-GPL projects. :versions: [] :when: 2016-05-02 05:29:27.637336000 Z -- - :blacklist +- - :restrict - GPLv3 - :who: Connor Shea :why: GPL-licensed libraries cannot be linked to from non-GPL projects. :versions: [] :when: 2016-05-02 05:29:43.904715000 Z -- - :blacklist +- - :restrict - OSL-3.0 - :who: Sean McGivern :why: The OSL license is a copyleft license @@ -188,13 +188,13 @@ :why: https://github.com/nodeca/pako/blob/master/LICENSE :versions: [] :when: 2017-04-05 10:43:45.897720000 Z -- - :whitelist +- - :permit - Unlicense - :who: Nick Thomas <nick@gitlab.com> :why: https://gitlab.com/gitlab-com/organization/issues/116 :versions: [] :when: 2017-09-01 17:17:51.996511844 Z -- - :blacklist +- - :restrict - Facebook BSD+PATENTS - :who: Nick Thomas <nick@gitlab.com> :why: https://gitlab.com/gitlab-com/organization/issues/117 @@ -281,19 +281,19 @@ :why: https://github.com/hexorx/countries/blob/master/LICENSE :versions: [] :when: 2019-09-11 13:08:28.431132000 Z -- - :whitelist +- - :permit - "(MIT OR CC0-1.0)" - :who: :why: :versions: [] :when: 2019-11-08 10:03:31.787226000 Z -- - :whitelist +- - :permit - CC0-1.0 - :who: Thomas Randolph :why: This license is public domain :versions: [] :when: 2020-06-03 05:04:44.632875345 Z -- - :whitelist +- - :permit - 0BSD - :who: Natalia Tepluhina :why: This license is public domain @@ -313,9 +313,15 @@ :why: "https://github.com/cure53/DOMPurify/blob/main/LICENSE and https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31928#note_346604841" :versions: [] :when: 2020-08-13 13:42:46.508082000 Z -- - :whitelist +- - :permit - Apache-2.0 WITH LLVM-exception - :who: Nathan Friend :why: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40670#note_403946372 :versions: [] :when: 2020-08-28 15:01:59.329048917 Z +- - :approve + - docutils + - :who: Mo Khan + :why: Used to generate documentation. https://pypi.org/project/docutils/0.13.1/ + :versions: [] + :when: 2020-10-05 20:22:55.955189491 Z diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 45ee56ea92d..2b5e46462e2 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -599,7 +599,6 @@ installations from source. ## Unicorn Logs -NOTE: **Note:** Starting with GitLab 13.0, Puma is the default web server used in GitLab all-in-one package based installations as well as GitLab Helm chart deployments. @@ -674,10 +673,8 @@ This log records: - Information whenever [Rack Attack](../security/rack_attack.md) registers an abusive request. - Requests over the [Rate Limit](../user/admin_area/settings/rate_limits_on_raw_endpoints.md) on raw endpoints. - [Protected paths](../user/admin_area/settings/protected_paths.md) abusive requests. - -NOTE: **Note:** -In GitLab versions [12.3](https://gitlab.com/gitlab-org/gitlab/-/issues/29239) and greater, user ID and username are also -recorded on this log, if available. +- In GitLab versions [12.3](https://gitlab.com/gitlab-org/gitlab/-/issues/29239) and greater, + user ID and username, if available. ## `graphql_json.log` diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md index 136a2749e80..cba695cf6f6 100644 --- a/doc/administration/monitoring/performance/grafana_configuration.md +++ b/doc/administration/monitoring/performance/grafana_configuration.md @@ -17,7 +17,6 @@ or Grafana supplies package repositories (Yum/Apt) for easy installation. See [Grafana installation documentation](https://grafana.com/docs/grafana/latest/installation/) for detailed steps. -NOTE: **Note:** Before starting Grafana for the first time, set the admin user and password in `/etc/grafana/grafana.ini`. If you don't, the default password is `admin`. diff --git a/doc/administration/monitoring/performance/request_profiling.md b/doc/administration/monitoring/performance/request_profiling.md index 5746b95eb44..bba269ec995 100644 --- a/doc/administration/monitoring/performance/request_profiling.md +++ b/doc/administration/monitoring/performance/request_profiling.md @@ -25,7 +25,6 @@ To profile a request: curl --header 'X-Profile-Token: <token>' --header 'X-Profile-Mode: <mode>' "https://gitlab.example.com/group/project" ``` - NOTE: **Note:** Profiled requests can take longer than usual. After the request completes, you can view the profiling output from the diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index fcfa253a1e3..dbf0d95bf7f 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -32,7 +32,7 @@ dashboard tool like [Grafana](https://grafana.com). ## Configuring Prometheus NOTE: **Note:** -For installations from source, you'll have to install and configure it yourself. +For installations from source, you must install and configure it yourself. Prometheus and its exporters are on by default, starting with GitLab 9.0. Prometheus will run as the `gitlab-prometheus` user and listen on @@ -179,7 +179,7 @@ The next step is to tell all the other nodes where the monitoring node is: take effect. NOTE: **Note:** -Once monitoring using Service Discovery is enabled with `consul['monitoring_service_discovery'] = true`, +After monitoring using Service Discovery is enabled with `consul['monitoring_service_discovery'] = true`, ensure that `prometheus['scrape_configs']` is not set in `/etc/gitlab/gitlab.rb`. Setting both `consul['monitoring_service_discovery'] = true` and `prometheus['scrape_configs']` in `/etc/gitlab/gitlab.rb` will result in errors. @@ -312,7 +312,6 @@ To use an external Prometheus server: You can visit `http://localhost:9090` for the dashboard that Prometheus offers by default. -NOTE: **Note:** If SSL has been enabled on your GitLab instance, you may not be able to access Prometheus on the same browser as GitLab if using the same FQDN due to [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security). We plan to [provide access via GitLab](https://gitlab.com/gitlab-org/multi-user-prometheus), but in the interim there are diff --git a/doc/administration/monitoring/prometheus/node_exporter.md b/doc/administration/monitoring/prometheus/node_exporter.md index 07f6b8b8e1e..d762e92d5be 100644 --- a/doc/administration/monitoring/prometheus/node_exporter.md +++ b/doc/administration/monitoring/prometheus/node_exporter.md @@ -10,7 +10,7 @@ The [node exporter](https://github.com/prometheus/node_exporter) enables you to various machine resources such as memory, disk and CPU utilization. NOTE: **Note:** -For installations from source you'll have to install and configure it yourself. +For installations from source you must install and configure it yourself. To enable the node exporter: diff --git a/doc/administration/monitoring/prometheus/pgbouncer_exporter.md b/doc/administration/monitoring/prometheus/pgbouncer_exporter.md index 62d0bf684b6..8b30ed6e0c4 100644 --- a/doc/administration/monitoring/prometheus/pgbouncer_exporter.md +++ b/doc/administration/monitoring/prometheus/pgbouncer_exporter.md @@ -12,7 +12,7 @@ The [PgBouncer exporter](https://github.com/prometheus-community/pgbouncer_expor you to measure various [PgBouncer](https://www.pgbouncer.org/) metrics. NOTE: **Note:** -For installations from source you'll have to install and configure it yourself. +For installations from source you must install and configure it yourself. To enable the PgBouncer exporter: diff --git a/doc/administration/monitoring/prometheus/postgres_exporter.md b/doc/administration/monitoring/prometheus/postgres_exporter.md index e3fff45fce3..b90e0a299d2 100644 --- a/doc/administration/monitoring/prometheus/postgres_exporter.md +++ b/doc/administration/monitoring/prometheus/postgres_exporter.md @@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w The [PostgreSQL Server Exporter](https://github.com/wrouesnel/postgres_exporter) allows you to export various PostgreSQL metrics. NOTE: **Note:** -For installations from source you will have to install and configure it yourself. +For installations from source you must install and configure it yourself. To enable the PostgreSQL Server Exporter: @@ -20,7 +20,6 @@ To enable the PostgreSQL Server Exporter: postgres_exporter['enable'] = true ``` - NOTE: **Note:** If PostgreSQL Server Exporter is configured on a separate node, make sure that the local address is [listed in `trust_auth_cidr_addresses`](../../postgresql/replication_and_failover.md#network-information) or the exporter will not be able to connect to the database. diff --git a/doc/administration/monitoring/prometheus/redis_exporter.md b/doc/administration/monitoring/prometheus/redis_exporter.md index b7c66959349..4e947a2d08e 100644 --- a/doc/administration/monitoring/prometheus/redis_exporter.md +++ b/doc/administration/monitoring/prometheus/redis_exporter.md @@ -11,7 +11,7 @@ various [Redis](https://redis.io) metrics. For more information on what is expor [read the upstream documentation](https://github.com/oliver006/redis_exporter/blob/master/README.md#whats-exported). NOTE: **Note:** -For installations from source you'll have to install and configure it yourself. +For installations from source you must install and configure it yourself. To enable the Redis exporter: diff --git a/doc/api/metrics_dashboard_annotations.md b/doc/api/metrics_dashboard_annotations.md index 10dfd3d1c3b..b9d68685c44 100644 --- a/doc/api/metrics_dashboard_annotations.md +++ b/doc/api/metrics_dashboard_annotations.md @@ -18,14 +18,11 @@ POST /environments/:id/metrics_dashboard/annotations/ POST /clusters/:id/metrics_dashboard/annotations/ ``` -NOTE: **Note:** -The value of `dashboard_path` will be treated as a CGI-escaped path, and automatically un-escaped. - Parameters: | Attribute | Type | Required | Description | |:---------------|:---------------|:---------|:-----------------------------------------------------------------------------| -| `dashboard_path` | string | yes | ID of the dashboard which needs to be annotated. | +| `dashboard_path` | string | yes | ID of the dashboard which needs to be annotated. Treated as a CGI-escaped path, and automatically un-escaped. | | `starting_at` | string | yes | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z`. Timestamp marking start point of annotation. | | `ending_at` | string | no | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z`. Timestamp marking end point of annotation. When not supplied annotation will be displayed as single event at start point. | | `description` | string | yes | Description of the annotation. | diff --git a/doc/development/contributing/community_roles.md b/doc/development/contributing/community_roles.md index 7d2d1b77a0e..d880361e3aa 100644 --- a/doc/development/contributing/community_roles.md +++ b/doc/development/contributing/community_roles.md @@ -1,3 +1,9 @@ +--- +stage: none +group: Development +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers +--- + # Community members & roles GitLab community members and their privileges/responsibilities. diff --git a/doc/development/contributing/design.md b/doc/development/contributing/design.md index 352392931c0..891c764f07f 100644 --- a/doc/development/contributing/design.md +++ b/doc/development/contributing/design.md @@ -1,3 +1,10 @@ +--- +type: reference, dev +stage: none +group: Development +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers +--- + # Implement design & UI elements For guidance on UX implementation at GitLab, please refer to our [Design System](https://design.gitlab.com/). diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md index 7550fe69546..6cbe57bf926 100644 --- a/doc/development/contributing/index.md +++ b/doc/development/contributing/index.md @@ -1,3 +1,10 @@ +--- +type: reference, dev +stage: none +group: Development +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers +--- + # Contribute to GitLab Thank you for your interest in contributing to GitLab. This guide details how diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md index d9a53f3b20e..b7c05a369f0 100644 --- a/doc/development/contributing/issue_workflow.md +++ b/doc/development/contributing/issue_workflow.md @@ -1,3 +1,10 @@ +--- +type: reference, dev +stage: none +group: Development +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers +--- + # Issues workflow ## Issue tracker guidelines diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md index b26a8a775b3..31f59ad875c 100644 --- a/doc/development/contributing/merge_request_workflow.md +++ b/doc/development/contributing/merge_request_workflow.md @@ -1,3 +1,10 @@ +--- +type: reference, dev +stage: none +group: Development +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers +--- + # Merge requests workflow We welcome merge requests from everyone, with fixes and improvements diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md index f6e64c1f1e6..773c1a771cd 100644 --- a/doc/development/contributing/style_guides.md +++ b/doc/development/contributing/style_guides.md @@ -1,3 +1,10 @@ +--- +type: reference, dev +stage: none +group: Development +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers +--- + # Style guides ## Editor/IDE styling standardization diff --git a/doc/development/distributed_tracing.md b/doc/development/distributed_tracing.md index 9a4c15c5c19..05fac7c7c19 100644 --- a/doc/development/distributed_tracing.md +++ b/doc/development/distributed_tracing.md @@ -6,10 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Distributed Tracing - development guidelines -NOTE: **Note:** -Distributed Tracing in GitLab is currently considered **experimental**, as it has not yet been tested at scale on GitLab.com. - -GitLab is instrumented for distributed tracing. +GitLab is instrumented for distributed tracing. Distributed Tracing in GitLab is currently considered **experimental**, as it has not yet been tested at scale on GitLab.com. According to [Open Tracing](https://opentracing.io/docs/overview/what-is-tracing/): diff --git a/doc/operations/error_tracking.md b/doc/operations/error_tracking.md index c5699ee3d22..1ea152f8263 100644 --- a/doc/operations/error_tracking.md +++ b/doc/operations/error_tracking.md @@ -20,10 +20,8 @@ You can sign up to the cloud hosted <https://sentry.io>, deploy your own [on-pre ### Enabling Sentry -NOTE: **Note:** -You will need at least Maintainer [permissions](../user/permissions.md) to enable the Sentry integration. - -GitLab provides an easy way to connect Sentry to your project: +GitLab provides an easy way to connect Sentry to your project. You will need at +least Maintainer [permissions](../user/permissions.md) to enable the Sentry integration. 1. Sign up to Sentry.io or [deploy your own](#deploying-sentry) Sentry instance. 1. [Create](https://docs.sentry.io/product/sentry-basics/guides/integrate-frontend/create-new-project/) a new Sentry project. For each GitLab project that you want to integrate, we recommend that you create a new Sentry project. @@ -47,9 +45,8 @@ You may also want to enable Sentry's GitLab integration by following the steps i ## Error Tracking List NOTE: **Note:** -You will need at least Reporter [permissions](../user/permissions.md) to view the Error Tracking list. - -You can find the Error Tracking list at **Operations > Error Tracking** in your project's sidebar. +Users with at least Reporter [permissions](../user/permissions.md) +can find the Error Tracking list at **Operations > Error Tracking** in your project's sidebar. Here, you can filter errors by title or by status (one of Ignored , Resolved, or Unresolved) and sort in descending order by Frequency, First Seen, or Last Seen. By default, the error list is ordered by Last Seen and filtered to Unresolved errors. ![Error Tracking list](img/error_tracking_list_v12_6.png) diff --git a/doc/operations/incident_management/alerts.md b/doc/operations/incident_management/alerts.md index 9d662013c6d..8c59a0709e2 100644 --- a/doc/operations/incident_management/alerts.md +++ b/doc/operations/incident_management/alerts.md @@ -40,13 +40,11 @@ in GitLab to examine alerts in action. ## Enable Alerts -NOTE: **Note:** -You need at least Maintainer [permissions](../../user/permissions.md) to enable -the Alerts feature. - There are several ways to accept alerts into your GitLab project. Enabling any -of these methods enables the Alert list. After configuring alerts, visit -**Operations > Alerts** in your project's sidebar to view the list of alerts. +of these methods enables the Alert list. You need at least Maintainer +[permissions](../../user/permissions.md) to enable the Alerts feature. After +configuring alerts, visit **Operations > Alerts** in your project's sidebar to view +the list of alerts. ### Enable GitLab-managed Prometheus alerts @@ -83,7 +81,6 @@ for requests to the alerts endpoint. You can monitor alerts using a GitLab integration with [Opsgenie](https://www.atlassian.com/software/opsgenie). -NOTE: **Note:** If you enable the Opsgenie integration, you can't have other GitLab alert services, such as [Generic Alerts](generic_alerts.md) or Prometheus alerts, active at the same time. @@ -168,14 +165,12 @@ about alert statuses. > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab 13.1. The Alert detail view allows users to update the Alert assignee. +GitLab supports only a single assignee per alert. In large teams, where there is shared ownership of an alert, it can be difficult to track who is investigating and working on it. The Alert detail view enables you to update the Alert assignee: -NOTE: **Note:** -GitLab supports only a single assignee per alert. - 1. To display the list of current alerts, navigate to **Operations > Alerts**: ![Alert List View Assignee(s)](./img/alert_list_assignees_v13_1.png) diff --git a/doc/operations/incident_management/img/timeline_view_toggle_v13_5.png b/doc/operations/incident_management/img/timeline_view_toggle_v13_5.png Binary files differnew file mode 100644 index 00000000000..542ca139f7e --- /dev/null +++ b/doc/operations/incident_management/img/timeline_view_toggle_v13_5.png diff --git a/doc/operations/incident_management/incidents.md b/doc/operations/incident_management/incidents.md index 1086885a58b..9c6bca10abc 100644 --- a/doc/operations/incident_management/incidents.md +++ b/doc/operations/incident_management/incidents.md @@ -4,7 +4,7 @@ group: Health info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers --- -# Create and manage incidents in GitLab +# Incidents While no configuration is required to use the [manual features](#create-an-incident-manually) of incident management, some simple [configuration](#configure-incidents) is needed to automate incident creation. @@ -27,8 +27,7 @@ in your project's sidebar. The list contains the following metrics: - **{severity-low}** **Low - S4** - **{severity-unknown}** **Unknown** - NOTE: **Note:** - Editing incident severity on the incident details page was + [Editing incident severity](#incident-details) on the incident details page was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229402) in GitLab 13.4. - **Incident** - The description of the incident, which attempts to capture the @@ -45,13 +44,12 @@ The Incident list displays incidents sorted by incident created date. To see if a column is sortable, point your mouse at the header. Sortable columns display an arrow next to the column name. +Incidents share the [Issues API](../../user/project/issues/index.md). + TIP: **Tip:** For a live example of the incident list in action, visit this [demo project](https://gitlab.com/gitlab-examples/ops/incident-setup/everyone/tanuki-inc/-/incidents). -NOTE: **Note:** -Incidents share the [Issues API](../../user/project/issues/index.md). - ## Configure incidents > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4925) in GitLab Ultimate 11.11. @@ -134,24 +132,55 @@ confirm that a GitLab issue is created from the incident. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230847) in GitLab 13.4. +Users with at least Reporter [permissions](../../user/permissions.md) can view +the Incident Details page. Navigate to **Operations > Incidents** in your project's +sidebar, and select an incident from the list. + +When you take any of these actions on an incident, GitLab logs a system note and +displays it in the Incident Details view: + +- Updating the severity of an incident + ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42358) in GitLab 13.5.) + +For live examples of GitLab incidents, visit the `tanuki-inc` project's +[incident list page](https://gitlab.com/gitlab-examples/ops/incident-setup/everyone/tanuki-inc/-/incidents). +Click any incident in the list to display its incident details page. + ### Summary The summary section for incidents provides both critical details about and the contents of the issue template (if one was used). The highlighted bar at the top -of the incident displays from left to right: the link to the original alert, the -alert start time, and the event count. Beneath the highlight bar, GitLab -displays a summary that includes the following fields: +of the incident displays from left to right: + +- The link to the original alert. +- The alert start time. +- The event count. + +Beneath the highlight bar, GitLab displays a summary that includes the following fields: - Start time - Severity - `full_query` - Monitoring tool +Comments are displayed in threads, but can be displayed chronologically +[in a timeline view](#timeline-view). + ### Alert details Incidents show the details of linked alerts in a separate tab. To populate this tab, the incident must have been created with a linked alert. Incidents -[created automatically](#configure-incidents) from alerts will have this +[created automatically](#configure-incidents) from alerts have this field populated. ![Incident alert details](./img/incident_alert_details_v13_4.png) + +### Timeline view + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227836) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.5. + +To quickly see the latest updates on an incident, click +**{comments}** **Turn timeline view on** in the comment bar to display comments +un-threaded and ordered chronologically, newest to oldest: + +![Timeline view toggle](./img/timeline_view_toggle_v13_5.png) diff --git a/doc/operations/incident_management/status_page.md b/doc/operations/incident_management/status_page.md index 9db3593caec..18d5c6e10ef 100644 --- a/doc/operations/incident_management/status_page.md +++ b/doc/operations/incident_management/status_page.md @@ -37,11 +37,10 @@ To configure a GitLab Status Page you must: ### Configure GitLab with cloud provider information -To provide GitLab with the AWS account information needed to push content to your Status Page: - -NOTE: **Note:** Only AWS S3 is supported as a deploy target. +To provide GitLab with the AWS account information needed to push content to your Status Page: + 1. Sign into GitLab as a user with Maintainer or greater [permissions](../../user/permissions.md). 1. Navigate to **{settings}** **Settings > Operations**. Next to **Status Page**, click **Expand**. @@ -74,8 +73,6 @@ the necessary CI/CD variables to deploy the Status Page to AWS S3: 1. Scroll to **Variables**, and click **Expand**. 1. Add the following variables from your Amazon Console: - `S3_BUCKET_NAME` - The name of the Amazon S3 bucket. - - NOTE: **Note:** If no bucket with the provided name exists, the first pipeline run creates one and configures it for [static website hosting](https://docs.aws.amazon.com/AmazonS3/latest/dev/HostingWebsiteOnS3Setup.html). @@ -128,10 +125,7 @@ To publish an incident: 1. Create an issue in the project you enabled the GitLab Status Page settings in. 1. A [project or group owner](../../user/permissions.md) must use the `/publish` [quick action](../../user/project/quick_actions.md) to publish the - issue to the GitLab Status Page. - - NOTE: **Note:** - Confidential issues can't be published. + issue to the GitLab Status Page. Confidential issues can't be published. A background worker publishes the issue onto the Status Page using the credentials you provided during setup. As part of publication, GitLab will: diff --git a/doc/operations/metrics/alerts.md b/doc/operations/metrics/alerts.md index c0a16da3702..b7d0576d6c7 100644 --- a/doc/operations/metrics/alerts.md +++ b/doc/operations/metrics/alerts.md @@ -78,7 +78,6 @@ For GitLab to associate your alerts with an [environment](../../ci/environments/ you must configure a `gitlab_environment_name` label on the alerts you set up in Prometheus. The value of this should match the name of your environment in GitLab. -NOTE: **Note:** In GitLab versions 13.1 and greater, you can configure your manually configured Prometheus server to use the [Generic alerts integration](../incident_management/generic_alerts.md). diff --git a/doc/operations/metrics/dashboards/default.md b/doc/operations/metrics/dashboards/default.md index f086d7737bd..21cb3d5a5a6 100644 --- a/doc/operations/metrics/dashboards/default.md +++ b/doc/operations/metrics/dashboards/default.md @@ -25,7 +25,6 @@ metrics about the [deployed application](../index.md#configure-prometheus-to-gat ## Kubernetes pod health dashboard -NOTE: **Note:** This dashboard requires Kubernetes v1.14 or higher, due to the [change in metric labels](https://github.com/kubernetes/kubernetes/pull/69099) in Kubernetes 1.14. diff --git a/doc/operations/metrics/dashboards/index.md b/doc/operations/metrics/dashboards/index.md index 42dc5bbfb6d..12e6b7b5740 100644 --- a/doc/operations/metrics/dashboards/index.md +++ b/doc/operations/metrics/dashboards/index.md @@ -14,7 +14,6 @@ includes a few key metrics, but you can also define your own custom dashboards. You may create a [new dashboard from scratch](#add-a-new-dashboard-to-your-project) or [duplicate a GitLab-defined Prometheus dashboard](#duplicate-a-gitlab-defined-dashboard). -NOTE: **Note:** The metrics as defined below do not support alerts, unlike [custom metrics](../index.md#adding-custom-metrics). @@ -86,7 +85,7 @@ with the **Add Panel** page: 1. Click **Add panel** in the **{ellipsis_v}** **More actions** menu. NOTE: **Note:** - You can add panel only to custom dashboards. + You can only add panels to custom dashboards. ![Monitoring Dashboard actions menu with add panel item](img/actions_menu_create_add_panel_v13_3.png) 1. In the **Define and preview panel** section, paste in the YAML you want to @@ -100,16 +99,12 @@ with the **Add Panel** page: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/37238) in GitLab 12.7. > - From [GitLab 12.8 onwards](https://gitlab.com/gitlab-org/gitlab/-/issues/39505), custom metrics are also duplicated when you duplicate a dashboard. -You can save a complete copy of a GitLab defined dashboard along with all custom metrics added to it. +You can save a complete copy of a GitLab-defined dashboard along with all custom metrics added to it. The resulting `.yml` file can be customized and adapted to your project. You can decide to save the dashboard `.yml` file in the project's **default** branch or in a -new branch. +new branch. To duplicate a GitLab-defined dashboard: 1. Click **Duplicate current dashboard** in the **{ellipsis_v}** **More actions** menu. - - NOTE: **Note:** - You can duplicate only GitLab-defined dashboards. - 1. Enter the filename and other information, such as the new commit's message, and click **Duplicate**. 1. Select a branch to add your dashboard to: - *If you select your **default** branch,* the new dashboard becomes immediately available. diff --git a/doc/operations/metrics/dashboards/variables.md b/doc/operations/metrics/dashboards/variables.md index 22c8814e8bd..745e53150fc 100644 --- a/doc/operations/metrics/dashboards/variables.md +++ b/doc/operations/metrics/dashboards/variables.md @@ -16,7 +16,10 @@ Queries that continue to use the old format will show no data. ## Predefined variables -GitLab supports a limited set of [CI variables](../../../ci/variables/README.md) in the Prometheus query. This is particularly useful for identifying a specific environment, for example with `ci_environment_slug`. The supported variables are: +GitLab supports a limited set of [CI variables](../../../ci/variables/README.md) +in the Prometheus query. This is particularly useful for identifying a specific +environment, for example with `ci_environment_slug`. Variables for Prometheus queries +must be lowercase. The supported variables are: - `environment_filter` - `ci_environment_slug` @@ -27,9 +30,6 @@ GitLab supports a limited set of [CI variables](../../../ci/variables/README.md) - `ci_environment_name` - `__range` -NOTE: **Note:** -Variables for Prometheus queries must be lowercase. - ### environment_filter `environment_filter` is automatically expanded to `container_name!="POD",environment="ENVIRONMENT_NAME"` diff --git a/doc/operations/metrics/embed.md b/doc/operations/metrics/embed.md index fcf9679d164..a57b7216f1a 100644 --- a/doc/operations/metrics/embed.md +++ b/doc/operations/metrics/embed.md @@ -17,8 +17,7 @@ metrics to others, and you want to have relevant information directly available. > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/29691) in GitLab 12.2. -NOTE: **Note:** -Requires [Kubernetes](../../user/project/integrations/prometheus_library/kubernetes.md) metrics. +This feature requires [Kubernetes](../../user/project/integrations/prometheus_library/kubernetes.md) metrics. Note: **Note:** In GitLab versions 13.3 and earlier, metrics dashboard links were in the form diff --git a/doc/operations/metrics/embed_grafana.md b/doc/operations/metrics/embed_grafana.md index 2843a4319a8..30207179736 100644 --- a/doc/operations/metrics/embed_grafana.md +++ b/doc/operations/metrics/embed_grafana.md @@ -12,14 +12,13 @@ Grafana metrics can be embedded in [GitLab Flavored Markdown](../../user/markdow You can embed live [Grafana](https://docs.gitlab.com/omnibus/settings/grafana.html) charts in issues as a -[direct linked rendered image](https://grafana.com/docs/grafana/latest/reference/share_panel/#direct-link-rendered-image). -The **Direct link rendered image** sharing dialog within Grafana provides the link: +[direct linked rendered image](https://grafana.com/docs/grafana/latest/reference/share_panel/#direct-link-rendered-image). Your Grafana instance must be available to the +target user, either as a public dashboard or on the same network. The +**Direct link rendered image** sharing dialog within Grafana provides the link: ![Grafana Direct Linked Rendered Image](img/grafana_live_embed.png) -NOTE: **Note:** -For this embed to display correctly, the Grafana instance must be available to the -target user, either as a public dashboard or on the same network. +For this embed to display correctly, the Copy the link and add an image tag as [inline HTML](../../user/markdown.md#inline-html) in your Markdown. You can tweak the query parameters to meet your needs, such as diff --git a/doc/topics/cron/index.md b/doc/topics/cron/index.md index a3dd3b77c22..851dd6d3f77 100644 --- a/doc/topics/cron/index.md +++ b/doc/topics/cron/index.md @@ -24,7 +24,7 @@ Cron scheduling uses a series of five numbers, separated by spaces: # * * * * * <command to execute> ``` -[Source: [Wikipedia](https://en.wikipedia.org/wiki/Cron)] +(Source: [Wikipedia](https://en.wikipedia.org/wiki/Cron)) In cron syntax, the asterisk (`*`) means 'every,' so the following cron strings are valid: diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index e60e3fcd4e7..00be3ea54b3 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -55,7 +55,6 @@ Currently, GitLab supports the following Kubernetes versions: - 1.14 - 1.13 (deprecated, support ends on November 22, 2020) -NOTE: **Note:** Some GitLab features may support versions outside the range provided here. ### Adding and removing clusters @@ -195,7 +194,6 @@ To clear the cache: > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/24580) in GitLab 11.8. -NOTE: **Note:** You do not need to specify a base domain on cluster settings when using GitLab Serverless. The domain in that case will be specified as part of the Knative installation. See [Installing Applications](#installing-applications). @@ -223,13 +221,11 @@ Auto DevOps automatically detects, builds, tests, deploys, and monitors your applications. To make full use of Auto DevOps (Auto Deploy, Auto Review Apps, and -Auto Monitoring) you will need the Kubernetes project integration enabled. +Auto Monitoring) you will need the Kubernetes project integration enabled, but +Kubernetes clusters can be used without Auto DevOps. [Read more about Auto DevOps](../../../topics/autodevops/index.md) -NOTE: **Note:** -Kubernetes clusters can be used without Auto DevOps. - ## Deploying to a Kubernetes cluster A Kubernetes cluster can be the destination for a deployment job. If @@ -252,20 +248,13 @@ GitLab CI/CD build environment. | Variable | Description | | -------- | ----------- | | `KUBE_URL` | Equal to the API URL. | -| `KUBE_TOKEN` | The Kubernetes token of the [environment service account](add_remove_clusters.md#access-controls). | -| `KUBE_NAMESPACE` | The namespace associated with the project's deployment service account. In the format `<project_name>-<project_id>-<environment>`. For GitLab-managed clusters, a matching namespace is automatically created by GitLab in the cluster. | +| `KUBE_TOKEN` | The Kubernetes token of the [environment service account](add_remove_clusters.md#access-controls). Prior to GitLab 11.5, `KUBE_TOKEN` was the Kubernetes token of the main service account of the cluster integration. | +| `KUBE_NAMESPACE` | The namespace associated with the project's deployment service account. In the format `<project_name>-<project_id>-<environment>`. For GitLab-managed clusters, a matching namespace is automatically created by GitLab in the cluster. If your cluster was created before GitLab 12.2, the default `KUBE_NAMESPACE` is set to `<project_name>-<project_id>`. | | `KUBE_CA_PEM_FILE` | Path to a file containing PEM data. Only present if a custom CA bundle was specified. | | `KUBE_CA_PEM` | (**deprecated**) Raw PEM data. Only if a custom CA bundle was specified. | | `KUBECONFIG` | Path to a file containing `kubeconfig` for this deployment. CA bundle would be embedded if specified. This config also embeds the same token defined in `KUBE_TOKEN` so you likely will only need this variable. This variable name is also automatically picked up by `kubectl` so you won't actually need to reference it explicitly if using `kubectl`. | | `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](#base-domain) for more information. | -NOTE: **Note:** -Prior to GitLab 11.5, `KUBE_TOKEN` was the Kubernetes token of the main -service account of the cluster integration. - -NOTE: **Note:** -If your cluster was created before GitLab 12.2, default `KUBE_NAMESPACE` will be set to `<project_name>-<project_id>`. - ### Custom namespace > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27630) in GitLab 12.6. @@ -290,7 +279,6 @@ You can customize the deployment namespace in a few ways: [`environment:kubernetes:namespace`](../../../ci/environments/index.md#configuring-kubernetes-deployments) in `.gitlab-ci.yml`. -NOTE: **Note:** When you customize the namespace, existing environments remain linked to their current namespaces until you [clear the cluster cache](#clearing-the-cluster-cache). diff --git a/doc/user/project/clusters/kubernetes_pod_logs.md b/doc/user/project/clusters/kubernetes_pod_logs.md index afb6d016f45..9ef7c586066 100644 --- a/doc/user/project/clusters/kubernetes_pod_logs.md +++ b/doc/user/project/clusters/kubernetes_pod_logs.md @@ -28,7 +28,6 @@ above the log file data, depending on your configuration: <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> To learn more about the Log Explorer, see [APM - Log Explorer](https://www.youtube.com/watch?v=hWclZHA7Dgw). -NOTE: **Note:** [Learn more about Kubernetes + GitLab](https://about.gitlab.com/solutions/kubernetes/). Everything you need to build, test, deploy, and run your application at scale. diff --git a/doc/user/project/import/bitbucket_server.md b/doc/user/project/import/bitbucket_server.md index d8ba4f86924..ac5be2b46a4 100644 --- a/doc/user/project/import/bitbucket_server.md +++ b/doc/user/project/import/bitbucket_server.md @@ -89,7 +89,7 @@ Alternatively, if there is also no email address, the project creator is set as User assignment by username is under development and not ready for production use. It is deployed behind a feature flag that is **disabled by default**. -[GitLab administrators with access to the GitLab Rails console](<replace with path to>/administration/feature_flags.md) +[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) can enable it. To enable it: diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md index 7bebe7b1e65..5dafd9d8da6 100644 --- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md +++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md @@ -8,11 +8,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/22133) in GitLab 11.7. +GitLab has support for automatically detecting and monitoring the Kubernetes NGINX Ingress controller. This is provided by leveraging the built-in Prometheus metrics included with Kubernetes NGINX Ingress controller [version 0.16.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#0160) onward. + NOTE: **Note:** NGINX Ingress versions prior to 0.16.0 offer an included [VTS Prometheus metrics exporter](nginx_ingress_vts.md), which exports metrics different than the built-in metrics. -GitLab has support for automatically detecting and monitoring the Kubernetes NGINX Ingress controller. This is provided by leveraging the built-in Prometheus metrics included with Kubernetes NGINX Ingress controller [version 0.16.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#0160) onward. - ## Requirements [Prometheus integration](../prometheus.md) must be active. diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb index 47d0b9ba8cb..4b65bbcc791 100644 --- a/lib/gitlab/middleware/go.rb +++ b/lib/gitlab/middleware/go.rb @@ -18,6 +18,15 @@ module Gitlab request = ActionDispatch::Request.new(env) render_go_doc(request) || @app.call(env) + rescue Gitlab::Auth::IpBlacklisted + Gitlab::AuthLogger.error( + message: 'Rack_Attack', + env: :blocklist, + remote_ip: request.ip, + request_method: request.request_method, + path: request.fullpath + ) + Rack::Response.new('', 403).finish end private diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index b9b9164d746..792ff0d1f93 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -201,7 +201,7 @@ module Gitlab personal_snippets: count(PersonalSnippet.where(last_28_days_time_period)), project_snippets: count(ProjectSnippet.where(last_28_days_time_period)) }.merge( - snowplow_event_counts(time_period: last_28_days_time_period(column: :collector_tstamp)) + snowplow_event_counts(last_28_days_time_period(column: :collector_tstamp)) ).tap do |data| data[:snippets] = data[:personal_snippets] + data[:project_snippets] end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3c9785b31e8..f5bee1a4854 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2222,13 +2222,13 @@ msgstr "" msgid "After a successful password update, you will be redirected to the login page where you can log in with your new password." msgstr "" -msgid "After that, you will not to be able to use merge approvals or code quality as well as many other features." +msgid "After that, you will not be able to use merge approvals or code quality as well as many other features." msgstr "" -msgid "After that, you will not to be able to use merge approvals or epics as well as many other features." +msgid "After that, you will not be able to use merge approvals or epics as well as many other features." msgstr "" -msgid "After that, you will not to be able to use merge approvals or epics as well as many security features." +msgid "After that, you will not be able to use merge approvals or epics as well as many security features." msgstr "" msgid "Alert" @@ -29944,7 +29944,7 @@ msgstr "" msgid "Your %{strong}%{plan_name}%{strong_close} subscription for %{strong}%{namespace_name}%{strong_close} will expire on %{strong}%{expires_on}%{strong_close}." msgstr "" -msgid "Your %{strong}%{plan_name}%{strong_close} subscription will expire on %{strong}%{expires_on}%{strong_close}. After that, you will not to be able to create issues or merge requests as well as many other features." +msgid "Your %{strong}%{plan_name}%{strong_close} subscription will expire on %{strong}%{expires_on}%{strong_close}. After that, you will not be able to create issues or merge requests as well as many other features." msgstr "" msgid "Your CSV export has started. It will be emailed to %{email} when complete." diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb index 6a657b4ab39..2ecb27e05b2 100644 --- a/qa/qa/page/project/job/show.rb +++ b/qa/qa/page/project/job/show.rb @@ -58,6 +58,10 @@ module QA click_element :retry_button end + def has_job_log? + has_element? :job_log_content + end + private def loaded?(wait: 60) @@ -70,3 +74,5 @@ module QA end end end + +QA::Page::Project::Job::Show.prepend_if_ee('QA::EE::Page::Project::Job::Show') diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb index b2ecbb2e988..aff2378330a 100644 --- a/qa/qa/page/project/pipeline/index.rb +++ b/qa/qa/page/project/pipeline/index.rb @@ -57,3 +57,5 @@ module QA end end end + +QA::Page::Project::Pipeline::Index.prepend_if_ee('QA::EE::Page::Project::Pipeline::Index') diff --git a/qa/qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb index a2190a8cf41..13761244300 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Plan' do + RSpec.describe 'Plan', :reliable do describe 'Related issues' do let(:project) do Resource::Project.fabricate_via_api! do |project| diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 90f348a3421..df7e018b35e 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -551,6 +551,37 @@ RSpec.describe GroupsController, factory_default: :keep do end end + context "updating default_branch_name" do + let(:example_branch_name) { "example_branch_name" } + + subject(:update_action) do + put :update, + params: { + id: group.to_param, + group: { default_branch_name: example_branch_name } + } + end + + it "updates the attribute" do + expect { subject } + .to change { group.namespace_settings.reload.default_branch_name } + .from(nil) + .to(example_branch_name) + + expect(response).to have_gitlab_http_status(:found) + end + + context "to empty string" do + let(:example_branch_name) { '' } + + it "does not update the attribute" do + subject + + expect(group.namespace_settings.reload.default_branch_name).not_to eq('') + end + end + end + context 'when there is a conflicting group path' do let!(:conflict_group) { create(:group, path: SecureRandom.hex(12) ) } let!(:old_name) { group.name } diff --git a/spec/controllers/projects/tracings_controller_spec.rb b/spec/controllers/projects/tracings_controller_spec.rb index 1877822df54..1f8a68cc861 100644 --- a/spec/controllers/projects/tracings_controller_spec.rb +++ b/spec/controllers/projects/tracings_controller_spec.rb @@ -43,31 +43,17 @@ RSpec.describe Projects::TracingsController do end end - describe 'with valid license' do - before do - stub_licensed_features(tracing: true) - sign_in(user) - end - - context 'with maintainer role' do - it_behaves_like 'user with read access', :public - it_behaves_like 'user with read access', :internal - it_behaves_like 'user with read access', :private - end - - context 'without maintainer role' do - it_behaves_like 'user without read access', :public - it_behaves_like 'user without read access', :internal - it_behaves_like 'user without read access', :private - end + before do + sign_in(user) end - context 'with invalid license' do - before do - stub_licensed_features(tracing: false) - sign_in(user) - end + context 'with maintainer role' do + it_behaves_like 'user with read access', :public + it_behaves_like 'user with read access', :internal + it_behaves_like 'user with read access', :private + end + context 'without maintainer role' do it_behaves_like 'user without read access', :public it_behaves_like 'user without read access', :internal it_behaves_like 'user without read access', :private diff --git a/spec/features/projects/tracings_spec.rb b/spec/features/projects/tracings_spec.rb new file mode 100644 index 00000000000..c4a4f1382ed --- /dev/null +++ b/spec/features/projects/tracings_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Tracings Content Security Policy' do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + + subject { response_headers['Content-Security-Policy'] } + + before_all do + project.add_maintainer(user) + end + + before do + sign_in(user) + end + + context 'when there is no global config' do + before do + expect_next_instance_of(Projects::TracingsController) do |controller| + expect(controller).to receive(:current_content_security_policy) + .and_return(ActionDispatch::ContentSecurityPolicy.new) + end + end + + it 'does not add CSP directives' do + visit project_tracing_path(project) + + is_expected.to be_blank + end + end + + context 'when a global CSP config exists' do + before do + csp = ActionDispatch::ContentSecurityPolicy.new do |p| + p.frame_src 'https://global-policy.com' + end + + expect_next_instance_of(Projects::TracingsController) do |controller| + expect(controller).to receive(:current_content_security_policy).and_return(csp) + end + end + + context 'when external_url is set' do + let!(:project_tracing_setting) { create(:project_tracing_setting, project: project) } + + it 'overwrites frame-src' do + visit project_tracing_path(project) + + is_expected.to eq("frame-src https://example.com") + end + end + + context 'when external_url is not set' do + it 'uses global policy' do + visit project_tracing_path(project) + + is_expected.to eq("frame-src https://global-policy.com") + end + end + end +end diff --git a/spec/frontend/feature_flags/components/edit_feature_flag_spec.js b/spec/frontend/feature_flags/components/edit_feature_flag_spec.js index f2e587bb8d9..49551568e6a 100644 --- a/spec/frontend/feature_flags/components/edit_feature_flag_spec.js +++ b/spec/frontend/feature_flags/components/edit_feature_flag_spec.js @@ -6,7 +6,7 @@ import { TEST_HOST } from 'spec/test_constants'; import { mockTracking } from 'helpers/tracking_helper'; import { LEGACY_FLAG, NEW_VERSION_FLAG, NEW_FLAG_ALERT } from '~/feature_flags/constants'; import Form from '~/feature_flags/components/form.vue'; -import editModule from '~/feature_flags/store/modules/edit'; +import createStore from '~/feature_flags/store/edit'; import EditFeatureFlag from '~/feature_flags/components/edit_feature_flag.vue'; import axios from '~/lib/utils/axios_utils'; @@ -20,10 +20,9 @@ describe('Edit feature flag form', () => { let wrapper; let mock; - const store = new Vuex.Store({ - modules: { - edit: editModule, - }, + const store = createStore({ + path: '/feature_flags', + endpoint: `${TEST_HOST}/feature_flags.json`, }); const factory = (opts = {}) => { @@ -34,8 +33,6 @@ describe('Edit feature flag form', () => { wrapper = shallowMount(EditFeatureFlag, { localVue, propsData: { - endpoint: `${TEST_HOST}/feature_flags.json`, - path: '/feature_flags', environmentsEndpoint: 'environments.json', projectId: '8', featureFlagIssuesEndpoint: `${TEST_HOST}/feature_flags/5/issues`, @@ -105,7 +102,7 @@ describe('Edit feature flag form', () => { describe('with error', () => { it('should render the error', () => { - store.dispatch('edit/receiveUpdateFeatureFlagError', { message: ['The name is required'] }); + store.dispatch('receiveUpdateFeatureFlagError', { message: ['The name is required'] }); return wrapper.vm.$nextTick(() => { expect(wrapper.find('.alert-danger').exists()).toEqual(true); expect(wrapper.find('.alert-danger').text()).toContain('The name is required'); diff --git a/spec/frontend/feature_flags/components/feature_flags_spec.js b/spec/frontend/feature_flags/components/feature_flags_spec.js index 5ff39937113..c9ab7695158 100644 --- a/spec/frontend/feature_flags/components/feature_flags_spec.js +++ b/spec/frontend/feature_flags/components/feature_flags_spec.js @@ -1,9 +1,10 @@ -import { shallowMount } from '@vue/test-utils'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; import MockAdapter from 'axios-mock-adapter'; import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui'; import { TEST_HOST } from 'spec/test_constants'; import Api from '~/api'; -import { createStore } from '~/feature_flags/store'; +import createStore from '~/feature_flags/store/index'; import FeatureFlagsTab from '~/feature_flags/components/feature_flags_tab.vue'; import FeatureFlagsComponent from '~/feature_flags/components/feature_flags.vue'; import FeatureFlagsTable from '~/feature_flags/components/feature_flags_table.vue'; @@ -14,19 +15,25 @@ import TablePagination from '~/vue_shared/components/pagination/table_pagination import axios from '~/lib/utils/axios_utils'; import { getRequestData, userList } from '../mock_data'; +const localVue = createLocalVue(); +localVue.use(Vuex); + describe('Feature flags', () => { const mockData = { - endpoint: `${TEST_HOST}/endpoint.json`, csrfToken: 'testToken', featureFlagsClientLibrariesHelpPagePath: '/help/feature-flags#unleash-clients', featureFlagsClientExampleHelpPagePath: '/help/feature-flags#client-example', unleashApiUrl: `${TEST_HOST}/api/unleash`, - unleashApiInstanceId: 'oP6sCNRqtRHmpy1gw2-F', canUserConfigure: true, canUserRotateToken: true, newFeatureFlagPath: 'feature-flags/new', newUserListPath: '/user-list/new', + }; + + const mockState = { + endpoint: `${TEST_HOST}/endpoint.json`, projectId: '8', + unleashApiInstanceId: 'oP6sCNRqtRHmpy1gw2-F', }; let wrapper; @@ -34,8 +41,9 @@ describe('Feature flags', () => { let store; const factory = (propsData = mockData, fn = shallowMount) => { - store = createStore(); + store = createStore(mockState); wrapper = fn(FeatureFlagsComponent, { + localVue, store, propsData, provide: { @@ -76,7 +84,6 @@ describe('Feature flags', () => { describe('without permissions', () => { const propsData = { - endpoint: `${TEST_HOST}/endpoint.json`, csrfToken: 'testToken', errorStateSvgPath: '/assets/illustrations/feature_flag.svg', featureFlagsHelpPagePath: '/help/feature-flags', @@ -85,8 +92,6 @@ describe('Feature flags', () => { featureFlagsClientLibrariesHelpPagePath: '/help/feature-flags#unleash-clients', featureFlagsClientExampleHelpPagePath: '/help/feature-flags#client-example', unleashApiUrl: `${TEST_HOST}/api/unleash`, - unleashApiInstanceId: 'oP6sCNRqtRHmpy1gw2-F', - projectId: '8', }; beforeEach(done => { @@ -134,7 +139,7 @@ describe('Feature flags', () => { let emptyState; beforeEach(async () => { - mock.onGet(mockData.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } }).reply( + mock.onGet(mockState.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } }).reply( 200, { feature_flags: [], @@ -154,8 +159,6 @@ describe('Feature flags', () => { }); it('should render the empty state', async () => { - await axios.waitForAll(); - emptyState = wrapper.find(GlEmptyState); expect(emptyState.exists()).toBe(true); }); @@ -182,7 +185,7 @@ describe('Feature flags', () => { describe('with paginated feature flags', () => { beforeEach(done => { mock - .onGet(mockData.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } }) + .onGet(mockState.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } }) .replyOnce(200, getRequestData, { 'x-next-page': '2', 'x-page': '1', @@ -218,7 +221,7 @@ describe('Feature flags', () => { const [flag] = table.props(FEATURE_FLAG_SCOPE); table.vm.$emit('toggle-flag', flag); - expect(store.dispatch).toHaveBeenCalledWith('index/toggleFeatureFlag', flag); + expect(store.dispatch).toHaveBeenCalledWith('toggleFeatureFlag', flag); }); it('renders configure button', () => { @@ -287,7 +290,7 @@ describe('Feature flags', () => { describe('unsuccessful request', () => { beforeEach(done => { mock - .onGet(mockData.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } }) + .onGet(mockState.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } }) .replyOnce(500, {}); Api.fetchFeatureFlagUserLists.mockRejectedValueOnce(); diff --git a/spec/frontend/feature_flags/components/new_feature_flag_spec.js b/spec/frontend/feature_flags/components/new_feature_flag_spec.js index 284ba09d7fd..1844806ed95 100644 --- a/spec/frontend/feature_flags/components/new_feature_flag_spec.js +++ b/spec/frontend/feature_flags/components/new_feature_flag_spec.js @@ -1,10 +1,10 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; -import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import { GlAlert } from '@gitlab/ui'; import { TEST_HOST } from 'spec/test_constants'; import Form from '~/feature_flags/components/form.vue'; -import newModule from '~/feature_flags/store/modules/new'; +import createStore from '~/feature_flags/store/new'; import NewFeatureFlag from '~/feature_flags/components/new_feature_flag.vue'; import { ROLLOUT_STRATEGY_ALL_USERS, @@ -17,13 +17,15 @@ import { allUsersStrategy } from '../mock_data'; const userCalloutId = 'feature_flags_new_version'; const userCalloutsPath = `${TEST_HOST}/user_callouts`; +const localVue = createLocalVue(); +localVue.use(Vuex); + describe('New feature flag form', () => { let wrapper; - const store = new Vuex.Store({ - modules: { - new: newModule, - }, + const store = createStore({ + endpoint: `${TEST_HOST}/feature_flags.json`, + path: '/feature_flags', }); const factory = (opts = {}) => { @@ -32,9 +34,8 @@ describe('New feature flag form', () => { wrapper = null; } wrapper = shallowMount(NewFeatureFlag, { + localVue, propsData: { - endpoint: `${TEST_HOST}/feature_flags.json`, - path: '/feature_flags', environmentsEndpoint: 'environments.json', projectId: '8', showUserCallout: true, @@ -63,7 +64,7 @@ describe('New feature flag form', () => { describe('with error', () => { it('should render the error', () => { - store.dispatch('new/receiveCreateFeatureFlagError', { message: ['The name is required'] }); + store.dispatch('receiveCreateFeatureFlagError', { message: ['The name is required'] }); return wrapper.vm.$nextTick(() => { expect(wrapper.find('.alert').exists()).toEqual(true); expect(wrapper.find('.alert').text()).toContain('The name is required'); diff --git a/spec/frontend/feature_flags/store/edit/actions_spec.js b/spec/frontend/feature_flags/store/edit/actions_spec.js index 4f20b9713bf..9d764799d09 100644 --- a/spec/frontend/feature_flags/store/edit/actions_spec.js +++ b/spec/frontend/feature_flags/store/edit/actions_spec.js @@ -2,8 +2,6 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; import { TEST_HOST } from 'spec/test_constants'; import { - setEndpoint, - setPath, updateFeatureFlag, requestUpdateFeatureFlag, receiveUpdateFeatureFlagSuccess, @@ -13,18 +11,15 @@ import { receiveFeatureFlagSuccess, receiveFeatureFlagError, toggleActive, -} from '~/feature_flags/store/modules/edit/actions'; -import state from '~/feature_flags/store/modules/edit/state'; -import { - mapStrategiesToRails, - mapFromScopesViewModel, -} from '~/feature_flags/store/modules/helpers'; +} from '~/feature_flags/store/edit/actions'; +import state from '~/feature_flags/store/edit/state'; +import { mapStrategiesToRails, mapFromScopesViewModel } from '~/feature_flags/store/helpers'; import { NEW_VERSION_FLAG, LEGACY_FLAG, ROLLOUT_STRATEGY_ALL_USERS, } from '~/feature_flags/constants'; -import * as types from '~/feature_flags/store/modules/edit/mutation_types'; +import * as types from '~/feature_flags/store/edit/mutation_types'; import axios from '~/lib/utils/axios_utils'; jest.mock('~/lib/utils/url_utility'); @@ -33,33 +28,7 @@ describe('Feature flags Edit Module actions', () => { let mockedState; beforeEach(() => { - mockedState = state(); - }); - - describe('setEndpoint', () => { - it('should commit SET_ENDPOINT mutation', done => { - testAction( - setEndpoint, - 'feature_flags.json', - mockedState, - [{ type: types.SET_ENDPOINT, payload: 'feature_flags.json' }], - [], - done, - ); - }); - }); - - describe('setPath', () => { - it('should commit SET_PATH mutation', done => { - testAction( - setPath, - '/feature_flags', - mockedState, - [{ type: types.SET_PATH, payload: '/feature_flags' }], - [], - done, - ); - }); + mockedState = state({ endpoint: 'feature_flags.json', path: '/feature_flags' }); }); describe('updateFeatureFlag', () => { diff --git a/spec/frontend/feature_flags/store/edit/mutations_spec.js b/spec/frontend/feature_flags/store/edit/mutations_spec.js index 21d4e962b48..1d817fb8004 100644 --- a/spec/frontend/feature_flags/store/edit/mutations_spec.js +++ b/spec/frontend/feature_flags/store/edit/mutations_spec.js @@ -1,28 +1,12 @@ -import state from '~/feature_flags/store/modules/edit/state'; -import mutations from '~/feature_flags/store/modules/edit/mutations'; -import * as types from '~/feature_flags/store/modules/edit/mutation_types'; +import state from '~/feature_flags/store/edit/state'; +import mutations from '~/feature_flags/store/edit/mutations'; +import * as types from '~/feature_flags/store/edit/mutation_types'; describe('Feature flags Edit Module Mutations', () => { let stateCopy; beforeEach(() => { - stateCopy = state(); - }); - - describe('SET_ENDPOINT', () => { - it('should set endpoint', () => { - mutations[types.SET_ENDPOINT](stateCopy, 'feature_flags.json'); - - expect(stateCopy.endpoint).toEqual('feature_flags.json'); - }); - }); - - describe('SET_PATH', () => { - it('should set provided options', () => { - mutations[types.SET_PATH](stateCopy, 'feature_flags'); - - expect(stateCopy.path).toEqual('feature_flags'); - }); + stateCopy = state({ endpoint: 'feature_flags.json', path: '/feature_flags' }); }); describe('REQUEST_FEATURE_FLAG', () => { diff --git a/spec/frontend/feature_flags/store/helpers_spec.js b/spec/frontend/feature_flags/store/helpers_spec.js index 0bc15ab70aa..301b1d09fcc 100644 --- a/spec/frontend/feature_flags/store/helpers_spec.js +++ b/spec/frontend/feature_flags/store/helpers_spec.js @@ -5,7 +5,7 @@ import { createNewEnvironmentScope, mapStrategiesToViewModel, mapStrategiesToRails, -} from '~/feature_flags/store/modules/helpers'; +} from '~/feature_flags/store/helpers'; import { ROLLOUT_STRATEGY_ALL_USERS, ROLLOUT_STRATEGY_PERCENT_ROLLOUT, diff --git a/spec/frontend/feature_flags/store/index/actions_spec.js b/spec/frontend/feature_flags/store/index/actions_spec.js index 0ada84aed33..d223bb2c292 100644 --- a/spec/frontend/feature_flags/store/index/actions_spec.js +++ b/spec/frontend/feature_flags/store/index/actions_spec.js @@ -7,10 +7,7 @@ import { receiveFeatureFlagsSuccess, receiveFeatureFlagsError, fetchFeatureFlags, - setFeatureFlagsEndpoint, setFeatureFlagsOptions, - setInstanceIdEndpoint, - setInstanceId, rotateInstanceId, requestRotateInstanceId, receiveRotateInstanceIdSuccess, @@ -26,10 +23,10 @@ import { deleteUserList, receiveDeleteUserListError, clearAlert, -} from '~/feature_flags/store/modules/index/actions'; -import { mapToScopesViewModel } from '~/feature_flags/store/modules/helpers'; -import state from '~/feature_flags/store/modules/index/state'; -import * as types from '~/feature_flags/store/modules/index/mutation_types'; +} from '~/feature_flags/store/index/actions'; +import { mapToScopesViewModel } from '~/feature_flags/store/helpers'; +import state from '~/feature_flags/store/index/state'; +import * as types from '~/feature_flags/store/index/mutation_types'; import axios from '~/lib/utils/axios_utils'; import { getRequestData, rotateData, featureFlag, userList } from '../../mock_data'; @@ -39,20 +36,7 @@ describe('Feature flags actions', () => { let mockedState; beforeEach(() => { - mockedState = state(); - }); - - describe('setFeatureFlagsEndpoint', () => { - it('should commit SET_FEATURE_FLAGS_ENDPOINT mutation', done => { - testAction( - setFeatureFlagsEndpoint, - 'feature_flags.json', - mockedState, - [{ type: types.SET_FEATURE_FLAGS_ENDPOINT, payload: 'feature_flags.json' }], - [], - done, - ); - }); + mockedState = state({}); }); describe('setFeatureFlagsOptions', () => { @@ -68,32 +52,6 @@ describe('Feature flags actions', () => { }); }); - describe('setInstanceIdEndpoint', () => { - it('should commit SET_INSTANCE_ID_ENDPOINT mutation', done => { - testAction( - setInstanceIdEndpoint, - 'instance_id.json', - mockedState, - [{ type: types.SET_INSTANCE_ID_ENDPOINT, payload: 'instance_id.json' }], - [], - done, - ); - }); - }); - - describe('setInstanceId', () => { - it('should commit SET_INSTANCE_ID mutation', done => { - testAction( - setInstanceId, - 'test_instance_id', - mockedState, - [{ type: types.SET_INSTANCE_ID, payload: 'test_instance_id' }], - [], - done, - ); - }); - }); - describe('fetchFeatureFlags', () => { let mock; diff --git a/spec/frontend/feature_flags/store/index/mutations_spec.js b/spec/frontend/feature_flags/store/index/mutations_spec.js index 5e236fe2222..376c7b069fa 100644 --- a/spec/frontend/feature_flags/store/index/mutations_spec.js +++ b/spec/frontend/feature_flags/store/index/mutations_spec.js @@ -1,7 +1,7 @@ -import state from '~/feature_flags/store/modules/index/state'; -import mutations from '~/feature_flags/store/modules/index/mutations'; -import * as types from '~/feature_flags/store/modules/index/mutation_types'; -import { mapToScopesViewModel } from '~/feature_flags/store/modules/helpers'; +import state from '~/feature_flags/store/index/state'; +import mutations from '~/feature_flags/store/index/mutations'; +import * as types from '~/feature_flags/store/index/mutation_types'; +import { mapToScopesViewModel } from '~/feature_flags/store/helpers'; import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; import { getRequestData, rotateData, featureFlag, userList } from '../../mock_data'; @@ -9,15 +9,7 @@ describe('Feature flags store Mutations', () => { let stateCopy; beforeEach(() => { - stateCopy = state(); - }); - - describe('SET_FEATURE_FLAGS_ENDPOINT', () => { - it('should set endpoint', () => { - mutations[types.SET_FEATURE_FLAGS_ENDPOINT](stateCopy, 'feature_flags.json'); - - expect(stateCopy.endpoint).toEqual('feature_flags.json'); - }); + stateCopy = state({}); }); describe('SET_FEATURE_FLAGS_OPTIONS', () => { @@ -27,23 +19,6 @@ describe('Feature flags store Mutations', () => { expect(stateCopy.options).toEqual({ page: '1', scope: 'all' }); }); }); - - describe('SET_INSTANCE_ID_ENDPOINT', () => { - it('should set provided endpoint', () => { - mutations[types.SET_INSTANCE_ID_ENDPOINT](stateCopy, 'rotate_token.json'); - - expect(stateCopy.rotateEndpoint).toEqual('rotate_token.json'); - }); - }); - - describe('SET_INSTANCE_ID', () => { - it('should set provided token', () => { - mutations[types.SET_INSTANCE_ID](stateCopy, rotateData.token); - - expect(stateCopy.instanceId).toEqual(rotateData.token); - }); - }); - describe('REQUEST_FEATURE_FLAGS', () => { it('should set isLoading to true', () => { mutations[types.REQUEST_FEATURE_FLAGS](stateCopy); diff --git a/spec/frontend/feature_flags/store/new/actions_spec.js b/spec/frontend/feature_flags/store/new/actions_spec.js index cfcddd9451f..130c5235aa0 100644 --- a/spec/frontend/feature_flags/store/new/actions_spec.js +++ b/spec/frontend/feature_flags/store/new/actions_spec.js @@ -2,25 +2,20 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; import { TEST_HOST } from 'spec/test_constants'; import { - setEndpoint, - setPath, createFeatureFlag, requestCreateFeatureFlag, receiveCreateFeatureFlagSuccess, receiveCreateFeatureFlagError, -} from '~/feature_flags/store/modules/new/actions'; -import state from '~/feature_flags/store/modules/new/state'; -import * as types from '~/feature_flags/store/modules/new/mutation_types'; +} from '~/feature_flags/store/new/actions'; +import state from '~/feature_flags/store/new/state'; +import * as types from '~/feature_flags/store/new/mutation_types'; import { ROLLOUT_STRATEGY_ALL_USERS, ROLLOUT_STRATEGY_PERCENT_ROLLOUT, LEGACY_FLAG, NEW_VERSION_FLAG, } from '~/feature_flags/constants'; -import { - mapFromScopesViewModel, - mapStrategiesToRails, -} from '~/feature_flags/store/modules/helpers'; +import { mapFromScopesViewModel, mapStrategiesToRails } from '~/feature_flags/store/helpers'; import axios from '~/lib/utils/axios_utils'; jest.mock('~/lib/utils/url_utility'); @@ -29,33 +24,7 @@ describe('Feature flags New Module Actions', () => { let mockedState; beforeEach(() => { - mockedState = state(); - }); - - describe('setEndpoint', () => { - it('should commit SET_ENDPOINT mutation', done => { - testAction( - setEndpoint, - 'feature_flags.json', - mockedState, - [{ type: types.SET_ENDPOINT, payload: 'feature_flags.json' }], - [], - done, - ); - }); - }); - - describe('setPath', () => { - it('should commit SET_PATH mutation', done => { - testAction( - setPath, - '/feature_flags', - mockedState, - [{ type: types.SET_PATH, payload: '/feature_flags' }], - [], - done, - ); - }); + mockedState = state({ endpoint: 'feature_flags.json', path: '/feature_flags' }); }); describe('createFeatureFlag', () => { diff --git a/spec/frontend/feature_flags/store/new/mutations_spec.js b/spec/frontend/feature_flags/store/new/mutations_spec.js index 95eba96ed72..e8609a6d116 100644 --- a/spec/frontend/feature_flags/store/new/mutations_spec.js +++ b/spec/frontend/feature_flags/store/new/mutations_spec.js @@ -1,28 +1,12 @@ -import state from '~/feature_flags/store/modules/new/state'; -import mutations from '~/feature_flags/store/modules/new/mutations'; -import * as types from '~/feature_flags/store/modules/new/mutation_types'; +import state from '~/feature_flags/store/new/state'; +import mutations from '~/feature_flags/store/new/mutations'; +import * as types from '~/feature_flags/store/new/mutation_types'; describe('Feature flags New Module Mutations', () => { let stateCopy; beforeEach(() => { - stateCopy = state(); - }); - - describe('SET_ENDPOINT', () => { - it('should set endpoint', () => { - mutations[types.SET_ENDPOINT](stateCopy, 'feature_flags.json'); - - expect(stateCopy.endpoint).toEqual('feature_flags.json'); - }); - }); - - describe('SET_PATH', () => { - it('should set provided options', () => { - mutations[types.SET_PATH](stateCopy, 'feature_flags'); - - expect(stateCopy.path).toEqual('feature_flags'); - }); + stateCopy = state({ endpoint: 'feature_flags.json', path: 'feature_flags' }); }); describe('REQUEST_CREATE_FEATURE_FLAG', () => { diff --git a/spec/frontend/vue_shared/components/__snapshots__/editor_lite_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/editor_lite_spec.js.snap new file mode 100644 index 00000000000..e7ccecf5f61 --- /dev/null +++ b/spec/frontend/vue_shared/components/__snapshots__/editor_lite_spec.js.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Editor Lite component rendering matches the snapshot 1`] = ` +<div + class="file-content code" +> + <div + data-editor-loading="" + id="editor" + > + <pre + class="editor-loading-content" + > + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + </pre> + </div> +</div> +`; diff --git a/spec/frontend/vue_shared/components/editor_lite_spec.js b/spec/frontend/vue_shared/components/editor_lite_spec.js new file mode 100644 index 00000000000..514c1526e2e --- /dev/null +++ b/spec/frontend/vue_shared/components/editor_lite_spec.js @@ -0,0 +1,118 @@ +import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import EditorLite from '~/vue_shared/components/editor_lite.vue'; +import Editor from '~/editor/editor_lite'; + +jest.mock('~/editor/editor_lite'); + +describe('Editor Lite component', () => { + let wrapper; + const onDidChangeModelContent = jest.fn(); + const updateModelLanguage = jest.fn(); + const getValue = jest.fn(); + const value = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; + const fileName = 'lorem.txt'; + const fileGlobalId = 'snippet_777'; + const createInstanceMock = jest.fn().mockImplementation(() => ({ + onDidChangeModelContent, + updateModelLanguage, + getValue, + dispose: jest.fn(), + })); + Editor.mockImplementation(() => { + return { + createInstance: createInstanceMock, + }; + }); + function createComponent(props = {}) { + wrapper = shallowMount(EditorLite, { + propsData: { + value, + fileName, + fileGlobalId, + ...props, + }, + }); + } + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const triggerChangeContent = val => { + getValue.mockReturnValue(val); + const [cb] = onDidChangeModelContent.mock.calls[0]; + + cb(); + + jest.runOnlyPendingTimers(); + }; + + describe('rendering', () => { + it('matches the snapshot', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + it('renders content', () => { + expect(wrapper.text()).toContain(value); + }); + }); + + describe('functionality', () => { + it('does not fail without content', () => { + const spy = jest.spyOn(global.console, 'error'); + createComponent({ value: undefined }); + + expect(spy).not.toHaveBeenCalled(); + expect(wrapper.find('#editor').exists()).toBe(true); + }); + + it('initialises Editor Lite instance', () => { + const el = wrapper.find({ ref: 'editor' }).element; + expect(createInstanceMock).toHaveBeenCalledWith({ + el, + blobPath: fileName, + blobGlobalId: fileGlobalId, + blobContent: value, + extensions: null, + }); + }); + + it('reacts to the changes in fileName', () => { + const newFileName = 'ipsum.txt'; + + wrapper.setProps({ + fileName: newFileName, + }); + + return nextTick().then(() => { + expect(updateModelLanguage).toHaveBeenCalledWith(newFileName); + }); + }); + + it('registers callback with editor onChangeContent', () => { + expect(onDidChangeModelContent).toHaveBeenCalledWith(expect.any(Function)); + }); + + it('emits input event when the blob content is changed', () => { + expect(wrapper.emitted().input).toBeUndefined(); + + triggerChangeContent(value); + + expect(wrapper.emitted().input).toEqual([[value]]); + }); + + it('emits editor-ready event when the Editor Lite is ready', async () => { + const el = wrapper.find({ ref: 'editor' }).element; + expect(wrapper.emitted()['editor-ready']).toBeUndefined(); + + await el.dispatchEvent(new Event('editor-ready')); + + expect(wrapper.emitted()['editor-ready']).toBeDefined(); + }); + }); +}); diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index 1fffef53a82..af6146ea93c 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -135,6 +135,17 @@ RSpec.describe Gitlab::Middleware::Go do it_behaves_like 'unauthorized' end + + context 'with a blacklisted ip' do + it 'returns forbidden' do + expect(Gitlab::Auth).to receive(:find_for_git_client).and_raise(Gitlab::Auth::IpBlacklisted) + response = go + + expect(response[0]).to eq(403) + expect(response[1]['Content-Length']).to eq('0') + expect(response[2].body).to eq(['']) + end + end end end end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 27b7394cc15..ab83a39bdc9 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -587,8 +587,17 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do end describe '.system_usage_data_monthly' do + let_it_be(:project) { create(:project) } let!(:ud) { build(:usage_data) } + before do + stub_application_setting(self_monitoring_project: project) + + for_defined_days_back do + create(:product_analytics_event, project: project, se_category: 'epics', se_action: 'promote') + end + end + subject { described_class.system_usage_data_monthly } it 'gathers monthly usage counts correctly' do @@ -601,6 +610,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do expect(counts_monthly[:personal_snippets]).to eq(1) expect(counts_monthly[:project_snippets]).to eq(2) expect(counts_monthly[:packages]).to eq(3) + expect(counts_monthly[:promoted_issues]).to eq(1) end end diff --git a/spec/models/namespace_setting_spec.rb b/spec/models/namespace_setting_spec.rb index 257d78dfa2c..cee0138e41d 100644 --- a/spec/models/namespace_setting_spec.rb +++ b/spec/models/namespace_setting_spec.rb @@ -3,5 +3,45 @@ require 'spec_helper' RSpec.describe NamespaceSetting, type: :model do + # Relationships + # it { is_expected.to belong_to(:namespace) } + + describe "validations" do + describe "#default_branch_name_content" do + let_it_be(:group) { create(:group) } + + let(:namespace_settings) { group.namespace_settings } + + shared_examples "doesn't return an error" do + it "doesn't return an error" do + expect(namespace_settings.valid?).to be_truthy + expect(namespace_settings.errors.full_messages).to be_empty + end + end + + context "when not set" do + it_behaves_like "doesn't return an error" + end + + context "when set" do + before do + namespace_settings.default_branch_name = "example_branch_name" + end + + it_behaves_like "doesn't return an error" + end + + context "when an empty string" do + before do + namespace_settings.default_branch_name = '' + end + + it "returns an error" do + expect(namespace_settings.valid?).to be_falsey + expect(namespace_settings.errors.full_messages).not_to be_empty + end + end + end + end end diff --git a/spec/services/namespace_settings/update_service_spec.rb b/spec/services/namespace_settings/update_service_spec.rb index 82e2480e03b..b588bf2034d 100644 --- a/spec/services/namespace_settings/update_service_spec.rb +++ b/spec/services/namespace_settings/update_service_spec.rb @@ -33,5 +33,16 @@ RSpec.describe NamespaceSettings::UpdateService do end.not_to change { NamespaceSetting.count } end end + + context "updating :default_branch_name" do + let(:example_branch_name) { "example_branch_name" } + let(:settings) { { default_branch_name: example_branch_name } } + + it "changes settings" do + expect { service.execute } + .to change { group.namespace_settings.default_branch_name } + .from(nil).to(example_branch_name) + end + end end end diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb index e58cbad726c..27c832d88c6 100644 --- a/spec/support/shared_contexts/navbar_structure_context.rb +++ b/spec/support/shared_contexts/navbar_structure_context.rb @@ -65,6 +65,7 @@ RSpec.shared_context 'project navbar structure' do nav_sub_items: [ _('Metrics'), _('Logs'), + _('Tracing'), _('Error Tracking'), _('Alerts'), _('Incidents'), diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb index bf5b5785b8d..3dda971ae0e 100644 --- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'layouts/nav/sidebar/_project' do - let(:project) { create(:project, :repository) } + let_it_be_with_reload(:project) { create(:project, :repository) } before do assign(:project, project) @@ -246,6 +246,30 @@ RSpec.describe 'layouts/nav/sidebar/_project' do end end + describe 'Tracing' do + it 'is not visible to unauthorized user' do + allow(view).to receive(:can?).and_return(false) + + render + + expect(rendered).not_to have_text 'Tracing' + end + + it 'links to Tracing page' do + render + + expect(rendered).to have_link('Tracing', href: project_tracing_path(project)) + end + + context 'without project.tracing_external_url' do + it 'links to Tracing page' do + render + + expect(rendered).to have_link('Tracing', href: project_tracing_path(project)) + end + end + end + describe 'Alert Management' do it 'shows the Alerts sidebar entry' do render diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb index b4d20da0a5c..24ab64b20f5 100644 --- a/spec/views/projects/settings/operations/show.html.haml_spec.rb +++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb @@ -6,37 +6,85 @@ RSpec.describe 'projects/settings/operations/show' do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } + let_it_be(:error_tracking_setting) do + create(:project_error_tracking_setting, project: project) + end + + let_it_be_with_reload(:tracing_setting) do + create(:project_tracing_setting, project: project) + end + + let_it_be(:prometheus_service) { create(:prometheus_service, project: project) } + let_it_be(:alerts_service) { create(:alerts_service, project: project) } + let(:operations_show_locals) do { - prometheus_service: project.find_or_initialize_service('prometheus'), - alerts_service: project.find_or_initialize_service('alerts') + prometheus_service: prometheus_service, + alerts_service: alerts_service } end + before_all do + project.add_reporter(user) + end + before do assign :project, project + + allow(view).to receive(:error_tracking_setting) + .and_return(error_tracking_setting) + allow(view).to receive(:tracing_setting) + .and_return(tracing_setting) + allow(view).to receive(:current_user).and_return(user) end describe 'Operations > Error Tracking' do - before do - project.add_reporter(user) + context 'Settings page ' do + it 'renders the Operations Settings page' do + render template: 'projects/settings/operations/show', locals: operations_show_locals - allow(view).to receive(:error_tracking_setting) - .and_return(error_tracking_setting) - allow(view).to receive(:current_user).and_return(user) - allow(view).to receive(:incident_management_available?) { false } + expect(rendered).to have_content _('Error tracking') + expect(rendered).to have_content _('To link Sentry to GitLab, enter your Sentry URL and Auth Token') + end end + end - let_it_be(:error_tracking_setting) do - create(:project_error_tracking_setting, project: project) + describe 'Operations > Tracing' do + context 'with project.tracing_external_url' do + it 'links to project.tracing_external_url' do + render template: 'projects/settings/operations/show', locals: operations_show_locals + + expect(rendered).to have_link('Tracing', href: tracing_setting.external_url) + end + + context 'with malicious external_url' do + let(:malicious_tracing_url) { "https://replaceme.com/'><script>alert(document.cookie)</script>" } + let(:cleaned_url) { "https://replaceme.com/'>" } + + before do + tracing_setting.update_column(:external_url, malicious_tracing_url) + end + + it 'sanitizes external_url' do + render template: 'projects/settings/operations/show', locals: operations_show_locals + + expect(tracing_setting.external_url).to eq(malicious_tracing_url) + expect(rendered).to have_link('Tracing', href: cleaned_url) + end + end end - context 'Settings page ' do - it 'renders the Operations Settings page' do + context 'without project.tracing_external_url' do + let(:tracing_setting) { build(:project_tracing_setting, project: project) } + + before do + tracing_setting.external_url = nil + end + + it 'links to Tracing page' do render template: 'projects/settings/operations/show', locals: operations_show_locals - expect(rendered).to have_content _('Error tracking') - expect(rendered).to have_content _('To link Sentry to GitLab, enter your Sentry URL and Auth Token') + expect(rendered).to have_link('Tracing', href: project_tracing_path(project)) end end end diff --git a/vendor/assets/stylesheets/cropper.css b/vendor/assets/stylesheets/cropper.css deleted file mode 100644 index 8668c7c049a..00000000000 --- a/vendor/assets/stylesheets/cropper.css +++ /dev/null @@ -1,379 +0,0 @@ -/*! - * Cropper v2.3.0 - * https://github.com/fengyuanchen/cropper - * - * Copyright (c) 2014-2016 Fengyuan Chen and contributors - * Released under the MIT license - * - * Date: 2016-02-22T02:13:13.332Z - */ -.cropper-container { - font-size: 0; - line-height: 0; - - position: relative; - - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - - direction: ltr !important; - -ms-touch-action: none; - touch-action: none; - -webkit-tap-highlight-color: transparent; - -webkit-touch-callout: none; -} - -.cropper-container img { - display: block; - - width: 100%; - min-width: 0 !important; - max-width: none !important; - height: 100%; - min-height: 0 !important; - max-height: none !important; - - image-orientation: 0deg !important; -} - -.cropper-wrap-box, -.cropper-canvas, -.cropper-drag-box, -.cropper-crop-box, -.cropper-modal { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; -} - -.cropper-wrap-box { - overflow: hidden; -} - -.cropper-drag-box { - opacity: 0; - background-color: #fff; - - filter: alpha(opacity=0); -} - -.cropper-modal { - opacity: .5; - background-color: #000; - - filter: alpha(opacity=50); -} - -.cropper-view-box { - display: block; - overflow: hidden; - - width: 100%; - height: 100%; - - outline: 1px solid #39f; - outline-color: rgba(51, 153, 255, .75); -} - -.cropper-dashed { - position: absolute; - - display: block; - - opacity: .5; - border: 0 dashed #eee; - - filter: alpha(opacity=50); -} - -.cropper-dashed.dashed-h { - top: 33.33333%; - left: 0; - - width: 100%; - height: 33.33333%; - - border-top-width: 1px; - border-bottom-width: 1px; -} - -.cropper-dashed.dashed-v { - top: 0; - left: 33.33333%; - - width: 33.33333%; - height: 100%; - - border-right-width: 1px; - border-left-width: 1px; -} - -.cropper-center { - position: absolute; - top: 50%; - left: 50%; - - display: block; - - width: 0; - height: 0; - - opacity: .75; - - filter: alpha(opacity=75); -} - -.cropper-center:before, -.cropper-center:after { - position: absolute; - - display: block; - - content: ' '; - - background-color: #eee; -} - -.cropper-center:before { - top: 0; - left: -3px; - - width: 7px; - height: 1px; -} - -.cropper-center:after { - top: -3px; - left: 0; - - width: 1px; - height: 7px; -} - -.cropper-face, -.cropper-line, -.cropper-point { - position: absolute; - - display: block; - - width: 100%; - height: 100%; - - opacity: .1; - - filter: alpha(opacity=10); -} - -.cropper-face { - top: 0; - left: 0; - - background-color: #fff; -} - -.cropper-line { - background-color: #39f; -} - -.cropper-line.line-e { - top: 0; - right: -3px; - - width: 5px; - - cursor: e-resize; -} - -.cropper-line.line-n { - top: -3px; - left: 0; - - height: 5px; - - cursor: n-resize; -} - -.cropper-line.line-w { - top: 0; - left: -3px; - - width: 5px; - - cursor: w-resize; -} - -.cropper-line.line-s { - bottom: -3px; - left: 0; - - height: 5px; - - cursor: s-resize; -} - -.cropper-point { - width: 5px; - height: 5px; - - opacity: .75; - background-color: #39f; - - filter: alpha(opacity=75); -} - -.cropper-point.point-e { - top: 50%; - right: -3px; - - margin-top: -3px; - - cursor: e-resize; -} - -.cropper-point.point-n { - top: -3px; - left: 50%; - - margin-left: -3px; - - cursor: n-resize; -} - -.cropper-point.point-w { - top: 50%; - left: -3px; - - margin-top: -3px; - - cursor: w-resize; -} - -.cropper-point.point-s { - bottom: -3px; - left: 50%; - - margin-left: -3px; - - cursor: s-resize; -} - -.cropper-point.point-ne { - top: -3px; - right: -3px; - - cursor: ne-resize; -} - -.cropper-point.point-nw { - top: -3px; - left: -3px; - - cursor: nw-resize; -} - -.cropper-point.point-sw { - bottom: -3px; - left: -3px; - - cursor: sw-resize; -} - -.cropper-point.point-se { - right: -3px; - bottom: -3px; - - width: 20px; - height: 20px; - - cursor: se-resize; - - opacity: 1; - - filter: alpha(opacity=100); -} - -.cropper-point.point-se:before { - position: absolute; - right: -50%; - bottom: -50%; - - display: block; - - width: 200%; - height: 200%; - - content: ' '; - - opacity: 0; - background-color: #39f; - - filter: alpha(opacity=0); -} - -@media (min-width: 768px) { - .cropper-point.point-se { - width: 15px; - height: 15px; - } -} - -@media (min-width: 992px) { - .cropper-point.point-se { - width: 10px; - height: 10px; - } -} - -@media (min-width: 1200px) { - .cropper-point.point-se { - width: 5px; - height: 5px; - - opacity: .75; - - filter: alpha(opacity=75); - } -} - -.cropper-invisible { - opacity: 0; - - filter: alpha(opacity=0); -} - -.cropper-bg { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC'); -} - -.cropper-hide { - position: absolute; - - display: block; - - width: 0; - height: 0; -} - -.cropper-hidden { - display: none !important; -} - -.cropper-move { - cursor: move; -} - -.cropper-crop { - cursor: crosshair; -} - -.cropper-disabled .cropper-drag-box, -.cropper-disabled .cropper-face, -.cropper-disabled .cropper-line, -.cropper-disabled .cropper-point { - cursor: not-allowed; -} |