summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc.yml6
-rw-r--r--.gitlab/issue_templates/Acceptance_Testing.md12
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/event_tracking/issue_sidebar.js2
-rw-r--r--app/assets/javascripts/event_tracking/notes.js2
-rw-r--r--app/assets/javascripts/filterable_list.js1
-rw-r--r--app/assets/javascripts/integrations/integration_settings_form.js1
-rw-r--r--app/assets/javascripts/issue_show/index.js4
-rw-r--r--app/assets/javascripts/main.js1
-rw-r--r--app/assets/javascripts/notes.js1
-rw-r--r--app/assets/javascripts/notes/components/note_actions/reply_button.vue4
-rw-r--r--app/assets/javascripts/notes/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/commit/pipelines/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/commit/show/index.js1
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_title.vue10
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue11
-rw-r--r--app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue11
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue8
-rw-r--r--app/assets/javascripts/tracking.js103
-rw-r--r--app/assets/stylesheets/framework/flash.scss2
-rw-r--r--app/models/repository.rb28
-rw-r--r--changelogs/unreleased/add-first-parent-to-find-commits.yml5
-rw-r--r--config/application.rb49
-rw-r--r--doc/api/commits.md3
-rw-r--r--lib/api/commits.rb9
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb4
-rw-r--r--package.json1
-rw-r--r--spec/frontend/tracking_spec.js81
-rw-r--r--spec/javascripts/helpers/tracking_helper.js25
-rw-r--r--spec/javascripts/integrations/integration_settings_form_spec.js1
-rw-r--r--spec/javascripts/sidebar/assignee_title_spec.js14
-rw-r--r--spec/javascripts/sidebar/confidential_issue_sidebar_spec.js13
-rw-r--r--spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js13
-rw-r--r--spec/javascripts/sidebar/subscriptions_spec.js9
-rw-r--r--spec/models/repository_spec.rb4
-rw-r--r--spec/requests/api/commits_spec.rb12
-rw-r--r--yarn.lock5
38 files changed, 277 insertions, 190 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml
index 70a71baa590..59eec634e8b 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -10,6 +10,7 @@ plugins:
- import
- "@gitlab/i18n"
- "@gitlab/vue-i18n"
+ - no-jquery
settings:
import/resolver:
webpack:
@@ -36,6 +37,11 @@ rules:
vue/no-use-v-if-with-v-for: off
vue/no-v-html: off
vue/use-v-on-exact: off
+ no-jquery/no-ajax: error
+ no-jquery/no-ajax-events: error
+ no-jquery/no-load: error
+ no-jquery/no-load-shorthand: error
+ no-jquery/no-serialize: error
overrides:
files:
- '**/spec/**/*'
diff --git a/.gitlab/issue_templates/Acceptance_Testing.md b/.gitlab/issue_templates/Acceptance_Testing.md
index f1fbb96ce61..5a6c35f28ad 100644
--- a/.gitlab/issue_templates/Acceptance_Testing.md
+++ b/.gitlab/issue_templates/Acceptance_Testing.md
@@ -25,7 +25,7 @@ Then leave running while monitoring and performing some testing through web, api
- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
-- [ ] [Check for errors in GitLab Dev Sentry](https://sentry.gitlap.com/gitlab/devgitlaborg/?query=is%3Aunresolved)
+- [ ] [Check for errors in GitLab Dev Sentry](https://sentry.gitlab.net/gitlab/devgitlaborg/?query=is%3Aunresolved)
## 2. Staging Trial
@@ -41,7 +41,7 @@ Then leave running while monitoring for at least **15 minutes** while performing
- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
-- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
+- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlab.net/gitlab/gitlabcom/?query=is%3Aunresolved)
## 4. Production Server Version Check
@@ -57,7 +57,7 @@ Then leave running while monitoring for at least **15 minutes** while performing
- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
-- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
+- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlab.net/gitlab/gitlabcom/?query=is%3Aunresolved)
## 6. Low Impact Check
@@ -69,7 +69,7 @@ Then leave running while monitoring for at least **30 minutes** while performing
- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
-- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
+- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlab.net/gitlab/gitlabcom/?query=is%3Aunresolved)
## 7. Mid Impact Trial
@@ -81,7 +81,7 @@ Then leave running while monitoring for at least **12 hours** while performing s
- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
-- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
+- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlab.net/gitlab/gitlabcom/?query=is%3Aunresolved)
## 8. Full Impact Trial
@@ -93,7 +93,7 @@ Then leave running while monitoring for at least **1 week**.
- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
-- [ ] [Check for errors in GitLab Dev Sentry](https://sentry.gitlap.com/gitlab/devgitlaborg/?query=is%3Aunresolved)
+- [ ] [Check for errors in GitLab Dev Sentry](https://sentry.gitlab.net/gitlab/devgitlaborg/?query=is%3Aunresolved)
#### Success?
diff --git a/Gemfile b/Gemfile
index 1901455af07..5a3a6791b29 100644
--- a/Gemfile
+++ b/Gemfile
@@ -446,7 +446,7 @@ group :ed25519 do
end
# Gitaly GRPC protocol definitions
-gem 'gitaly', '~> 1.58.0'
+gem 'gitaly', '~> 1.65.0'
gem 'grpc', '~> 1.19.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 7b450c262da..3142ba094cd 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -358,7 +358,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
git (1.5.0)
- gitaly (1.58.0)
+ gitaly (1.65.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-labkit (0.5.2)
@@ -1168,7 +1168,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly (~> 1.58.0)
+ gitaly (~> 1.65.0)
github-markup (~> 1.7.0)
gitlab-labkit (~> 0.5)
gitlab-license (~> 1.0)
diff --git a/app/assets/javascripts/event_tracking/issue_sidebar.js b/app/assets/javascripts/event_tracking/issue_sidebar.js
deleted file mode 100644
index 6909f82c66f..00000000000
--- a/app/assets/javascripts/event_tracking/issue_sidebar.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export const initSidebarTracking = () => {};
-export const trackEvent = () => {};
diff --git a/app/assets/javascripts/event_tracking/notes.js b/app/assets/javascripts/event_tracking/notes.js
deleted file mode 100644
index 1f70290c397..00000000000
--- a/app/assets/javascripts/event_tracking/notes.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// Noop function which has a EE counter-part
-export default () => {};
diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js
index 77080691dcb..c21fba06d42 100644
--- a/app/assets/javascripts/filterable_list.js
+++ b/app/assets/javascripts/filterable_list.js
@@ -22,6 +22,7 @@ export default class FilterableList {
getPagePath() {
const action = this.filterForm.getAttribute('action');
+ // eslint-disable-next-line no-jquery/no-serialize
const params = $(this.filterForm).serialize();
return `${action}${action.indexOf('?') > 0 ? '&' : '?'}${params}`;
}
diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js
index a7746bb3a0b..1c9b94ade8a 100644
--- a/app/assets/javascripts/integrations/integration_settings_form.js
+++ b/app/assets/javascripts/integrations/integration_settings_form.js
@@ -42,6 +42,7 @@ export default class IntegrationSettingsForm {
// and test the service using provided configuration.
if (this.$form.get(0).checkValidity() && this.canTestService) {
e.preventDefault();
+ // eslint-disable-next-line no-jquery/no-serialize
this.testSettings(this.$form.serialize());
}
}
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index 4bcba72bc23..e170d338408 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -1,5 +1,4 @@
import Vue from 'vue';
-import { initSidebarTracking } from 'ee_else_ce/event_tracking/issue_sidebar';
import issuableApp from './components/app.vue';
import { parseIssuableData } from './utils/parse_data';
@@ -9,9 +8,6 @@ export default function initIssueableApp() {
components: {
issuableApp,
},
- mounted() {
- initSidebarTracking();
- },
render(createElement) {
return createElement('issuable-app', {
props: parseIssuableData(),
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 49be11d466d..c19a845eb69 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -314,6 +314,7 @@ document.addEventListener('DOMContentLoaded', () => {
const action = `${this.action}${link.search === '' ? '?' : '&'}`;
event.preventDefault();
+ // eslint-disable-next-line no-jquery/no-serialize
visitUrl(`${action}${$(this).serialize()}`);
});
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 9cc56b34c75..ed52eec8b18 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1461,6 +1461,7 @@ export default class Notes {
getFormData($form) {
const content = $form.find('.js-note-text').val();
return {
+ // eslint-disable-next-line no-jquery/no-serialize
formData: $form.serialize(),
formContent: _.escape(content),
formAction: $form.attr('action'),
diff --git a/app/assets/javascripts/notes/components/note_actions/reply_button.vue b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
index 1aeb07d6608..8bdee759a23 100644
--- a/app/assets/javascripts/notes/components/note_actions/reply_button.vue
+++ b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
@@ -19,7 +19,9 @@ export default {
<gl-button
ref="button"
v-gl-tooltip
- class="note-action-button js-note-action-reply"
+ class="note-action-button"
+ data-track-event="click_button"
+ data-track-label="reply_comment_button"
variant="transparent"
:title="__('Reply to comment')"
@click="$emit('startReplying')"
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index c70c0e4095c..30372103590 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -1,5 +1,4 @@
import Vue from 'vue';
-import initNoteStats from 'ee_else_ce/event_tracking/notes';
import notesApp from './components/notes_app.vue';
import initDiscussionFilters from './discussion_filters';
import createStore from './stores';
@@ -39,9 +38,6 @@ document.addEventListener('DOMContentLoaded', () => {
notesData: JSON.parse(notesDataset.notesData),
};
},
- mounted() {
- initNoteStats();
- },
render(createElement) {
return createElement('notes-app', {
props: {
diff --git a/app/assets/javascripts/pages/projects/commit/pipelines/index.js b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
index 8cc3cb0a57c..9f08260c3d6 100644
--- a/app/assets/javascripts/pages/projects/commit/pipelines/index.js
+++ b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
@@ -6,6 +6,7 @@ document.addEventListener('DOMContentLoaded', () => {
new MiniPipelineGraph({
container: '.js-commit-pipeline-graph',
}).bindEvents();
+ // eslint-disable-next-line no-jquery/no-load
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
initPipelines();
});
diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js
index 6fc982967eb..5aa4734244e 100644
--- a/app/assets/javascripts/pages/projects/commit/show/index.js
+++ b/app/assets/javascripts/pages/projects/commit/show/index.js
@@ -21,6 +21,7 @@ document.addEventListener('DOMContentLoaded', () => {
}).bindEvents();
initNotes();
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight + performanceHeight);
+ // eslint-disable-next-line no-jquery/no-load
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
fetchCommitMergeRequests();
initDiffNotes();
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
index 63b93a80ead..f4dac38b9e1 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
@@ -1,6 +1,5 @@
<script>
import { n__ } from '~/locale';
-import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar';
export default {
name: 'AssigneeTitle',
@@ -30,11 +29,6 @@ export default {
return n__('Assignee', `%d Assignees`, assignees);
},
},
- methods: {
- trackEdit() {
- trackEvent('click_edit_button', 'assignee');
- },
- },
};
</script>
<template>
@@ -45,7 +39,9 @@ export default {
v-if="editable"
class="js-sidebar-dropdown-toggle edit-link float-right"
href="#"
- @click.prevent="trackEdit"
+ data-track-event="click_edit_button"
+ data-track-label="right_sidebar"
+ data-track-property="assignee"
>
{{ __('Edit') }}
</a>
diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
index 1c75b6148e8..e350264de96 100644
--- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
@@ -5,7 +5,6 @@ import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '~/sidebar/event_hub';
import editForm from './edit_form.vue';
-import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar';
export default {
components: {
@@ -52,11 +51,6 @@ export default {
toggleForm() {
this.edit = !this.edit;
},
- onEditClick() {
- this.toggleForm();
-
- trackEvent('click_edit_button', 'confidentiality');
- },
updateConfidentialAttribute(confidential) {
this.service
.update('issue', { confidential })
@@ -88,7 +82,10 @@ export default {
v-if="isEditable"
class="float-right confidential-edit"
href="#"
- @click.prevent="onEditClick"
+ data-track-event="click_edit_button"
+ data-track-label="right_sidebar"
+ data-track-property="confidentiality"
+ @click.prevent="toggleForm"
>
{{ __('Edit') }}
</a>
diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
index ec2a7b93a98..c7c5e0e20f1 100644
--- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
@@ -6,7 +6,6 @@ import issuableMixin from '~/vue_shared/mixins/issuable';
import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '~/sidebar/event_hub';
import editForm from './edit_form.vue';
-import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar';
export default {
components: {
@@ -66,11 +65,6 @@ export default {
toggleForm() {
this.mediator.store.isLockDialogOpen = !this.mediator.store.isLockDialogOpen;
},
- onEditClick() {
- this.toggleForm();
-
- trackEvent('click_edit_button', 'lock_issue');
- },
updateLockedAttribute(locked) {
this.mediator.service
.update(this.issuableType, {
@@ -114,7 +108,10 @@ export default {
v-if="isEditable"
class="float-right lock-edit"
type="button"
- @click.prevent="onEditClick"
+ data-track-event="click_edit_button"
+ data-track-label="right_sidebar"
+ data-track-property="lock_issue"
+ @click.prevent="toggleForm"
>
{{ __('Edit') }}
</button>
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
index 1f5f19d1931..ea5edb3ce3f 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
@@ -1,10 +1,10 @@
<script>
import { __ } from '~/locale';
+import Tracking from '~/tracking';
import icon from '~/vue_shared/components/icon.vue';
import toggleButton from '~/vue_shared/components/toggle_button.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
-import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar';
const ICON_ON = 'notifications';
const ICON_OFF = 'notifications-off';
@@ -19,6 +19,7 @@ export default {
icon,
toggleButton,
},
+ mixins: [Tracking.mixin({ label: 'right_sidebar' })],
props: {
loading: {
type: Boolean,
@@ -65,7 +66,10 @@ export default {
// Component event emission.
this.$emit('toggleSubscription', this.id);
- trackEvent('toggle_button', 'notifications', this.subscribed ? 0 : 1);
+ this.track('toggle_button', {
+ property: 'notifications',
+ value: this.subscribed ? 0 : 1,
+ });
},
onClickCollapsedIcon() {
this.$emit('toggleSidebar');
diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js
index 1b4ca1d5741..7c0097fbe37 100644
--- a/app/assets/javascripts/tracking.js
+++ b/app/assets/javascripts/tracking.js
@@ -1,4 +1,4 @@
-import $ from 'jquery';
+import _ from 'underscore';
const DEFAULT_SNOWPLOW_OPTIONS = {
namespace: 'gl',
@@ -14,18 +14,31 @@ const DEFAULT_SNOWPLOW_OPTIONS = {
linkClickTracking: false,
};
-const extractData = (el, opts = {}) => {
- const { trackEvent, trackLabel = '', trackProperty = '' } = el.dataset;
- let trackValue = el.dataset.trackValue || el.value || '';
- if (el.type === 'checkbox' && !el.checked) trackValue = false;
- return [
- trackEvent + (opts.suffix || ''),
- {
- label: trackLabel,
- property: trackProperty,
- value: trackValue,
- },
- ];
+const eventHandler = (e, func, opts = {}) => {
+ const el = e.target.closest('[data-track-event]');
+ const action = el && el.dataset.trackEvent;
+ if (!action) return;
+
+ let value = el.dataset.trackValue || el.value || undefined;
+ if (el.type === 'checkbox' && !el.checked) value = false;
+
+ const data = {
+ label: el.dataset.trackLabel,
+ property: el.dataset.trackProperty,
+ value,
+ context: el.dataset.trackContext,
+ };
+
+ func(opts.category, action + (opts.suffix || ''), _.omit(data, _.isUndefined));
+};
+
+const eventHandlers = (category, func) => {
+ const handler = opts => e => eventHandler(e, func, { ...{ category }, ...opts });
+ const handlers = [];
+ handlers.push({ name: 'click', func: handler() });
+ handlers.push({ name: 'show.bs.dropdown', func: handler({ suffix: '_show' }) });
+ handlers.push({ name: 'hide.bs.dropdown', func: handler({ suffix: '_hide' }) });
+ return handlers;
};
export default class Tracking {
@@ -39,49 +52,43 @@ export default class Tracking {
return typeof window.snowplow === 'function' && this.trackable();
}
- static event(category = document.body.dataset.page, event = 'generic', data = {}) {
+ static event(category = document.body.dataset.page, action = 'generic', data = {}) {
if (!this.enabled()) return false;
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
if (!category) throw new Error('Tracking: no category provided for tracking.');
- return window.snowplow(
- 'trackStructEvent',
- category,
- event,
- Object.assign({}, { label: '', property: '', value: '' }, data),
- );
+ const { label, property, value, context } = data;
+ const contexts = context ? [context] : undefined;
+ return window.snowplow('trackStructEvent', category, action, label, property, value, contexts);
}
- constructor(category = document.body.dataset.page) {
- this.category = category;
- }
-
- bind(container = document) {
- if (!this.constructor.enabled()) return;
- container.querySelectorAll(`[data-track-event]`).forEach(el => {
- if (this.customHandlingFor(el)) return;
- // jquery is required for select2, so we use it always
- // see: https://github.com/select2/select2/issues/4686
- $(el).on('click', this.eventHandler(this.category));
- });
- }
+ static bindDocument(category = document.body.dataset.page, documentOverride = null) {
+ const el = documentOverride || document;
+ if (!this.enabled() || el.trackingBound) return [];
- customHandlingFor(el) {
- const classes = el.classList;
+ el.trackingBound = true;
- // bootstrap dropdowns
- if (classes.contains('dropdown')) {
- $(el).on('show.bs.dropdown', this.eventHandler(this.category, { suffix: '_show' }));
- $(el).on('hide.bs.dropdown', this.eventHandler(this.category, { suffix: '_hide' }));
- return true;
- }
-
- return false;
+ const handlers = eventHandlers(category, (...args) => this.event(...args));
+ handlers.forEach(event => el.addEventListener(event.name, event.func));
+ return handlers;
}
- eventHandler(category = null, opts = {}) {
- return e => {
- this.constructor.event(category || this.category, ...extractData(e.currentTarget, opts));
+ static mixin(opts) {
+ return {
+ data() {
+ return {
+ tracking: {
+ // eslint-disable-next-line no-underscore-dangle
+ category: this.$options.name || this.$options._componentTag,
+ },
+ };
+ },
+ methods: {
+ track(action, data) {
+ const category = opts.category || data.category || this.tracking.category;
+ Tracking.event(category || 'unspecified', action, { ...opts, ...this.tracking, ...data });
+ },
+ },
};
}
}
@@ -89,7 +96,7 @@ export default class Tracking {
export function initUserTracking() {
if (!Tracking.enabled()) return;
- const opts = Object.assign({}, DEFAULT_SNOWPLOW_OPTIONS, window.snowplowOptions);
+ const opts = { ...DEFAULT_SNOWPLOW_OPTIONS, ...window.snowplowOptions };
window.snowplow('newTracker', opts.namespace, opts.hostname, opts);
window.snowplow('enableActivityTracking', 30, 30);
@@ -97,4 +104,6 @@ export function initUserTracking() {
if (opts.formTracking) window.snowplow('enableFormTracking');
if (opts.linkClickTracking) window.snowplow('enableLinkClickTracking');
+
+ Tracking.bindDocument();
}
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 7e7b08797b2..13a2a74fdab 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -12,7 +12,7 @@ $notification-box-shadow-color: rgba(0, 0, 0, 0.25);
position: sticky;
position: -webkit-sticky;
top: $flash-container-top;
- z-index: 200;
+ z-index: 251;
.flash-content {
box-shadow: 0 2px 4px 0 $notification-box-shadow-color;
diff --git a/app/models/repository.rb b/app/models/repository.rb
index f084a314392..96b1b55e2b1 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -133,18 +133,28 @@ class Repository
end
end
- def commits(ref = nil, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil, all: nil)
+ # the opts are:
+ # - :path
+ # - :limit
+ # - :offset
+ # - :skip_merges
+ # - :after
+ # - :before
+ # - :all
+ # - :first_parent
+ def commits(ref = nil, opts = {})
options = {
repo: raw_repository,
ref: ref,
- path: path,
- limit: limit,
- offset: offset,
- after: after,
- before: before,
- follow: Array(path).length == 1,
- skip_merges: skip_merges,
- all: all
+ path: opts[:path],
+ follow: Array(opts[:path]).length == 1,
+ limit: opts[:limit],
+ offset: opts[:offset],
+ skip_merges: !!opts[:skip_merges],
+ after: opts[:after],
+ before: opts[:before],
+ all: !!opts[:all],
+ first_parent: !!opts[:first_parent]
}
commits = Gitlab::Git::Commit.where(options)
diff --git a/changelogs/unreleased/add-first-parent-to-find-commits.yml b/changelogs/unreleased/add-first-parent-to-find-commits.yml
new file mode 100644
index 00000000000..076eed90f68
--- /dev/null
+++ b/changelogs/unreleased/add-first-parent-to-find-commits.yml
@@ -0,0 +1,5 @@
+---
+title: Add first_parent option to list commits api
+merge_request: 32410
+author: jhenkens
+type: added
diff --git a/config/application.rb b/config/application.rb
index c1e3b6f7a20..5d7c52c5d81 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -13,6 +13,7 @@ Bundler.require(*Rails.groups)
module Gitlab
class Application < Rails::Application
require_dependency Rails.root.join('lib/gitlab')
+ require_dependency Rails.root.join('lib/gitlab/utils')
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/queues')
@@ -46,18 +47,20 @@ module Gitlab
config.generators.templates.push("#{config.root}/generator_templates")
- ee_paths = config.eager_load_paths.each_with_object([]) do |path, memo|
- ee_path = config.root.join('ee', Pathname.new(path).relative_path_from(config.root))
- memo << ee_path.to_s if ee_path.exist?
- end
+ if Gitlab.ee?
+ ee_paths = config.eager_load_paths.each_with_object([]) do |path, memo|
+ ee_path = config.root.join('ee', Pathname.new(path).relative_path_from(config.root))
+ memo << ee_path.to_s
+ end
- # Eager load should load CE first
- config.eager_load_paths.push(*ee_paths)
- config.helpers_paths.push "#{config.root}/ee/app/helpers"
+ # Eager load should load CE first
+ config.eager_load_paths.push(*ee_paths)
+ config.helpers_paths.push "#{config.root}/ee/app/helpers"
- # Other than Ruby modules we load EE first
- config.paths['lib/tasks'].unshift "#{config.root}/ee/lib/tasks"
- config.paths['app/views'].unshift "#{config.root}/ee/app/views"
+ # Other than Ruby modules we load EE first
+ config.paths['lib/tasks'].unshift "#{config.root}/ee/lib/tasks"
+ config.paths['app/views'].unshift "#{config.root}/ee/app/views"
+ end
# Rake tasks ignore the eager loading settings, so we need to set the
# autoload paths explicitly
@@ -178,16 +181,18 @@ module Gitlab
config.assets.paths << "#{config.root}/node_modules/xterm/src/"
config.assets.precompile << "xterm.css"
- %w[images javascripts stylesheets].each do |path|
- config.assets.paths << "#{config.root}/ee/app/assets/#{path}"
- config.assets.precompile << "jira_connect.js"
- config.assets.precompile << "pages/jira_connect.css"
+ if Gitlab.ee?
+ %w[images javascripts stylesheets].each do |path|
+ config.assets.paths << "#{config.root}/ee/app/assets/#{path}"
+ config.assets.precompile << "jira_connect.js"
+ config.assets.precompile << "pages/jira_connect.css"
+ end
end
# Import path for EE specific SCSS entry point
# In CE it will import a noop file, in EE a functioning file
# Order is important, so that the ee file takes precedence:
- config.assets.paths << "#{config.root}/ee/app/assets/stylesheets/_ee"
+ config.assets.paths << "#{config.root}/ee/app/assets/stylesheets/_ee" if Gitlab.ee?
config.assets.paths << "#{config.root}/app/assets/stylesheets/_ee"
config.assets.paths << "#{config.root}/vendor/assets/javascripts/"
@@ -197,13 +202,15 @@ module Gitlab
# See https://gitlab.com/gitlab-org/gitlab-foss/issues/64091#note_194512508
config.assets.paths << "#{config.root}/node_modules"
- # Compile non-JS/CSS assets in the ee/app/assets folder by default
- # Mimic sprockets-rails default: https://github.com/rails/sprockets-rails/blob/v3.2.1/lib/sprockets/railtie.rb#L84-L87
- LOOSE_EE_APP_ASSETS = lambda do |logical_path, filename|
- filename.start_with?(config.root.join("ee/app/assets").to_s) &&
- !['.js', '.css', ''].include?(File.extname(logical_path))
+ if Gitlab.ee?
+ # Compile non-JS/CSS assets in the ee/app/assets folder by default
+ # Mimic sprockets-rails default: https://github.com/rails/sprockets-rails/blob/v3.2.1/lib/sprockets/railtie.rb#L84-L87
+ LOOSE_EE_APP_ASSETS = lambda do |logical_path, filename|
+ filename.start_with?(config.root.join("ee/app/assets").to_s) &&
+ !['.js', '.css', ''].include?(File.extname(logical_path))
+ end
+ config.assets.precompile << LOOSE_EE_APP_ASSETS
end
- config.assets.precompile << LOOSE_EE_APP_ASSETS
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
diff --git a/doc/api/commits.md b/doc/api/commits.md
index b41409b4b92..3927a4bbc62 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -11,12 +11,13 @@ GET /projects/:id/repository/commits
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
-| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
+| `ref_name` | string | no | The name of a repository branch, tag or revision range, or if not given the default branch |
| `since` | string | no | Only commits after or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
| `until` | string | no | Only commits before or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
| `path` | string | no | The file path |
| `all` | boolean | no | Retrieve every commit from the repository |
| `with_stats` | boolean | no | Stats about each commit will be added to the response |
+| `first_parent` | boolean | no | Follow only the first parent commit upon seeing a merge commit |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/commits"
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index a2f3e87ebd2..ffff40141de 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -37,6 +37,7 @@ module API
optional :path, type: String, desc: 'The file path'
optional :all, type: Boolean, desc: 'Every commit will be returned'
optional :with_stats, type: Boolean, desc: 'Stats about each commit will be added to the response'
+ optional :first_parent, type: Boolean, desc: 'Only include the first parent of merges'
use :pagination
end
get ':id/repository/commits' do
@@ -47,6 +48,7 @@ module API
offset = (params[:page] - 1) * params[:per_page]
all = params[:all]
with_stats = params[:with_stats]
+ first_parent = params[:first_parent]
commits = user_project.repository.commits(ref,
path: path,
@@ -54,11 +56,12 @@ module API
offset: offset,
before: before,
after: after,
- all: all)
+ all: all,
+ first_parent: first_parent)
commit_count =
- if all || path || before || after
- user_project.repository.count_commits(ref: ref, path: path, before: before, after: after, all: all)
+ if all || path || before || after || first_parent
+ user_project.repository.count_commits(ref: ref, path: path, before: before, after: after, all: all, first_parent: first_parent)
else
# Cacheable commit count.
user_project.repository.commit_count_for_ref(ref)
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 9d4c04b0591..2eaf52355dd 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -140,7 +140,8 @@ module Gitlab
request = Gitaly::CountCommitsRequest.new(
repository: @gitaly_repo,
revision: encode_binary(ref),
- all: !!options[:all]
+ all: !!options[:all],
+ first_parent: !!options[:first_parent]
)
request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present?
request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present?
@@ -325,6 +326,7 @@ module Gitlab
follow: options[:follow],
skip_merges: options[:skip_merges],
all: !!options[:all],
+ first_parent: !!options[:first_parent],
disable_walk: true # This option is deprecated. The 'walk' implementation is being removed.
)
request.after = GitalyClient.timestamp(options[:after]) if options[:after]
diff --git a/package.json b/package.json
index 3e9b7fca773..9e1ea86b692 100644
--- a/package.json
+++ b/package.json
@@ -166,6 +166,7 @@
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jasmine": "^2.10.1",
"eslint-plugin-jest": "^22.3.0",
+ "eslint-plugin-no-jquery": "^2.1.0",
"gettext-extractor": "^3.4.3",
"gettext-extractor-vue": "^4.0.2",
"graphql-tag": "^2.10.0",
diff --git a/spec/frontend/tracking_spec.js b/spec/frontend/tracking_spec.js
index dfc068ab6ea..964f8b8787e 100644
--- a/spec/frontend/tracking_spec.js
+++ b/spec/frontend/tracking_spec.js
@@ -1,10 +1,9 @@
-import $ from 'jquery';
import { setHTMLFixture } from './helpers/fixtures';
-
import Tracking, { initUserTracking } from '~/tracking';
describe('Tracking', () => {
let snowplowSpy;
+ let bindDocumentSpy;
beforeEach(() => {
window.snowplow = window.snowplow || (() => {});
@@ -17,6 +16,10 @@ describe('Tracking', () => {
});
describe('initUserTracking', () => {
+ beforeEach(() => {
+ bindDocumentSpy = jest.spyOn(Tracking, 'bindDocument').mockImplementation(() => null);
+ });
+
it('calls through to get a new tracker with the expected options', () => {
initUserTracking();
expect(snowplowSpy).toHaveBeenCalledWith('newTracker', '_namespace_', 'app.gitfoo.com', {
@@ -50,6 +53,11 @@ describe('Tracking', () => {
expect(snowplowSpy).toHaveBeenCalledWith('enableFormTracking');
expect(snowplowSpy).toHaveBeenCalledWith('enableLinkClickTracking');
});
+
+ it('binds the document event handling', () => {
+ initUserTracking();
+ expect(bindDocumentSpy).toHaveBeenCalled();
+ });
});
describe('.event', () => {
@@ -62,11 +70,15 @@ describe('Tracking', () => {
it('tracks to snowplow (our current tracking system)', () => {
Tracking.event('_category_', '_eventName_', { label: '_label_' });
- expect(snowplowSpy).toHaveBeenCalledWith('trackStructEvent', '_category_', '_eventName_', {
- label: '_label_',
- property: '',
- value: '',
- });
+ expect(snowplowSpy).toHaveBeenCalledWith(
+ 'trackStructEvent',
+ '_category_',
+ '_eventName_',
+ '_label_',
+ undefined,
+ undefined,
+ undefined,
+ );
});
it('skips tracking if snowplow is unavailable', () => {
@@ -99,83 +111,70 @@ describe('Tracking', () => {
});
describe('tracking interface events', () => {
- let eventSpy = null;
- let subject = null;
+ let eventSpy;
+
+ const trigger = (selector, eventName = 'click') => {
+ const event = new Event(eventName, { bubbles: true });
+ document.querySelector(selector).dispatchEvent(event);
+ };
beforeEach(() => {
eventSpy = jest.spyOn(Tracking, 'event');
- subject = new Tracking('_category_');
+ Tracking.bindDocument('_category_'); // only happens once
setHTMLFixture(`
<input data-track-event="click_input1" data-track-label="_label_" value="_value_"/>
<input data-track-event="click_input2" data-track-value="_value_override_" value="_value_"/>
<input type="checkbox" data-track-event="toggle_checkbox" value="_value_" checked/>
<input class="dropdown" data-track-event="toggle_dropdown"/>
- <div class="js-projects-list-holder"></div>
+ <div data-track-event="nested_event"><span class="nested"></span></div>
`);
});
it('binds to clicks on elements matching [data-track-event]', () => {
- subject.bind(document);
- $('[data-track-event="click_input1"]').click();
+ trigger('[data-track-event="click_input1"]');
expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input1', {
label: '_label_',
value: '_value_',
- property: '',
});
});
it('allows value override with the data-track-value attribute', () => {
- subject.bind(document);
- $('[data-track-event="click_input2"]').click();
+ trigger('[data-track-event="click_input2"]');
expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input2', {
- label: '',
value: '_value_override_',
- property: '',
});
});
it('handles checkbox values correctly', () => {
- subject.bind(document);
- const $checkbox = $('[data-track-event="toggle_checkbox"]');
-
- $checkbox.click(); // unchecking
+ trigger('[data-track-event="toggle_checkbox"]'); // checking
expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', {
- label: '',
- property: '',
value: false,
});
- $checkbox.click(); // checking
+ trigger('[data-track-event="toggle_checkbox"]'); // unchecking
expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', {
- label: '',
- property: '',
value: '_value_',
});
});
it('handles bootstrap dropdowns', () => {
- new Tracking('_category_').bind(document);
- const $dropdown = $('[data-track-event="toggle_dropdown"]');
+ trigger('[data-track-event="toggle_dropdown"]', 'show.bs.dropdown'); // showing
- $dropdown.trigger('show.bs.dropdown'); // showing
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_show', {});
- expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_show', {
- label: '',
- property: '',
- value: '',
- });
+ trigger('[data-track-event="toggle_dropdown"]', 'hide.bs.dropdown'); // hiding
+
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_hide', {});
+ });
- $dropdown.trigger('hide.bs.dropdown'); // hiding
+ it('handles nested elements inside an element with tracking', () => {
+ trigger('span.nested', 'click');
- expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_hide', {
- label: '',
- property: '',
- value: '',
- });
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'nested_event', {});
});
});
});
diff --git a/spec/javascripts/helpers/tracking_helper.js b/spec/javascripts/helpers/tracking_helper.js
new file mode 100644
index 00000000000..68c1bd2dbca
--- /dev/null
+++ b/spec/javascripts/helpers/tracking_helper.js
@@ -0,0 +1,25 @@
+import Tracking from '~/tracking';
+
+export default Tracking;
+
+let document;
+let handlers;
+
+export function mockTracking(category = '_category_', documentOverride, spyMethod) {
+ document = documentOverride || window.document;
+ window.snowplow = () => {};
+ Tracking.bindDocument(category, document);
+ return spyMethod ? spyMethod(Tracking, 'event') : null;
+}
+
+export function unmockTracking() {
+ window.snowplow = undefined;
+ handlers.forEach(event => document.removeEventListener(event.name, event.func));
+}
+
+export function triggerEvent(selectorOrEl, eventName = 'click') {
+ const event = new Event(eventName, { bubbles: true });
+ const el = typeof selectorOrEl === 'string' ? document.querySelector(selectorOrEl) : selectorOrEl;
+
+ el.dispatchEvent(event);
+}
diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js
index 069e2cb07b5..82d1f815ca8 100644
--- a/spec/javascripts/integrations/integration_settings_form_spec.js
+++ b/spec/javascripts/integrations/integration_settings_form_spec.js
@@ -126,6 +126,7 @@ describe('IntegrationSettingsForm', () => {
spyOn(axios, 'put').and.callThrough();
integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ // eslint-disable-next-line no-jquery/no-serialize
formData = integrationSettingsForm.$form.serialize();
});
diff --git a/spec/javascripts/sidebar/assignee_title_spec.js b/spec/javascripts/sidebar/assignee_title_spec.js
index 7fff7c075d9..6c65a55ff29 100644
--- a/spec/javascripts/sidebar/assignee_title_spec.js
+++ b/spec/javascripts/sidebar/assignee_title_spec.js
@@ -1,13 +1,12 @@
import Vue from 'vue';
import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue';
+import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
describe('AssigneeTitle component', () => {
let component;
let AssigneeTitleComponent;
- let statsSpy;
beforeEach(() => {
- statsSpy = spyOnDependency(AssigneeTitle, 'trackEvent');
AssigneeTitleComponent = Vue.extend(AssigneeTitle);
});
@@ -105,15 +104,20 @@ describe('AssigneeTitle component', () => {
expect(component.$el.querySelector('.edit-link')).not.toBeNull();
});
- it('calls trackEvent when edit is clicked', () => {
+ it('tracks the event when edit is clicked', () => {
component = new AssigneeTitleComponent({
propsData: {
numberOfAssignees: 0,
editable: true,
},
}).$mount();
- component.$el.querySelector('.js-sidebar-dropdown-toggle').click();
- expect(statsSpy).toHaveBeenCalled();
+ const spy = mockTracking('_category_', component.$el, spyOn);
+ triggerEvent('.js-sidebar-dropdown-toggle');
+
+ expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', {
+ label: 'right_sidebar',
+ property: 'assignee',
+ });
});
});
diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
index ea9e5677bc5..50374b8973f 100644
--- a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
@@ -1,13 +1,12 @@
import Vue from 'vue';
import confidentialIssueSidebar from '~/sidebar/components/confidential/confidential_issue_sidebar.vue';
+import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
describe('Confidential Issue Sidebar Block', () => {
let vm1;
let vm2;
- let statsSpy;
beforeEach(() => {
- statsSpy = spyOnDependency(confidentialIssueSidebar, 'trackEvent');
const Component = Vue.extend(confidentialIssueSidebar);
const service = {
update: () => Promise.resolve(true),
@@ -70,9 +69,13 @@ describe('Confidential Issue Sidebar Block', () => {
});
});
- it('calls trackEvent when "Edit" is clicked', () => {
- vm1.$el.querySelector('.confidential-edit').click();
+ it('tracks the event when "Edit" is clicked', () => {
+ const spy = mockTracking('_category_', vm1.$el, spyOn);
+ triggerEvent('.confidential-edit');
- expect(statsSpy).toHaveBeenCalled();
+ expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', {
+ label: 'right_sidebar',
+ property: 'confidentiality',
+ });
});
});
diff --git a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
index 2d930428230..decccbb8964 100644
--- a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
@@ -1,13 +1,12 @@
import Vue from 'vue';
import lockIssueSidebar from '~/sidebar/components/lock/lock_issue_sidebar.vue';
+import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
describe('LockIssueSidebar', () => {
let vm1;
let vm2;
- let statsSpy;
beforeEach(() => {
- statsSpy = spyOnDependency(lockIssueSidebar, 'trackEvent');
const Component = Vue.extend(lockIssueSidebar);
const mediator = {
@@ -61,10 +60,14 @@ describe('LockIssueSidebar', () => {
});
});
- it('calls trackEvent when "Edit" is clicked', () => {
- vm1.$el.querySelector('.lock-edit').click();
+ it('tracks an event when "Edit" is clicked', () => {
+ const spy = mockTracking('_category_', vm1.$el, spyOn);
+ triggerEvent('.lock-edit');
- expect(statsSpy).toHaveBeenCalled();
+ expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', {
+ label: 'right_sidebar',
+ property: 'lock_issue',
+ });
});
it('displays the edit form when opened from collapsed state', done => {
diff --git a/spec/javascripts/sidebar/subscriptions_spec.js b/spec/javascripts/sidebar/subscriptions_spec.js
index 2efa13f3fe8..a97608d6b8a 100644
--- a/spec/javascripts/sidebar/subscriptions_spec.js
+++ b/spec/javascripts/sidebar/subscriptions_spec.js
@@ -2,14 +2,13 @@ import Vue from 'vue';
import subscriptions from '~/sidebar/components/subscriptions/subscriptions.vue';
import eventHub from '~/sidebar/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { mockTracking } from 'spec/helpers/tracking_helper';
describe('Subscriptions', function() {
let vm;
let Subscriptions;
- let statsSpy;
beforeEach(() => {
- statsSpy = spyOnDependency(subscriptions, 'trackEvent');
Subscriptions = Vue.extend(subscriptions);
});
@@ -53,6 +52,7 @@ describe('Subscriptions', function() {
vm = mountComponent(Subscriptions, { subscribed: true });
spyOn(eventHub, '$emit');
spyOn(vm, '$emit');
+ spyOn(vm, 'track');
vm.toggleSubscription();
@@ -60,11 +60,12 @@ describe('Subscriptions', function() {
expect(vm.$emit).toHaveBeenCalledWith('toggleSubscription', jasmine.any(Object));
});
- it('calls trackEvent when toggled', () => {
+ it('tracks the event when toggled', () => {
vm = mountComponent(Subscriptions, { subscribed: true });
+ const spy = mockTracking('_category_', vm.$el, spyOn);
vm.toggleSubscription();
- expect(statsSpy).toHaveBeenCalled();
+ expect(spy).toHaveBeenCalled();
});
it('onClickCollapsedIcon method emits `toggleSidebar` event on component', () => {
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 6dc47e0e501..011b46c7f1a 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -279,7 +279,7 @@ describe Repository do
describe '#commits' do
context 'when neither the all flag nor a ref are specified' do
it 'returns every commit from default branch' do
- expect(repository.commits(limit: 60).size).to eq(37)
+ expect(repository.commits(nil, limit: 60).size).to eq(37)
end
end
@@ -320,7 +320,7 @@ describe Repository do
context "when 'all' flag is set" do
it 'returns every commit from the repository' do
- expect(repository.commits(all: true, limit: 60).size).to eq(60)
+ expect(repository.commits(nil, all: true, limit: 60).size).to eq(60)
end
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 5e6ff40e8cf..90ff1d12bf1 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -169,6 +169,18 @@ describe API::Commits do
end
end
+ context 'first_parent optional parameter' do
+ it 'returns all first_parent commits' do
+ commit_count = project.repository.count_commits(ref: SeedRepo::Commit::ID, first_parent: true)
+
+ get api("/projects/#{project_id}/repository/commits", user), params: { ref_name: SeedRepo::Commit::ID, first_parent: 'true' }
+
+ expect(response).to include_pagination_headers
+ expect(commit_count).to eq(12)
+ expect(response.headers['X-Total']).to eq(commit_count.to_s)
+ end
+ end
+
context 'with_stats optional parameter' do
let(:project) { create(:project, :public, :repository) }
diff --git a/yarn.lock b/yarn.lock
index 923f0305ea4..8e6fd5b1a2e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4624,6 +4624,11 @@ eslint-plugin-jest@^22.3.0:
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.3.0.tgz#a10f10dedfc92def774ec9bb5bfbd2fb8e1c96d2"
integrity sha512-P1mYVRNlOEoO5T9yTqOfucjOYf1ktmJ26NjwjH8sxpCFQa6IhBGr5TpKl3hcAAT29hOsRJVuMWmTsHoUVo9FoA==
+eslint-plugin-no-jquery@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.1.0.tgz#d03b74224c5cfbc7fc0bdd12ce4eb400d09e0c0b"
+ integrity sha512-5sr5tOJRfuRviyAvFTe/mr80TXWxTteD/JHRuJtDN8q/bxAh16eSKoKLAevLC7wZCRN2iwnEfhQPQV4rp/gYtg==
+
eslint-plugin-promise@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.1.1.tgz#1e08cb68b5b2cd8839f8d5864c796f56d82746db"