Commit d7ce7307 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent e43077ab
# Make sure to update all the similar conditions in other CI config files if you modify these conditions
.if-not-ee: &if-not-ee
if: '$CI_PROJECT_NAME !~ /^gitlab(-ee)?$/'
# Make sure to update all the similar conditions in other CI config files if you modify these conditions
.if-default-refs: &if-default-refs
if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG'
# Make sure to update all the similar patterns in other CI config files if you modify these patterns
.code-backstage-patterns: &code-backstage-patterns
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- ".csscomb.json"
- "Dockerfile.assets"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
- "{babel.config,jest.config}.js"
- "config.ru"
- "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
# Backstage changes
- "Dangerfile"
- "danger/**/*"
- "{,ee/}fixtures/**/*"
- "{,ee/}rubocop/**/*"
- "{,ee/}spec/**/*"
- "doc/README.md" # Some RSpec test rely on this file
.dev-fixtures:rules:ee-and-foss:
rules:
- <<: *if-default-refs
changes: *code-backstage-patterns
when: on_success
.dev-fixtures:rules:ee-only:
rules:
- <<: *if-not-ee
when: never
- <<: *if-default-refs
changes: *code-backstage-patterns
when: on_success
.run-dev-fixtures:
extends:
- .only-code-rails-job-base
- .default-tags
- .default-retry
- .default-cache
- .default-before_script
- .use-pg9
stage: test
needs: ["setup-test-env"]
......@@ -13,17 +61,19 @@
SIZE: 0 # number of external projects to fork, requires network connection
# SEED_NESTED_GROUPS: "false" # requires network connection
run-dev-fixtures-foss:
extends: .run-dev-fixtures
run-dev-fixtures:
extends:
- .run-dev-fixtures
- .dev-fixtures:rules:ee-and-foss
script:
- scripts/gitaly-test-spawn
- RAILS_ENV=test bundle exec rake db:seed_fu
run-dev-fixtures-ee:
extends:
- .only-ee
- .use-pg9-ee
- .run-dev-fixtures
- .dev-fixtures:rules:ee-only
- .use-pg9-ee
script:
- scripts/gitaly-test-spawn
- cp ee/db/fixtures/development/* $FIXTURE_PATH
......
This diff is collapsed.
<script>
/**
* This component is an iterative step towards refactoring and simplifying `vue_shared/components/file_row.vue`
* https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23720
*/
import FileRow from '~/vue_shared/components/file_row.vue';
export default {
components: {
FileRow,
},
};
</script>
<template>
<file-row v-bind="$attrs" v-on="$listeners" />
</template>
......@@ -3,7 +3,8 @@ import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
import FileTree from '~/vue_shared/components/file_tree.vue';
import DiffFileRow from './diff_file_row.vue';
import FileRowStats from './file_row_stats.vue';
export default {
......@@ -12,7 +13,7 @@ export default {
},
components: {
Icon,
FileRow,
FileTree,
},
props: {
hideFileStats: {
......@@ -61,6 +62,7 @@ export default {
searchPlaceholder: sprintf(s__('MergeRequest|Search files (%{modifier_key}P)'), {
modifier_key: /Mac/i.test(navigator.userAgent) ? '' : 'Ctrl+',
}),
DiffFileRow,
};
</script>
......@@ -91,7 +93,7 @@ export default {
</div>
<div :class="{ 'pt-0 tree-list-blobs': !renderTreeList }" class="tree-list-scroll">
<template v-if="filteredTreeList.length">
<file-row
<file-tree
v-for="file in filteredTreeList"
:key="file.key"
:file="file"
......@@ -99,6 +101,7 @@ export default {
:hide-extra-on-tree="true"
:extra-component="fileRowExtraComponent"
:show-changed-icon="true"
:file-row-component="$options.DiffFileRow"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="scrollToFile"
/>
......
<script>
/**
* This component is an iterative step towards refactoring and simplifying `vue_shared/components/file_row.vue`
* https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23720
*/
import FileRow from '~/vue_shared/components/file_row.vue';
export default {
components: {
FileRow,
},
};
</script>
<template>
<file-row v-bind="$attrs" v-on="$listeners" />
</template>
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlSkeletonLoading } from '@gitlab/ui';
import FileRow from '~/vue_shared/components/file_row.vue';
import FileTree from '~/vue_shared/components/file_tree.vue';
import IdeFileRow from './ide_file_row.vue';
import NavDropdown from './nav_dropdown.vue';
import FileRowExtra from './file_row_extra.vue';
......@@ -9,7 +10,7 @@ export default {
components: {
GlSkeletonLoading,
NavDropdown,
FileRow,
FileTree,
},
props: {
viewerType: {
......@@ -36,6 +37,7 @@ export default {
...mapActions(['updateViewer', 'toggleTreeOpen']),
},
FileRowExtra,
IdeFileRow,
};
</script>
......@@ -53,12 +55,13 @@ export default {
</header>
<div class="ide-tree-body h-100">
<template v-if="currentTree.tree.length">
<file-row
<file-tree
v-for="file in currentTree.tree"
:key="file.key"
:file="file"
:level="0"
:extra-component="$options.FileRowExtra"
:file-row-component="$options.IdeFileRow"
@toggleTreeOpen="toggleTreeOpen"
/>
</template>
......
......@@ -195,8 +195,8 @@ export default {
</gl-dropdown-item>
<gl-dropdown-item
v-if="clipboardText"
ref="copyChartLink"
v-track-event="generateLinkToChartOptions(clipboardText)"
class="js-chart-link"
:data-clipboard-text="clipboardText"
@click="showToast(clipboardText)"
>
......
......@@ -62,9 +62,6 @@ export default {
'is-open': this.file.opened,
};
},
childFilesLevel() {
return this.file.isHeader ? 0 : this.level + 1;
},
},
watch: {
'file.active': function fileActiveWatch(active) {
......@@ -131,53 +128,38 @@ export default {
</script>
<template>
<div>
<file-header v-if="file.isHeader" :path="file.path" />
<div
v-else
:class="fileClass"
:title="file.name"
class="file-row"
role="button"
@click="clickFile"
@mouseleave="toggleDropdown(false)"
>
<div class="file-row-name-container">
<span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated">
<file-icon
v-if="!showChangedIcon || file.type === 'tree'"
class="file-row-icon"
:file-name="file.name"
:loading="file.loading"
:folder="isTree"
:opened="file.opened"
:size="16"
/>
<changed-file-icon v-else :file="file" :size="16" class="append-right-5" />
{{ file.name }}
</span>
<component
:is="extraComponent"
v-if="extraComponent && !(hideExtraOnTree && file.type === 'tree')"
:file="file"
:dropdown-open="dropdownOpen"
@toggle="toggleDropdown($event)"
<file-header v-if="file.isHeader" :path="file.path" />
<div
v-else
:class="fileClass"
:title="file.name"
class="file-row"
role="button"
@click="clickFile"
@mouseleave="toggleDropdown(false)"
>
<div class="file-row-name-container">
<span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated">
<file-icon
v-if="!showChangedIcon || file.type === 'tree'"
class="file-row-icon"
:file-name="file.name"
:loading="file.loading"
:folder="isTree"
:opened="file.opened"
:size="16"
/>
</div>
</div>
<template v-if="file.opened || file.isHeader">
<file-row
v-for="childFile in file.tree"
:key="childFile.key"
:file="childFile"
:level="childFilesLevel"
:hide-extra-on-tree="hideExtraOnTree"
:extra-component="extraComponent"
:show-changed-icon="showChangedIcon"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="clickedFile"
<changed-file-icon v-else :file="file" :size="16" class="append-right-5" />
{{ file.name }}
</span>
<component
:is="extraComponent"
v-if="extraComponent && !(hideExtraOnTree && file.type === 'tree')"
:file="file"
:dropdown-open="dropdownOpen"
@toggle="toggleDropdown($event)"
/>
</template>
</div>
</div>
</template>
......
<script>
export default {
name: 'FileTree',
props: {
fileRowComponent: {
type: Object,
required: true,
},
level: {
type: Number,
required: true,
},
file: {
type: Object,
required: true,
},
},
computed: {
childFilesLevel() {
return this.file.isHeader ? 0 : this.level + 1;
},
},
};
</script>
<template>
<div>
<component
:is="fileRowComponent"
:level="level"
:file="file"
v-bind="$attrs"
v-on="$listeners"
/>
<template v-if="file.opened || file.isHeader">
<file-tree
v-for="childFile in file.tree"
:key="childFile.key"
:file-row-component="fileRowComponent"
:level="childFilesLevel"
:file="childFile"
v-bind="$attrs"
v-on="$listeners"
/>
</template>
</div>
</template>
---
title: Expose issue link type in REST API
merge_request: 24175
author:
type: added
......@@ -14,7 +14,7 @@ class MigrateEpicNotesMentionsToDb < ActiveRecord::Migration[5.2]
INDEX_NAME = 'epic_mentions_temp_index'
INDEX_CONDITION = "note LIKE '%@%'::text AND notes.noteable_type = 'Epic'"
QUERY_CONDITIONS = "#{INDEX_CONDITION} AND epic_user_mentions.epic_id IS NULL"
JOIN = 'LEFT JOIN epic_user_mentions ON notes.id = epic_user_mentions.note_id'
JOIN = 'INNER JOIN epics ON epics.id = notes.noteable_id LEFT JOIN epic_user_mentions ON notes.id = epic_user_mentions.note_id'
class Note < ActiveRecord::Base
include EachBatch
......
......@@ -288,10 +288,13 @@ For source installations, edit the `gitlab.yml` and set the Sidekiq
## `gitlab-shell.log`
This file lives in `/var/log/gitlab/gitlab-shell/gitlab-shell.log` for
Omnibus GitLab packages or in `/home/git/gitlab-shell/gitlab-shell.log` for
This file lives in `/var/log/gitlab/gitaly/gitlab-shell.log` for
Omnibus GitLab packages or in `/home/git/gitaly/gitlab-shell.log` for
installations from source.
NOTE: **Note**
For GitLab 12.5 and earlier the file lives in `/var/log/gitlab/gitlab-shell/gitlab-shell.log`.
GitLab Shell is used by GitLab for executing Git commands and provide
SSH access to Git repositories. For example:
......
......@@ -67,7 +67,7 @@ POST /projects/:id/issues/:issue_iid/links
| `issue_iid` | integer | yes | The internal ID of a project's issue |
| `target_project_id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) of a target project |
| `target_issue_iid` | integer/string | yes | The internal ID of a target project's issue |
| `link_type` | string | no | The type of the relation ("relates_to", "blocks", "is_blocked_by"), defaults to "relates_to"). Ignored unless `issue_link_types` feature flag is enabled. |
| `link_type` | string | no | The type of the relation ("relates_to", "blocks", "is_blocked_by"), defaults to "relates_to"). |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/issues/1/links?target_project_id=5&target_issue_iid=1"
......
......@@ -95,7 +95,7 @@ The following table depicts the various user permission levels in a project.
| Stop environments | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
| Cancel and retry jobs | | | ✓ | ✓ | ✓ |
| Create or update commit status | | | ✓ | ✓ | ✓ |
| Create or update commit status | | | ✓ (*5*) | ✓ | ✓ |
| Update a container registry | | | ✓ | ✓ | ✓ |
| Remove a container registry image | | | ✓ | ✓ | ✓ |
| Create/edit/delete project milestones | | | ✓ | ✓ | ✓ |
......@@ -144,6 +144,7 @@ The following table depicts the various user permission levels in a project.
(*2*): Guest users can only view the confidential issues they created themselves.
(*3*): If **Public pipelines** is enabled in **Project Settings > CI/CD**.
(*4*): Not allowed for Guest, Reporter, Developer, Maintainer, or Owner. See [Protected Branches](./project/protected_branches.md).
(*5*): If the [branch is protected](./project/protected_branches.md#using-the-allowed-to-merge-and-allowed-to-push-settings), this depends on the access Developers and Maintainers are given.
## Project features permissions
......
......@@ -13,7 +13,7 @@ module Gitlab
def perform(resource_model, join, conditions, with_notes, start_id, end_id)
resource_model = "#{ISOLATION_MODULE}::#{resource_model}".constantize if resource_model.is_a?(String)
model = with_notes ? "#{ISOLATION_MODULE}::Note".constantize : resource_model
model = with_notes ? Gitlab::BackgroundMigration::UserMentions::Models::Note : resource_model
resource_user_mention_model = resource_model.user_mention_model
records = model.joins(join).where(conditions).where(id: start_id..end_id)
......@@ -21,7 +21,7 @@ module Gitlab
records.in_groups_of(BULK_INSERT_SIZE, false).each do |records|
mentions = []
records.each do |record|
mentions << record.build_mention_values
mentions << record.build_mention_values(resource_user_mention_model.resource_foreign_key)
end
Gitlab::Database.bulk_insert(
......
......@@ -65,11 +65,11 @@ module Gitlab
false
end
def build_mention_values
def build_mention_values(resource_foreign_key)
refs = all_references(author)
{
"#{self.user_mention_model.resource_foreign_key}": user_mention_resource_id,
"#{resource_foreign_key}": user_mention_resource_id,
note_id: user_mention_note_id,
mentioned_users_ids: array_to_sql(refs.mentioned_users.pluck(:id)),
mentioned_projects_ids: array_to_sql(refs.mentioned_projects.pluck(:id)),
......
......@@ -19,10 +19,6 @@ module Gitlab
belongs_to :noteable, polymorphic: true
belongs_to :project
def user_mention_model
"#{CreateResourceUserMention::ISOLATION_MODULE}::#{noteable.class}".constantize.user_mention_model
end
def for_personal_snippet?
noteable.class.name == 'PersonalSnippet'
end
......
import { shallowMount } from '@vue/test-utils';
import DiffFileRow from '~/diffs/components/diff_file_row.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
describe('Diff File Row component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(DiffFileRow, {
propsData: { ...props },
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders file row component', () => {
createComponent({
level: 4,
file: {},
});
expect(wrapper.find(FileRow).exists()).toEqual(true);
});
});
import { shallowMount } from '@vue/test-utils';
import IdeFileRow from '~/ide/components/ide_file_row.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
describe('Ide File Row component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(IdeFileRow, {
propsData: { ...props },
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders file row component', () => {
createComponent({
level: 4,
file: {},
});
expect(wrapper.find(FileRow).exists()).toEqual(true);
});
});
import { shallowMount, createLocalVue, mount } from '@vue/test-utils';
import { GlDropdownItem, GlButton, GlToast } from '@gitlab/ui';
import { GlDropdownItem, GlButton } from '@gitlab/ui';
import VueDraggable from 'vuedraggable';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
......@@ -10,6 +10,7 @@ import Dashboard from '~/monitoring/components/dashboard.vue';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
import GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import { setupComponentStore, propsData } from '../init_utils';
......@@ -540,37 +541,36 @@ describe('Dashboard', () => {
});
});
// https://gitlab.com/gitlab-org/gitlab-ce/issues/66922
// eslint-disable-next-line jest/no-disabled-tests
describe.skip('link to chart', () => {
describe('Clipboard text in panels', () => {
const currentDashboard = 'TEST_DASHBOARD';
localVue.use(GlToast);
const link = () => wrapper.find('.js-chart-link');
const clipboardText = () => link().element.dataset.clipboardText;
const getClipboardTextAt = i =>
wrapper
.findAll(PanelType)
.at(i)
.props('clipboardText');
beforeEach(done => {
createShallowWrapper({ hasMetrics: true, currentDashboard });
setTimeout(done);
});
setupComponentStore(wrapper);
it('adds a copy button to the dropdown', () => {
expect(link().text()).toContain('Generate link to chart');
wrapper.vm.$nextTick(done);
});
it('contains a link to the dashboard', () => {
expect(clipboardText()).toContain(`dashboard=${currentDashboard}`);
expect(clipboardText()).toContain(`group=`);
expect(clipboardText()).toContain(`title=`);
expect(clipboardText()).toContain(`y_label=`);
expect(getClipboardTextAt(0)).toContain(`dashboard=${currentDashboard}`);
expect(getClipboardTextAt(0)).toContain(`group=`);
expect(getClipboardTextAt(0)).toContain(`title=`);
expect(getClipboardTextAt(0)).toContain(`y_label=`);
});
it('undefined parameter is stripped', done => {
it('strips the undefined parameter', done => {
wrapper.setProps({ currentDashboard: undefined });
wrapper.vm.$nextTick(() => {
expect(clipboardText()).not.toContain(`dashboard=`);
expect(clipboardText()).toContain(`y_label=`);
expect(getClipboardTextAt(0)).not.toContain(`dashboard=`);
expect(getClipboardTextAt(0)).toContain(`y_label=`);
done();
});
});
......@@ -579,18 +579,10 @@ describe('Dashboard', () => {
wrapper.setProps({ currentDashboard: null });
wrapper.vm.$nextTick(() => {
expect(clipboardText()).not.toContain(`dashboard=`);
expect(clipboardText()).toContain(`y_label=`);
expect(getClipboardTextAt(0)).not.toContain(`dashboard=`);
expect(getClipboardTextAt(0)).toContain(`y_label=`);
done();
});
});
it('creates a toast when clicked', () => {
jest.spyOn(wrapper.vm.$toast, 'show').and.stub();
link().vm.$emit('click');
expect(wrapper.vm.$toast.show).toHaveBeenCalled();
});
});
});
......@@ -3,22 +3,29 @@ import AxiosMockAdapter from 'axios-mock-adapter';
import { setTestTimeout } from 'helpers/timeout';
import invalidUrl from '~/lib/utils/invalid_url';
import axios from '~/lib/utils/axios_utils';
import PanelType from '~/monitoring/components/panel_type.vue';
import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
import TimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
import AnomalyChart from '~/monitoring/components/charts/anomaly.vue';
import { graphDataPrometheusQueryRange } from '../../javascripts/monitoring/mock_data';
import { anomalyMockGraphData } from '../../frontend/monitoring/mock_data';
import { anomalyMockGraphData, graphDataPrometheusQueryRange } from 'jest/monitoring/mock_data';
import { createStore } from '~/monitoring/stores';
global.IS_EE = true;
global.URL.createObjectURL = jest.fn();
const mocks = {
$toast: {
show: jest.fn(),
},
};
describe('Panel Type component', () => {
let axiosMock;
let store;
let state;
let wrapper;
const exampleText = 'example_text';
const createWrapper = props => {
......@@ -27,6 +34,7 @@ describe('Panel Type component', () => {
...props,
},
store,
mocks,
});
};
......@@ -88,7 +96,7 @@ describe('Panel Type component', () => {
});
it('sets no clipboard copy link on dropdown by default', () => {
const link = () => wrapper.find('.js-chart-link');
const link = () => wrapper.find({ ref: 'copyChartLink' });
expect(link().exists()).toBe(false);
});
......@@ -196,6 +204,7 @@ describe('Panel Type component', () => {
});
describe('when cliboard data is available', () => {
const link = () => wrapper.find({ ref: 'copyChartLink' });
const clipboardText = 'A value to copy.';
beforeEach(() => {
......@@ -210,11 +219,19 @@ describe('Panel Type component', () => {
});
it('sets clipboard text on the dropdown', () => {
const link = () => wrapper.find('.js-chart-link');
expect(link().exists()).toBe(true);
expect(link().element.dataset.clipboardText).toBe(clipboardText);
});
it('adds a copy button to the dropdown', () => {
expect(link().text()).toContain('Generate link to chart');
});
it('opens a toast on click', () => {
link().vm.$emit('click');
expect(wrapper.vm.$toast.show).toHaveBeenCalled();
});
});
describe('when downloading metrics data as CSV', () => {
......
import { pick } from 'lodash';
import { shallowMount } from '@vue/test-utils';
import FileTree from '~/vue_shared/components/file_tree.vue';
const MockFileRow = {
name: 'MockFileRow',
render() {
return this.$slots.default;
},
};
const TEST_LEVEL = 4;
const TEST_EXTA_ARGS = {
foo: 'lorem-ipsum',
bar: 'zoo',
};
describe('File Tree component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(FileTree, {
propsData: { level: TEST_LEVEL, fileRowComponent: MockFileRow, ...props },
attrs: { ...TEST_EXTA_ARGS },
});
};
const findFileRow = () => wrapper.find(MockFileRow);
const findChildrenTrees = () => wrapper.findAll(FileTree).wrappers.slice(1);
const findChildrenTreeProps = () =>
findChildrenTrees().map(x => ({
...x.props(),
...pick(x.attributes(), Object.keys(TEST_EXTA_ARGS)),
}));
afterEach(() => {
wrapper.destroy();
});
describe('file row component', () => {
beforeEach(() => {
createComponent({ file: {} });
});
it('renders file row component', () => {
expect(findFileRow().exists()).toEqual(true);
});
it('contains the required attribute keys', () => {
const fileRow = findFileRow();
// Checking strings b/c value in attributes are always strings
expect(fileRow.attributes()).toEqual({
file: {}.toString(),
level: TEST_LEVEL.toString(),
...TEST_EXTA_ARGS,
});
});
});
describe('file tree', () => {
const createChildren = () => [{ id: 1 }, { id: 2 }];
const createChildrenExpectation = (props = {}) =>
createChildren().map(file => ({
fileRowComponent: MockFileRow,
file,
...TEST_EXTA_ARGS,
...props,
}));
it.each`
key | value | desc | expectedChildren
${'isHeader'} | ${true} | ${'is shown if file is header'} | ${createChildrenExpectation({ level: 0 })}
${'opened'} | ${true} | ${'is shown if file is open'} | ${createChildrenExpectation({ level: TEST_LEVEL + 1 })}
${'isHeader'} | ${false} | ${'is hidden if file is header'} | ${[]}
${'opened'} | ${false} | ${'is hidden if file is open'} | ${[]}
`('$desc', ({ key, value, expectedChildren }) => {
createComponent({
file: {
[key]: value,
tree: createChildren(),
},
});
expect(findChildrenTreeProps()).toEqual(expectedChildren);
});
});
});
......@@ -19,7 +19,6 @@ describe('File row component', () => {
const findNewDropdown = () => vm.$el.querySelector('.ide-new-btn .dropdown');
const findNewDropdownButton = () => vm.$el.querySelector('.ide-new-btn .dropdown button');
const findFileRow = () => vm.$el.querySelector('.file-row');
it('renders name', () => {
createComponent({
......@@ -42,7 +41,7 @@ describe('File row component', () => {
});
spyOn(vm, '$emit').and.stub();
vm.$el.querySelector('.file-row').click();
vm.$el.click();
expect(vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', vm.file.path);
});
......@@ -87,7 +86,7 @@ describe('File row component', () => {
level: 0,
});
expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null);
expect(vm.$el.classList).toContain('js-file-row-header');
});
describe('new dropdown', () => {
......@@ -138,7 +137,7 @@ describe('File row component', () => {
});
it('closes when row triggers mouseleave', () => {
findFileRow().dispatchEvent(new Event('mouseleave'));
vm.$el.dispatchEvent(new Event('mouseleave'));
expect(vm.dropdownOpen).toBe(false);
});
......
# frozen_string_literal: true
shared_examples 'resource mentions migration' do |migration_class, resource_class|
it 'migrates resource mentions' do
join = migration_class::JOIN
conditions = migration_class::QUERY_CONDITIONS
expect do
subject.perform(resource_class.name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
end.to change { user_mentions.count }.by(1)
user_mention = user_mentions.last
expect(user_mention.mentioned_users_ids.sort).to eq(mentioned_users.pluck(:id).sort)
expect(user_mention.mentioned_groups_ids.sort).to eq([group.id])
expect(user_mention.mentioned_groups_ids.sort).not_to include(inaccessible_group.id)
# check that performing the same job twice does not fail and does not change counts
expect do
subject.perform(resource_class.name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
end.to change { user_mentions.count }.by(0)
end
end
shared_examples 'resource notes mentions migration' do |migration_class, resource_class|
before do
note1.becomes(Note).save!
note2.becomes(Note).save!
note3.becomes(Note).save!
# note4.becomes(Note).save(validate: false)
end
it 'migrates mentions from note' do
join = migration_class::JOIN
conditions = migration_class::QUERY_CONDITIONS
# there are 4 notes for each noteable_type, but one does not have mentions and
# another one's noteable_id points to an inexistent resource
expect(notes.where(noteable_type: resource_class.to_s).count).to eq 4
expect do
subject.perform(resource_class.name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
end.to change { user_mentions.count }.by(2)
# check that the user_mention for regular note is created
user_mention = user_mentions.first
expect(Note.find(user_mention.note_id).system).to be false
expect(user_mention.mentioned_users_ids.sort).to eq(users.pluck(:id).sort)
expect(user_mention.mentioned_groups_ids.sort).to eq([group.id])
expect(user_mention.mentioned_groups_ids.sort).not_to include(inaccessible_group.id)
# check that the user_mention for system note is created
user_mention = user_mentions.second
expect(Note.find(user_mention.note_id).system).to be true
expect(user_mention.mentioned_users_ids.sort).to eq(users.pluck(:id).sort)
expect(user_mention.mentioned_groups_ids.sort).to eq([group.id])
expect(user_mention.mentioned_groups_ids.sort).not_to include(inaccessible_group.id)
# check that performing the same job twice does not fail and does not change counts
expect do
subject.perform(resource_class.name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
end.to change { user_mentions.count }.by(0)
end
end
shared_examples 'schedules resource mentions migration' do |resource_class, is_for_notes|
it 'schedules background migrations' do
Sidekiq::Testing.fake! do
Timecop.freeze do
migrate!
migration = described_class::MIGRATION
join = described_class::JOIN
conditions = described_class::QUERY_CONDITIONS
expect(migration).to be_scheduled_delayed_migration(2.minutes, resource_class.name, join, conditions, is_for_notes, resource1.id, resource1.id)
expect(migration).to be_scheduled_delayed_migration(4.minutes, resource_class.name, join, conditions, is_for_notes, resource2.id, resource2.id)
expect(migration).to be_scheduled_delayed_migration(6.minutes, resource_class.name, join, conditions, is_for_notes, resource3.id, resource3.id)
expect(BackgroundMigrationWorker.jobs.size).to eq 3
end
end
end
end
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