Commit a8fe17de authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 6078-add-permissions-checks-dismiss-issue

* master: (144 commits)
  resolve conflict in app/assets/javascripts/boards/components/sidebar/remove_issue.vue
  Resolve conflicts in app/assets/javascripts/boards/components/board_sidebar.js
  Resolve conflicts in config/sidekiq_queues.yml
  Resolve conflicts in app/workers/all_queues.yml
  Resolve conflicts in .gitignore
  Fix conflict on saml.md
  fix conflict
  Rails5 fix NoMethodError: undefined method `join' for "":String
  Add CHANGELOG
  Improve shelling out in bin/changelog
  Fix sorting by name on explore projects page
  Do not style alert links that mimic buttons
  Resolve "100% CPU for webpack-dev-server running in GDK"
  Add back copy for existing gcp accounts within offer banner
  Resolve "Introduce hover, active, focus states for files in sidebar of Web IDE"
  Adds a status prop to report_issues.vue and passes it through to modal.vue
  Bring changes from EE
  Adds a `.modal--hide-footer` class to the security modal so we can conditionally hide it with CSS
  Adds the changelog entry for MR!6169
  Adds a "resolved" key to resolved vulnerabilities and conditionally displays action button based on that key
  ...
parents 21f1b16e c488e07b
...@@ -29,8 +29,7 @@ eslint-report.html ...@@ -29,8 +29,7 @@ eslint-report.html
/app/assets/javascripts/locale/**/app.js /app/assets/javascripts/locale/**/app.js
/backups/* /backups/*
/config/aws.yml /config/aws.yml
/config/database.yml /config/database*.yml
/config/database_geo.yml
/config/gitlab.yml /config/gitlab.yml
/config/gitlab_ci.yml /config/gitlab_ci.yml
/config/initializers/rack_attack.rb /config/initializers/rack_attack.rb
......
...@@ -308,18 +308,6 @@ stages: ...@@ -308,18 +308,6 @@ stages:
paths: paths:
- log/development.log - log/development.log
# Review docs base
.review-docs: &review-docs
<<: *dedicated-runner
<<: *except-qa
<<: *single-script-job
variables:
<<: *single-script-job-variables
SCRIPT_NAME: trigger-build-docs
when: manual
only:
- branches
# DB migration, rollback, and seed jobs # DB migration, rollback, and seed jobs
.db-migrate-reset: &db-migrate-reset .db-migrate-reset: &db-migrate-reset
<<: *dedicated-no-docs-and-no-qa-pull-cache-job <<: *dedicated-no-docs-and-no-qa-pull-cache-job
...@@ -379,20 +367,44 @@ package-and-qa: ...@@ -379,20 +367,44 @@ package-and-qa:
- //@gitlab-org/gitlab-ce - //@gitlab-org/gitlab-ce
- //@gitlab-org/gitlab-ee - //@gitlab-org/gitlab-ee
# Trigger a docs build in gitlab-docs # Review docs base
# Useful to preview the docs changes live .review-docs: &review-docs
review-docs-deploy: <<: *dedicated-runner
<<: *review-docs <<: *single-script-job
stage: build variables:
<<: *single-script-job-variables
SCRIPT_NAME: trigger-build-docs
environment: environment:
name: review-docs/$CI_COMMIT_REF_NAME name: review-docs/$CI_COMMIT_REF_NAME
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables # DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables
# Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693 # Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
url: http://$DOCS_GITLAB_REPO_SUFFIX-$CI_COMMIT_REF_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX url: http://$DOCS_GITLAB_REPO_SUFFIX-$CI_ENVIRONMENT_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
on_stop: review-docs-cleanup on_stop: review-docs-cleanup
# Trigger a manual docs build in gitlab-docs only on non docs-only branches.
# Useful to preview the docs changes live.
review-docs-deploy-manual:
<<: *review-docs
stage: build
script:
- gem install gitlab --no-ri --no-rdoc
- ./$SCRIPT_NAME deploy
when: manual
only:
- branches
<<: *except-docs-and-qa
# Always trigger a docs build in gitlab-docs only on docs-only branches.
# Useful to preview the docs changes live.
review-docs-deploy:
<<: *review-docs
stage: post-test
script: script:
- gem install gitlab --no-ri --no-rdoc - gem install gitlab --no-ri --no-rdoc
- ./$SCRIPT_NAME deploy - ./$SCRIPT_NAME deploy
only:
- /(^docs[\/-].*|.*-docs$)/
<<: *except-qa
# Cleanup remote environment of gitlab-docs # Cleanup remote environment of gitlab-docs
review-docs-cleanup: review-docs-cleanup:
...@@ -401,9 +413,10 @@ review-docs-cleanup: ...@@ -401,9 +413,10 @@ review-docs-cleanup:
environment: environment:
name: review-docs/$CI_COMMIT_REF_NAME name: review-docs/$CI_COMMIT_REF_NAME
action: stop action: stop
when: manual
script: script:
- gem install gitlab --no-ri --no-rdoc - gem install gitlab --no-ri --no-rdoc
- ./SCRIPT_NAME cleanup - ./$SCRIPT_NAME cleanup
## ##
# Trigger a docker image build in CNG (Cloud Native GitLab) repository # Trigger a docker image build in CNG (Cloud Native GitLab) repository
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 11.0.1 (2018-06-21)
- No changes.
## 11.0.0 (2018-06-22) ## 11.0.0 (2018-06-22)
### Security (2 changes) ### Security (2 changes)
...@@ -71,6 +75,10 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -71,6 +75,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Allow viewing only one when multiple issue boards is not enabled. - Allow viewing only one when multiple issue boards is not enabled.
## 10.8.5 (2018-06-21)
- No changes.
## 10.8.4 (2018-06-06) ## 10.8.4 (2018-06-06)
### Fixed (4 changes) ### Fixed (4 changes)
...@@ -191,6 +199,10 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -191,6 +199,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Remove `features/group_active_tab.feature`. !5554 (@blackst0ne) - Remove `features/group_active_tab.feature`. !5554 (@blackst0ne)
## 10.7.6 (2018-06-21)
- No changes.
## 10.7.5 (2018-05-28) ## 10.7.5 (2018-05-28)
### Security (3 changes) ### Security (3 changes)
......
...@@ -2,6 +2,17 @@ ...@@ -2,6 +2,17 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 11.0.1 (2018-06-21)
### Security (5 changes)
- Fix XSS vulnerability for table of content generation.
- Update sanitize gem to 4.6.5 to fix HTML injection vulnerability.
- HTML escape branch name in project graphs page.
- HTML escape the name of the user in ProjectsHelper#link_to_member.
- Don't show events from internal projects for anonymous users in public feed.
## 11.0.0 (2018-06-22) ## 11.0.0 (2018-06-22)
### Security (3 changes) ### Security (3 changes)
...@@ -242,6 +253,17 @@ entry. ...@@ -242,6 +253,17 @@ entry.
- Workhorse to send raw diff and patch for commits. - Workhorse to send raw diff and patch for commits.
## 10.8.5 (2018-06-21)
### Security (5 changes)
- Fix XSS vulnerability for table of content generation.
- Update sanitize gem to 4.6.5 to fix HTML injection vulnerability.
- HTML escape branch name in project graphs page.
- HTML escape the name of the user in ProjectsHelper#link_to_member.
- Don't show events from internal projects for anonymous users in public feed.
## 10.8.4 (2018-06-06) ## 10.8.4 (2018-06-06)
- No changes. - No changes.
...@@ -460,6 +482,22 @@ entry. ...@@ -460,6 +482,22 @@ entry.
- Gitaly handles repository forks by default. - Gitaly handles repository forks by default.
## 10.7.6 (2018-06-21)
### Security (6 changes)
- Fix XSS vulnerability for table of content generation.
- Update sanitize gem to 4.6.5 to fix HTML injection vulnerability.
- HTML escape branch name in project graphs page.
- HTML escape the name of the user in ProjectsHelper#link_to_member.
- Don't show events from internal projects for anonymous users in public feed.
- XSS fix to use safe_params instead of params in url_for helpers.
### Other (1 change)
- Replacing gollum libraries for gitlab custom libs. !18343
## 10.7.5 (2018-05-28) ## 10.7.5 (2018-05-28)
### Security (3 changes) ### Security (3 changes)
......
...@@ -240,7 +240,7 @@ gem 'ruby-fogbugz', '~> 0.2.1' ...@@ -240,7 +240,7 @@ gem 'ruby-fogbugz', '~> 0.2.1'
gem 'kubeclient', '~> 3.1.0' gem 'kubeclient', '~> 3.1.0'
# Sanitize user input # Sanitize user input
gem 'sanitize', '~> 2.0' gem 'sanitize', '~> 4.6.5'
gem 'babosa', '~> 1.0.2' gem 'babosa', '~> 1.0.2'
# Sanitizes SVG input # Sanitizes SVG input
......
...@@ -319,13 +319,13 @@ GEM ...@@ -319,13 +319,13 @@ GEM
flowdock (~> 0.7) flowdock (~> 0.7)
gitlab-grit (>= 2.4.1) gitlab-grit (>= 2.4.1)
multi_json multi_json
gitlab-gollum-lib (4.2.7.4) gitlab-gollum-lib (4.2.7.5)
gemojione (~> 3.2) gemojione (~> 3.2)
github-markup (~> 1.6) github-markup (~> 1.6)
gollum-grit_adapter (~> 1.0) gollum-grit_adapter (~> 1.0)
nokogiri (>= 1.6.1, < 2.0) nokogiri (>= 1.6.1, < 2.0)
rouge (~> 3.1) rouge (~> 3.1)
sanitize (~> 2.1) sanitize (~> 4.6.4)
stringex (~> 2.6) stringex (~> 2.6)
gitlab-gollum-rugged_adapter (0.4.4.1) gitlab-gollum-rugged_adapter (0.4.4.1)
mime-types (>= 1.15) mime-types (>= 1.15)
...@@ -543,6 +543,8 @@ GEM ...@@ -543,6 +543,8 @@ GEM
netrc (0.11.0) netrc (0.11.0)
nokogiri (1.8.2) nokogiri (1.8.2)
mini_portile2 (~> 2.3.0) mini_portile2 (~> 2.3.0)
nokogumbo (1.5.0)
nokogiri
numerizer (0.1.1) numerizer (0.1.1)
oauth (0.5.4) oauth (0.5.4)
oauth2 (1.4.0) oauth2 (1.4.0)
...@@ -833,8 +835,10 @@ GEM ...@@ -833,8 +835,10 @@ GEM
et-orbi (~> 1.0) et-orbi (~> 1.0)
rugged (0.27.2) rugged (0.27.2)
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (2.1.0) sanitize (4.6.5)
crass (~> 1.0.2)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
nokogumbo (~> 1.4)
sass (3.5.5) sass (3.5.5)
sass-listen (~> 4.0.0) sass-listen (~> 4.0.0)
sass-listen (4.0.0) sass-listen (4.0.0)
...@@ -1188,7 +1192,7 @@ DEPENDENCIES ...@@ -1188,7 +1192,7 @@ DEPENDENCIES
ruby_parser (~> 3.8) ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4) rufus-scheduler (~> 3.4)
rugged (~> 0.27) rugged (~> 0.27)
sanitize (~> 2.0) sanitize (~> 4.6.5)
sass-rails (~> 5.0.6) sass-rails (~> 5.0.6)
scss_lint (~> 0.56.0) scss_lint (~> 0.56.0)
seed-fu (~> 2.3.7) seed-fu (~> 2.3.7)
......
...@@ -322,13 +322,13 @@ GEM ...@@ -322,13 +322,13 @@ GEM
flowdock (~> 0.7) flowdock (~> 0.7)
gitlab-grit (>= 2.4.1) gitlab-grit (>= 2.4.1)
multi_json multi_json
gitlab-gollum-lib (4.2.7.4) gitlab-gollum-lib (4.2.7.5)
gemojione (~> 3.2) gemojione (~> 3.2)
github-markup (~> 1.6) github-markup (~> 1.6)
gollum-grit_adapter (~> 1.0) gollum-grit_adapter (~> 1.0)
nokogiri (>= 1.6.1, < 2.0) nokogiri (>= 1.6.1, < 2.0)
rouge (~> 3.1) rouge (~> 3.1)
sanitize (~> 2.1) sanitize (~> 4.6.4)
stringex (~> 2.6) stringex (~> 2.6)
gitlab-gollum-rugged_adapter (0.4.4.1) gitlab-gollum-rugged_adapter (0.4.4.1)
mime-types (>= 1.15) mime-types (>= 1.15)
...@@ -547,6 +547,8 @@ GEM ...@@ -547,6 +547,8 @@ GEM
nio4r (2.3.1) nio4r (2.3.1)
nokogiri (1.8.2) nokogiri (1.8.2)
mini_portile2 (~> 2.3.0) mini_portile2 (~> 2.3.0)
nokogumbo (1.5.0)
nokogiri
numerizer (0.1.1) numerizer (0.1.1)
oauth (0.5.4) oauth (0.5.4)
oauth2 (1.4.0) oauth2 (1.4.0)
...@@ -842,8 +844,10 @@ GEM ...@@ -842,8 +844,10 @@ GEM
et-orbi (~> 1.0) et-orbi (~> 1.0)
rugged (0.27.1) rugged (0.27.1)
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (2.1.0) sanitize (4.6.5)
crass (~> 1.0.2)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
nokogumbo (~> 1.4)
sass (3.5.5) sass (3.5.5)
sass-listen (~> 4.0.0) sass-listen (~> 4.0.0)
sass-listen (4.0.0) sass-listen (4.0.0)
...@@ -1199,7 +1203,7 @@ DEPENDENCIES ...@@ -1199,7 +1203,7 @@ DEPENDENCIES
ruby_parser (~> 3.8) ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4) rufus-scheduler (~> 3.4)
rugged (~> 0.27) rugged (~> 0.27)
sanitize (~> 2.0) sanitize (~> 4.6.5)
sass-rails (~> 5.0.6) sass-rails (~> 5.0.6)
scss_lint (~> 0.56.0) scss_lint (~> 0.56.0)
seed-fu (~> 2.3.7) seed-fu (~> 2.3.7)
......
...@@ -2,18 +2,18 @@ ...@@ -2,18 +2,18 @@
import $ from 'jquery'; import $ from 'jquery';
import Vue from 'vue'; import Vue from 'vue';
import weight from 'ee/sidebar/components/weight/weight.vue'; import Weight from 'ee/sidebar/components/weight/weight.vue';
import Flash from '../../flash'; import Flash from '../../flash';
import { __ } from '../../locale'; import { __ } from '../../locale';
import Sidebar from '../../right_sidebar'; import Sidebar from '../../right_sidebar';
import eventHub from '../../sidebar/event_hub'; import eventHub from '../../sidebar/event_hub';
import assigneeTitle from '../../sidebar/components/assignees/assignee_title.vue'; import AssigneeTitle from '../../sidebar/components/assignees/assignee_title.vue';
import assignees from '../../sidebar/components/assignees/assignees.vue'; import Assignees from '../../sidebar/components/assignees/assignees.vue';
import DueDateSelectors from '../../due_date_select'; import DueDateSelectors from '../../due_date_select';
import './sidebar/remove_issue'; import RemoveBtn from './sidebar/remove_issue.vue';
import IssuableContext from '../../issuable_context'; import IssuableContext from '../../issuable_context';
import LabelsSelect from '../../labels_select'; import LabelsSelect from '../../labels_select';
import subscriptions from '../../sidebar/components/subscriptions/subscriptions.vue'; import Subscriptions from '../../sidebar/components/subscriptions/subscriptions.vue';
import MilestoneSelect from '../../milestone_select'; import MilestoneSelect from '../../milestone_select';
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -23,11 +23,11 @@ window.gl.issueBoards = window.gl.issueBoards || {}; ...@@ -23,11 +23,11 @@ window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.BoardSidebar = Vue.extend({ gl.issueBoards.BoardSidebar = Vue.extend({
components: { components: {
assigneeTitle, AssigneeTitle,
assignees, Assignees,
removeBtn: gl.issueBoards.RemoveIssueBtn, RemoveBtn,
subscriptions, Subscriptions,
weight, Weight,
}, },
props: { props: {
currentUser: { currentUser: {
......
import Vue from 'vue'; <script>
import Flash from '../../../flash'; import Flash from '../../../flash';
import { __ } from '../../../locale'; import { __ } from '../../../locale';
import './lists_dropdown'; import ListsDropdown from './lists_dropdown.vue';
import { pluralize } from '../../../lib/utils/text_utility'; import { pluralize } from '../../../lib/utils/text_utility';
import ModalStore from '../../stores/modal_store'; import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins'; import modalMixin from '../../mixins/modal_mixins';
gl.issueBoards.ModalFooter = Vue.extend({ export default {
components: { components: {
'lists-dropdown': gl.issueBoards.ModalFooterListsDropdown, ListsDropdown,
}, },
mixins: [modalMixin], mixins: [modalMixin],
data() { data() {
...@@ -61,28 +61,32 @@ gl.issueBoards.ModalFooter = Vue.extend({ ...@@ -61,28 +61,32 @@ gl.issueBoards.ModalFooter = Vue.extend({
this.toggleModal(false); this.toggleModal(false);
}, },
}, },
template: ` };
<footer </script>
class="form-actions add-issues-footer"> <template>
<div class="float-left"> <footer
<button class="form-actions add-issues-footer"
class="btn btn-success" >
type="button" <div class="float-left">
:disabled="submitDisabled"
@click="addIssues">
{{ submitText }}
</button>
<span class="inline add-issues-footer-to-list">
to list
</span>
<lists-dropdown></lists-dropdown>
</div>
<button <button
class="btn btn-default float-right" :disabled="submitDisabled"
class="btn btn-success"
type="button" type="button"
@click="toggleModal(false)"> @click="addIssues"
Cancel >
{{ submitText }}
</button> </button>
</footer> <span class="inline add-issues-footer-to-list">
`, to list
}); </span>
<lists-dropdown/>
</div>
<button
class="btn btn-default float-right"
type="button"
@click="toggleModal(false)"
>
Cancel
</button>
</footer>
</template>
import Vue from 'vue'; import Vue from 'vue';
import modalFilters from './filters'; import modalFilters from './filters';
import './tabs'; import modalTabs from './tabs.vue';
import ModalStore from '../../stores/modal_store'; import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins'; import modalMixin from '../../mixins/modal_mixins';
gl.issueBoards.ModalHeader = Vue.extend({ gl.issueBoards.ModalHeader = Vue.extend({
components: { components: {
'modal-tabs': gl.issueBoards.ModalTabs, modalTabs,
modalFilters, modalFilters,
}, },
mixins: [modalMixin], mixins: [modalMixin],
......
...@@ -5,7 +5,7 @@ import queryData from '~/boards/utils/query_data'; ...@@ -5,7 +5,7 @@ import queryData from '~/boards/utils/query_data';
import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import './header'; import './header';
import './list'; import './list';
import './footer'; import ModalFooter from './footer.vue';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import ModalStore from '../../stores/modal_store'; import ModalStore from '../../stores/modal_store';
...@@ -14,7 +14,7 @@ gl.issueBoards.IssuesModal = Vue.extend({ ...@@ -14,7 +14,7 @@ gl.issueBoards.IssuesModal = Vue.extend({
EmptyState, EmptyState,
'modal-header': gl.issueBoards.ModalHeader, 'modal-header': gl.issueBoards.ModalHeader,
'modal-list': gl.issueBoards.ModalList, 'modal-list': gl.issueBoards.ModalList,
'modal-footer': gl.issueBoards.ModalFooter, ModalFooter,
loadingIcon, loadingIcon,
}, },
props: { props: {
......
import Vue from 'vue';
import ModalStore from '../../stores/modal_store';
gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
data() {
return {
modal: ModalStore.store,
state: gl.issueBoards.BoardsStore.state,
};
},
computed: {
selected() {
return this.modal.selectedList || this.state.lists[1];
},
},
destroyed() {
this.modal.selectedList = null;
},
template: `
<div class="dropdown inline">
<button
class="dropdown-menu-toggle"
type="button"
data-toggle="dropdown"
aria-expanded="false">
<span
class="dropdown-label-box"
:style="{ backgroundColor: selected.label.color }">
</span>
{{ selected.title }}
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
<ul>
<li
v-for="list in state.lists"
v-if="list.type == 'label'">
<a
href="#"
role="button"
:class="{ 'is-active': list.id == selected.id }"
@click.prevent="modal.selectedList = list">
<span
class="dropdown-label-box"
:style="{ backgroundColor: list.label.color }">
</span>
{{ list.title }}
</a>
</li>
</ul>
</div>
</div>
`,
});
<script>
import ModalStore from '../../stores/modal_store';
export default {
data() {
return {
modal: ModalStore.store,
state: gl.issueBoards.BoardsStore.state,
};
},
computed: {
selected() {
return this.modal.selectedList || this.state.lists[1];
},
},
destroyed() {
this.modal.selectedList = null;
},
};
</script>
<template>
<div class="dropdown inline">
<button
class="dropdown-menu-toggle"
type="button"
data-toggle="dropdown"
aria-expanded="false">
<span
:style="{ backgroundColor: selected.label.color }"
class="dropdown-label-box">
</span>
{{ selected.title }}
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
<ul>
<li
v-for="(list, i) in state.lists"
v-if="list.type == 'label'"
:key="i">
<a
:class="{ 'is-active': list.id == selected.id }"
href="#"
role="button"
@click.prevent="modal.selectedList = list">
<span
:style="{ backgroundColor: list.label.color }"
class="dropdown-label-box">
</span>
{{ list.title }}
</a>
</li>
</ul>
</div>
</div>
</template>
import Vue from 'vue';
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
gl.issueBoards.ModalTabs = Vue.extend({
mixins: [modalMixin],
data() {
return ModalStore.store;
},
computed: {
selectedCount() {
return ModalStore.selectedCount();
},
},
destroyed() {
this.activeTab = 'all';
},
template: `
<div class="top-area prepend-top-10 append-bottom-10">
<ul class="nav-links issues-state-filters">
<li :class="{ 'active': activeTab == 'all' }">
<a
href="#"
role="button"
@click.prevent="changeTab('all')">
Open issues
<span class="badge badge-pill">
{{ issuesCount }}
</span>
</a>
</li>
<li :class="{ 'active': activeTab == 'selected' }">
<a
href="#"
role="button"
@click.prevent="changeTab('selected')">
Selected issues
<span class="badge badge-pill">
{{ selectedCount }}
</span>
</a>
</li>
</ul>
</div>
`,
});
<script>
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
export default {
mixins: [modalMixin],
data() {
return ModalStore.store;
},
computed: {
selectedCount() {
return ModalStore.selectedCount();
},
},
destroyed() {
this.activeTab = 'all';
},
};
</script>
<template>
<div class="top-area prepend-top-10 append-bottom-10">
<ul class="nav-links issues-state-filters">
<li :class="{ 'active': activeTab == 'all' }">
<a
href="#"
role="button"
@click.prevent="changeTab('all')"
>
Open issues
<span class="badge badge-pill">
{{ issuesCount }}
</span>
</a>
</li>
<li :class="{ 'active': activeTab == 'selected' }">
<a
href="#"
role="button"
@click.prevent="changeTab('selected')"
>
Selected issues
<span class="badge badge-pill">
{{ selectedCount }}
</span>
</a>
</li>
</ul>
</div>
</template>
import Vue from 'vue';
import Flash from '../../../flash';
import { __ } from '../../../locale';
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.RemoveIssueBtn = Vue.extend({
props: {
issue: {
type: Object,
required: true,
},
list: {
type: Object,
required: true,
},
},
computed: {
updateUrl() {
return this.issue.path;
},
},
methods: {
removeIssue() {
const board = Store.state.currentBoard;
const issue = this.issue;
const lists = issue.getLists();
const boardLabelIds = board.labels.map(label => label.id);
const listLabelIds = lists.map(list => list.label.id);
let labelIds = issue.labels
.map(label => label.id)
.filter(id => !listLabelIds.includes(id))
.filter(id => !boardLabelIds.includes(id));
if (labelIds.length === 0) {
labelIds = [''];
}
let assigneeIds = issue.assignees
.map(assignee => assignee.id)
.filter(id => id !== board.assignee.id);
if (assigneeIds.length === 0) {
// for backend to explicitly set No Assignee
assigneeIds = ['0'];
}
const data = {
issue: {
label_ids: labelIds,
assignee_ids: assigneeIds,
},
};
if (board.milestone_id) {
data.issue.milestone_id = -1;
}
if (board.weight) {
data.issue.weight = null;
}
// Post the remove data
Vue.http.patch(this.updateUrl, data).catch(() => {
Flash(__('Failed to remove issue from board, please try again.'));
lists.forEach((list) => {
list.addIssue(issue);
});
});
// Remove from the frontend store
lists.forEach((list) => {
list.removeIssue(issue);
});
Store.detail.issue = {};
},
},
template: `
<div
class="block list">
<button
class="btn btn-default btn-block"
type="button"
@click="removeIssue">
Remove from board
</button>
</div>
`,
});
<script>
import Vue from 'vue';
import Flash from '../../../flash';
import { __ } from '../../../locale';
const Store = gl.issueBoards.BoardsStore;
export default {
props: {
issue: {
type: Object,
required: true,
},
list: {
type: Object,
required: true,
},
},
computed: {
updateUrl() {
return this.issue.path;
},
},
methods: {
removeIssue() {
const board = Store.state.currentBoard;
const issue = this.issue;
const lists = issue.getLists();
const boardLabelIds = board.labels.map(label => label.id);
const listLabelIds = lists.map(list => list.label.id);
let labelIds = issue.labels
.map(label => label.id)
.filter(id => !listLabelIds.includes(id))
.filter(id => !boardLabelIds.includes(id));
if (labelIds.length === 0) {
labelIds = [''];
}
let assigneeIds = issue.assignees
.map(assignee => assignee.id)
.filter(id => id !== board.assignee.id);
if (assigneeIds.length === 0) {
// for backend to explicitly set No Assignee
assigneeIds = ['0'];
}
const data = {
issue: {
label_ids: labelIds,
assignee_ids: assigneeIds,
},
};
if (board.milestone_id) {
data.issue.milestone_id = -1;
}
if (board.weight) {
data.issue.weight = null;
}
// Post the remove data
Vue.http.patch(this.updateUrl, data).catch(() => {
Flash(__('Failed to remove issue from board, please try again.'));
lists.forEach(list => {
list.addIssue(issue);
});
});
// Remove from the frontend store
lists.forEach(list => {
list.removeIssue(issue);
});
Store.detail.issue = {};
},
},
};
</script>
<template>
<div
class="block list"
>
<button
class="btn btn-default btn-block"
type="button"
@click="removeIssue"
>
Remove from board
</button>
</div>
</template>
...@@ -7,6 +7,16 @@ function sanitize(str) { ...@@ -7,6 +7,16 @@ function sanitize(str) {
return str.replace(/<(?:.|\n)*?>/gm, ''); return str.replace(/<(?:.|\n)*?>/gm, '');
} }
export const defaultAutocompleteConfig = {
emojis: true,
members: true,
issues: true,
mergeRequests: true,
epics: false,
milestones: true,
labels: true,
};
class GfmAutoComplete { class GfmAutoComplete {
constructor(dataSources) { constructor(dataSources) {
this.dataSources = dataSources || {}; this.dataSources = dataSources || {};
...@@ -14,14 +24,7 @@ class GfmAutoComplete { ...@@ -14,14 +24,7 @@ class GfmAutoComplete {
this.isLoadingData = {}; this.isLoadingData = {};
} }
setup(input, enableMap = { setup(input, enableMap = defaultAutocompleteConfig) {
emojis: true,
members: true,
issues: true,
milestones: true,
mergeRequests: true,
labels: true,
}) {
// Add GFM auto-completion to all input fields, that accept GFM input. // Add GFM auto-completion to all input fields, that accept GFM input.
this.input = input || $('.js-gfm-input'); this.input = input || $('.js-gfm-input');
this.enableMap = enableMap; this.enableMap = enableMap;
......
import $ from 'jquery'; import $ from 'jquery';
import autosize from 'autosize'; import autosize from 'autosize';
import GfmAutoComplete from './gfm_auto_complete'; import GfmAutoComplete, * as GFMConfig from './gfm_auto_complete';
import dropzoneInput from './dropzone_input'; import dropzoneInput from './dropzone_input';
import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown'; import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown';
export default class GLForm { export default class GLForm {
constructor(form, enableGFM = false) { constructor(form, enableGFM = {}) {
this.form = form; this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input'); this.textarea = this.form.find('textarea.js-gfm-input');
this.enableGFM = enableGFM; this.enableGFM = Object.assign({}, GFMConfig.defaultAutocompleteConfig, enableGFM);
// Before we start, we should clean up any previous data for this form // Before we start, we should clean up any previous data for this form
this.destroy(); this.destroy();
// Setup the form // Setup the form
...@@ -34,14 +34,7 @@ export default class GLForm { ...@@ -34,14 +34,7 @@ export default class GLForm {
// remove notify commit author checkbox for non-commit notes // remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion')); gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
this.autoComplete.setup(this.form.find('.js-gfm-input'), { this.autoComplete.setup(this.form.find('.js-gfm-input'), this.enableGFM);
emojis: true,
members: this.enableGFM,
issues: this.enableGFM,
milestones: this.enableGFM,
mergeRequests: this.enableGFM,
labels: this.enableGFM,
});
dropzoneInput(this.form); dropzoneInput(this.form);
autosize(this.textarea); autosize(this.textarea);
} }
......
...@@ -89,14 +89,14 @@ export default { ...@@ -89,14 +89,14 @@ export default {
<template> <template>
<div class="multi-file-commit-list-item position-relative"> <div class="multi-file-commit-list-item position-relative">
<button <div
v-tooltip v-tooltip
:title="tooltipTitle" :title="tooltipTitle"
:class="{ :class="{
'is-active': isActive 'is-active': isActive
}" }"
type="button"
class="multi-file-commit-list-path w-100 border-0 ml-0 mr-0" class="multi-file-commit-list-path w-100 border-0 ml-0 mr-0"
role="button"
@dblclick="fileAction" @dblclick="fileAction"
@click="openFileInEditor" @click="openFileInEditor"
> >
...@@ -107,7 +107,7 @@ export default { ...@@ -107,7 +107,7 @@ export default {
:css-classes="iconClass" :css-classes="iconClass"
/>{{ file.name }} />{{ file.name }}
</span> </span>
</button> </div>
<component <component
:is="actionComponent" :is="actionComponent"
:path="file.path" :path="file.path"
......
...@@ -44,6 +44,8 @@ export default { ...@@ -44,6 +44,8 @@ export default {
methods: { methods: {
...mapActions(['closeFile', 'updateDelayViewerUpdated', 'openPendingTab']), ...mapActions(['closeFile', 'updateDelayViewerUpdated', 'openPendingTab']),
clickFile(tab) { clickFile(tab) {
if (tab.active) return;
this.updateDelayViewerUpdated(true); this.updateDelayViewerUpdated(true);
if (tab.pending) { if (tab.pending) {
......
...@@ -7,10 +7,10 @@ export default () => { ...@@ -7,10 +7,10 @@ export default () => {
notesIds, notesIds,
now, now,
diffView, diffView,
autocomplete, enableGFM,
} = JSON.parse(dataEl.innerHTML); } = JSON.parse(dataEl.innerHTML);
// Create a singleton so that we don't need to assign // Create a singleton so that we don't need to assign
// into the window object, we can just access the current isntance with Notes.instance // into the window object, we can just access the current isntance with Notes.instance
Notes.initialize(notesUrl, notesIds, now, diffView, autocomplete); Notes.initialize(notesUrl, notesIds, now, diffView, enableGFM);
}; };
...@@ -20,6 +20,7 @@ import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_c ...@@ -20,6 +20,7 @@ import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_c
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { getLocationHash } from './lib/utils/url_utility'; import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash'; import Flash from './flash';
import { defaultAutocompleteConfig } from './gfm_auto_complete';
import CommentTypeToggle from './comment_type_toggle'; import CommentTypeToggle from './comment_type_toggle';
import GLForm from './gl_form'; import GLForm from './gl_form';
import loadAwardsHandler from './awards_handler'; import loadAwardsHandler from './awards_handler';
...@@ -45,7 +46,7 @@ const MAX_VISIBLE_COMMIT_LIST_COUNT = 3; ...@@ -45,7 +46,7 @@ const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm; const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
export default class Notes { export default class Notes {
static initialize(notes_url, note_ids, last_fetched_at, view, enableGFM = true) { static initialize(notes_url, note_ids, last_fetched_at, view, enableGFM) {
if (!this.instance) { if (!this.instance) {
this.instance = new Notes(notes_url, note_ids, last_fetched_at, view, enableGFM); this.instance = new Notes(notes_url, note_ids, last_fetched_at, view, enableGFM);
} }
...@@ -55,7 +56,7 @@ export default class Notes { ...@@ -55,7 +56,7 @@ export default class Notes {
return this.instance; return this.instance;
} }
constructor(notes_url, note_ids, last_fetched_at, view, enableGFM = true) { constructor(notes_url, note_ids, last_fetched_at, view, enableGFM = defaultAutocompleteConfig) {
this.updateTargetButtons = this.updateTargetButtons.bind(this); this.updateTargetButtons = this.updateTargetButtons.bind(this);
this.updateComment = this.updateComment.bind(this); this.updateComment = this.updateComment.bind(this);
this.visibilityChange = this.visibilityChange.bind(this); this.visibilityChange = this.visibilityChange.bind(this);
...@@ -94,7 +95,7 @@ export default class Notes { ...@@ -94,7 +95,7 @@ export default class Notes {
this.cleanBinding(); this.cleanBinding();
this.addBinding(); this.addBinding();
this.setPollingInterval(); this.setPollingInterval();
this.setupMainTargetNoteForm(); this.setupMainTargetNoteForm(enableGFM);
this.taskList = new TaskList({ this.taskList = new TaskList({
dataType: 'note', dataType: 'note',
fieldName: 'note', fieldName: 'note',
...@@ -598,14 +599,14 @@ export default class Notes { ...@@ -598,14 +599,14 @@ export default class Notes {
* *
* Sets some hidden fields in the form. * Sets some hidden fields in the form.
*/ */
setupMainTargetNoteForm() { setupMainTargetNoteForm(enableGFM) {
var form; var form;
// find the form // find the form
form = $('.js-new-note-form'); form = $('.js-new-note-form');
// Set a global clone of the form for later cloning // Set a global clone of the form for later cloning
this.formClone = form.clone(); this.formClone = form.clone();
// show the form // show the form
this.setupNoteForm(form); this.setupNoteForm(form, enableGFM);
// fix classes // fix classes
form.removeClass('js-new-note-form'); form.removeClass('js-new-note-form');
form.addClass('js-main-target-form'); form.addClass('js-main-target-form');
...@@ -633,9 +634,9 @@ export default class Notes { ...@@ -633,9 +634,9 @@ export default class Notes {
* setup GFM auto complete * setup GFM auto complete
* show the form * show the form
*/ */
setupNoteForm(form) { setupNoteForm(form, enableGFM = defaultAutocompleteConfig) {
var textarea, key; var textarea, key;
this.glForm = new GLForm(form, this.enableGFM); this.glForm = new GLForm(form, enableGFM);
textarea = form.find('.js-note-text'); textarea = form.find('.js-note-text');
key = [ key = [
'Note', 'Note',
......
...@@ -194,7 +194,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea" ...@@ -194,7 +194,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
class="btn btn-cancel note-edit-cancel js-close-discussion-note-form" class="btn btn-cancel note-edit-cancel js-close-discussion-note-form"
type="button" type="button"
@click="cancelHandler()"> @click="cancelHandler()">
Cancel {{ __('Discard draft') }}
</button> </button>
</div> </div>
</form> </form>
......
...@@ -3,5 +3,5 @@ import GLForm from '~/gl_form'; ...@@ -3,5 +3,5 @@ import GLForm from '~/gl_form';
export default function ($formEl) { export default function ($formEl) {
new ZenMode(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new
new GLForm($formEl, true); // eslint-disable-line no-new new GLForm($formEl); // eslint-disable-line no-new
} }
...@@ -11,7 +11,7 @@ import WeightSelect from 'ee/weight_select'; ...@@ -11,7 +11,7 @@ import WeightSelect from 'ee/weight_select';
export default () => { export default () => {
new ShortcutsNavigation(); new ShortcutsNavigation();
new GLForm($('.issue-form'), true); new GLForm($('.issue-form'));
new IssuableForm($('.issue-form')); new IssuableForm($('.issue-form'));
new LabelsSelect(); new LabelsSelect();
new MilestoneSelect(); new MilestoneSelect();
......
...@@ -12,7 +12,7 @@ import IssuableTemplateSelectors from '~/templates/issuable_template_selectors'; ...@@ -12,7 +12,7 @@ import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
export default () => { export default () => {
new Diff(); new Diff();
new ShortcutsNavigation(); new ShortcutsNavigation();
new GLForm($('.merge-request-form'), true); new GLForm($('.merge-request-form'));
new IssuableForm($('.merge-request-form')); new IssuableForm($('.merge-request-form'));
new LabelsSelect(); new LabelsSelect();
new MilestoneSelect(); new MilestoneSelect();
......
...@@ -5,6 +5,6 @@ import GLForm from '../../../../gl_form'; ...@@ -5,6 +5,6 @@ import GLForm from '../../../../gl_form';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new ZenMode(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new
new GLForm($('.tag-form'), true); // eslint-disable-line no-new new GLForm($('.tag-form')); // eslint-disable-line no-new
new RefSelectDropdown($('.js-branch-select')); // eslint-disable-line no-new new RefSelectDropdown($('.js-branch-select')); // eslint-disable-line no-new
}); });
...@@ -12,7 +12,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -12,7 +12,7 @@ document.addEventListener('DOMContentLoaded', () => {
new Wikis(); // eslint-disable-line no-new new Wikis(); // eslint-disable-line no-new
new ShortcutsWiki(); // eslint-disable-line no-new new ShortcutsWiki(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new
new GLForm($('.wiki-form'), true); // eslint-disable-line no-new new GLForm($('.wiki-form')); // eslint-disable-line no-new
const deleteWikiButton = document.getElementById('delete-wiki-button'); const deleteWikiButton = document.getElementById('delete-wiki-button');
......
...@@ -3,6 +3,13 @@ import GLForm from '~/gl_form'; ...@@ -3,6 +3,13 @@ import GLForm from '~/gl_form';
import ZenMode from '~/zen_mode'; import ZenMode from '~/zen_mode';
export default () => { export default () => {
new GLForm($('.snippet-form'), false); // eslint-disable-line no-new // eslint-disable-next-line no-new
new GLForm($('.snippet-form'), {
members: false,
issues: false,
mergeRequests: false,
milestones: false,
labels: false,
});
new ZenMode(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new
}; };
...@@ -6,5 +6,13 @@ import GLForm from '../../gl_form'; ...@@ -6,5 +6,13 @@ import GLForm from '../../gl_form';
export default (initGFM = true) => { export default (initGFM = true) => {
new ZenMode(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new
new DueDateSelectors(); // eslint-disable-line no-new new DueDateSelectors(); // eslint-disable-line no-new
new GLForm($('.milestone-form'), initGFM); // eslint-disable-line no-new // eslint-disable-next-line no-new
new GLForm($('.milestone-form'), {
emojis: initGFM,
members: initGFM,
issues: initGFM,
mergeRequests: initGFM,
milestones: initGFM,
labels: initGFM,
});
}; };
...@@ -62,7 +62,14 @@ ...@@ -62,7 +62,14 @@
/* /*
GLForm class handles all the toolbar buttons GLForm class handles all the toolbar buttons
*/ */
return new GLForm($(this.$refs['gl-form']), this.enableAutocomplete); return new GLForm($(this.$refs['gl-form']), {
emojis: this.enableAutocomplete,
members: this.enableAutocomplete,
issues: this.enableAutocomplete,
mergeRequests: this.enableAutocomplete,
milestones: this.enableAutocomplete,
labels: this.enableAutocomplete,
});
}, },
beforeDestroy() { beforeDestroy() {
const glForm = $(this.$refs['gl-form']).data('glForm'); const glForm = $(this.$refs['gl-form']).data('glForm');
......
...@@ -310,7 +310,7 @@ pre code { ...@@ -310,7 +310,7 @@ pre code {
color: $white-light; color: $white-light;
h4, h4,
a, a:not(.btn),
.alert-link { .alert-link {
color: $white-light; color: $white-light;
} }
......
...@@ -180,10 +180,6 @@ ...@@ -180,10 +180,6 @@
color: $border-and-box-shadow; color: $border-and-box-shadow;
} }
.ide-file-list .file.file-active {
color: $border-and-box-shadow;
}
.ide-sidebar-link { .ide-sidebar-link {
&.active { &.active {
color: $border-and-box-shadow; color: $border-and-box-shadow;
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
margin-top: 0; margin-top: 0;
border-top: 1px solid $white-dark; border-top: 1px solid $white-dark;
padding-bottom: $ide-statusbar-height; padding-bottom: $ide-statusbar-height;
color: $gl-text-color;
&.is-collapsed { &.is-collapsed {
.ide-file-list { .ide-file-list {
...@@ -45,12 +46,8 @@ ...@@ -45,12 +46,8 @@
.file { .file {
cursor: pointer; cursor: pointer;
&.file-open {
background: $white-normal;
}
&.file-active { &.file-active {
font-weight: $gl-font-weight-bold; background: $theme-gray-100;
} }
.ide-file-name { .ide-file-name {
...@@ -58,7 +55,9 @@ ...@@ -58,7 +55,9 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: inherit; max-width: inherit;
line-height: 22px; line-height: 16px;
display: inline-block;
height: 18px;
svg { svg {
vertical-align: middle; vertical-align: middle;
...@@ -86,12 +85,14 @@ ...@@ -86,12 +85,14 @@
.ide-new-btn { .ide-new-btn {
display: none; display: none;
.btn {
padding: 2px 5px;
}
} }
&:hover, &:hover,
&:focus { &:focus {
background: $white-normal;
.ide-new-btn { .ide-new-btn {
display: block; display: block;
} }
...@@ -281,8 +282,8 @@ ...@@ -281,8 +282,8 @@
} }
.margin { .margin {
background-color: $gray-light; background-color: $white-light;
border-right: 1px solid $white-normal; border-right: 1px solid $theme-gray-100;
.line-insert { .line-insert {
border-right: 1px solid $line-added-dark; border-right: 1px solid $line-added-dark;
...@@ -303,6 +304,15 @@ ...@@ -303,6 +304,15 @@
.multi-file-editor-holder { .multi-file-editor-holder {
height: 100%; height: 100%;
min-height: 0; min-height: 0;
&.is-readonly,
.editor.original {
.monaco-editor,
.monaco-editor-background,
.monaco-editor .inputarea.ime-input {
background-color: $theme-gray-50;
}
}
} }
.preview-container { .preview-container {
...@@ -587,11 +597,17 @@ ...@@ -587,11 +597,17 @@
&:hover, &:hover,
&:focus { &:focus {
background: $white-normal; background: $theme-gray-100;
}
&:active {
background: $theme-gray-200;
} }
} }
.multi-file-commit-list-path { .multi-file-commit-list-path {
cursor: pointer;
&.is-active { &.is-active {
background-color: $white-normal; background-color: $white-normal;
} }
...@@ -611,10 +627,6 @@ ...@@ -611,10 +627,6 @@
.multi-file-commit-list-file-path { .multi-file-commit-list-file-path {
@include str-truncated(calc(100% - 30px)); @include str-truncated(calc(100% - 30px));
&:hover {
text-decoration: underline;
}
&:active { &:active {
text-decoration: none; text-decoration: none;
} }
......
...@@ -11,6 +11,5 @@ class Admin::DashboardController < Admin::ApplicationController ...@@ -11,6 +11,5 @@ class Admin::DashboardController < Admin::ApplicationController
@projects = Project.order_id_desc.without_deleted.with_route.limit(10) @projects = Project.order_id_desc.without_deleted.with_route.limit(10)
@users = User.order_id_desc.limit(10) @users = User.order_id_desc.limit(10)
@groups = Group.order_id_desc.with_route.limit(10) @groups = Group.order_id_desc.with_route.limit(10)
@license = License.current
end end
end end
...@@ -130,7 +130,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -130,7 +130,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
set_remember_me(user) set_remember_me(user)
if user.two_factor_enabled? if user.two_factor_enabled? && !auth_user.bypass_two_factor?
prompt_for_two_factor(user) prompt_for_two_factor(user)
else else
sign_in_and_redirect(user) sign_in_and_redirect(user)
......
...@@ -56,7 +56,7 @@ class UserRecentEventsFinder ...@@ -56,7 +56,7 @@ class UserRecentEventsFinder
visible = target_user visible = target_user
.project_interactions .project_interactions
.where(visibility_level: [Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC]) .where(visibility_level: Gitlab::VisibilityLevel.levels_for_user(current_user))
.select(:id) .select(:id)
Gitlab::SQL::Union.new([authorized, visible]).to_sql Gitlab::SQL::Union.new([authorized, visible]).to_sql
......
...@@ -145,7 +145,14 @@ module NotesHelper ...@@ -145,7 +145,14 @@ module NotesHelper
notesIds: @notes.map(&:id), notesIds: @notes.map(&:id),
now: Time.now.to_i, now: Time.now.to_i,
diffView: diff_view, diffView: diff_view,
autocomplete: autocomplete enableGFM: {
emojis: true,
members: autocomplete,
issues: autocomplete,
mergeRequests: autocomplete,
milestones: autocomplete,
labels: autocomplete
}
} }
end end
......
...@@ -42,7 +42,8 @@ module ProjectsHelper ...@@ -42,7 +42,8 @@ module ProjectsHelper
name_tag_options[:class] << 'has-tooltip' name_tag_options[:class] << 'has-tooltip'
end end
content_tag(:span, sanitize(username), name_tag_options) # NOTE: ActionView::Helpers::TagHelper#content_tag HTML escapes username
content_tag(:span, username, name_tag_options)
end end
def link_to_member(project, author, opts = {}, &block) def link_to_member(project, author, opts = {}, &block)
...@@ -508,6 +509,14 @@ module ProjectsHelper ...@@ -508,6 +509,14 @@ module ProjectsHelper
end end
end end
def sidebar_projects_paths
%w[
projects#show
projects#activity
cycle_analytics#show
]
end
def sidebar_settings_paths def sidebar_settings_paths
%w[ %w[
projects#edit projects#edit
......
...@@ -578,9 +578,9 @@ module Ci ...@@ -578,9 +578,9 @@ module Ci
.append(key: 'CI_PIPELINE_IID', value: iid.to_s) .append(key: 'CI_PIPELINE_IID', value: iid.to_s)
.append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path) .append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path)
.append(key: 'CI_PIPELINE_SOURCE', value: source.to_s) .append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
.append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message) .append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message.to_s)
.append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title) .append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s)
.append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description) .append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s)
end end
def queued_duration def queued_duration
......
...@@ -12,8 +12,8 @@ module Sortable ...@@ -12,8 +12,8 @@ module Sortable
scope :order_created_asc, -> { reorder(created_at: :asc) } scope :order_created_asc, -> { reorder(created_at: :asc) }
scope :order_updated_desc, -> { reorder(updated_at: :desc) } scope :order_updated_desc, -> { reorder(updated_at: :desc) }
scope :order_updated_asc, -> { reorder(updated_at: :asc) } scope :order_updated_asc, -> { reorder(updated_at: :asc) }
scope :order_name_asc, -> { reorder("lower(name) asc") } scope :order_name_asc, -> { reorder(Arel::Nodes::Ascending.new(arel_table[:name].lower)) }
scope :order_name_desc, -> { reorder("lower(name) desc") } scope :order_name_desc, -> { reorder(Arel::Nodes::Descending.new(arel_table[:name].lower)) }
end end
module ClassMethods module ClassMethods
......
...@@ -133,9 +133,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -133,9 +133,7 @@ class MergeRequest < ActiveRecord::Base
after_transition unchecked: :cannot_be_merged do |merge_request, transition| after_transition unchecked: :cannot_be_merged do |merge_request, transition|
begin begin
# Merge request can become unmergeable due to many reasons. if merge_request.notify_conflict?
# We only notify if it is due to conflict.
unless merge_request.project.repository.can_be_merged?(merge_request.diff_head_sha, merge_request.target_branch)
NotificationService.new.merge_request_unmergeable(merge_request) NotificationService.new.merge_request_unmergeable(merge_request)
TodoService.new.merge_request_became_unmergeable(merge_request) TodoService.new.merge_request_became_unmergeable(merge_request)
end end
...@@ -714,6 +712,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -714,6 +712,10 @@ class MergeRequest < ActiveRecord::Base
should_remove_source_branch? || force_remove_source_branch? should_remove_source_branch? || force_remove_source_branch?
end end
def notify_conflict?
(opened? || locked?) && !project.repository.can_be_merged?(diff_head_sha, target_branch)
end
def related_notes def related_notes
# Fetch comments only from last 100 commits # Fetch comments only from last 100 commits
commits_for_notes_limit = 100 commits_for_notes_limit = 100
......
...@@ -29,8 +29,8 @@ class ProjectAutoDevops < ActiveRecord::Base ...@@ -29,8 +29,8 @@ class ProjectAutoDevops < ActiveRecord::Base
end end
if manual? if manual?
variables.append(key: 'STAGING_ENABLED', value: 1) variables.append(key: 'STAGING_ENABLED', value: '1')
variables.append(key: 'INCREMENTAL_ROLLOUT_ENABLED', value: 1) variables.append(key: 'INCREMENTAL_ROLLOUT_ENABLED', value: '1')
end end
end end
end end
......
...@@ -26,7 +26,7 @@ class Repository ...@@ -26,7 +26,7 @@ class Repository
attr_accessor :full_path, :disk_path, :project, :is_wiki attr_accessor :full_path, :disk_path, :project, :is_wiki
delegate :ref_name_for_sha, to: :raw_repository delegate :ref_name_for_sha, to: :raw_repository
delegate :bundle_to_disk, :create_from_bundle, to: :raw_repository delegate :bundle_to_disk, to: :raw_repository
CreateTreeError = Class.new(StandardError) CreateTreeError = Class.new(StandardError)
......
...@@ -82,7 +82,7 @@ class WebHookService ...@@ -82,7 +82,7 @@ class WebHookService
post_url = hook.url.gsub("#{parsed_url.userinfo}@", '') post_url = hook.url.gsub("#{parsed_url.userinfo}@", '')
basic_auth = { basic_auth = {
username: CGI.unescape(parsed_url.user), username: CGI.unescape(parsed_url.user),
password: CGI.unescape(parsed_url.password) password: CGI.unescape(parsed_url.password.presence || '')
} }
make_request(post_url, basic_auth) make_request(post_url, basic_auth)
end end
......
...@@ -373,3 +373,5 @@ ...@@ -373,3 +373,5 @@
= _('Geo allows you to replicate your GitLab instance to other geographical locations.') = _('Geo allows you to replicate your GitLab instance to other geographical locations.')
.settings-content .settings-content
= render partial: 'slack' = render partial: 'slack'
= render_if_exists 'admin/application_settings/pseudonymizer_settings', expanded: expanded
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.sidebar-context-title .sidebar-context-title
= @project.name = @project.name
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do = nav_link(path: sidebar_projects_paths, html_options: { class: 'home' }) do
= link_to project_path(@project), class: 'shortcuts-project' do = link_to project_path(@project), class: 'shortcuts-project' do
.nav-icon-container .nav-icon-container
= sprite_icon('project') = sprite_icon('project')
...@@ -29,13 +29,13 @@ ...@@ -29,13 +29,13 @@
= link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
%span= _('Activity') %span= _('Activity')
= render_if_exists 'projects/sidebar/security_dashboard'
- if can?(current_user, :read_cycle_analytics, @project) - if can?(current_user, :read_cycle_analytics, @project)
= nav_link(path: 'cycle_analytics#show') do = nav_link(path: 'cycle_analytics#show') do
= link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do = link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
%span= _('Cycle Analytics') %span= _('Cycle Analytics')
= render_if_exists 'projects/sidebar/security_dashboard'
- if project_nav_tab? :files - if project_nav_tab? :files
= nav_link(controller: sidebar_repository_paths) do = nav_link(controller: sidebar_repository_paths) do
= link_to project_tree_path(@project), class: 'shortcuts-tree' do = link_to project_tree_path(@project), class: 'shortcuts-tree' do
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
= image_tag 'illustrations/logos/google-cloud-platform_logo.svg' = image_tag 'illustrations/logos/google-cloud-platform_logo.svg'
.col-sm-10 .col-sm-10
%h4= s_('ClusterIntegration|Redeem up to $500 in free credit for Google Cloud Platform') %h4= s_('ClusterIntegration|Redeem up to $500 in free credit for Google Cloud Platform')
%p= s_('ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for new GCP accounts to get started with GitLab\'s Google Kubernetes Engine Integration.').html_safe % { sign_up_link: link } %p= s_('ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab\'s Google Kubernetes Engine Integration.').html_safe % { sign_up_link: link }
%a.btn.btn-info{ href: 'https://goo.gl/AaJzRW', target: '_blank', rel: 'noopener noreferrer' } %a.btn.btn-info{ href: 'https://goo.gl/AaJzRW', target: '_blank', rel: 'noopener noreferrer' }
Apply for credit Apply for credit
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
#{@commits_graph.start_date.strftime('%b %d')} #{@commits_graph.start_date.strftime('%b %d')}
- end_time = capture do - end_time = capture do
#{@commits_graph.end_date.strftime('%b %d')} #{@commits_graph.end_date.strftime('%b %d')}
= (_("Commit statistics for %{ref} %{start_time} - %{end_time}") % { ref: "<strong>#{@ref}</strong>", start_time: start_time, end_time: end_time }).html_safe = (_("Commit statistics for %{ref} %{start_time} - %{end_time}") % { ref: "<strong>#{h @ref}</strong>", start_time: start_time, end_time: end_time }).html_safe
.col-md-6 .col-md-6
.tree-ref-container .tree-ref-container
......
...@@ -40,5 +40,5 @@ ...@@ -40,5 +40,5 @@
= yield(:note_actions) = yield(:note_actions)
%a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } } %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Discard draft" } }
Discard draft Discard draft
...@@ -145,6 +145,7 @@ ...@@ -145,6 +145,7 @@
- cronjob:ldap_all_groups_sync - cronjob:ldap_all_groups_sync
- cronjob:ldap_sync - cronjob:ldap_sync
- cronjob:update_all_mirrors - cronjob:update_all_mirrors
- cronjob:pseudonymizer
- geo:geo_scheduler_scheduler - geo:geo_scheduler_scheduler
- geo:geo_scheduler_primary_scheduler - geo:geo_scheduler_primary_scheduler
......
...@@ -19,7 +19,24 @@ Options = Struct.new( ...@@ -19,7 +19,24 @@ Options = Struct.new(
) )
INVALID_TYPE = -1 INVALID_TYPE = -1
module ChangelogHelpers
Abort = Class.new(StandardError)
Done = Class.new(StandardError)
def capture_stdout(cmd)
output = IO.popen(cmd, &:read)
fail_with "command failed: #{cmd.join(' ')}" unless $?.success?
output
end
def fail_with(message)
raise Abort, "\e[31merror\e[0m #{message}"
end
end
class ChangelogOptionParser class ChangelogOptionParser
extend ChangelogHelpers
Type = Struct.new(:name, :description) Type = Struct.new(:name, :description)
TYPES = [ TYPES = [
Type.new('added', 'New feature'), Type.new('added', 'New feature'),
...@@ -68,7 +85,7 @@ class ChangelogOptionParser ...@@ -68,7 +85,7 @@ class ChangelogOptionParser
opts.on('-h', '--help', 'Print help message') do opts.on('-h', '--help', 'Print help message') do
$stdout.puts opts $stdout.puts opts
exit raise Done.new
end end
end end
...@@ -108,18 +125,19 @@ class ChangelogOptionParser ...@@ -108,18 +125,19 @@ class ChangelogOptionParser
def assert_valid_type!(type) def assert_valid_type!(type)
unless type unless type
$stderr.puts "Invalid category index, please select an index between 1 and #{TYPES.length}" raise Abort, "Invalid category index, please select an index between 1 and #{TYPES.length}"
exit 1
end end
end end
def git_user_name def git_user_name
%x{git config user.name}.strip capture_stdout(%w[git config user.name]).strip
end end
end end
end end
class ChangelogEntry class ChangelogEntry
include ChangelogHelpers
attr_reader :options attr_reader :options
def initialize(options) def initialize(options)
...@@ -159,13 +177,9 @@ class ChangelogEntry ...@@ -159,13 +177,9 @@ class ChangelogEntry
end end
def amend_commit def amend_commit
%x{git add #{file_path}} fail_with "git add failed" unless system(*%W[git add #{file_path}])
exec("git commit --amend")
end
def fail_with(message) Kernel.exec(*%w[git commit --amend])
$stderr.puts "\e[31merror\e[0m #{message}"
exit 1
end end
def assert_feature_branch! def assert_feature_branch!
...@@ -203,7 +217,7 @@ class ChangelogEntry ...@@ -203,7 +217,7 @@ class ChangelogEntry
end end
def last_commit_subject def last_commit_subject
%x{git log --format="%s" -1}.strip capture_stdout(%w[git log --format=%s -1]).strip
end end
def file_path def file_path
...@@ -225,7 +239,7 @@ class ChangelogEntry ...@@ -225,7 +239,7 @@ class ChangelogEntry
end end
def branch_name def branch_name
@branch_name ||= %x{git symbolic-ref --short HEAD}.strip @branch_name ||= capture_stdout(%w[git symbolic-ref --short HEAD]).strip
end end
def remove_trailing_whitespace(yaml_content) def remove_trailing_whitespace(yaml_content)
...@@ -234,8 +248,15 @@ class ChangelogEntry ...@@ -234,8 +248,15 @@ class ChangelogEntry
end end
if $0 == __FILE__ if $0 == __FILE__
options = ChangelogOptionParser.parse(ARGV) begin
ChangelogEntry.new(options) options = ChangelogOptionParser.parse(ARGV)
ChangelogEntry.new(options)
rescue ChangelogHelpers::Abort => ex
$stderr.puts ex.message
exit 1
rescue ChangelogHelpers::Done
exit
end
end end
# vim: ft=ruby # vim: ft=ruby
---
title: Fade uneditable area in Web IDE
merge_request: 20008
author:
type: changed
---
title: Update Web IDE file tree styles
merge_request: 19969
author:
type: changed
---
title: Fix webhook error when password is not present
merge_request: 19945
author: Jan Beckmann
type: fixed
---
title: Fix sorting by name on explore projects page
merge_request: 20162
author:
type: fixed
---
title: Only load Omniauth if enabled
merge_request: 20132
author:
type: fixed
---
title: Notify conflict for only open merge request
merge_request: 20125
author:
type: fixed
---
title: Fix incremental rollouts for Auto DevOps
merge_request: 20061
author:
type: fixed
---
title: Add back copy for existing gcp accounts within offer banner
merge_request:
author:
type: changed
---
title: Fix alert button styling so that they don't show up white
merge_request:
author:
type: fixed
---
title: Fix XSS vulnerability for table of content generation
merge_request:
author:
type: security
---
title: Update sanitize gem to 4.6.5 to fix HTML injection vulnerability
merge_request:
author:
type: security
---
title: HTML escape branch name in project graphs page
merge_request:
author:
type: security
---
title: HTML escape the name of the user in ProjectsHelper#link_to_member
merge_request:
author:
type: security
---
title: Don't show events from internal projects for anonymous users in public feed
merge_request:
author:
type: security
...@@ -7,6 +7,12 @@ Bundler.require(:default, Rails.env) ...@@ -7,6 +7,12 @@ Bundler.require(:default, Rails.env)
require 'elasticsearch/rails/instrumentation' require 'elasticsearch/rails/instrumentation'
module Gitlab module Gitlab
# This method is used for smooth upgrading from the current Rails 4.x to Rails 5.0.
# https://gitlab.com/gitlab-org/gitlab-ce/issues/14286
def self.rails5?
ENV["RAILS5"].in?(%w[1 true])
end
class Application < Rails::Application class Application < Rails::Application
require_dependency Rails.root.join('lib/gitlab/redis/wrapper') require_dependency Rails.root.join('lib/gitlab/redis/wrapper')
require_dependency Rails.root.join('lib/gitlab/redis/cache') require_dependency Rails.root.join('lib/gitlab/redis/cache')
...@@ -16,6 +22,11 @@ module Gitlab ...@@ -16,6 +22,11 @@ module Gitlab
require_dependency Rails.root.join('lib/gitlab/current_settings') require_dependency Rails.root.join('lib/gitlab/current_settings')
require_dependency Rails.root.join('lib/gitlab/middleware/read_only') require_dependency Rails.root.join('lib/gitlab/middleware/read_only')
# This needs to be loaded before DB connection is made
# to make sure that all connections have NO_ZERO_DATE
# setting disabled
require_dependency Rails.root.join('lib/mysql_zero_date')
# Settings in config/environments/* take precedence over those specified here. # Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers # Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded. # -- all .rb files in that directory are automatically loaded.
...@@ -239,10 +250,4 @@ module Gitlab ...@@ -239,10 +250,4 @@ module Gitlab
Gitlab::Routing.add_helpers(MilestonesRoutingHelper) Gitlab::Routing.add_helpers(MilestonesRoutingHelper)
end end
end end
# This method is used for smooth upgrading from the current Rails 4.x to Rails 5.0.
# https://gitlab.com/gitlab-org/gitlab-ce/issues/14286
def self.rails5?
ENV["RAILS5"].in?(%w[1 true])
end
end end
...@@ -311,6 +311,10 @@ production: &base ...@@ -311,6 +311,10 @@ production: &base
geo_migrated_local_files_clean_up_worker: geo_migrated_local_files_clean_up_worker:
cron: "15 */6 * * *" cron: "15 */6 * * *"
# Export pseudonymized data in CSV format for analysis
pseudonymizer_worker:
cron: "0 * * * *"
registry: registry:
# enabled: true # enabled: true
# host: registry.example.com # host: registry.example.com
...@@ -726,6 +730,20 @@ production: &base ...@@ -726,6 +730,20 @@ production: &base
# # Specifies Amazon S3 storage class to use for backups, this is optional # # Specifies Amazon S3 storage class to use for backups, this is optional
# # storage_class: 'STANDARD' # # storage_class: 'STANDARD'
## Pseudonymizer exporter
pseudonymizer:
# Tables manifest that specifies the fields to extract and pseudonymize.
manifest: config/pseudonymizer.yml
upload:
# remote_directory: 'gitlab-elt'
# Fog storage connection settings, see http://fog.io/storage/ .
connection:
# provider: AWS
# region: eu-west-1
# aws_access_key_id: AKIAKIAKI
# aws_secret_access_key: 'secret123'
# # The remote 'directory' to store the CSV files. For S3, this would be the bucket name.
## GitLab Shell settings ## GitLab Shell settings
gitlab_shell: gitlab_shell:
path: /home/git/gitlab-shell/ path: /home/git/gitlab-shell/
...@@ -876,6 +894,17 @@ test: ...@@ -876,6 +894,17 @@ test:
token: secret token: secret
backup: backup:
path: tmp/tests/backups path: tmp/tests/backups
pseudonymizer:
manifest: config/pseudonymizer.yml
upload:
# The remote 'directory' to store the CSV files. For S3, this would be the bucket name.
remote_directory: gitlab-elt.test
# Fog storage connection settings, see http://fog.io/storage/
connection:
provider: AWS # Only AWS supported at the moment
aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: us-east-1
gitlab_shell: gitlab_shell:
path: tmp/tests/gitlab-shell/ path: tmp/tests/gitlab-shell/
hooks_path: tmp/tests/gitlab-shell/hooks/ hooks_path: tmp/tests/gitlab-shell/hooks/
......
...@@ -370,6 +370,10 @@ Settings.cron_jobs['gitlab_usage_ping_worker'] ||= Settingslogic.new({}) ...@@ -370,6 +370,10 @@ Settings.cron_jobs['gitlab_usage_ping_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['gitlab_usage_ping_worker']['cron'] ||= Settings.__send__(:cron_for_usage_ping) Settings.cron_jobs['gitlab_usage_ping_worker']['cron'] ||= Settings.__send__(:cron_for_usage_ping)
Settings.cron_jobs['gitlab_usage_ping_worker']['job_class'] = 'GitlabUsagePingWorker' Settings.cron_jobs['gitlab_usage_ping_worker']['job_class'] = 'GitlabUsagePingWorker'
Settings.cron_jobs['pseudonymizer_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['pseudonymizer_worker']['cron'] ||= '0 23 * * *'
Settings.cron_jobs['pseudonymizer_worker']['job_class'] ||= 'PseudonymizerWorker'
Settings.cron_jobs['schedule_update_user_activity_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['schedule_update_user_activity_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['schedule_update_user_activity_worker']['cron'] ||= '30 0 * * *' Settings.cron_jobs['schedule_update_user_activity_worker']['cron'] ||= '30 0 * * *'
Settings.cron_jobs['schedule_update_user_activity_worker']['job_class'] = 'ScheduleUpdateUserActivityWorker' Settings.cron_jobs['schedule_update_user_activity_worker']['job_class'] = 'ScheduleUpdateUserActivityWorker'
...@@ -470,6 +474,14 @@ Settings.backup['upload']['multipart_chunk_size'] ||= 104857600 ...@@ -470,6 +474,14 @@ Settings.backup['upload']['multipart_chunk_size'] ||= 104857600
Settings.backup['upload']['encryption'] ||= nil Settings.backup['upload']['encryption'] ||= nil
Settings.backup['upload']['storage_class'] ||= nil Settings.backup['upload']['storage_class'] ||= nil
#
# Pseudonymizer
#
Settings['pseudonymizer'] ||= Settingslogic.new({})
Settings.pseudonymizer['manifest'] = Settings.absolute(Settings.pseudonymizer['manifest'] || Rails.root.join("config/pseudonymizer.yml"))
Settings.pseudonymizer['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'connection' => nil })
# Settings.pseudonymizer['upload']['multipart_chunk_size'] ||= 104857600
# #
# Git # Git
# #
......
...@@ -65,7 +65,7 @@ elsif Gitlab::Database.mysql? ...@@ -65,7 +65,7 @@ elsif Gitlab::Database.mysql?
prepend RegisterDateTimeWithTimeZone prepend RegisterDateTimeWithTimeZone
# Add the class `DateTimeWithTimeZone` so we can map `timestamp` to it. # Add the class `DateTimeWithTimeZone` so we can map `timestamp` to it.
class MysqlDateTimeWithTimeZone < MysqlDateTime class MysqlDateTimeWithTimeZone < (Gitlab.rails5? ? ActiveRecord::Type::DateTime : MysqlDateTime)
def type def type
:datetime_with_timezone :datetime_with_timezone
end end
......
...@@ -219,5 +219,7 @@ Devise.setup do |config| ...@@ -219,5 +219,7 @@ Devise.setup do |config|
end end
end end
Gitlab::OmniauthInitializer.new(config).execute(Gitlab.config.omniauth.providers) if Gitlab.config.omniauth.enabled
Gitlab::OmniauthInitializer.new(config).execute(Gitlab.config.omniauth.providers)
end
end end
This diff is collapsed.
...@@ -4,7 +4,7 @@ class MergeRequestDiffFileLimitsToMysql < ActiveRecord::Migration ...@@ -4,7 +4,7 @@ class MergeRequestDiffFileLimitsToMysql < ActiveRecord::Migration
def up def up
return unless Gitlab::Database.mysql? return unless Gitlab::Database.mysql?
change_column :merge_request_diff_files, :diff, :text, limit: 2147483647 change_column :merge_request_diff_files, :diff, :text, limit: 2147483647, default: nil
end end
def down def down
......
...@@ -206,6 +206,7 @@ ActiveRecord::Schema.define(version: 20180612175636) do ...@@ -206,6 +206,7 @@ ActiveRecord::Schema.define(version: 20180612175636) do
t.string "encrypted_external_auth_client_key_pass_iv" t.string "encrypted_external_auth_client_key_pass_iv"
t.string "email_additional_text" t.string "email_additional_text"
t.boolean "enforce_terms", default: false t.boolean "enforce_terms", default: false
t.boolean "pseudonymizer_enabled", default: false, null: false
end end
create_table "approvals", force: :cascade do |t| create_table "approvals", force: :cascade do |t|
......
...@@ -167,6 +167,10 @@ created in snippets, wikis, and repos. ...@@ -167,6 +167,10 @@ created in snippets, wikis, and repos.
- [Request Profiling](monitoring/performance/request_profiling.md): Get a detailed profile on slow requests. - [Request Profiling](monitoring/performance/request_profiling.md): Get a detailed profile on slow requests.
- [Performance Bar](monitoring/performance/performance_bar.md): Get performance information for the current page. - [Performance Bar](monitoring/performance/performance_bar.md): Get performance information for the current page.
## Analytics
- [Pseudonymizer](pseudonymizer.md): Export data from GitLab's database to CSV files in a secure way.
## Troubleshooting ## Troubleshooting
- [Debugging tips](troubleshooting/debug.md): Tips to debug problems when things go wrong - [Debugging tips](troubleshooting/debug.md): Tips to debug problems when things go wrong
......
# Pseudonymizer
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5532) in [GitLab Ultimate][ee] 11.1.
As GitLab's database hosts sensitive information, using it unfiltered for analytics
implies high security requirements. To help alleviate this constraint, the Pseudonymizer
service is used to export GitLab's data in a pseudonymized way.
CAUTION: **Warning:**
This process is not impervious. If the source data is available, it's possible for
a user to correlate data to the pseudonymized version.
The Pseudonymizer currently uses `HMAC(SHA256)` to mutate fields that shouldn't
be textually exported. This ensures that:
- the end-user of the data source cannot infer/revert the pseudonymized fields
- the referential integrity is maintained
## Configuration
To configure the pseudonymizer, you need to:
- Provide a manifest file that describes which fields should be included or
pseudonymized ([example `manifest.yml` file](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/config/pseudonymizer.yml)).
A default manifest is provided with the GitLab installation. Using a relative file path will be resolved from the Rails root.
Alternatively, you can use an absolute file path.
- Use an object storage and specify the connection parameters in the `pseudonymizer.upload.connection` configuration option.
**For Omnibus installations:**
1. Edit `/etc/gitlab/gitlab.rb` and add the following lines by replacing with
the values you want:
```ruby
gitlab_rails['pseudonymizer_manifest'] = 'config/pseudonymizer.yml'
gitlab_rails['pseudonymizer_upload_remote_directory'] = 'gitlab-elt'
gitlab_rails['pseudonymizer_upload_connection'] = {
'provider' => 'AWS',
'region' => 'eu-central-1',
'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'
}
```
NOTE: **Note:**
If you are using AWS IAM profiles, be sure to omit the AWS access key and secret access key/value pairs.
```ruby
gitlab_rails['pseudonymizer_upload_connection'] = {
'provider' => 'AWS',
'region' => 'eu-central-1',
'use_iam_profile' => true
}
```
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure)
for the changes to take effect.
---
**For installations from source:**
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
lines:
```yaml
pseudonymizer:
manifest: config/pseudonymizer.yml
upload:
remote_directory: 'gitlab-elt' # The bucket name
connection:
provider: AWS # Only AWS supported at the moment
aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: eu-central-1
```
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source)
for the changes to take effect.
## Usage
You can optionally run the pseudonymizer using the following environment variables:
- `PSEUDONYMIZER_OUTPUT_DIR` - where to store the output CSV files (defaults to `/tmp`)
- `PSEUDONYMIZER_BATCH` - the batch size when querying the DB (defaults to `100000`)
```bash
## Omnibus
sudo gitlab-rake gitlab:db:pseudonymizer
## Source
sudo -u git -H bundle exec rake gitlab:db:pseudonymizer RAILS_ENV=production
```
This will produce some CSV files that might be very large, so make sure the
`PSEUDONYMIZER_OUTPUT_DIR` has sufficient space. As a rule of thumb, at least
10% of the database size is recommended.
After the pseudonymizer has run, the output CSV files should be uploaded to the
configured object storage and deleted from the local disk.
[ee]: https://about.gitlab.com/pricing/
...@@ -322,50 +322,49 @@ to EE only. ...@@ -322,50 +322,49 @@ to EE only.
## Previewing the changes live ## Previewing the changes live
To preview your changes to documentation locally, please follow NOTE: **Note:**
this [development guide](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md#development). To preview your changes to documentation locally, follow this
[development guide](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md#development).
If you want to preview the doc changes of your merge request live, you can use The live preview is currently enabled for the following projects:
the manual `review-docs-deploy` job in your merge request. You will need at
least Maintainer permissions to be able to run it and is currently enabled for the
following projects:
- https://gitlab.com/gitlab-org/gitlab-ce - https://gitlab.com/gitlab-org/gitlab-ce
- https://gitlab.com/gitlab-org/gitlab-ee - https://gitlab.com/gitlab-org/gitlab-ee
- https://gitlab.com/gitlab-org/gitlab-runner
NOTE: **Note:**
You will need to push a branch to those repositories, it doesn't work for forks.
TIP: **Tip:**
If your branch contains only documentation changes, you can use If your branch contains only documentation changes, you can use
[special branch names](#branch-naming) to avoid long running pipelines. [special branch names](#branch-naming) to avoid long running pipelines.
In the mini pipeline graph, you should see an `>>` icon. Clicking on it will For [docs-only changes](#branch-naming), the review app is run automatically.
reveal the `review-docs-deploy` job. Hit the play button for the job to start. For all other branches, you can use the manual `review-docs-deploy-manual` job
in your merge request. You will need at least Maintainer permissions to be able
to run it. In the mini pipeline graph, you should see an `>>` icon. Clicking on it will
reveal the `review-docs-deploy-manual` job. Hit the play button for the job to start.
![Manual trigger a docs build](img/manual_build_docs.png) ![Manual trigger a docs build](img/manual_build_docs.png)
This job will: NOTE: **Note:**
You will need to push a branch to those repositories, it doesn't work for forks.
The `review-docs-deploy*` job will:
1. Create a new branch in the [gitlab-docs](https://gitlab.com/gitlab-com/gitlab-docs) 1. Create a new branch in the [gitlab-docs](https://gitlab.com/gitlab-com/gitlab-docs)
project named after the scheme: `preview-<branch-slug>` project named after the scheme: `$DOCS_GITLAB_REPO_SUFFIX-$CI_ENVIRONMENT_SLUG`,
where `DOCS_GITLAB_REPO_SUFFIX` is the suffix for each product, e.g, `ce` for
CE, etc.
1. Trigger a cross project pipeline and build the docs site with your changes 1. Trigger a cross project pipeline and build the docs site with your changes
After a few minutes, the Review App will be deployed and you will be able to After a few minutes, the Review App will be deployed and you will be able to
preview the changes. The docs URL can be found in two places: preview the changes. The docs URL can be found in two places:
- In the merge request widget - In the merge request widget
- In the output of the `review-docs-deploy` job, which also includes the - In the output of the `review-docs-deploy*` job, which also includes the
triggered pipeline so that you can investigate whether something went wrong triggered pipeline so that you can investigate whether something went wrong
In case the Review App URL returns 404, follow these steps to debug: In case the Review App URL returns 404, follow these steps to debug:
1. **Did you follow the URL from the merge request widget?** If yes, then check if 1. **Did you follow the URL from the merge request widget?** If yes, then check if
the link is the same as the one in the job output. It can happen that if the the link is the same as the one in the job output.
branch name slug is longer than 35 characters, it is automatically
truncated. That means that the merge request widget will not show the proper
URL due to a limitation of how `environment: url` works, but you can find the
real URL from the output of the `review-docs-deploy` job.
1. **Did you follow the URL from the job output?** If yes, then it means that 1. **Did you follow the URL from the job output?** If yes, then it means that
either the site is not yet deployed or something went wrong with the remote either the site is not yet deployed or something went wrong with the remote
pipeline. Give it a few minutes and it should appear online, otherwise you pipeline. Give it a few minutes and it should appear online, otherwise you
......
...@@ -21,7 +21,7 @@ and click a button to begin the upgrade process. ...@@ -21,7 +21,7 @@ and click a button to begin the upgrade process.
## Features ## Features
The GitLab Pivotal Tile is based on [GitLab Premium] and includes nearly all of its features. The features in Premium but _not_ supported on the Tile are: The GitLab Pivotal Tile is based on [GitLab Premium][eep] and includes nearly all of its features. The features in Premium but _not_ supported on the Tile are:
* PostgreSQL * PostgreSQL
* Pages * Pages
...@@ -41,5 +41,5 @@ website: ...@@ -41,5 +41,5 @@ website:
- [Product page](https://network.pivotal.io/products/p-gitlab/) - [Product page](https://network.pivotal.io/products/p-gitlab/)
- [Documentation](https://docs.pivotal.io/partners/gitlab/index.html) - [Documentation](https://docs.pivotal.io/partners/gitlab/index.html)
[premium]: https://about.gitlab.com/products/ [eep]: https://about.gitlab.com/products/
[pcf]: https://pivotal.io/platform [pcf]: https://pivotal.io/platform
...@@ -102,5 +102,5 @@ Click the links to see your GitLab repository data. ...@@ -102,5 +102,5 @@ Click the links to see your GitLab repository data.
[existing-jira]: ../user/project/integrations/jira.md [existing-jira]: ../user/project/integrations/jira.md
[jira-development-panel]: https://confluence.atlassian.com/adminjiraserver070/integrating-with-development-tools-776637096.html#Integratingwithdevelopmenttools-Developmentpanelonissues [jira-development-panel]: https://confluence.atlassian.com/adminjiraserver070/integrating-with-development-tools-776637096.html#Integratingwithdevelopmenttools-Developmentpanelonissues
[eep]: https://about.gitlab.com/products/ [eep]: https://about.gitlab.com/products/
[ee-2786]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2786 [ee-2381]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2381
[relative-url]: https://docs.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab [relative-url]: https://docs.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab
...@@ -230,6 +230,81 @@ considered `admin groups`. ...@@ -230,6 +230,81 @@ considered `admin groups`.
} } } }
``` ```
## Bypass two factor authentication
If you want some SAML authentication methods to count as 2FA on a per session basis, you can register them in the
`upstream_two_factor_authn_contexts` list:
**For Omnibus installations:**
1. Edit `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_rails['omniauth_providers'] = [
{
name: 'saml',
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
upstream_two_factor_authn_contexts:
%w(
urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport
urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS
urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN
)
},
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
]
```
1. Save the file and [reconfigure][] GitLab for the changes to take effect.
---
**For installations from source:**
1. Edit `config/gitlab.yml`:
```yaml
- {
name: 'saml',
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
upstream_two_factor_authn_contexts:
[
'urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport',
'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS',
'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN'
]
},
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
```
1. Save the file and [restart GitLab][] for the changes ot take effect
In addition to the changes in GitLab, make sure that your Idp is returning the
`AuthnContext`. For example:
```xml
<saml:AuthnStatement>
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:MediumStrongCertificateProtectedTransport</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
```
## Customization ## Customization
### `auto_sign_in_with_provider` ### `auto_sign_in_with_provider`
......
...@@ -113,7 +113,7 @@ by yourself (except when an issue is due). You will only receive automatic ...@@ -113,7 +113,7 @@ by yourself (except when an issue is due). You will only receive automatic
notifications when somebody else comments or adds changes to the ones that notifications when somebody else comments or adds changes to the ones that
you've created or mentions you. you've created or mentions you.
If a merge request becomes unmergeable, its author will be notified about the cause. If an open merge request becomes unmergeable due to conflict, its author will be notified about the cause.
If a user has also set the merge request to automatically merge once pipeline succeeds, If a user has also set the merge request to automatically merge once pipeline succeeds,
then that user will also be notified. then that user will also be notified.
......
...@@ -31,7 +31,7 @@ A Todo appears in your Todos dashboard when: ...@@ -31,7 +31,7 @@ A Todo appears in your Todos dashboard when:
- you are `@mentioned` in a comment on a commit, - you are `@mentioned` in a comment on a commit,
- a job in the CI pipeline running for your merge request failed, but this - a job in the CI pipeline running for your merge request failed, but this
job is not allowed to fail. job is not allowed to fail.
- a merge request becomes unmergeable, and you are either: - an open merge request becomes unmergeable due to conflict, and you are either:
- the author, or - the author, or
- have set it to automatically merge once pipeline succeeds. - have set it to automatically merge once pipeline succeeds.
......
...@@ -21,6 +21,11 @@ export default { ...@@ -21,6 +21,11 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
// failed || success
status: {
type: String,
required: true,
},
}, },
}; };
</script> </script>
...@@ -31,6 +36,7 @@ export default { ...@@ -31,6 +36,7 @@ export default {
<modal-open-name <modal-open-name
:issue="issue" :issue="issue"
:status="status"
class="js-modal-dast" class="js-modal-dast"
/> />
</div> </div>
......
...@@ -64,6 +64,7 @@ ...@@ -64,6 +64,7 @@
<modal <modal
id="modal-mrwidget-security-issue" id="modal-mrwidget-security-issue"
:header-title-text="modal.title" :header-title-text="modal.title"
:class="{'modal-hide-footer': modal.isResolved}"
class="modal-security-report-dast" class="modal-security-report-dast"
> >
<slot> <slot>
...@@ -202,39 +203,41 @@ ...@@ -202,39 +203,41 @@
</div> </div>
</slot> </slot>
<div slot="footer"> <div slot="footer">
<button <template v-if="!modal.isResolved">
type="button" <button
class="btn btn-default" type="button"
data-dismiss="modal" class="btn btn-default"
> data-dismiss="modal"
{{ __('Cancel' ) }} >
</button> {{ __('Cancel' ) }}
</button>
<loading-button <loading-button
v-if="canCreateFeedbackPermission" v-if="canCreateFeedbackPermission"
:loading="modal.isDismissingIssue" :loading="modal.isDismissingIssue"
:disabled="modal.isDismissingIssue" :disabled="modal.isDismissingIssue"
:label="revertTitle" :label="revertTitle"
container-class="js-dismiss-btn btn btn-close" container-class="js-dismiss-btn btn btn-close"
@click="handleDismissClick" @click="handleDismissClick"
/> />
<a <a
v-if="modal.vulnerability.hasIssue" v-if="modal.vulnerability.hasIssue"
:href="modal.vulnerability.issueFeedback && modal.vulnerability.issueFeedback.issue_url" :href="modal.vulnerability.issueFeedback && modal.vulnerability.issueFeedback.issue_url"
rel="noopener noreferrer nofollow" rel="noopener noreferrer nofollow"
class="btn btn-success btn-inverted" class="btn btn-success btn-inverted"
> >
{{ __('View issue' ) }} {{ __('View issue' ) }}
</a> </a>
<loading-button <loading-button
v-else-if="!modal.vulnerability.hasIssue && canCreateIssuePermission" v-else-if="!modal.vulnerability.hasIssue && canCreateIssuePermission"
:loading="modal.isCreatingNewIssue" :loading="modal.isCreatingNewIssue"
:disabled="modal.isCreatingNewIssue" :disabled="modal.isCreatingNewIssue"
:label="__('Create issue')" :label="__('Create issue')"
container-class="js-create-issue-btn btn btn-success btn-inverted" container-class="btn btn-success btn-inverted"
@click="createNewIssue" @click="createNewIssue"
/> />
</template>
</div> </div>
</modal> </modal>
</template> </template>
...@@ -7,11 +7,17 @@ export default { ...@@ -7,11 +7,17 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
// failed || success
status: {
type: String,
required: true,
},
}, },
methods: { methods: {
...mapActions(['openModal']), ...mapActions(['openModal']),
handleIssueClick() { handleIssueClick() {
this.openModal(this.issue); const { issue, status, openModal } = this;
openModal({ issue, status });
}, },
}, },
}; };
......
...@@ -109,17 +109,20 @@ export default { ...@@ -109,17 +109,20 @@ export default {
<sast-issue <sast-issue
v-if="isTypeSast" v-if="isTypeSast"
:issue="issue" :issue="issue"
:status="status"
/> />
<dast-issue <dast-issue
v-else-if="isTypeDast" v-else-if="isTypeDast"
:issue="issue" :issue="issue"
:issue-index="index" :issue-index="index"
:status="status"
/> />
<sast-container-issue <sast-container-issue
v-else-if="isTypeSastContainer" v-else-if="isTypeSastContainer"
:issue="issue" :issue="issue"
:status="status"
/> />
<codequality-issue <codequality-issue
......
...@@ -17,6 +17,11 @@ export default { ...@@ -17,6 +17,11 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
// failed || success
status: {
type: String,
required: true,
},
}, },
}; };
</script> </script>
...@@ -25,7 +30,10 @@ export default { ...@@ -25,7 +30,10 @@ export default {
<div class="report-block-list-issue-description-text"> <div class="report-block-list-issue-description-text">
<template v-if="issue.severity">{{ issue.severity }}:</template> <template v-if="issue.severity">{{ issue.severity }}:</template>
<modal-open-name :issue="issue" /> <modal-open-name
:issue="issue"
:status="status"
/>
</div> </div>
<report-link <report-link
......
...@@ -19,6 +19,11 @@ export default { ...@@ -19,6 +19,11 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
// failed || success
status: {
type: String,
required: true,
},
}, },
}; };
</script> </script>
...@@ -36,7 +41,10 @@ export default { ...@@ -36,7 +41,10 @@ export default {
</template> </template>
<template v-else-if="issue.priority">{{ issue.priority }}:</template> <template v-else-if="issue.priority">{{ issue.priority }}:</template>
<modal-open-name :issue="issue" /> <modal-open-name
:issue="issue"
:status="status"
/>
</div> </div>
<report-link <report-link
......
...@@ -204,13 +204,13 @@ export const fetchDependencyScanningReports = ({ state, dispatch }) => { ...@@ -204,13 +204,13 @@ export const fetchDependencyScanningReports = ({ state, dispatch }) => {
export const updateDependencyScanningIssue = ({ commit }, issue) => export const updateDependencyScanningIssue = ({ commit }, issue) =>
commit(types.UPDATE_DEPENDENCY_SCANNING_ISSUE, issue); commit(types.UPDATE_DEPENDENCY_SCANNING_ISSUE, issue);
export const openModal = ({ dispatch }, issue) => { export const openModal = ({ dispatch }, payload) => {
dispatch('setModalData', issue); dispatch('setModalData', payload);
$('#modal-mrwidget-security-issue').modal('show'); $('#modal-mrwidget-security-issue').modal('show');
}; };
export const setModalData = ({ commit }, issue) => commit(types.SET_ISSUE_MODAL_DATA, issue); export const setModalData = ({ commit }, payload) => commit(types.SET_ISSUE_MODAL_DATA, payload);
export const requestDismissIssue = ({ commit }) => commit(types.REQUEST_DISMISS_ISSUE); export const requestDismissIssue = ({ commit }) => commit(types.REQUEST_DISMISS_ISSUE);
export const receiveDismissIssue = ({ commit }) => commit(types.RECEIVE_DISMISS_ISSUE_SUCCESS); export const receiveDismissIssue = ({ commit }) => commit(types.RECEIVE_DISMISS_ISSUE_SUCCESS);
export const receiveDismissIssueError = ({ commit }, error) => export const receiveDismissIssueError = ({ commit }, error) =>
......
...@@ -255,7 +255,9 @@ export default { ...@@ -255,7 +255,9 @@ export default {
state.dependencyScanning.hasError = true; state.dependencyScanning.hasError = true;
}, },
[types.SET_ISSUE_MODAL_DATA](state, issue) { [types.SET_ISSUE_MODAL_DATA](state, payload) {
const { issue, status } = payload;
state.modal.title = issue.title; state.modal.title = issue.title;
state.modal.data.description.value = issue.description; state.modal.data.description.value = issue.description;
state.modal.data.file.value = issue.location && issue.location.file; state.modal.data.file.value = issue.location && issue.location.file;
...@@ -280,6 +282,7 @@ export default { ...@@ -280,6 +282,7 @@ export default {
} }
state.modal.data.instances.value = issue.instances; state.modal.data.instances.value = issue.instances;
state.modal.vulnerability = issue; state.modal.vulnerability = issue;
state.modal.isResolved = status === 'success';
// clear previous state // clear previous state
state.modal.error = null; state.modal.error = null;
......
...@@ -139,4 +139,9 @@ ...@@ -139,4 +139,9 @@
width: $modal-lg; width: $modal-lg;
max-width: $modal-lg; max-width: $modal-lg;
} }
// This is temporary till we get the new modals hooked up
&.modal-hide-footer .modal-footer {
display: none;
}
} }
...@@ -20,6 +20,10 @@ module EE ...@@ -20,6 +20,10 @@ module EE
attrs << :email_additional_text attrs << :email_additional_text
end end
if License.feature_available?(:pseudonymizer)
attrs << :pseudonymizer_enabled
end
attrs attrs
end end
end end
......
...@@ -3,6 +3,14 @@ module EE ...@@ -3,6 +3,14 @@ module EE
module Admin module Admin
module DashboardController module DashboardController
extend ActiveSupport::Concern extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
override :index
def index
super
@license = License.current
end
def stats def stats
@admin_count = ::User.admins.count @admin_count = ::User.admins.count
......
...@@ -35,6 +35,18 @@ module EE ...@@ -35,6 +35,18 @@ module EE
"and the value is encrypted at rest.") "and the value is encrypted at rest.")
end end
def pseudonymizer_enabled_help_text
_("Enable Pseudonymizer data collection")
end
def pseudonymizer_description_text
_("GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory.")
end
def pseudonymizer_disabled_description_text
_("The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory.")
end
override :visible_attributes override :visible_attributes
def visible_attributes def visible_attributes
super + [ super + [
...@@ -55,7 +67,8 @@ module EE ...@@ -55,7 +67,8 @@ module EE
:slack_app_id, :slack_app_id,
:slack_app_secret, :slack_app_secret,
:slack_app_verification_token, :slack_app_verification_token,
:allow_group_owners_to_manage_ldap :allow_group_owners_to_manage_ldap,
:pseudonymizer_enabled
] ]
end end
......
...@@ -100,11 +100,20 @@ module EE ...@@ -100,11 +100,20 @@ module EE
slack_app_enabled: false, slack_app_enabled: false,
slack_app_id: nil, slack_app_id: nil,
slack_app_secret: nil, slack_app_secret: nil,
slack_app_verification_token: nil slack_app_verification_token: nil,
pseudonymizer_enabled: false
) )
end end
end end
def pseudonymizer_available?
License.feature_available?(:pseudonymizer)
end
def pseudonymizer_enabled?
pseudonymizer_available? && super
end
def should_check_namespace_plan? def should_check_namespace_plan?
check_namespace_plan? && (Rails.env.test? || ::Gitlab.dev_env_or_com?) check_namespace_plan? && (Rails.env.test? || ::Gitlab.dev_env_or_com?)
end end
......
...@@ -73,6 +73,7 @@ class License < ActiveRecord::Base ...@@ -73,6 +73,7 @@ class License < ActiveRecord::Base
ide ide
chatops chatops
pod_logs pod_logs
pseudonymizer
].freeze ].freeze
# List all features available for early adopters, # List all features available for early adopters,
......
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group.row
.offset-sm-2.col-sm-10
- is_enabled = @application_setting.pseudonymizer_enabled?
.form-check
= f.label :pseudonymizer_enabled do
= f.check_box :pseudonymizer_enabled
= pseudonymizer_enabled_help_text
.form-text.text-muted
- if is_enabled
= pseudonymizer_description_text
- else
= pseudonymizer_disabled_description_text
= f.submit 'Save changes', class: "btn btn-success"
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment