Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
45760607bc
commit
e5d3d8c323
|
@ -15,7 +15,7 @@ stages:
|
|||
# in cases where jobs require Docker-in-Docker, the job
|
||||
# definition must be extended with `.use-docker-in-docker`
|
||||
default:
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.31-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.36"
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.33-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.36"
|
||||
tags:
|
||||
- gitlab-org
|
||||
# All jobs are interruptible by default
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
- .default-retry
|
||||
- .default-before_script
|
||||
- .assets-compile-cache
|
||||
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7-git-2.31-lfs-2.9-node-14.15-yarn-1.22-graphicsmagick-1.3.36
|
||||
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7-git-2.33-lfs-2.9-node-14.15-yarn-1.22-graphicsmagick-1.3.36
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
WEBPACK_VENDOR_DLL: "true"
|
||||
|
|
|
@ -204,7 +204,7 @@
|
|||
- *storybook-node-modules-cache-push
|
||||
|
||||
.use-pg11:
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.31-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.36"
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.33-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.36"
|
||||
services:
|
||||
- name: postgres:11.6
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
|
@ -213,7 +213,7 @@
|
|||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
|
||||
.use-pg12:
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.31-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-12-graphicsmagick-1.3.36"
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.33-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-12-graphicsmagick-1.3.36"
|
||||
services:
|
||||
- name: postgres:12
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
|
@ -222,7 +222,7 @@
|
|||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
|
||||
.use-pg13:
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.31-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-13-graphicsmagick-1.3.36"
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.33-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-13-graphicsmagick-1.3.36"
|
||||
services:
|
||||
- name: postgres:13
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
|
@ -231,7 +231,7 @@
|
|||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
|
||||
.use-pg11-ee:
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.31-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.36"
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.33-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.36"
|
||||
services:
|
||||
- name: postgres:11.6
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
|
@ -242,7 +242,7 @@
|
|||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
|
||||
.use-pg12-ee:
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.31-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-12-graphicsmagick-1.3.36"
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.33-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-12-graphicsmagick-1.3.36"
|
||||
services:
|
||||
- name: postgres:12
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
|
@ -253,7 +253,7 @@
|
|||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
|
||||
.use-pg13-ee:
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.31-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-13-graphicsmagick-1.3.36"
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.patched-golang-1.16-git-2.33-lfs-2.9-chrome-89-node-14.15-yarn-1.22-postgresql-13-graphicsmagick-1.3.36"
|
||||
services:
|
||||
- name: postgres:13
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
|
|
|
@ -1165,6 +1165,7 @@ Gitlab/NamespacedClass:
|
|||
- 'app/models/members/group_member.rb'
|
||||
- 'app/models/members/last_group_owner_assigner.rb'
|
||||
- 'app/models/members/project_member.rb'
|
||||
- 'app/models/members/member_task.rb'
|
||||
- 'app/models/members_preloader.rb'
|
||||
- 'app/models/merge_request.rb'
|
||||
- 'app/models/merge_request_assignee.rb'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import {
|
||||
GlAlert,
|
||||
GlFormGroup,
|
||||
GlModal,
|
||||
GlDropdown,
|
||||
|
@ -16,12 +17,14 @@ import Api from '~/api';
|
|||
import ExperimentTracking from '~/experimentation/experiment_tracking';
|
||||
import { sanitize } from '~/lib/dompurify';
|
||||
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
|
||||
import { getParameterValues } from '~/lib/utils/url_utility';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import {
|
||||
INVITE_MEMBERS_IN_COMMENT,
|
||||
GROUP_FILTERS,
|
||||
USERS_FILTER_ALL,
|
||||
MEMBER_AREAS_OF_FOCUS,
|
||||
INVITE_MEMBERS_FOR_TASK,
|
||||
} from '../constants';
|
||||
import eventHub from '../event_hub';
|
||||
import {
|
||||
|
@ -34,6 +37,7 @@ import MembersTokenSelect from './members_token_select.vue';
|
|||
export default {
|
||||
name: 'InviteMembersModal',
|
||||
components: {
|
||||
GlAlert,
|
||||
GlFormGroup,
|
||||
GlDatepicker,
|
||||
GlLink,
|
||||
|
@ -47,6 +51,7 @@ export default {
|
|||
MembersTokenSelect,
|
||||
GroupSelect,
|
||||
},
|
||||
inject: ['newProjectPath'],
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
|
@ -100,6 +105,14 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
tasksToBeDoneOptions: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
projects: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -110,6 +123,8 @@ export default {
|
|||
newUsersToInvite: [],
|
||||
selectedDate: undefined,
|
||||
selectedAreasOfFocus: [],
|
||||
selectedTasksToBeDone: [],
|
||||
selectedTaskProject: this.projects[0],
|
||||
groupToBeSharedWith: {},
|
||||
source: 'unknown',
|
||||
invalidFeedbackMessage: '',
|
||||
|
@ -156,7 +171,7 @@ export default {
|
|||
);
|
||||
},
|
||||
areasOfFocusEnabled() {
|
||||
return this.areasOfFocusOptions.length !== 0;
|
||||
return !this.tasksToBeDoneEnabled && this.areasOfFocusOptions.length !== 0;
|
||||
},
|
||||
areasOfFocusForPost() {
|
||||
if (this.selectedAreasOfFocus.length === 0 && this.areasOfFocusEnabled) {
|
||||
|
@ -172,12 +187,40 @@ export default {
|
|||
|
||||
return this.$options.labels[this.inviteeType].placeHolder;
|
||||
},
|
||||
tasksToBeDoneEnabled() {
|
||||
return (
|
||||
getParameterValues('open_modal')[0] === 'invite_members_for_task' &&
|
||||
this.tasksToBeDoneOptions.length
|
||||
);
|
||||
},
|
||||
showTasksToBeDone() {
|
||||
return (
|
||||
this.tasksToBeDoneEnabled &&
|
||||
this.selectedAccessLevel >= INVITE_MEMBERS_FOR_TASK.minimum_access_level
|
||||
);
|
||||
},
|
||||
showTaskProjects() {
|
||||
return !this.isProject && this.selectedTasksToBeDone.length;
|
||||
},
|
||||
tasksToBeDoneForPost() {
|
||||
return this.showTasksToBeDone ? this.selectedTasksToBeDone : [];
|
||||
},
|
||||
tasksProjectForPost() {
|
||||
return this.showTasksToBeDone && this.selectedTasksToBeDone.length
|
||||
? this.selectedTaskProject.id
|
||||
: '';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
eventHub.$on('openModal', (options) => {
|
||||
this.openModal(options);
|
||||
this.trackEvent(MEMBER_AREAS_OF_FOCUS.name, MEMBER_AREAS_OF_FOCUS.view);
|
||||
});
|
||||
|
||||
if (this.tasksToBeDoneEnabled) {
|
||||
this.openModal({ inviteeType: 'members', source: 'in_product_marketing_email' });
|
||||
this.trackEvent(INVITE_MEMBERS_FOR_TASK.name, INVITE_MEMBERS_FOR_TASK.view);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
partitionNewUsersToInvite() {
|
||||
|
@ -219,6 +262,12 @@ export default {
|
|||
|
||||
this.trackEvent(MEMBER_AREAS_OF_FOCUS.name, MEMBER_AREAS_OF_FOCUS.submit);
|
||||
},
|
||||
trackinviteMembersForTask() {
|
||||
const label = 'selected_tasks_to_be_done';
|
||||
const property = this.selectedTasksToBeDone.join(',');
|
||||
const tracking = new ExperimentTracking(INVITE_MEMBERS_FOR_TASK.name, { label, property });
|
||||
tracking.event(INVITE_MEMBERS_FOR_TASK.submit);
|
||||
},
|
||||
resetFields() {
|
||||
this.isLoading = false;
|
||||
this.selectedAccessLevel = this.defaultAccessLevel;
|
||||
|
@ -227,10 +276,15 @@ export default {
|
|||
this.groupToBeSharedWith = {};
|
||||
this.invalidFeedbackMessage = '';
|
||||
this.selectedAreasOfFocus = [];
|
||||
this.selectedTasksToBeDone = [];
|
||||
[this.selectedTaskProject] = this.projects;
|
||||
},
|
||||
changeSelectedItem(item) {
|
||||
this.selectedAccessLevel = item;
|
||||
},
|
||||
changeSelectedTaskProject(project) {
|
||||
this.selectedTaskProject = project;
|
||||
},
|
||||
submitShareWithGroup() {
|
||||
const apiShareWithGroup = this.isProject
|
||||
? Api.projectShareWithGroup.bind(Api)
|
||||
|
@ -263,6 +317,7 @@ export default {
|
|||
promises.push(apiAddByUserId(this.id, this.addByUserIdPostData(usersToAddById)));
|
||||
}
|
||||
this.trackInvite();
|
||||
this.trackinviteMembersForTask();
|
||||
|
||||
Promise.all(promises)
|
||||
.then(this.conditionallyShowToastSuccess)
|
||||
|
@ -275,6 +330,8 @@ export default {
|
|||
access_level: this.selectedAccessLevel,
|
||||
invite_source: this.source,
|
||||
areas_of_focus: this.areasOfFocusForPost,
|
||||
tasks_to_be_done: this.tasksToBeDoneForPost,
|
||||
tasks_project_id: this.tasksProjectForPost,
|
||||
};
|
||||
},
|
||||
addByUserIdPostData(usersToAddById) {
|
||||
|
@ -284,6 +341,8 @@ export default {
|
|||
access_level: this.selectedAccessLevel,
|
||||
invite_source: this.source,
|
||||
areas_of_focus: this.areasOfFocusForPost,
|
||||
tasks_to_be_done: this.tasksToBeDoneForPost,
|
||||
tasks_project_id: this.tasksProjectForPost,
|
||||
};
|
||||
},
|
||||
shareWithGroupPostData(groupToBeSharedWith) {
|
||||
|
@ -337,6 +396,17 @@ export default {
|
|||
"InviteMembersModal|You're inviting members to the %{strongStart}%{name}%{strongEnd} project.",
|
||||
),
|
||||
},
|
||||
tasksToBeDone: {
|
||||
title: s__(
|
||||
'InviteMembersModal|Create issues for your new team member to work on (optional)',
|
||||
),
|
||||
noProjects: s__(
|
||||
'InviteMembersModal|To assign issues to a new team member, you need a project for the issues. %{linkStart}Create a project to get started.%{linkEnd}',
|
||||
),
|
||||
},
|
||||
tasksProject: {
|
||||
title: s__('InviteMembersModal|Choose a project for the issues'),
|
||||
},
|
||||
},
|
||||
group: {
|
||||
modalTitle: s__('InviteMembersModal|Invite a group'),
|
||||
|
@ -476,6 +546,54 @@ export default {
|
|||
data-testid="area-of-focus-checks"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="showTasksToBeDone" data-testid="invite-members-modal-tasks-to-be-done">
|
||||
<label class="gl-mt-5">
|
||||
{{ $options.labels.members.tasksToBeDone.title }}
|
||||
</label>
|
||||
<template v-if="projects.length">
|
||||
<gl-form-checkbox-group
|
||||
v-model="selectedTasksToBeDone"
|
||||
:options="tasksToBeDoneOptions"
|
||||
data-testid="invite-members-modal-tasks"
|
||||
/>
|
||||
<template v-if="showTaskProjects">
|
||||
<label class="gl-mt-5 gl-display-block">
|
||||
{{ $options.labels.members.tasksProject.title }}
|
||||
</label>
|
||||
<gl-dropdown
|
||||
class="gl-w-half gl-xs-w-full"
|
||||
:text="selectedTaskProject.title"
|
||||
data-testid="invite-members-modal-project-select"
|
||||
>
|
||||
<template v-for="project in projects">
|
||||
<gl-dropdown-item
|
||||
:key="project.id"
|
||||
active-class="is-active"
|
||||
is-check-item
|
||||
:is-checked="project.id === selectedTaskProject.id"
|
||||
@click="changeSelectedTaskProject(project)"
|
||||
>
|
||||
{{ project.title }}
|
||||
</gl-dropdown-item>
|
||||
</template>
|
||||
</gl-dropdown>
|
||||
</template>
|
||||
</template>
|
||||
<gl-alert
|
||||
v-else-if="tasksToBeDoneEnabled"
|
||||
variant="tip"
|
||||
:dismissible="false"
|
||||
data-testid="invite-members-modal-no-projects-alert"
|
||||
>
|
||||
<gl-sprintf :message="$options.labels.members.tasksToBeDone.noProjects">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="newProjectPath" target="_blank" class="gl-label-link">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</gl-alert>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #modal-footer>
|
||||
|
|
|
@ -8,6 +8,12 @@ export const MEMBER_AREAS_OF_FOCUS = {
|
|||
view: 'view',
|
||||
submit: 'submit',
|
||||
};
|
||||
export const INVITE_MEMBERS_FOR_TASK = {
|
||||
minimum_access_level: 30,
|
||||
name: 'invite_members_for_task',
|
||||
view: 'modal_opened_from_email',
|
||||
submit: 'submit',
|
||||
};
|
||||
|
||||
export const GROUP_FILTERS = {
|
||||
ALL: 'all',
|
||||
|
|
|
@ -14,6 +14,9 @@ export default function initInviteMembersModal() {
|
|||
|
||||
return new Vue({
|
||||
el,
|
||||
provide: {
|
||||
newProjectPath: el.dataset.newProjectPath,
|
||||
},
|
||||
render: (createElement) =>
|
||||
createElement(InviteMembersModal, {
|
||||
props: {
|
||||
|
@ -24,6 +27,8 @@ export default function initInviteMembersModal() {
|
|||
groupSelectFilter: el.dataset.groupsFilter,
|
||||
groupSelectParentId: parseInt(el.dataset.parentId, 10),
|
||||
areasOfFocusOptions: JSON.parse(el.dataset.areasOfFocusOptions),
|
||||
tasksToBeDoneOptions: JSON.parse(el.dataset.tasksToBeDoneOptions || '[]'),
|
||||
projects: JSON.parse(el.dataset.projects || '[]'),
|
||||
noSelectionAreasOfFocus: JSON.parse(el.dataset.noSelectionAreasOfFocus),
|
||||
usersFilter: el.dataset.usersFilter,
|
||||
filterId: parseInt(el.dataset.filterId, 10),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
|
||||
import 'mathjax/es5/tex-svg';
|
||||
import Prompt from '../prompt.vue';
|
||||
|
||||
|
@ -7,6 +8,9 @@ export default {
|
|||
components: {
|
||||
Prompt,
|
||||
},
|
||||
directives: {
|
||||
SafeHtml,
|
||||
},
|
||||
props: {
|
||||
count: {
|
||||
type: Number,
|
||||
|
@ -33,13 +37,16 @@ export default {
|
|||
return svg.outerHTML;
|
||||
},
|
||||
},
|
||||
safeHtmlConfig: {
|
||||
// to support SVGs and custom tags for mathjax
|
||||
ADD_TAGS: ['use', 'mjx-container', 'mjx-tool', 'mjx-status', 'mjx-tip'],
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="output">
|
||||
<prompt type="Out" :count="count" :show-output="index === 0" />
|
||||
<!-- eslint-disable -->
|
||||
<div ref="maths" v-html="code"></div>
|
||||
<div ref="maths" v-safe-html:[$options.safeHtmlConfig]="code"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -21,6 +21,9 @@ export const i18n = {
|
|||
`Pipeline|Pipeline %{idStart}#%{idEnd} %{statusStart}%{statusEnd} for %{commitStart}%{commitEnd}`,
|
||||
),
|
||||
viewBtn: s__('Pipeline|View pipeline'),
|
||||
pipelineNotTriggeredMsg: s__(
|
||||
'Pipeline|No pipeline was triggered for the latest changes due to the current CI/CD configuration.',
|
||||
),
|
||||
};
|
||||
|
||||
export default {
|
||||
|
@ -73,16 +76,22 @@ export default {
|
|||
result(res) {
|
||||
if (res.data?.project?.pipeline) {
|
||||
this.hasError = false;
|
||||
} else {
|
||||
this.hasError = true;
|
||||
this.pipelineNotTriggered = true;
|
||||
}
|
||||
},
|
||||
error() {
|
||||
this.hasError = true;
|
||||
this.networkError = true;
|
||||
},
|
||||
pollInterval: POLL_INTERVAL,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
networkError: false,
|
||||
pipelineNotTriggered: false,
|
||||
hasError: false,
|
||||
};
|
||||
},
|
||||
|
@ -126,10 +135,16 @@ export default {
|
|||
</div>
|
||||
</template>
|
||||
<template v-else-if="hasError">
|
||||
<div>
|
||||
<div v-if="networkError">
|
||||
<gl-icon class="gl-mr-auto" name="warning-solid" />
|
||||
<span data-testid="pipeline-error-msg">{{ $options.i18n.fetchError }}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<gl-icon class="gl-mr-auto" name="information-o" />
|
||||
<span data-testid="pipeline-not-triggered-error-msg">
|
||||
{{ $options.i18n.pipelineNotTriggeredMsg }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { IssuableType } from '~/issue_show/constants';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import AssigneeAvatarLink from './assignee_avatar_link.vue';
|
||||
|
@ -11,6 +12,7 @@ export default {
|
|||
AssigneeAvatarLink,
|
||||
UserNameWithStatus,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
users: {
|
||||
type: Array,
|
||||
|
@ -32,6 +34,10 @@ export default {
|
|||
return this.users[0];
|
||||
},
|
||||
hasOneUser() {
|
||||
if (this.showVerticalList) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.users.length === 1;
|
||||
},
|
||||
hiddenAssigneesLabel() {
|
||||
|
@ -45,6 +51,10 @@ export default {
|
|||
return this.users.length - DEFAULT_RENDER_COUNT;
|
||||
},
|
||||
uncollapsedUsers() {
|
||||
if (this.showVerticalList) {
|
||||
return this.users;
|
||||
}
|
||||
|
||||
const uncollapsedLength = this.showLess
|
||||
? Math.min(this.users.length, DEFAULT_RENDER_COUNT)
|
||||
: this.users.length;
|
||||
|
@ -53,6 +63,12 @@ export default {
|
|||
username() {
|
||||
return `@${this.firstUser.username}`;
|
||||
},
|
||||
showVerticalList() {
|
||||
return this.glFeatures.mrAttentionRequests && this.isMergeRequest;
|
||||
},
|
||||
isMergeRequest() {
|
||||
return this.issuableType === IssuableType.MergeRequest;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleShowLess() {
|
||||
|
@ -84,11 +100,28 @@ export default {
|
|||
<div v-else>
|
||||
<div class="gl-display-flex gl-flex-wrap">
|
||||
<div
|
||||
v-for="user in uncollapsedUsers"
|
||||
v-for="(user, index) in uncollapsedUsers"
|
||||
:key="user.id"
|
||||
class="user-item gl-display-inline-block"
|
||||
:class="{
|
||||
'user-item': !showVerticalList,
|
||||
'gl-mb-3': index !== users.length - 1 && showVerticalList,
|
||||
}"
|
||||
class="gl-display-inline-block"
|
||||
>
|
||||
<assignee-avatar-link :user="user" :issuable-type="issuableType" />
|
||||
<assignee-avatar-link
|
||||
:user="user"
|
||||
:issuable-type="issuableType"
|
||||
:tooltip-has-name="!showVerticalList"
|
||||
>
|
||||
<div
|
||||
v-if="showVerticalList"
|
||||
class="gl-ml-3 gl-line-height-normal gl-display-grid"
|
||||
data-testid="username"
|
||||
>
|
||||
<user-name-with-status :name="user.name" :availability="userAvailability(user)" />
|
||||
<span>@{{ user.username }}</span>
|
||||
</div>
|
||||
</assignee-avatar-link>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="renderShowMoreSection" class="user-list-more gl-hover-text-blue-800">
|
||||
|
|
|
@ -143,6 +143,10 @@ table.content {
|
|||
line-height: 1.4;
|
||||
padding: 15px 5px;
|
||||
text-align: center;
|
||||
|
||||
ul.list-style-position-inside {
|
||||
list-style-position: inside;
|
||||
}
|
||||
}
|
||||
|
||||
td.mailer-align-left {
|
||||
|
|
|
@ -92,6 +92,7 @@ class GroupsController < Groups::ApplicationController
|
|||
if @group.import_state&.in_progress?
|
||||
redirect_to group_import_path(@group)
|
||||
else
|
||||
publish_invite_members_for_task_experiment
|
||||
render_show_html
|
||||
end
|
||||
end
|
||||
|
@ -379,6 +380,13 @@ class GroupsController < Groups::ApplicationController
|
|||
def captcha_required?
|
||||
captcha_enabled? && !params[:parent_id]
|
||||
end
|
||||
|
||||
def publish_invite_members_for_task_experiment
|
||||
return unless params[:open_modal] == 'invite_members_for_task'
|
||||
return unless current_user&.can?(:admin_group_member, @group)
|
||||
|
||||
experiment(:invite_members_for_task, namespace: @group).publish_to_client
|
||||
end
|
||||
end
|
||||
|
||||
GroupsController.prepend_mod_with('GroupsController')
|
||||
|
|
|
@ -41,6 +41,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:diffs_virtual_scrolling, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:restructured_mr_widget, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:mr_changes_fluid_layout, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml)
|
||||
|
||||
# Usage data feature flags
|
||||
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
|
||||
|
|
|
@ -16,6 +16,8 @@ module Registrations
|
|||
result = ::Users::SignupService.new(current_user, update_params).execute
|
||||
|
||||
if result[:status] == :success
|
||||
return redirect_to issues_dashboard_path(assignee_username: current_user.username) if show_tasks_to_be_done?
|
||||
|
||||
return redirect_to experiment(:combined_registration, user: current_user).redirect_path(trial_params) if show_signup_onboarding?
|
||||
|
||||
members = current_user.members
|
||||
|
@ -68,6 +70,12 @@ module Registrations
|
|||
false
|
||||
end
|
||||
|
||||
def show_tasks_to_be_done?
|
||||
return unless experiment(:invite_members_for_task).enabled?
|
||||
|
||||
MemberTask.for_members(current_user.members).exists?
|
||||
end
|
||||
|
||||
def trial_params
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -42,6 +42,14 @@ module InviteMembersHelper
|
|||
e.candidate { dataset.merge!(areas_of_focus_options: member_areas_of_focus_options.to_json, no_selection_areas_of_focus: ['no_selection']) }
|
||||
end
|
||||
|
||||
if show_invite_members_for_task?
|
||||
dataset.merge!(
|
||||
tasks_to_be_done_options: tasks_to_be_done_options.to_json,
|
||||
projects: projects_for_source(source).to_json,
|
||||
new_project_path: source.is_a?(Group) ? new_project_path(namespace_id: source.id) : ''
|
||||
)
|
||||
end
|
||||
|
||||
dataset
|
||||
end
|
||||
|
||||
|
@ -71,4 +79,19 @@ module InviteMembersHelper
|
|||
def users_filter_data(group)
|
||||
{}
|
||||
end
|
||||
|
||||
def show_invite_members_for_task?
|
||||
return unless current_user && experiment(:invite_members_for_task).enabled?
|
||||
|
||||
params[:open_modal] == 'invite_members_for_task'
|
||||
end
|
||||
|
||||
def tasks_to_be_done_options
|
||||
::MemberTask::TASKS.keys.map { |task| { value: task, text: localized_tasks_to_be_done_choices[task] } }
|
||||
end
|
||||
|
||||
def projects_for_source(source)
|
||||
projects = source.is_a?(Project) ? [source] : source.projects
|
||||
projects.map { |project| { id: project.id, title: project.title } }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -56,6 +56,14 @@ module MembersHelper
|
|||
end
|
||||
end
|
||||
|
||||
def localized_tasks_to_be_done_choices
|
||||
{
|
||||
code: s_('TasksToBeDone|Create/import code into a project (repository)'),
|
||||
ci: s_('TasksToBeDone|Set up CI/CD pipelines to build, test, deploy, and monitor code'),
|
||||
issues: s_('TasksToBeDone|Create/import issues (tickets) to collaborate on ideas and plan work')
|
||||
}.freeze
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def source_text(member)
|
||||
|
|
|
@ -425,6 +425,14 @@ class Environment < ApplicationRecord
|
|||
clear_reactive_cache!
|
||||
end
|
||||
|
||||
def should_link_to_merge_requests?
|
||||
unfoldered? || production? || staging?
|
||||
end
|
||||
|
||||
def unfoldered?
|
||||
environment_type.nil?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rollout_status_available?
|
||||
|
|
|
@ -317,13 +317,15 @@ class Group < Namespace
|
|||
owners.include?(user)
|
||||
end
|
||||
|
||||
def add_users(users, access_level, current_user: nil, expires_at: nil)
|
||||
def add_users(users, access_level, current_user: nil, expires_at: nil, tasks_to_be_done: [], tasks_project_id: nil)
|
||||
Members::Groups::BulkCreatorService.add_users( # rubocop:disable CodeReuse/ServiceClass
|
||||
self,
|
||||
users,
|
||||
access_level,
|
||||
current_user: current_user,
|
||||
expires_at: expires_at
|
||||
expires_at: expires_at,
|
||||
tasks_to_be_done: tasks_to_be_done,
|
||||
tasks_project_id: tasks_project_id
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ class Member < ApplicationRecord
|
|||
include FromUnion
|
||||
include UpdateHighestRole
|
||||
include RestrictedSignup
|
||||
include Gitlab::Experiment::Dsl
|
||||
|
||||
AVATAR_SIZE = 40
|
||||
ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10
|
||||
|
@ -22,8 +23,10 @@ class Member < ApplicationRecord
|
|||
belongs_to :created_by, class_name: "User"
|
||||
belongs_to :user
|
||||
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
||||
has_one :member_task
|
||||
|
||||
delegate :name, :username, :email, to: :user, prefix: true
|
||||
delegate :tasks_to_be_done, to: :member_task, allow_nil: true
|
||||
|
||||
validates :expires_at, allow_blank: true, future_date: true
|
||||
validates :user, presence: true, unless: :invite?
|
||||
|
@ -413,6 +416,14 @@ class Member < ApplicationRecord
|
|||
|
||||
def after_accept_invite
|
||||
post_create_hook
|
||||
|
||||
if experiment(:invite_members_for_task).enabled?
|
||||
run_after_commit_or_now do
|
||||
if member_task
|
||||
TasksToBeDone::CreateWorker.perform_async(member_task.id, created_by_id, [user_id.to_i])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def after_decline_invite
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MemberTask < ApplicationRecord
|
||||
TASKS = {
|
||||
code: 0,
|
||||
ci: 1,
|
||||
issues: 2
|
||||
}.freeze
|
||||
|
||||
belongs_to :member
|
||||
belongs_to :project
|
||||
|
||||
validates :member, :project, presence: true
|
||||
validates :tasks, inclusion: { in: TASKS.values }
|
||||
validate :tasks_uniqueness
|
||||
validate :project_in_member_source
|
||||
|
||||
scope :for_members, -> (members) { joins(:member).where(member: members) }
|
||||
|
||||
def tasks_to_be_done
|
||||
Array(self[:tasks]).map { |task| TASKS.key(task) }
|
||||
end
|
||||
|
||||
def tasks_to_be_done=(tasks)
|
||||
self[:tasks] = Array(tasks).map do |task|
|
||||
TASKS[task.to_sym]
|
||||
end.uniq
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tasks_uniqueness
|
||||
errors.add(:tasks, 'are not unique') unless Array(tasks).length == Array(tasks).uniq.length
|
||||
end
|
||||
|
||||
def project_in_member_source
|
||||
if member.is_a?(GroupMember)
|
||||
errors.add(:project, _('is not in the member group')) unless project.namespace == member.source
|
||||
elsif member.is_a?(ProjectMember)
|
||||
errors.add(:project, _('is not the member project')) unless project == member.source
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,6 +6,11 @@ class MergeRequestDiffCommit < ApplicationRecord
|
|||
include BulkInsertSafe
|
||||
include ShaAttribute
|
||||
include CachedCommit
|
||||
include IgnorableColumns
|
||||
|
||||
ignore_column %i[author_name author_email committer_name committer_email],
|
||||
remove_with: '14.6',
|
||||
remove_after: '2021-11-22'
|
||||
|
||||
belongs_to :merge_request_diff
|
||||
|
||||
|
@ -51,9 +56,14 @@ class MergeRequestDiffCommit < ApplicationRecord
|
|||
committer =
|
||||
users[[commit_hash[:committer_name], commit_hash[:committer_email]]]
|
||||
|
||||
# These fields are only used to determine the author/committer IDs, we
|
||||
# don't store them in the DB.
|
||||
commit_hash = commit_hash
|
||||
.except(:author_name, :author_email, :committer_name, :committer_email)
|
||||
|
||||
commit_hash.merge(
|
||||
commit_author_id: author&.id,
|
||||
committer_id: committer&.id,
|
||||
commit_author_id: author.id,
|
||||
committer_id: committer.id,
|
||||
merge_request_diff_id: merge_request_diff_id,
|
||||
relative_order: index,
|
||||
sha: Gitlab::Database::ShaAttribute.serialize(sha), # rubocop:disable Cop/ActiveRecordSerialize
|
||||
|
@ -104,18 +114,18 @@ class MergeRequestDiffCommit < ApplicationRecord
|
|||
end
|
||||
|
||||
def author_name
|
||||
commit_author_id ? commit_author.name : super
|
||||
commit_author.name
|
||||
end
|
||||
|
||||
def author_email
|
||||
commit_author_id ? commit_author.email : super
|
||||
commit_author.email
|
||||
end
|
||||
|
||||
def committer_name
|
||||
committer_id ? committer.name : super
|
||||
committer.name
|
||||
end
|
||||
|
||||
def committer_email
|
||||
committer_id ? committer.email : super
|
||||
committer.email
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,7 +63,11 @@ module Namespaces
|
|||
# Make sure we drop the STI `type = 'Group'` condition for better performance.
|
||||
# Logically equivalent so long as hierarchies remain homogeneous.
|
||||
def without_sti_condition
|
||||
unscope(where: :type)
|
||||
if Feature.enabled?(:include_sti_condition, default_enabled: :yaml)
|
||||
all
|
||||
else
|
||||
unscope(where: :type)
|
||||
end
|
||||
end
|
||||
|
||||
def order_by_depth(hierarchy_order)
|
||||
|
|
|
@ -41,13 +41,15 @@ class ProjectTeam
|
|||
member
|
||||
end
|
||||
|
||||
def add_users(users, access_level, current_user: nil, expires_at: nil)
|
||||
def add_users(users, access_level, current_user: nil, expires_at: nil, tasks_to_be_done: [], tasks_project_id: nil)
|
||||
Members::Projects::BulkCreatorService.add_users( # rubocop:disable CodeReuse/ServiceClass
|
||||
project,
|
||||
users,
|
||||
access_level,
|
||||
current_user: current_user,
|
||||
expires_at: expires_at
|
||||
expires_at: expires_at,
|
||||
tasks_to_be_done: tasks_to_be_done,
|
||||
tasks_project_id: tasks_project_id
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ module Members
|
|||
|
||||
included do
|
||||
class << self
|
||||
def add_users(source, users, access_level, current_user: nil, expires_at: nil)
|
||||
def add_users(source, users, access_level, current_user: nil, expires_at: nil, tasks_to_be_done: [], tasks_project_id: nil)
|
||||
return [] unless users.present?
|
||||
|
||||
emails, users, existing_members = parse_users_list(source, users)
|
||||
|
@ -18,7 +18,9 @@ module Members
|
|||
access_level,
|
||||
existing_members: existing_members,
|
||||
current_user: current_user,
|
||||
expires_at: expires_at)
|
||||
expires_at: expires_at,
|
||||
tasks_to_be_done: tasks_to_be_done,
|
||||
tasks_project_id: tasks_project_id)
|
||||
.execute
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,7 +16,7 @@ module Deployments
|
|||
# Review apps have the environment type set (e.g. to `review`, though the
|
||||
# exact value may differ). We don't want to link merge requests to review
|
||||
# app deployments, as this is not useful.
|
||||
return if deployment.environment.environment_type
|
||||
return unless deployment.environment.should_link_to_merge_requests?
|
||||
|
||||
# This service is triggered by a Sidekiq worker, which only runs when a
|
||||
# deployment is successful. We add an extra check here in case we ever
|
||||
|
|
|
@ -63,10 +63,14 @@ module Members
|
|||
invites,
|
||||
params[:access_level],
|
||||
expires_at: params[:expires_at],
|
||||
current_user: current_user
|
||||
current_user: current_user,
|
||||
tasks_to_be_done: params[:tasks_to_be_done],
|
||||
tasks_project_id: params[:tasks_project_id]
|
||||
)
|
||||
|
||||
members.each { |member| process_result(member) }
|
||||
|
||||
create_tasks_to_be_done
|
||||
end
|
||||
|
||||
def process_result(member)
|
||||
|
@ -112,6 +116,19 @@ module Members
|
|||
end
|
||||
end
|
||||
|
||||
def create_tasks_to_be_done
|
||||
return unless experiment(:invite_members_for_task).enabled?
|
||||
return if params[:tasks_to_be_done].blank? || params[:tasks_project_id].blank?
|
||||
|
||||
valid_members = members.select { |member| member.valid? && member.member_task.valid? }
|
||||
return unless valid_members.present?
|
||||
|
||||
# We can take the first `member_task` here, since all tasks will have the same attributes needed
|
||||
# for the `TasksToBeDone::CreateWorker`, ie. `project` and `tasks_to_be_done`.
|
||||
member_task = valid_members[0].member_task
|
||||
TasksToBeDone::CreateWorker.perform_async(member_task.id, current_user.id, valid_members.map(&:user_id))
|
||||
end
|
||||
|
||||
def areas_of_focus
|
||||
params[:areas_of_focus] || []
|
||||
end
|
||||
|
|
|
@ -4,6 +4,8 @@ module Members
|
|||
# This class serves as more of an app-wide way we add/create members
|
||||
# All roads to add members should take this path.
|
||||
class CreatorService
|
||||
include Gitlab::Experiment::Dsl
|
||||
|
||||
class << self
|
||||
def parsed_access_level(access_level)
|
||||
access_levels.fetch(access_level) { access_level.to_i }
|
||||
|
@ -24,6 +26,7 @@ module Members
|
|||
def execute
|
||||
find_or_build_member
|
||||
update_member
|
||||
create_member_task
|
||||
|
||||
member
|
||||
end
|
||||
|
@ -61,6 +64,21 @@ module Members
|
|||
}
|
||||
end
|
||||
|
||||
def create_member_task
|
||||
return unless experiment(:invite_members_for_task).enabled?
|
||||
return unless member.persisted?
|
||||
return if member_task_attributes.value?(nil)
|
||||
|
||||
member.create_member_task(member_task_attributes)
|
||||
end
|
||||
|
||||
def member_task_attributes
|
||||
{
|
||||
tasks_to_be_done: args[:tasks_to_be_done],
|
||||
project_id: args[:tasks_project_id]
|
||||
}
|
||||
end
|
||||
|
||||
def approve_request
|
||||
::Members::ApproveAccessRequestService.new(current_user,
|
||||
access_level: access_level)
|
||||
|
|
|
@ -39,6 +39,11 @@ module Members
|
|||
errors[invite_email(member)] = member.errors.full_messages.to_sentence
|
||||
end
|
||||
|
||||
override :create_tasks_to_be_done
|
||||
def create_tasks_to_be_done
|
||||
# Only create task issues for existing users. Tasks for new users are created when they signup.
|
||||
end
|
||||
|
||||
def invite_email(member)
|
||||
member.invite_email || member.user.email
|
||||
end
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module TasksToBeDone
|
||||
class BaseService < ::IssuableBaseService
|
||||
LABEL_PREFIX = 'tasks to be done'
|
||||
|
||||
def initialize(project:, current_user:, assignee_ids: [])
|
||||
params = {
|
||||
assignee_ids: assignee_ids,
|
||||
title: title,
|
||||
description: description,
|
||||
add_labels: label_name
|
||||
}
|
||||
super(project: project, current_user: current_user, params: params)
|
||||
end
|
||||
|
||||
def execute
|
||||
if (issue = existing_task_issue)
|
||||
update_service = Issues::UpdateService.new(project: project, current_user: current_user, params: { add_assignee_ids: params[:assignee_ids] })
|
||||
update_service.execute(issue)
|
||||
else
|
||||
build_service = Issues::BuildService.new(project: project, current_user: current_user, params: params)
|
||||
create(build_service.execute)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def existing_task_issue
|
||||
IssuesFinder.new(
|
||||
current_user,
|
||||
project_id: project.id,
|
||||
state: 'opened',
|
||||
non_archived: true,
|
||||
label_name: label_name
|
||||
).execute.last
|
||||
end
|
||||
|
||||
def title
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def description
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def label_suffix
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def label_name
|
||||
"#{LABEL_PREFIX}:#{label_suffix}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module TasksToBeDone
|
||||
class CreateCiTaskService < BaseService
|
||||
protected
|
||||
|
||||
def title
|
||||
'Set up CI/CD'
|
||||
end
|
||||
|
||||
def description
|
||||
<<~DESCRIPTION
|
||||
GitLab CI/CD is a tool built into GitLab for software development through the [continuous methodologies](https://docs.gitlab.com/ee/ci/introduction/index.html#introduction-to-cicd-methodologies):
|
||||
|
||||
* Continuous Integration (CI)
|
||||
* Continuous Delivery (CD)
|
||||
* Continuous Deployment (CD)
|
||||
|
||||
Continuous Integration works by pushing small changes to your application’s codebase hosted in a Git repository, and, to every push, run a pipeline of scripts to build, test, and validate the code changes before merging them into the main branch.
|
||||
|
||||
Continuous Delivery and Deployment consist of a step further CI, deploying your application to production at every push to the default branch of the repository.
|
||||
|
||||
These methodologies allow you to catch bugs and errors early in the development cycle, ensuring that all the code deployed to production complies with the code standards you established for your app.
|
||||
|
||||
* :book: [Read the documentation](https://docs.gitlab.com/ee/ci/introduction/index.html)
|
||||
* :clapper: [Watch a Demo](https://www.youtube.com/watch?v=1iXFbchozdY)
|
||||
|
||||
## Next steps
|
||||
|
||||
* [ ] To start we recommend reviewing the following documentation:
|
||||
* [ ] [How GitLab CI/CD works.](https://docs.gitlab.com/ee/ci/introduction/index.html#how-gitlab-cicd-works)
|
||||
* [ ] [Fundamental pipeline architectures.](https://docs.gitlab.com/ee/ci/pipelines/pipeline_architectures.html)
|
||||
* [ ] [GitLab CI/CD basic workflow.](https://docs.gitlab.com/ee/ci/introduction/index.html#basic-cicd-workflow)
|
||||
* [ ] [Step-by-step guide for writing .gitlab-ci.yml for the first time.](https://docs.gitlab.com/ee/user/project/pages/getting_started_part_four.html)
|
||||
* [ ] When you're ready select **Projects** (in the top navigation bar) > **Your projects** > select the Project you've already created.
|
||||
* [ ] Select **CI / CD** in the left navigation to start setting up CI / CD in your project.
|
||||
DESCRIPTION
|
||||
end
|
||||
|
||||
def label_suffix
|
||||
'ci'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module TasksToBeDone
|
||||
class CreateCodeTaskService < BaseService
|
||||
protected
|
||||
|
||||
def title
|
||||
'Create or import your code into your Project (Repository)'
|
||||
end
|
||||
|
||||
def description
|
||||
<<~DESCRIPTION
|
||||
You've already created your Group and Project within GitLab; we'll quickly review this hierarchy below. Once you're within your project you can easily create or import repositories.
|
||||
|
||||
**With GitLab Groups, you can:**
|
||||
|
||||
* Create one or multiple Projects for hosting your codebase (repositories).
|
||||
* Assemble related projects together.
|
||||
* Grant members access to several projects at once.
|
||||
|
||||
Groups can also be nested in subgroups.
|
||||
|
||||
Read more about groups in our [documentation](https://docs.gitlab.com/ee/user/group/).
|
||||
|
||||
**Within GitLab Projects, you can**
|
||||
|
||||
* Use it as an issue tracker.
|
||||
* Collaborate on code.
|
||||
* Continuously build, test, and deploy your app with built-in GitLab CI/CD.
|
||||
|
||||
You can also import an existing repository by providing the Git URL.
|
||||
|
||||
* :book: [Read the documentation](https://docs.gitlab.com/ee/user/project/index.html).
|
||||
|
||||
## Next steps
|
||||
|
||||
Create or import your first repository into the project you created:
|
||||
|
||||
* [ ] Click **Projects** in the top navigation bar, then click **Your projects**.
|
||||
* [ ] Select the Project that you created, then select **Repository**.
|
||||
* [ ] Once on the Repository page you can select the **+** icon to add or import files.
|
||||
* [ ] You can review our full documentation on creating [repositories](https://docs.gitlab.com/ee/user/project/repository/) in GitLab.
|
||||
|
||||
:tada: All done, you can close this issue!
|
||||
DESCRIPTION
|
||||
end
|
||||
|
||||
def label_suffix
|
||||
'code'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module TasksToBeDone
|
||||
class CreateIssuesTaskService < BaseService
|
||||
protected
|
||||
|
||||
def title
|
||||
'Create/import issues (tickets) to collaborate on ideas and plan work'
|
||||
end
|
||||
|
||||
def description
|
||||
<<~DESCRIPTION
|
||||
Issues allow you and your team to discuss proposals before, and during, their implementation. They can be used for a variety of other purposes, customized to your needs and workflow.
|
||||
|
||||
Issues are always associated with a specific project. If you have multiple projects in a group, you can view all the issues at the group level. [You can review our full Issue documentation here.](https://docs.gitlab.com/ee/user/project/issues/)
|
||||
|
||||
If you have existing issues or equivalent tickets you can import them as long as they are formatted as a CSV file, [the import process is covered here](https://docs.gitlab.com/ee/user/project/issues/csv_import.html).
|
||||
|
||||
**Common use cases include:**
|
||||
|
||||
* Discussing the implementation of a new idea
|
||||
* Tracking tasks and work status
|
||||
* Accepting feature proposals, questions, support requests, or bug reports
|
||||
* Elaborating on new code implementations
|
||||
|
||||
## Next steps
|
||||
|
||||
* [ ] Select **Projects** in the top navigation > **Your Projects** > select the Project you've already created.
|
||||
* [ ] Once you've selected that project, you can select **Issues** in the left navigation, then click **New issue**.
|
||||
* [ ] Fill in the title and description in the **New issue** page.
|
||||
* [ ] Click on **Create issue**.
|
||||
|
||||
Pro tip: When you're in a group or project you can always utilize the **+** icon in the top navigation (located to the left of the search bar) to quickly create new issues.
|
||||
|
||||
That's it! You can close this issue.
|
||||
DESCRIPTION
|
||||
end
|
||||
|
||||
def label_suffix
|
||||
'issues'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -210,6 +210,12 @@
|
|||
%td{ style: "padding: 10px 20px 30px 20px; font-family: 'Source Sans Pro', helvetica, arial, sans-serif; color:#000000; font-size: 18px; line-height: 24px;" }
|
||||
%p{ style: "margin: 0 0 50px 0;" }
|
||||
= @message.feedback_thanks
|
||||
- if @message.invite_members?
|
||||
%tr
|
||||
%td{ align: "center", style: "padding: 0 20px 80px 20px; font-family: 'Source Sans Pro', helvetica, arial, sans-serif;" }
|
||||
= @message.invite_text
|
||||
%br
|
||||
= @message.invite_link
|
||||
%tr{ style: "background-color: #ffffff;" }
|
||||
%td{ align: "center", style: "padding:75px 20px 25px;" }
|
||||
= about_link('gitlab_logo.png', 80)
|
||||
|
|
|
@ -21,6 +21,10 @@
|
|||
|
||||
<%= @message.feedback_thanks %>
|
||||
<% end %>
|
||||
<% if @message.invite_members? %>
|
||||
<%= @message.invite_text %>
|
||||
<%= @message.invite_link %>
|
||||
<% end %>
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
%td.text-content
|
||||
%p
|
||||
= _('You have been granted %{access_level} access to the %{source_link} %{source_type}.').html_safe % { access_level: access_level, source_link: source_link, source_type: source_type }
|
||||
- if member.tasks_to_be_done.present?
|
||||
= s_("InviteEmail|You were assigned the following tasks:")
|
||||
%ul.list-style-position-inside
|
||||
- member.tasks_to_be_done.each do |task|
|
||||
%li= localized_tasks_to_be_done_choices[task]
|
||||
%p
|
||||
- leave_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: leave_link }
|
||||
= _('If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}.').html_safe % { source_type: source_type, leave_link_start: leave_link_start, link_end: link_end }
|
||||
|
||||
|
|
|
@ -24,6 +24,11 @@
|
|||
%p
|
||||
- if member.created_by
|
||||
= html_escape(s_("InviteEmail|%{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}")) % placeholders.merge({ inviter: (link_to inviter_name, user_url(member.created_by)).html_safe })
|
||||
- if member.tasks_to_be_done.present?
|
||||
= s_("InviteEmail|and has assigned you the following tasks:")
|
||||
%ul.list-style-position-inside
|
||||
- member.tasks_to_be_done.each do |task|
|
||||
%li= localized_tasks_to_be_done_choices[task]
|
||||
- else
|
||||
= html_escape(s_("InviteEmail|You are invited to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}")) % placeholders
|
||||
%p.invite-actions
|
||||
|
|
|
@ -2808,6 +2808,15 @@
|
|||
:weight: 1
|
||||
:idempotent:
|
||||
:tags: []
|
||||
- :name: tasks_to_be_done_create
|
||||
:worker_name: TasksToBeDone::CreateWorker
|
||||
:feature_category: :onboarding
|
||||
:has_external_dependencies:
|
||||
:urgency: :low
|
||||
:resource_boundary: :cpu
|
||||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: update_external_pull_requests
|
||||
:worker_name: UpdateExternalPullRequestsWorker
|
||||
:feature_category: :source_code_management
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module TasksToBeDone
|
||||
class CreateWorker
|
||||
include ApplicationWorker
|
||||
|
||||
data_consistency :always
|
||||
idempotent!
|
||||
feature_category :onboarding
|
||||
urgency :low
|
||||
worker_resource_boundary :cpu
|
||||
|
||||
def perform(member_task_id, current_user_id, assignee_ids = [])
|
||||
member_task = MemberTask.find(member_task_id)
|
||||
current_user = User.find(current_user_id)
|
||||
project = member_task.project
|
||||
|
||||
member_task.tasks_to_be_done.each do |task|
|
||||
service_class(task)
|
||||
.new(project: project, current_user: current_user, assignee_ids: assignee_ids)
|
||||
.execute
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def service_class(task)
|
||||
"TasksToBeDone::Create#{task.to_s.camelize}TaskService".constantize
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: include_sti_condition
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72119
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343412
|
||||
milestone: '14.5'
|
||||
type: development
|
||||
group: group::workspace
|
||||
default_enabled: false
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: mr_attention_requests
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72773
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343528
|
||||
milestone: '14.4'
|
||||
type: development
|
||||
group: group::code review
|
||||
default_enabled: false
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: invite_members_for_task
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69299
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339747
|
||||
milestone: '14.5'
|
||||
type: experiment
|
||||
group: group::activation
|
||||
default_enabled: false
|
|
@ -393,6 +393,8 @@
|
|||
- 1
|
||||
- - system_hook_push
|
||||
- 1
|
||||
- - tasks_to_be_done_create
|
||||
- 1
|
||||
- - todos_destroyer
|
||||
- 1
|
||||
- - unassign_issuables
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateMemberTasks < Gitlab::Database::Migration[1.0]
|
||||
def change
|
||||
create_table :member_tasks do |t|
|
||||
t.references :member, index: true, null: false
|
||||
t.references :project, index: true, null: false
|
||||
t.timestamps_with_timezone null: false
|
||||
t.integer :tasks, limit: 2, array: true, null: false, default: []
|
||||
t.index [:member_id, :project_id], unique: true
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddMemberIdForeignKeyToMemberTasks < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :member_tasks, :members, column: :member_id, on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :member_tasks, column: :member_id
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProjectIdForeignKeyToMemberTasks < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :member_tasks, :projects, column: :project_id, on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :member_tasks, column: :project_id
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CleanUpMigrateMergeRequestDiffCommitUsers < Gitlab::Database::Migration[1.0]
|
||||
def up
|
||||
jobs = Gitlab::Database::BackgroundMigrationJob
|
||||
.for_migration_class('MigrateMergeRequestDiffCommitUsers')
|
||||
.pending
|
||||
.to_a
|
||||
|
||||
return if jobs.empty?
|
||||
|
||||
say("#{jobs.length} MigrateMergeRequestDiffCommitUsers are still pending")
|
||||
|
||||
# Normally we don't process background migrations in a regular migration, as
|
||||
# this could take a while to complete and thus block a deployment.
|
||||
#
|
||||
# In this case the jobs have all been processed for GitLab.com at the time
|
||||
# of writing. In addition, it's been a few releases since this migration was
|
||||
# introduced. As a result, self-hosted instances should have their
|
||||
# migrations finished a long time ago.
|
||||
#
|
||||
# For these reasons we clean up any pending jobs (just in case) before
|
||||
# deploying the code. This also allows us to immediately start using the new
|
||||
# setup only, instead of having to support both the old and new approach for
|
||||
# at least one more release.
|
||||
jobs.each do |job|
|
||||
Gitlab::BackgroundMigration::MigrateMergeRequestDiffCommitUsers
|
||||
.new
|
||||
.perform(*job.arguments)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddGroupTraversalIdIndex < Gitlab::Database::Migration[1.0]
|
||||
INDEX_NAME = 'index_namespaces_on_traversal_ids_for_groups'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index :namespaces, :traversal_ids, using: :gin, where: "type='Group'", name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :namespaces, INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveMergeRequestDiffCommitColumns < Gitlab::Database::Migration[1.0]
|
||||
enable_lock_retries!
|
||||
|
||||
COLUMNS = %i[author_name author_email committer_name committer_email].freeze
|
||||
|
||||
def change
|
||||
COLUMNS.each do |column|
|
||||
remove_column(:merge_request_diff_commits, column, :text)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
72358f01061f5296e21647d5da9bbb6a33e94055c9c9aded6088cfb9126564b2
|
|
@ -0,0 +1 @@
|
|||
f4fe6c4a2860dd35f767d98d5025326142cab7fc9c12b5efb1541e2604791691
|
|
@ -0,0 +1 @@
|
|||
59e5de7766dc55e820ec714fbb61b5db61a73959f1e877e66caf668f93d0d633
|
|
@ -0,0 +1 @@
|
|||
0f2578f0266154ad2790cc808233c71566b3a3ea87c40909feba9ccc5872927c
|
|
@ -0,0 +1 @@
|
|||
2685a534728ab1a50acb49a7a5ac7d9285fdc36ec3610b93a4219e6687c22b06
|
|
@ -0,0 +1 @@
|
|||
a62ac8920223469c6e4c5a7f67ce9eec972189c98a8c542b377afe4ab28ee25a
|
|
@ -15671,6 +15671,24 @@ CREATE SEQUENCE lists_id_seq
|
|||
|
||||
ALTER SEQUENCE lists_id_seq OWNED BY lists.id;
|
||||
|
||||
CREATE TABLE member_tasks (
|
||||
id bigint NOT NULL,
|
||||
member_id bigint NOT NULL,
|
||||
project_id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
tasks smallint[] DEFAULT '{}'::smallint[] NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE member_tasks_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE member_tasks_id_seq OWNED BY member_tasks.id;
|
||||
|
||||
CREATE TABLE members (
|
||||
id integer NOT NULL,
|
||||
access_level integer NOT NULL,
|
||||
|
@ -15819,10 +15837,6 @@ CREATE TABLE merge_request_diff_commits (
|
|||
merge_request_diff_id integer NOT NULL,
|
||||
relative_order integer NOT NULL,
|
||||
sha bytea NOT NULL,
|
||||
author_name text,
|
||||
author_email text,
|
||||
committer_name text,
|
||||
committer_email text,
|
||||
message text,
|
||||
trailers jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
commit_author_id bigint,
|
||||
|
@ -21502,6 +21516,8 @@ ALTER TABLE ONLY lists ALTER COLUMN id SET DEFAULT nextval('lists_id_seq'::regcl
|
|||
|
||||
ALTER TABLE ONLY loose_foreign_keys_deleted_records ALTER COLUMN id SET DEFAULT nextval('loose_foreign_keys_deleted_records_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY member_tasks ALTER COLUMN id SET DEFAULT nextval('member_tasks_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY members ALTER COLUMN id SET DEFAULT nextval('members_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY merge_request_assignees ALTER COLUMN id SET DEFAULT nextval('merge_request_assignees_id_seq'::regclass);
|
||||
|
@ -23204,6 +23220,9 @@ ALTER TABLE ONLY list_user_preferences
|
|||
ALTER TABLE ONLY lists
|
||||
ADD CONSTRAINT lists_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY member_tasks
|
||||
ADD CONSTRAINT member_tasks_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY members
|
||||
ADD CONSTRAINT members_pkey PRIMARY KEY (id);
|
||||
|
||||
|
@ -25654,6 +25673,12 @@ CREATE INDEX index_lists_on_milestone_id ON lists USING btree (milestone_id);
|
|||
|
||||
CREATE INDEX index_lists_on_user_id ON lists USING btree (user_id);
|
||||
|
||||
CREATE INDEX index_member_tasks_on_member_id ON member_tasks USING btree (member_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_member_tasks_on_member_id_and_project_id ON member_tasks USING btree (member_id, project_id);
|
||||
|
||||
CREATE INDEX index_member_tasks_on_project_id ON member_tasks USING btree (project_id);
|
||||
|
||||
CREATE INDEX index_members_on_access_level ON members USING btree (access_level);
|
||||
|
||||
CREATE INDEX index_members_on_expires_at ON members USING btree (expires_at);
|
||||
|
@ -25872,6 +25897,8 @@ CREATE INDEX index_namespaces_on_shared_and_extra_runners_minutes_limit ON names
|
|||
|
||||
CREATE INDEX index_namespaces_on_traversal_ids ON namespaces USING gin (traversal_ids);
|
||||
|
||||
CREATE INDEX index_namespaces_on_traversal_ids_for_groups ON namespaces USING gin (traversal_ids) WHERE ((type)::text = 'Group'::text);
|
||||
|
||||
CREATE INDEX index_namespaces_on_type_and_id ON namespaces USING btree (type, id);
|
||||
|
||||
CREATE INDEX index_namespaces_public_groups_name_id ON namespaces USING btree (name, id) WHERE (((type)::text = 'Group'::text) AND (visibility_level = 20));
|
||||
|
@ -27588,6 +27615,9 @@ ALTER TABLE ONLY project_pages_metadata
|
|||
ALTER TABLE ONLY group_deletion_schedules
|
||||
ADD CONSTRAINT fk_11e3ebfcdd FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY member_tasks
|
||||
ADD CONSTRAINT fk_12816d4bbb FOREIGN KEY (member_id) REFERENCES members(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY vulnerabilities
|
||||
ADD CONSTRAINT fk_1302949740 FOREIGN KEY (last_edited_by_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
|
@ -28068,6 +28098,9 @@ ALTER TABLE ONLY identities
|
|||
ALTER TABLE ONLY boards
|
||||
ADD CONSTRAINT fk_ab0a250ff6 FOREIGN KEY (iteration_cadence_id) REFERENCES iterations_cadences(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY member_tasks
|
||||
ADD CONSTRAINT fk_ab636303dd FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY dep_ci_build_trace_sections
|
||||
ADD CONSTRAINT fk_ab7c104e26 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ POST /projects/:id/invitations
|
|||
| `expires_at` | string | no | A date string in the format YEAR-MONTH-DAY |
|
||||
| `invite_source` | string | no | The source of the invitation that starts the member creation process. See [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/327120). |
|
||||
| `areas_of_focus` | string | no | Areas the inviter wants the member to focus upon. |
|
||||
| `tasks_to_be_done` | array of strings | no | Tasks the inviter wants the member to focus on. The tasks are added as issues to a specified project. The possible values are: `ci`, `code` and `issues`. If specified, requires `tasks_project_id`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69299) in GitLab 14.5 [with a flag](../administration/feature_flags.md) named `invite_members_for_task`. Disabled by default. |
|
||||
| `tasks_project_id` | integer | no | The project ID in which to create the task issues. If specified, requires `tasks_to_be_done`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69299) in GitLab 14.5 [with a flag](../administration/feature_flags.md) named `invite_members_for_task`. Disabled by default. |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
|
|
|
@ -422,6 +422,8 @@ POST /projects/:id/members
|
|||
| `expires_at` | string | no | A date string in the format `YEAR-MONTH-DAY` |
|
||||
| `invite_source` | string | no | The source of the invitation that starts the member creation process. See [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/327120). |
|
||||
| `areas_of_focus` | string | no | Areas the inviter wants the member to focus upon. |
|
||||
| `tasks_to_be_done` | array of strings | no | Tasks the inviter wants the member to focus on. The tasks are added as issues to a specified project. The possible values are: `ci`, `code` and `issues`. If specified, requires `tasks_project_id`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69299) in GitLab 14.5 [with a flag](../administration/feature_flags.md) named `invite_members_for_task`. Disabled by default. |
|
||||
| `tasks_project_id` | integer | no | The project ID in which to create the task issues. If specified, requires `tasks_to_be_done`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69299) in GitLab 14.5 [with a flag](../administration/feature_flags.md) named `invite_members_for_task`. Disabled by default. |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
|
|
|
@ -77,6 +77,26 @@ Data is shown for workflow items created during the selected date range. To filt
|
|||
1. Optionally select a project.
|
||||
1. Select a date range using the available date pickers.
|
||||
|
||||
### Upcoming date filter change
|
||||
|
||||
In the [epics](https://gitlab.com/groups/gitlab-org/-/epics/6046), we plan to alter
|
||||
the date filter behavior to filter the end event time of the currently selected stage.
|
||||
|
||||
The change makes it possible to get a much better picture about the completed items within the
|
||||
stage and helps uncover long-running items.
|
||||
|
||||
For example, an issue was created a year ago and the current stage was finished in the current month.
|
||||
If you were to look at the metrics for the last three months, this issue would not be included in the calculation of
|
||||
the stage metrics. With the new date filter, this item would be included.
|
||||
|
||||
DISCLAIMER:
|
||||
This page contains information related to upcoming products, features, and functionality.
|
||||
It is important to note that the information presented is for informational purposes only.
|
||||
Please do not rely on this information for purchasing or planning purposes.
|
||||
As with all projects, the items mentioned on this page are subject to change or delay.
|
||||
The development, release, and timing of any products, features, or functionality remain at the
|
||||
sole discretion of GitLab Inc.
|
||||
|
||||
## How metrics are measured
|
||||
|
||||
> DORA API-based deployment metrics [moved](https://gitlab.com/gitlab-org/gitlab/-/issues/337256)
|
||||
|
|
|
@ -25,6 +25,8 @@ module API
|
|||
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
|
||||
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'invitations-api'
|
||||
optional :areas_of_focus, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Areas the inviter wants the member to focus upon'
|
||||
optional :tasks_to_be_done, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Tasks the inviter wants the member to do'
|
||||
optional :tasks_project_id, type: Integer, desc: 'The project ID in which to create the task issues'
|
||||
end
|
||||
post ":id/invitations" do
|
||||
params[:source] = find_source(source_type, params[:id])
|
||||
|
|
|
@ -95,6 +95,8 @@ module API
|
|||
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
|
||||
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'members-api'
|
||||
optional :areas_of_focus, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Areas the inviter wants the member to focus upon'
|
||||
optional :tasks_to_be_done, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Tasks the inviter wants the member to do'
|
||||
optional :tasks_project_id, type: Integer, desc: 'The project ID in which to create the task issues'
|
||||
end
|
||||
|
||||
post ":id/members" do
|
||||
|
|
|
@ -36,6 +36,10 @@ module Gitlab
|
|||
def progress
|
||||
super(track_name: 'Admin')
|
||||
end
|
||||
|
||||
def invite_members?
|
||||
invite_members_for_task_experiment_enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,6 +7,7 @@ module Gitlab
|
|||
class Base
|
||||
include Gitlab::Email::Message::InProductMarketing::Helper
|
||||
include Gitlab::Routing
|
||||
include Gitlab::Experiment::Dsl
|
||||
|
||||
attr_accessor :format
|
||||
|
||||
|
@ -56,6 +57,18 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def invite_members?
|
||||
false
|
||||
end
|
||||
|
||||
def invite_text
|
||||
s_('InProductMarketing|Do you have a teammate who would be perfect for this task?')
|
||||
end
|
||||
|
||||
def invite_link
|
||||
action_link(s_('InProductMarketing|Invite them to help out.'), group_url(group, open_modal: 'invite_members_for_task'))
|
||||
end
|
||||
|
||||
def unsubscribe
|
||||
parts = Gitlab.com? ? unsubscribe_com : unsubscribe_self_managed(track, series)
|
||||
|
||||
|
@ -148,6 +161,16 @@ module Gitlab
|
|||
|
||||
link(s_('InProductMarketing|update your preferences'), preference_link)
|
||||
end
|
||||
|
||||
def invite_members_for_task_experiment_enabled?
|
||||
return unless user.can?(:admin_group_member, group)
|
||||
|
||||
experiment(:invite_members_for_task, namespace: group) do |e|
|
||||
e.candidate { true }
|
||||
e.record!
|
||||
e.run
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -61,6 +61,10 @@ module Gitlab
|
|||
][series]
|
||||
end
|
||||
|
||||
def invite_members?
|
||||
invite_members_for_task_experiment_enabled?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project_link
|
||||
|
|
|
@ -36,6 +36,15 @@ module Gitlab
|
|||
"#{text} (#{link})"
|
||||
end
|
||||
end
|
||||
|
||||
def action_link(text, link)
|
||||
case format
|
||||
when :html
|
||||
ActionController::Base.helpers.link_to text, link, target: '_blank', rel: 'noopener noreferrer'
|
||||
else
|
||||
[text, link].join(' >> ')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -65,6 +65,10 @@ module Gitlab
|
|||
][series]
|
||||
end
|
||||
|
||||
def invite_members?
|
||||
invite_members_for_task_experiment_enabled?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ci_link
|
||||
|
|
|
@ -399,6 +399,10 @@ excluded_attributes:
|
|||
- :verification_checksum
|
||||
- :verification_failure
|
||||
merge_request_diff_commits:
|
||||
- :author_name
|
||||
- :author_email
|
||||
- :committer_name
|
||||
- :committer_email
|
||||
- :merge_request_diff_id
|
||||
- :commit_author_id
|
||||
- :committer_id
|
||||
|
|
|
@ -17544,6 +17544,9 @@ msgstr ""
|
|||
msgid "InProductMarketing|Do you have a minute?"
|
||||
msgstr ""
|
||||
|
||||
msgid "InProductMarketing|Do you have a teammate who would be perfect for this task?"
|
||||
msgstr ""
|
||||
|
||||
msgid "InProductMarketing|Easy"
|
||||
msgstr ""
|
||||
|
||||
|
@ -17658,6 +17661,9 @@ msgstr ""
|
|||
msgid "InProductMarketing|Increase Operational Efficiencies"
|
||||
msgstr ""
|
||||
|
||||
msgid "InProductMarketing|Invite them to help out."
|
||||
msgstr ""
|
||||
|
||||
msgid "InProductMarketing|Invite your colleagues and start shipping code faster."
|
||||
msgstr ""
|
||||
|
||||
|
@ -18692,6 +18698,12 @@ msgstr ""
|
|||
msgid "InviteEmail|You have been invited to join the %{project_or_group_name} %{project_or_group} as a %{role}"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteEmail|You were assigned the following tasks:"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteEmail|and has assigned you the following tasks:"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersBanner|Collaborate with your team"
|
||||
msgstr ""
|
||||
|
||||
|
@ -18710,6 +18722,9 @@ msgstr ""
|
|||
msgid "InviteMembersModal|Cancel"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|Choose a project for the issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|Close invite team members"
|
||||
msgstr ""
|
||||
|
||||
|
@ -18725,6 +18740,9 @@ msgstr ""
|
|||
msgid "InviteMembersModal|Contribute to the codebase"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|Create issues for your new team member to work on (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|GitLab member or email address"
|
||||
msgstr ""
|
||||
|
||||
|
@ -18758,6 +18776,9 @@ msgstr ""
|
|||
msgid "InviteMembersModal|Something went wrong"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|To assign issues to a new team member, you need a project for the issues. %{linkStart}Create a project to get started.%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|What would you like new member(s) to focus on? (optional)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -25384,6 +25405,9 @@ msgstr ""
|
|||
msgid "Pipeline|Merged result pipeline"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|No pipeline was triggered for the latest changes due to the current CI/CD configuration."
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Passed"
|
||||
msgstr ""
|
||||
|
||||
|
@ -33412,6 +33436,15 @@ msgstr ""
|
|||
msgid "Task ID: %{elastic_task}"
|
||||
msgstr ""
|
||||
|
||||
msgid "TasksToBeDone|Create/import code into a project (repository)"
|
||||
msgstr ""
|
||||
|
||||
msgid "TasksToBeDone|Create/import issues (tickets) to collaborate on ideas and plan work"
|
||||
msgstr ""
|
||||
|
||||
msgid "TasksToBeDone|Set up CI/CD pipelines to build, test, deploy, and monitor code"
|
||||
msgstr ""
|
||||
|
||||
msgid "Team"
|
||||
msgstr ""
|
||||
|
||||
|
@ -37555,6 +37588,9 @@ msgstr ""
|
|||
msgid "ValueStreamAnalytics|Average number of deployments to production per day."
|
||||
msgstr ""
|
||||
|
||||
msgid "ValueStreamAnalytics|Items in Value Stream Analytics are currently filtered by their creation time. There is an %{epic_link_start}epic%{epic_link_end} that will change the Value Stream Analytics date filter to use the end event time for the selected stage."
|
||||
msgstr ""
|
||||
|
||||
msgid "ValueStreamAnalytics|Median time between merge request merge and deployment to a production environment for all MRs deployed in the given time period."
|
||||
msgstr ""
|
||||
|
||||
|
@ -40555,6 +40591,12 @@ msgstr ""
|
|||
msgid "is not in the group enforcing Group Managed Account"
|
||||
msgstr ""
|
||||
|
||||
msgid "is not in the member group"
|
||||
msgstr ""
|
||||
|
||||
msgid "is not the member project"
|
||||
msgstr ""
|
||||
|
||||
msgid "is not valid. The iteration group has to match the iteration cadence group."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -82,6 +82,16 @@ RSpec.describe GroupsController, factory_default: :keep do
|
|||
expect(subject).to redirect_to group_import_path(group)
|
||||
end
|
||||
end
|
||||
|
||||
context 'publishing the invite_members_for_task experiment' do
|
||||
it 'publishes the experiment data to the client' do
|
||||
wrapped_experiment(experiment(:invite_members_for_task)) do |e|
|
||||
expect(e).to receive(:publish_to_client)
|
||||
end
|
||||
|
||||
get :show, params: { id: group.to_param, open_modal: 'invite_members_for_task' }, format: format
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #details' do
|
||||
|
|
|
@ -97,6 +97,16 @@ RSpec.describe Registrations::WelcomeController do
|
|||
expect(subject).to redirect_to(dashboard_projects_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tasks to be done are assigned' do
|
||||
let!(:member1) { create(:group_member, user: user, tasks_to_be_done: %w(ci code)) }
|
||||
|
||||
before do
|
||||
stub_experiments(invite_members_for_task: true)
|
||||
end
|
||||
|
||||
it { is_expected.to redirect_to(issues_dashboard_path(assignee_username: user.username)) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,5 +34,18 @@ FactoryBot.define do
|
|||
|
||||
access_level { GroupMember::MINIMAL_ACCESS }
|
||||
end
|
||||
|
||||
transient do
|
||||
tasks_to_be_done { [] }
|
||||
end
|
||||
|
||||
after(:build) do |group_member, evaluator|
|
||||
if evaluator.tasks_to_be_done.present?
|
||||
build(:member_task,
|
||||
member: group_member,
|
||||
project: build(:project, namespace: group_member.source),
|
||||
tasks_to_be_done: evaluator.tasks_to_be_done)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :member_task do
|
||||
member { association(:group_member, :invited) }
|
||||
project { association(:project, namespace: member.source) }
|
||||
tasks_to_be_done { [:ci, :code] }
|
||||
end
|
||||
end
|
|
@ -23,5 +23,15 @@ FactoryBot.define do
|
|||
trait :blocked do
|
||||
after(:build) { |project_member, _| project_member.user.block! }
|
||||
end
|
||||
|
||||
transient do
|
||||
tasks_to_be_done { [] }
|
||||
end
|
||||
|
||||
after(:build) do |project_member, evaluator|
|
||||
if evaluator.tasks_to_be_done.present?
|
||||
build(:member_task, member: project_member, project: project_member.source, tasks_to_be_done: evaluator.tasks_to_be_done)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -75,6 +75,7 @@ RSpec.describe 'factories' do
|
|||
group_member
|
||||
import_state
|
||||
issue_customer_relations_contact
|
||||
member_task
|
||||
milestone_release
|
||||
namespace
|
||||
project_broken_repo
|
||||
|
|
|
@ -2795,11 +2795,7 @@
|
|||
"sha": "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
|
||||
"message": "Feature conflict added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-08-06T08:35:52.000+02:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-08-06T08:35:52.000+02:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -2815,11 +2811,7 @@
|
|||
"sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
|
||||
"message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T10:01:38.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T10:01:38.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -2835,11 +2827,7 @@
|
|||
"sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
|
||||
"message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T09:57:31.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:57:31.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -2855,11 +2843,7 @@
|
|||
"sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
|
||||
"message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T09:54:21.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:54:21.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -2875,11 +2859,7 @@
|
|||
"sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
|
||||
"message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T09:49:50.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:49:50.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -2895,11 +2875,7 @@
|
|||
"sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
|
||||
"message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T09:48:32.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:48:32.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -3291,11 +3267,7 @@
|
|||
"relative_order": 0,
|
||||
"message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T09:26:01.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:26:01.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -3562,11 +3534,7 @@
|
|||
"sha": "94b8d581c48d894b86661718582fecbc5e3ed2eb",
|
||||
"message": "fixes #10\n",
|
||||
"authored_date": "2016-01-19T13:22:56.000+01:00",
|
||||
"author_name": "James Lopez",
|
||||
"author_email": "james@jameslopez.es",
|
||||
"committed_date": "2016-01-19T13:22:56.000+01:00",
|
||||
"committer_name": "James Lopez",
|
||||
"committer_email": "james@jameslopez.es",
|
||||
"commit_author": {
|
||||
"name": "James Lopez",
|
||||
"email": "james@jameslopez.es"
|
||||
|
@ -3833,11 +3801,7 @@
|
|||
"sha": "ddd4ff416a931589c695eb4f5b23f844426f6928",
|
||||
"message": "fixes #10\n",
|
||||
"authored_date": "2016-01-19T14:14:43.000+01:00",
|
||||
"author_name": "James Lopez",
|
||||
"author_email": "james@jameslopez.es",
|
||||
"committed_date": "2016-01-19T14:14:43.000+01:00",
|
||||
"committer_name": "James Lopez",
|
||||
"committer_email": "james@jameslopez.es",
|
||||
"commit_author": {
|
||||
"name": "James Lopez",
|
||||
"email": "james@jameslopez.es"
|
||||
|
@ -3853,11 +3817,7 @@
|
|||
"sha": "be93687618e4b132087f430a4d8fc3a609c9b77c",
|
||||
"message": "Merge branch 'master' into 'master'\r\n\r\nLFS object pointer.\r\n\r\n\r\n\r\nSee merge request !6",
|
||||
"authored_date": "2015-12-07T12:52:12.000+01:00",
|
||||
"author_name": "Marin Jankovski",
|
||||
"author_email": "marin@gitlab.com",
|
||||
"committed_date": "2015-12-07T12:52:12.000+01:00",
|
||||
"committer_name": "Marin Jankovski",
|
||||
"committer_email": "marin@gitlab.com",
|
||||
"commit_author": {
|
||||
"name": "Marin Jankovski",
|
||||
"email": "marin@gitlab.com"
|
||||
|
@ -3873,11 +3833,7 @@
|
|||
"sha": "048721d90c449b244b7b4c53a9186b04330174ec",
|
||||
"message": "LFS object pointer.\n",
|
||||
"authored_date": "2015-12-07T11:54:28.000+01:00",
|
||||
"author_name": "Marin Jankovski",
|
||||
"author_email": "maxlazio@gmail.com",
|
||||
"committed_date": "2015-12-07T11:54:28.000+01:00",
|
||||
"committer_name": "Marin Jankovski",
|
||||
"committer_email": "maxlazio@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Marin Jankovski",
|
||||
"email": "maxlazio@gmail.com"
|
||||
|
@ -3893,11 +3849,7 @@
|
|||
"sha": "5f923865dde3436854e9ceb9cdb7815618d4e849",
|
||||
"message": "GitLab currently doesn't support patches that involve a merge commit: add a commit here\n",
|
||||
"authored_date": "2015-11-13T16:27:12.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T16:27:12.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -3913,11 +3865,7 @@
|
|||
"sha": "d2d430676773caa88cdaf7c55944073b2fd5561a",
|
||||
"message": "Merge branch 'add-svg' into 'master'\r\n\r\nAdd GitLab SVG\r\n\r\nAdded to test preview of sanitized SVG images\r\n\r\nSee merge request !5",
|
||||
"authored_date": "2015-11-13T08:50:17.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T08:50:17.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -3933,11 +3881,7 @@
|
|||
"sha": "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73",
|
||||
"message": "Add GitLab SVG\n",
|
||||
"authored_date": "2015-11-13T08:39:43.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T08:39:43.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -3953,11 +3897,7 @@
|
|||
"sha": "59e29889be61e6e0e5e223bfa9ac2721d31605b8",
|
||||
"message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd whitespace test file\r\n\r\nSorry, I did a mistake.\r\nGit ignore empty files.\r\nSo I add a new whitespace test file.\r\n\r\nSee merge request !4",
|
||||
"authored_date": "2015-11-13T07:21:40.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T07:21:40.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -3973,11 +3913,7 @@
|
|||
"sha": "66eceea0db202bb39c4e445e8ca28689645366c5",
|
||||
"message": "add spaces in whitespace file\n",
|
||||
"authored_date": "2015-11-13T06:01:27.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T06:01:27.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -3993,11 +3929,7 @@
|
|||
"sha": "08f22f255f082689c0d7d39d19205085311542bc",
|
||||
"message": "remove empty file.(beacase git ignore empty file)\nadd whitespace test file.\n",
|
||||
"authored_date": "2015-11-13T06:00:16.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T06:00:16.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -4013,11 +3945,7 @@
|
|||
"sha": "19e2e9b4ef76b422ce1154af39a91323ccc57434",
|
||||
"message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd spaces\r\n\r\nTo test this pull request.(https://github.com/gitlabhq/gitlabhq/pull/9757)\r\nJust add whitespaces.\r\n\r\nSee merge request !3",
|
||||
"authored_date": "2015-11-13T05:23:14.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T05:23:14.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -4033,11 +3961,7 @@
|
|||
"sha": "c642fe9b8b9f28f9225d7ea953fe14e74748d53b",
|
||||
"message": "add whitespace in empty\n",
|
||||
"authored_date": "2015-11-13T05:08:45.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T05:08:45.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -4053,11 +3977,7 @@
|
|||
"sha": "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0",
|
||||
"message": "add empty file\n",
|
||||
"authored_date": "2015-11-13T05:08:04.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T05:08:04.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -4073,11 +3993,7 @@
|
|||
"sha": "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd",
|
||||
"message": "Add ISO-8859 test file\n",
|
||||
"authored_date": "2015-08-25T17:53:12.000+02:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@packetzoom.com",
|
||||
"committed_date": "2015-08-25T17:53:12.000+02:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@packetzoom.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@packetzoom.com"
|
||||
|
@ -4093,11 +4009,7 @@
|
|||
"sha": "e56497bb5f03a90a51293fc6d516788730953899",
|
||||
"message": "Merge branch 'tree_helper_spec' into 'master'\n\nAdd directory structure for tree_helper spec\n\nThis directory structure is needed for a testing the method flatten_tree(tree) in the TreeHelper module\n\nSee [merge request #275](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/275#note_732774)\n\nSee merge request !2\n",
|
||||
"authored_date": "2015-01-10T22:23:29.000+01:00",
|
||||
"author_name": "Sytse Sijbrandij",
|
||||
"author_email": "sytse@gitlab.com",
|
||||
"committed_date": "2015-01-10T22:23:29.000+01:00",
|
||||
"committer_name": "Sytse Sijbrandij",
|
||||
"committer_email": "sytse@gitlab.com",
|
||||
"commit_author": {
|
||||
"name": "Sytse Sijbrandij",
|
||||
"email": "sytse@gitlab.com"
|
||||
|
@ -4113,11 +4025,7 @@
|
|||
"sha": "4cd80ccab63c82b4bad16faa5193fbd2aa06df40",
|
||||
"message": "add directory structure for tree_helper spec\n",
|
||||
"authored_date": "2015-01-10T21:28:18.000+01:00",
|
||||
"author_name": "marmis85",
|
||||
"author_email": "marmis85@gmail.com",
|
||||
"committed_date": "2015-01-10T21:28:18.000+01:00",
|
||||
"committer_name": "marmis85",
|
||||
"committer_email": "marmis85@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "marmis85",
|
||||
"email": "marmis85@gmail.com"
|
||||
|
@ -4133,11 +4041,7 @@
|
|||
"sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
|
||||
"message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T10:01:38.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T10:01:38.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -4153,11 +4057,7 @@
|
|||
"sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
|
||||
"message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T09:57:31.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:57:31.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -4173,11 +4073,7 @@
|
|||
"sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
|
||||
"message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T09:54:21.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:54:21.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -4193,11 +4089,7 @@
|
|||
"sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
|
||||
"message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T09:49:50.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:49:50.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -4213,11 +4105,7 @@
|
|||
"sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
|
||||
"message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T09:48:32.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:48:32.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -4678,11 +4566,7 @@
|
|||
"sha": "0bfedc29d30280c7e8564e19f654584b459e5868",
|
||||
"message": "fixes #10\n",
|
||||
"authored_date": "2016-01-19T15:25:23.000+01:00",
|
||||
"author_name": "James Lopez",
|
||||
"author_email": "james@jameslopez.es",
|
||||
"committed_date": "2016-01-19T15:25:23.000+01:00",
|
||||
"committer_name": "James Lopez",
|
||||
"committer_email": "james@jameslopez.es",
|
||||
"commit_author": {
|
||||
"name": "James Lopez",
|
||||
"email": "james@jameslopez.es"
|
||||
|
@ -4698,11 +4582,7 @@
|
|||
"sha": "be93687618e4b132087f430a4d8fc3a609c9b77c",
|
||||
"message": "Merge branch 'master' into 'master'\r\n\r\nLFS object pointer.\r\n\r\n\r\n\r\nSee merge request !6",
|
||||
"authored_date": "2015-12-07T12:52:12.000+01:00",
|
||||
"author_name": "Marin Jankovski",
|
||||
"author_email": "marin@gitlab.com",
|
||||
"committed_date": "2015-12-07T12:52:12.000+01:00",
|
||||
"committer_name": "Marin Jankovski",
|
||||
"committer_email": "marin@gitlab.com",
|
||||
"commit_author": {
|
||||
"name": "Marin Jankovski",
|
||||
"email": "marin@gitlab.com"
|
||||
|
@ -4718,11 +4598,7 @@
|
|||
"sha": "048721d90c449b244b7b4c53a9186b04330174ec",
|
||||
"message": "LFS object pointer.\n",
|
||||
"authored_date": "2015-12-07T11:54:28.000+01:00",
|
||||
"author_name": "Marin Jankovski",
|
||||
"author_email": "maxlazio@gmail.com",
|
||||
"committed_date": "2015-12-07T11:54:28.000+01:00",
|
||||
"committer_name": "Marin Jankovski",
|
||||
"committer_email": "maxlazio@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Marin Jankovski",
|
||||
"email": "maxlazio@gmail.com"
|
||||
|
@ -4738,11 +4614,7 @@
|
|||
"sha": "5f923865dde3436854e9ceb9cdb7815618d4e849",
|
||||
"message": "GitLab currently doesn't support patches that involve a merge commit: add a commit here\n",
|
||||
"authored_date": "2015-11-13T16:27:12.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T16:27:12.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -4758,11 +4630,7 @@
|
|||
"sha": "d2d430676773caa88cdaf7c55944073b2fd5561a",
|
||||
"message": "Merge branch 'add-svg' into 'master'\r\n\r\nAdd GitLab SVG\r\n\r\nAdded to test preview of sanitized SVG images\r\n\r\nSee merge request !5",
|
||||
"authored_date": "2015-11-13T08:50:17.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T08:50:17.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -4778,11 +4646,7 @@
|
|||
"sha": "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73",
|
||||
"message": "Add GitLab SVG\n",
|
||||
"authored_date": "2015-11-13T08:39:43.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T08:39:43.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -4798,11 +4662,7 @@
|
|||
"sha": "59e29889be61e6e0e5e223bfa9ac2721d31605b8",
|
||||
"message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd whitespace test file\r\n\r\nSorry, I did a mistake.\r\nGit ignore empty files.\r\nSo I add a new whitespace test file.\r\n\r\nSee merge request !4",
|
||||
"authored_date": "2015-11-13T07:21:40.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T07:21:40.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -4818,11 +4678,7 @@
|
|||
"sha": "66eceea0db202bb39c4e445e8ca28689645366c5",
|
||||
"message": "add spaces in whitespace file\n",
|
||||
"authored_date": "2015-11-13T06:01:27.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T06:01:27.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -4838,11 +4694,7 @@
|
|||
"sha": "08f22f255f082689c0d7d39d19205085311542bc",
|
||||
"message": "remove empty file.(beacase git ignore empty file)\nadd whitespace test file.\n",
|
||||
"authored_date": "2015-11-13T06:00:16.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T06:00:16.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -4858,11 +4710,7 @@
|
|||
"sha": "19e2e9b4ef76b422ce1154af39a91323ccc57434",
|
||||
"message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd spaces\r\n\r\nTo test this pull request.(https://github.com/gitlabhq/gitlabhq/pull/9757)\r\nJust add whitespaces.\r\n\r\nSee merge request !3",
|
||||
"authored_date": "2015-11-13T05:23:14.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T05:23:14.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -4878,11 +4726,7 @@
|
|||
"sha": "c642fe9b8b9f28f9225d7ea953fe14e74748d53b",
|
||||
"message": "add whitespace in empty\n",
|
||||
"authored_date": "2015-11-13T05:08:45.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T05:08:45.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -4898,11 +4742,7 @@
|
|||
"sha": "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0",
|
||||
"message": "add empty file\n",
|
||||
"authored_date": "2015-11-13T05:08:04.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T05:08:04.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -4918,11 +4758,7 @@
|
|||
"sha": "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd",
|
||||
"message": "Add ISO-8859 test file\n",
|
||||
"authored_date": "2015-08-25T17:53:12.000+02:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@packetzoom.com",
|
||||
"committed_date": "2015-08-25T17:53:12.000+02:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@packetzoom.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@packetzoom.com"
|
||||
|
@ -4938,11 +4774,7 @@
|
|||
"sha": "e56497bb5f03a90a51293fc6d516788730953899",
|
||||
"message": "Merge branch 'tree_helper_spec' into 'master'\n\nAdd directory structure for tree_helper spec\n\nThis directory structure is needed for a testing the method flatten_tree(tree) in the TreeHelper module\n\nSee [merge request #275](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/275#note_732774)\n\nSee merge request !2\n",
|
||||
"authored_date": "2015-01-10T22:23:29.000+01:00",
|
||||
"author_name": "Sytse Sijbrandij",
|
||||
"author_email": "sytse@gitlab.com",
|
||||
"committed_date": "2015-01-10T22:23:29.000+01:00",
|
||||
"committer_name": "Sytse Sijbrandij",
|
||||
"committer_email": "sytse@gitlab.com",
|
||||
"commit_author": {
|
||||
"name": "Sytse Sijbrandij",
|
||||
"email": "sytse@gitlab.com"
|
||||
|
@ -4958,11 +4790,7 @@
|
|||
"sha": "4cd80ccab63c82b4bad16faa5193fbd2aa06df40",
|
||||
"message": "add directory structure for tree_helper spec\n",
|
||||
"authored_date": "2015-01-10T21:28:18.000+01:00",
|
||||
"author_name": "marmis85",
|
||||
"author_email": "marmis85@gmail.com",
|
||||
"committed_date": "2015-01-10T21:28:18.000+01:00",
|
||||
"committer_name": "marmis85",
|
||||
"committer_email": "marmis85@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "marmis85",
|
||||
"email": "marmis85@gmail.com"
|
||||
|
@ -5307,11 +5135,7 @@
|
|||
"sha": "97a0df9696e2aebf10c31b3016f40214e0e8f243",
|
||||
"message": "fixes #10\n",
|
||||
"authored_date": "2016-01-19T14:08:21.000+01:00",
|
||||
"author_name": "James Lopez",
|
||||
"author_email": "james@jameslopez.es",
|
||||
"committed_date": "2016-01-19T14:08:21.000+01:00",
|
||||
"committer_name": "James Lopez",
|
||||
"committer_email": "james@jameslopez.es",
|
||||
"commit_author": {
|
||||
"name": "James Lopez",
|
||||
"email": "james@jameslopez.es"
|
||||
|
@ -5327,11 +5151,7 @@
|
|||
"sha": "be93687618e4b132087f430a4d8fc3a609c9b77c",
|
||||
"message": "Merge branch 'master' into 'master'\r\n\r\nLFS object pointer.\r\n\r\n\r\n\r\nSee merge request !6",
|
||||
"authored_date": "2015-12-07T12:52:12.000+01:00",
|
||||
"author_name": "Marin Jankovski",
|
||||
"author_email": "marin@gitlab.com",
|
||||
"committed_date": "2015-12-07T12:52:12.000+01:00",
|
||||
"committer_name": "Marin Jankovski",
|
||||
"committer_email": "marin@gitlab.com",
|
||||
"commit_author": {
|
||||
"name": "Marin Jankovski",
|
||||
"email": "marin@gitlab.com"
|
||||
|
@ -5347,11 +5167,7 @@
|
|||
"sha": "048721d90c449b244b7b4c53a9186b04330174ec",
|
||||
"message": "LFS object pointer.\n",
|
||||
"authored_date": "2015-12-07T11:54:28.000+01:00",
|
||||
"author_name": "Marin Jankovski",
|
||||
"author_email": "maxlazio@gmail.com",
|
||||
"committed_date": "2015-12-07T11:54:28.000+01:00",
|
||||
"committer_name": "Marin Jankovski",
|
||||
"committer_email": "maxlazio@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Marin Jankovski",
|
||||
"email": "maxlazio@gmail.com"
|
||||
|
@ -5367,11 +5183,7 @@
|
|||
"sha": "5f923865dde3436854e9ceb9cdb7815618d4e849",
|
||||
"message": "GitLab currently doesn't support patches that involve a merge commit: add a commit here\n",
|
||||
"authored_date": "2015-11-13T16:27:12.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T16:27:12.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -5387,11 +5199,7 @@
|
|||
"sha": "d2d430676773caa88cdaf7c55944073b2fd5561a",
|
||||
"message": "Merge branch 'add-svg' into 'master'\r\n\r\nAdd GitLab SVG\r\n\r\nAdded to test preview of sanitized SVG images\r\n\r\nSee merge request !5",
|
||||
"authored_date": "2015-11-13T08:50:17.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T08:50:17.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -5407,11 +5215,7 @@
|
|||
"sha": "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73",
|
||||
"message": "Add GitLab SVG\n",
|
||||
"authored_date": "2015-11-13T08:39:43.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T08:39:43.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -5427,11 +5231,7 @@
|
|||
"sha": "59e29889be61e6e0e5e223bfa9ac2721d31605b8",
|
||||
"message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd whitespace test file\r\n\r\nSorry, I did a mistake.\r\nGit ignore empty files.\r\nSo I add a new whitespace test file.\r\n\r\nSee merge request !4",
|
||||
"authored_date": "2015-11-13T07:21:40.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T07:21:40.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -5447,11 +5247,7 @@
|
|||
"sha": "66eceea0db202bb39c4e445e8ca28689645366c5",
|
||||
"message": "add spaces in whitespace file\n",
|
||||
"authored_date": "2015-11-13T06:01:27.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T06:01:27.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -5467,11 +5263,7 @@
|
|||
"sha": "08f22f255f082689c0d7d39d19205085311542bc",
|
||||
"message": "remove empty file.(beacase git ignore empty file)\nadd whitespace test file.\n",
|
||||
"authored_date": "2015-11-13T06:00:16.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T06:00:16.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -5487,11 +5279,7 @@
|
|||
"sha": "19e2e9b4ef76b422ce1154af39a91323ccc57434",
|
||||
"message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd spaces\r\n\r\nTo test this pull request.(https://github.com/gitlabhq/gitlabhq/pull/9757)\r\nJust add whitespaces.\r\n\r\nSee merge request !3",
|
||||
"authored_date": "2015-11-13T05:23:14.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T05:23:14.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -5507,11 +5295,7 @@
|
|||
"sha": "c642fe9b8b9f28f9225d7ea953fe14e74748d53b",
|
||||
"message": "add whitespace in empty\n",
|
||||
"authored_date": "2015-11-13T05:08:45.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T05:08:45.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -5527,11 +5311,7 @@
|
|||
"sha": "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0",
|
||||
"message": "add empty file\n",
|
||||
"authored_date": "2015-11-13T05:08:04.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T05:08:04.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -5547,11 +5327,7 @@
|
|||
"sha": "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd",
|
||||
"message": "Add ISO-8859 test file\n",
|
||||
"authored_date": "2015-08-25T17:53:12.000+02:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@packetzoom.com",
|
||||
"committed_date": "2015-08-25T17:53:12.000+02:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@packetzoom.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@packetzoom.com"
|
||||
|
@ -6119,11 +5895,7 @@
|
|||
"sha": "f998ac87ac9244f15e9c15109a6f4e62a54b779d",
|
||||
"message": "fixes #10\n",
|
||||
"authored_date": "2016-01-19T14:43:23.000+01:00",
|
||||
"author_name": "James Lopez",
|
||||
"author_email": "james@jameslopez.es",
|
||||
"committed_date": "2016-01-19T14:43:23.000+01:00",
|
||||
"committer_name": "James Lopez",
|
||||
"committer_email": "james@jameslopez.es",
|
||||
"commit_author": {
|
||||
"name": "James Lopez",
|
||||
"email": "james@jameslopez.es"
|
||||
|
@ -6139,11 +5911,7 @@
|
|||
"sha": "be93687618e4b132087f430a4d8fc3a609c9b77c",
|
||||
"message": "Merge branch 'master' into 'master'\r\n\r\nLFS object pointer.\r\n\r\n\r\n\r\nSee merge request !6",
|
||||
"authored_date": "2015-12-07T12:52:12.000+01:00",
|
||||
"author_name": "Marin Jankovski",
|
||||
"author_email": "marin@gitlab.com",
|
||||
"committed_date": "2015-12-07T12:52:12.000+01:00",
|
||||
"committer_name": "Marin Jankovski",
|
||||
"committer_email": "marin@gitlab.com",
|
||||
"commit_author": {
|
||||
"name": "Marin Jankovski",
|
||||
"email": "marin@gitlab.com"
|
||||
|
@ -6159,11 +5927,7 @@
|
|||
"sha": "048721d90c449b244b7b4c53a9186b04330174ec",
|
||||
"message": "LFS object pointer.\n",
|
||||
"authored_date": "2015-12-07T11:54:28.000+01:00",
|
||||
"author_name": "Marin Jankovski",
|
||||
"author_email": "maxlazio@gmail.com",
|
||||
"committed_date": "2015-12-07T11:54:28.000+01:00",
|
||||
"committer_name": "Marin Jankovski",
|
||||
"committer_email": "maxlazio@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Marin Jankovski",
|
||||
"email": "maxlazio@gmail.com"
|
||||
|
@ -6179,11 +5943,7 @@
|
|||
"sha": "5f923865dde3436854e9ceb9cdb7815618d4e849",
|
||||
"message": "GitLab currently doesn't support patches that involve a merge commit: add a commit here\n",
|
||||
"authored_date": "2015-11-13T16:27:12.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T16:27:12.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -6199,11 +5959,7 @@
|
|||
"sha": "d2d430676773caa88cdaf7c55944073b2fd5561a",
|
||||
"message": "Merge branch 'add-svg' into 'master'\r\n\r\nAdd GitLab SVG\r\n\r\nAdded to test preview of sanitized SVG images\r\n\r\nSee merge request !5",
|
||||
"authored_date": "2015-11-13T08:50:17.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T08:50:17.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -6219,11 +5975,7 @@
|
|||
"sha": "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73",
|
||||
"message": "Add GitLab SVG\n",
|
||||
"authored_date": "2015-11-13T08:39:43.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T08:39:43.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -6239,11 +5991,7 @@
|
|||
"sha": "59e29889be61e6e0e5e223bfa9ac2721d31605b8",
|
||||
"message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd whitespace test file\r\n\r\nSorry, I did a mistake.\r\nGit ignore empty files.\r\nSo I add a new whitespace test file.\r\n\r\nSee merge request !4",
|
||||
"authored_date": "2015-11-13T07:21:40.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T07:21:40.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -6259,11 +6007,7 @@
|
|||
"sha": "66eceea0db202bb39c4e445e8ca28689645366c5",
|
||||
"message": "add spaces in whitespace file\n",
|
||||
"authored_date": "2015-11-13T06:01:27.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T06:01:27.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -6279,11 +6023,7 @@
|
|||
"sha": "08f22f255f082689c0d7d39d19205085311542bc",
|
||||
"message": "remove empty file.(beacase git ignore empty file)\nadd whitespace test file.\n",
|
||||
"authored_date": "2015-11-13T06:00:16.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T06:00:16.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -6299,11 +6039,7 @@
|
|||
"sha": "19e2e9b4ef76b422ce1154af39a91323ccc57434",
|
||||
"message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd spaces\r\n\r\nTo test this pull request.(https://github.com/gitlabhq/gitlabhq/pull/9757)\r\nJust add whitespaces.\r\n\r\nSee merge request !3",
|
||||
"authored_date": "2015-11-13T05:23:14.000+01:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@gmail.com",
|
||||
"committed_date": "2015-11-13T05:23:14.000+01:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@gmail.com"
|
||||
|
@ -6319,11 +6055,7 @@
|
|||
"sha": "c642fe9b8b9f28f9225d7ea953fe14e74748d53b",
|
||||
"message": "add whitespace in empty\n",
|
||||
"authored_date": "2015-11-13T05:08:45.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T05:08:45.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -6339,11 +6071,7 @@
|
|||
"sha": "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0",
|
||||
"message": "add empty file\n",
|
||||
"authored_date": "2015-11-13T05:08:04.000+01:00",
|
||||
"author_name": "윤민식",
|
||||
"author_email": "minsik.yoon@samsung.com",
|
||||
"committed_date": "2015-11-13T05:08:04.000+01:00",
|
||||
"committer_name": "윤민식",
|
||||
"committer_email": "minsik.yoon@samsung.com",
|
||||
"commit_author": {
|
||||
"name": "윤민식",
|
||||
"email": "minsik.yoon@samsung.com"
|
||||
|
@ -6359,11 +6087,7 @@
|
|||
"sha": "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd",
|
||||
"message": "Add ISO-8859 test file\n",
|
||||
"authored_date": "2015-08-25T17:53:12.000+02:00",
|
||||
"author_name": "Stan Hu",
|
||||
"author_email": "stanhu@packetzoom.com",
|
||||
"committed_date": "2015-08-25T17:53:12.000+02:00",
|
||||
"committer_name": "Stan Hu",
|
||||
"committer_email": "stanhu@packetzoom.com",
|
||||
"commit_author": {
|
||||
"name": "Stan Hu",
|
||||
"email": "stanhu@packetzoom.com"
|
||||
|
@ -6379,11 +6103,7 @@
|
|||
"sha": "e56497bb5f03a90a51293fc6d516788730953899",
|
||||
"message": "Merge branch 'tree_helper_spec' into 'master'\n\nAdd directory structure for tree_helper spec\n\nThis directory structure is needed for a testing the method flatten_tree(tree) in the TreeHelper module\n\nSee [merge request #275](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/275#note_732774)\n\nSee merge request !2\n",
|
||||
"authored_date": "2015-01-10T22:23:29.000+01:00",
|
||||
"author_name": "Sytse Sijbrandij",
|
||||
"author_email": "sytse@gitlab.com",
|
||||
"committed_date": "2015-01-10T22:23:29.000+01:00",
|
||||
"committer_name": "Sytse Sijbrandij",
|
||||
"committer_email": "sytse@gitlab.com",
|
||||
"commit_author": {
|
||||
"name": "Sytse Sijbrandij",
|
||||
"email": "sytse@gitlab.com"
|
||||
|
@ -6399,11 +6119,7 @@
|
|||
"sha": "4cd80ccab63c82b4bad16faa5193fbd2aa06df40",
|
||||
"message": "add directory structure for tree_helper spec\n",
|
||||
"authored_date": "2015-01-10T21:28:18.000+01:00",
|
||||
"author_name": "marmis85",
|
||||
"author_email": "marmis85@gmail.com",
|
||||
"committed_date": "2015-01-10T21:28:18.000+01:00",
|
||||
"committer_name": "marmis85",
|
||||
"committer_email": "marmis85@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "marmis85",
|
||||
"email": "marmis85@gmail.com"
|
||||
|
@ -6419,11 +6135,7 @@
|
|||
"sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
|
||||
"message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T10:01:38.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T10:01:38.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -6439,11 +6151,7 @@
|
|||
"sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
|
||||
"message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T09:57:31.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:57:31.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -6459,11 +6167,7 @@
|
|||
"sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
|
||||
"message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T09:54:21.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:54:21.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -6479,11 +6183,7 @@
|
|||
"sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
|
||||
"message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T09:49:50.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:49:50.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -6499,11 +6199,7 @@
|
|||
"sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
|
||||
"message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
|
||||
"authored_date": "2014-02-27T09:48:32.000+01:00",
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:48:32.000+01:00",
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author": {
|
||||
"name": "Dmitriy Zaporozhets",
|
||||
"email": "dmitriy.zaporozhets@gmail.com"
|
||||
|
@ -6952,11 +6648,7 @@
|
|||
"sha": "a4e5dfebf42e34596526acb8611bc7ed80e4eb3f",
|
||||
"message": "fixes #10\n",
|
||||
"authored_date": "2016-01-19T15:44:02.000+01:00",
|
||||
"author_name": "James Lopez",
|
||||
"author_email": "james@jameslopez.es",
|
||||
"committed_date": "2016-01-19T15:44:02.000+01:00",
|
||||
"committer_name": "James Lopez",
|
||||
"committer_email": "james@jameslopez.es",
|
||||
"commit_author": {
|
||||
"name": "James Lopez",
|
||||
"email": "james@jameslopez.es"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -16,16 +16,25 @@ import Api from '~/api';
|
|||
import ExperimentTracking from '~/experimentation/experiment_tracking';
|
||||
import InviteMembersModal from '~/invite_members/components/invite_members_modal.vue';
|
||||
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
|
||||
import { INVITE_MEMBERS_IN_COMMENT, MEMBER_AREAS_OF_FOCUS } from '~/invite_members/constants';
|
||||
import {
|
||||
INVITE_MEMBERS_IN_COMMENT,
|
||||
MEMBER_AREAS_OF_FOCUS,
|
||||
INVITE_MEMBERS_FOR_TASK,
|
||||
} from '~/invite_members/constants';
|
||||
import eventHub from '~/invite_members/event_hub';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import httpStatus from '~/lib/utils/http_status';
|
||||
import { getParameterValues } from '~/lib/utils/url_utility';
|
||||
import { apiPaths, membersApiResponse, invitationsApiResponse } from '../mock_data/api_responses';
|
||||
|
||||
let wrapper;
|
||||
let mock;
|
||||
|
||||
jest.mock('~/experimentation/experiment_tracking');
|
||||
jest.mock('~/lib/utils/url_utility', () => ({
|
||||
...jest.requireActual('~/lib/utils/url_utility'),
|
||||
getParameterValues: jest.fn(() => []),
|
||||
}));
|
||||
|
||||
const id = '1';
|
||||
const name = 'test name';
|
||||
|
@ -40,6 +49,15 @@ const areasOfFocusOptions = [
|
|||
{ text: 'area1', value: 'area1' },
|
||||
{ text: 'area2', value: 'area2' },
|
||||
];
|
||||
const tasksToBeDoneOptions = [
|
||||
{ text: 'First task', value: 'first' },
|
||||
{ text: 'Second task', value: 'second' },
|
||||
];
|
||||
const newProjectPath = 'projects/new';
|
||||
const projects = [
|
||||
{ text: 'First project', value: '1' },
|
||||
{ text: 'Second project', value: '2' },
|
||||
];
|
||||
|
||||
const user1 = { id: 1, name: 'Name One', username: 'one_1', avatar_url: '' };
|
||||
const user2 = { id: 2, name: 'Name Two', username: 'one_2', avatar_url: '' };
|
||||
|
@ -59,6 +77,9 @@ const sharedGroup = { id: '981' };
|
|||
|
||||
const createComponent = (data = {}, props = {}) => {
|
||||
wrapper = shallowMountExtended(InviteMembersModal, {
|
||||
provide: {
|
||||
newProjectPath,
|
||||
},
|
||||
propsData: {
|
||||
id,
|
||||
name,
|
||||
|
@ -68,6 +89,8 @@ const createComponent = (data = {}, props = {}) => {
|
|||
areasOfFocusOptions,
|
||||
defaultAccessLevel,
|
||||
noSelectionAreasOfFocus,
|
||||
tasksToBeDoneOptions,
|
||||
projects,
|
||||
helpLink,
|
||||
...props,
|
||||
},
|
||||
|
@ -131,6 +154,10 @@ describe('InviteMembersModal', () => {
|
|||
const membersFormGroupDescription = () => findMembersFormGroup().props('description');
|
||||
const findMembersSelect = () => wrapper.findComponent(MembersTokenSelect);
|
||||
const findAreaofFocusCheckBoxGroup = () => wrapper.findComponent(GlFormCheckboxGroup);
|
||||
const findTasksToBeDone = () => wrapper.findByTestId('invite-members-modal-tasks-to-be-done');
|
||||
const findTasks = () => wrapper.findByTestId('invite-members-modal-tasks');
|
||||
const findProjectSelect = () => wrapper.findByTestId('invite-members-modal-project-select');
|
||||
const findNoProjectsAlert = () => wrapper.findByTestId('invite-members-modal-no-projects-alert');
|
||||
|
||||
describe('rendering the modal', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -191,6 +218,127 @@ describe('InviteMembersModal', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('rendering the tasks to be done', () => {
|
||||
const setupComponent = (
|
||||
extraData = {},
|
||||
props = {},
|
||||
urlParameter = ['invite_members_for_task'],
|
||||
) => {
|
||||
const data = {
|
||||
selectedAccessLevel: 30,
|
||||
selectedTasksToBeDone: ['ci', 'code'],
|
||||
...extraData,
|
||||
};
|
||||
getParameterValues.mockImplementation(() => urlParameter);
|
||||
createComponent(data, props);
|
||||
};
|
||||
|
||||
afterAll(() => {
|
||||
getParameterValues.mockImplementation(() => []);
|
||||
});
|
||||
|
||||
it('renders the tasks to be done', () => {
|
||||
setupComponent();
|
||||
|
||||
expect(findTasksToBeDone().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('when the selected access level is lower than 30', () => {
|
||||
it('does not render the tasks to be done', () => {
|
||||
setupComponent({ selectedAccessLevel: 20 });
|
||||
|
||||
expect(findTasksToBeDone().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the url does not contain the parameter `open_modal=invite_members_for_task`', () => {
|
||||
it('does not render the tasks to be done', () => {
|
||||
setupComponent({}, {}, []);
|
||||
|
||||
expect(findTasksToBeDone().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rendering the tasks', () => {
|
||||
it('renders the tasks', () => {
|
||||
setupComponent();
|
||||
|
||||
expect(findTasks().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not render an alert', () => {
|
||||
setupComponent();
|
||||
|
||||
expect(findNoProjectsAlert().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('when there are no projects passed in the data', () => {
|
||||
it('does not render the tasks', () => {
|
||||
setupComponent({}, { projects: [] });
|
||||
|
||||
expect(findTasks().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders an alert with a link to the new projects path', () => {
|
||||
setupComponent({}, { projects: [] });
|
||||
|
||||
expect(findNoProjectsAlert().exists()).toBe(true);
|
||||
expect(findNoProjectsAlert().findComponent(GlLink).attributes('href')).toBe(
|
||||
newProjectPath,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('rendering the project dropdown', () => {
|
||||
it('renders the project select', () => {
|
||||
setupComponent();
|
||||
|
||||
expect(findProjectSelect().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('when the modal is shown for a project', () => {
|
||||
it('does not render the project select', () => {
|
||||
setupComponent({}, { isProject: true });
|
||||
|
||||
expect(findProjectSelect().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when no tasks are selected', () => {
|
||||
it('does not render the project select', () => {
|
||||
setupComponent({ selectedTasksToBeDone: [] });
|
||||
|
||||
expect(findProjectSelect().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('tracking events', () => {
|
||||
it('tracks the view for invite_members_for_task', () => {
|
||||
setupComponent();
|
||||
|
||||
expect(ExperimentTracking).toHaveBeenCalledWith(INVITE_MEMBERS_FOR_TASK.name);
|
||||
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
|
||||
INVITE_MEMBERS_FOR_TASK.view,
|
||||
);
|
||||
});
|
||||
|
||||
it('tracks the submit for invite_members_for_task', () => {
|
||||
setupComponent();
|
||||
clickInviteButton();
|
||||
|
||||
expect(ExperimentTracking).toHaveBeenCalledWith(INVITE_MEMBERS_FOR_TASK.name, {
|
||||
label: 'selected_tasks_to_be_done',
|
||||
property: 'ci,code',
|
||||
});
|
||||
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
|
||||
INVITE_MEMBERS_FOR_TASK.submit,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('displaying the correct introText and form group description', () => {
|
||||
describe('when inviting to a project', () => {
|
||||
describe('when inviting members', () => {
|
||||
|
@ -267,6 +415,8 @@ describe('InviteMembersModal', () => {
|
|||
invite_source: inviteSource,
|
||||
format: 'json',
|
||||
areas_of_focus: noSelectionAreasOfFocus,
|
||||
tasks_to_be_done: [],
|
||||
tasks_project_id: '',
|
||||
};
|
||||
|
||||
describe('when member is added successfully', () => {
|
||||
|
@ -448,6 +598,8 @@ describe('InviteMembersModal', () => {
|
|||
email: 'email@example.com',
|
||||
invite_source: inviteSource,
|
||||
areas_of_focus: noSelectionAreasOfFocus,
|
||||
tasks_to_be_done: [],
|
||||
tasks_project_id: '',
|
||||
format: 'json',
|
||||
};
|
||||
|
||||
|
@ -576,6 +728,8 @@ describe('InviteMembersModal', () => {
|
|||
invite_source: inviteSource,
|
||||
areas_of_focus: noSelectionAreasOfFocus,
|
||||
format: 'json',
|
||||
tasks_to_be_done: [],
|
||||
tasks_project_id: '',
|
||||
};
|
||||
|
||||
const emailPostData = { ...postData, email: 'email@example.com' };
|
||||
|
|
|
@ -40,6 +40,8 @@ describe('Pipeline Status', () => {
|
|||
const findPipelineId = () => wrapper.find('[data-testid="pipeline-id"]');
|
||||
const findPipelineCommit = () => wrapper.find('[data-testid="pipeline-commit"]');
|
||||
const findPipelineErrorMsg = () => wrapper.find('[data-testid="pipeline-error-msg"]');
|
||||
const findPipelineNotTriggeredErrorMsg = () =>
|
||||
wrapper.find('[data-testid="pipeline-not-triggered-error-msg"]');
|
||||
const findPipelineLoadingMsg = () => wrapper.find('[data-testid="pipeline-loading-msg"]');
|
||||
const findPipelineViewBtn = () => wrapper.find('[data-testid="pipeline-view-btn"]');
|
||||
const findStatusIcon = () => wrapper.find('[data-testid="pipeline-status-icon"]');
|
||||
|
@ -117,7 +119,8 @@ describe('Pipeline Status', () => {
|
|||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('renders error', () => {
|
||||
it('renders api error', () => {
|
||||
expect(findPipelineNotTriggeredErrorMsg().exists()).toBe(false);
|
||||
expect(findIcon().attributes('name')).toBe('warning-solid');
|
||||
expect(findPipelineErrorMsg().text()).toBe(i18n.fetchError);
|
||||
});
|
||||
|
@ -129,6 +132,23 @@ describe('Pipeline Status', () => {
|
|||
expect(findPipelineViewBtn().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when pipeline is null', () => {
|
||||
beforeEach(() => {
|
||||
mockPipelineQuery.mockResolvedValue({
|
||||
data: { project: { pipeline: null } },
|
||||
});
|
||||
|
||||
createComponentWithApollo();
|
||||
waitForPromises();
|
||||
});
|
||||
|
||||
it('renders pipeline not triggered error', () => {
|
||||
expect(findPipelineErrorMsg().exists()).toBe(false);
|
||||
expect(findIcon().attributes('name')).toBe('information-o');
|
||||
expect(findPipelineNotTriggeredErrorMsg().text()).toBe(i18n.pipelineNotTriggeredMsg);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when feature flag for pipeline mini graph is enabled', () => {
|
||||
|
|
|
@ -10,7 +10,7 @@ const DEFAULT_RENDER_COUNT = 5;
|
|||
describe('UncollapsedAssigneeList component', () => {
|
||||
let wrapper;
|
||||
|
||||
function createComponent(props = {}) {
|
||||
function createComponent(props = {}, glFeatures = {}) {
|
||||
const propsData = {
|
||||
users: [],
|
||||
rootPath: TEST_HOST,
|
||||
|
@ -19,6 +19,7 @@ describe('UncollapsedAssigneeList component', () => {
|
|||
|
||||
wrapper = mount(UncollapsedAssigneeList, {
|
||||
propsData,
|
||||
provide: { glFeatures },
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -99,4 +100,22 @@ describe('UncollapsedAssigneeList component', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('merge requests', () => {
|
||||
it.each`
|
||||
numberOfUsers
|
||||
${1}
|
||||
${5}
|
||||
`('displays as a vertical list for $numberOfUsers of users', ({ numberOfUsers }) => {
|
||||
createComponent(
|
||||
{
|
||||
users: UsersMockHelper.createNumberRandomUsers(numberOfUsers),
|
||||
issuableType: 'merge_request',
|
||||
},
|
||||
{ mrAttentionRequests: true },
|
||||
);
|
||||
|
||||
expect(wrapper.findAll('[data-testid="username"]').length).toBe(numberOfUsers);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -59,7 +59,84 @@ RSpec.describe InviteMembersHelper do
|
|||
no_selection_areas_of_focus: []
|
||||
}
|
||||
|
||||
expect(helper.common_invite_modal_dataset(project)).to match(attributes)
|
||||
expect(helper.common_invite_modal_dataset(project)).to include(attributes)
|
||||
end
|
||||
end
|
||||
|
||||
context 'tasks_to_be_done' do
|
||||
subject(:output) { helper.common_invite_modal_dataset(source) }
|
||||
|
||||
let_it_be(:source) { project }
|
||||
|
||||
before do
|
||||
stub_experiments(invite_members_for_task: true)
|
||||
end
|
||||
|
||||
context 'when not logged in' do
|
||||
before do
|
||||
allow(helper).to receive(:params).and_return({ open_modal: 'invite_members_for_task' })
|
||||
end
|
||||
|
||||
it "doesn't have the tasks to be done attributes" do
|
||||
expect(output[:tasks_to_be_done_options]).to be_nil
|
||||
expect(output[:projects]).to be_nil
|
||||
expect(output[:new_project_path]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged in but the open_modal param is not present' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(developer)
|
||||
end
|
||||
|
||||
it "doesn't have the tasks to be done attributes" do
|
||||
expect(output[:tasks_to_be_done_options]).to be_nil
|
||||
expect(output[:projects]).to be_nil
|
||||
expect(output[:new_project_path]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged in and the open_modal param is present' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(developer)
|
||||
allow(helper).to receive(:params).and_return({ open_modal: 'invite_members_for_task' })
|
||||
end
|
||||
|
||||
context 'for a group' do
|
||||
let_it_be(:source) { create(:group, projects: [project]) }
|
||||
|
||||
it 'has the expected attributes', :aggregate_failures do
|
||||
expect(output[:tasks_to_be_done_options]).to eq(
|
||||
[
|
||||
{ value: :code, text: 'Create/import code into a project (repository)' },
|
||||
{ value: :ci, text: 'Set up CI/CD pipelines to build, test, deploy, and monitor code' },
|
||||
{ value: :issues, text: 'Create/import issues (tickets) to collaborate on ideas and plan work' }
|
||||
].to_json
|
||||
)
|
||||
expect(output[:projects]).to eq(
|
||||
[{ id: project.id, title: project.title }].to_json
|
||||
)
|
||||
expect(output[:new_project_path]).to eq(
|
||||
new_project_path(namespace_id: source.id)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a project' do
|
||||
it 'has the expected attributes', :aggregate_failures do
|
||||
expect(output[:tasks_to_be_done_options]).to eq(
|
||||
[
|
||||
{ value: :code, text: 'Create/import code into a project (repository)' },
|
||||
{ value: :ci, text: 'Set up CI/CD pipelines to build, test, deploy, and monitor code' },
|
||||
{ value: :issues, text: 'Create/import issues (tickets) to collaborate on ideas and plan work' }
|
||||
].to_json
|
||||
)
|
||||
expect(output[:projects]).to eq(
|
||||
[{ id: project.id, title: project.title }].to_json
|
||||
)
|
||||
expect(output[:new_project_path]).to eq('')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -68,4 +68,10 @@ RSpec.describe MembersHelper do
|
|||
it { expect(leave_confirmation_message(project)).to eq "Are you sure you want to leave the \"#{project.full_name}\" project?" }
|
||||
it { expect(leave_confirmation_message(group)).to eq "Are you sure you want to leave the \"#{group.name}\" group?" }
|
||||
end
|
||||
|
||||
describe '#localized_tasks_to_be_done_choices' do
|
||||
it 'has a translation for all `TASKS_TO_BE_DONE` keys' do
|
||||
expect(localized_tasks_to_be_done_choices).to include(*MemberTask::TASKS.keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::MigrateMergeRequestDiffCommitUsers do
|
||||
RSpec.describe Gitlab::BackgroundMigration::MigrateMergeRequestDiffCommitUsers, schema: 20211012134316 do
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
let(:projects) { table(:projects) }
|
||||
let(:users) { table(:users) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::StealMigrateMergeRequestDiffCommitUsers do
|
||||
RSpec.describe Gitlab::BackgroundMigration::StealMigrateMergeRequestDiffCommitUsers, schema: 20211012134316 do
|
||||
let(:migration) { described_class.new }
|
||||
|
||||
describe '#perform' do
|
||||
|
|
|
@ -133,6 +133,7 @@ project_members:
|
|||
- user
|
||||
- source
|
||||
- project
|
||||
- member_task
|
||||
merge_requests:
|
||||
- status_check_responses
|
||||
- subscriptions
|
||||
|
|
|
@ -23,7 +23,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
|
|||
]
|
||||
|
||||
RSpec::Mocks.with_temporary_scope do
|
||||
@project = create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project')
|
||||
@project = create(:project, :repository, :builds_enabled, :issues_disabled, name: 'project', path: 'project')
|
||||
@shared = @project.import_export_shared
|
||||
|
||||
stub_all_feature_flags
|
||||
|
@ -36,7 +36,6 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
|
|||
allow_any_instance_of(Gitlab::Git::Repository).to receive(:branch_exists?).and_return(false)
|
||||
|
||||
expect(@shared).not_to receive(:error)
|
||||
expect_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch).with('feature', 'DCBA')
|
||||
allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch)
|
||||
|
||||
project_tree_restorer = described_class.new(user: @user, shared: @shared, project: @project)
|
||||
|
|
|
@ -47,22 +47,30 @@ RSpec.describe Emails::InProductMarketing do
|
|||
end
|
||||
|
||||
where(:track, :series) do
|
||||
:create | 0
|
||||
:create | 1
|
||||
:create | 2
|
||||
:verify | 0
|
||||
:verify | 1
|
||||
:verify | 2
|
||||
:trial | 0
|
||||
:trial | 1
|
||||
:trial | 2
|
||||
:team | 0
|
||||
:team | 1
|
||||
:team | 2
|
||||
:experience | 0
|
||||
:create | 0
|
||||
:create | 1
|
||||
:create | 2
|
||||
:verify | 0
|
||||
:verify | 1
|
||||
:verify | 2
|
||||
:trial | 0
|
||||
:trial | 1
|
||||
:trial | 2
|
||||
:team | 0
|
||||
:team | 1
|
||||
:team | 2
|
||||
:experience | 0
|
||||
:team_short | 0
|
||||
:trial_short | 0
|
||||
:admin_verify | 0
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
stub_experiments(invite_members_for_task: :candidate)
|
||||
group.add_owner(user)
|
||||
end
|
||||
|
||||
it 'has the correct subject and content' do
|
||||
message = Gitlab::Email::Message::InProductMarketing.for(track).new(group: group, user: user, series: series)
|
||||
|
||||
|
@ -76,6 +84,14 @@ RSpec.describe Emails::InProductMarketing do
|
|||
else
|
||||
is_expected.to have_body_text(CGI.unescapeHTML(message.cta_link))
|
||||
end
|
||||
|
||||
if track =~ /(create|verify)/
|
||||
is_expected.to have_body_text(message.invite_text)
|
||||
is_expected.to have_body_text(CGI.unescapeHTML(message.invite_link))
|
||||
else
|
||||
is_expected.not_to have_body_text(message.invite_text)
|
||||
is_expected.not_to have_body_text(CGI.unescapeHTML(message.invite_link))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,7 @@ RSpec.describe Notify do
|
|||
include EmailSpec::Matchers
|
||||
include EmailHelpers
|
||||
include RepoHelpers
|
||||
include MembersHelper
|
||||
|
||||
include_context 'gitlab email notification'
|
||||
|
||||
|
@ -761,10 +762,21 @@ RSpec.describe Notify do
|
|||
is_expected.to have_body_text project_member.human_access
|
||||
is_expected.to have_body_text 'leave the project'
|
||||
is_expected.to have_body_text project_url(project, leave: 1)
|
||||
is_expected.not_to have_body_text 'You were assigned the following tasks:'
|
||||
end
|
||||
|
||||
context 'with tasks to be done present' do
|
||||
let(:project_member) { create(:project_member, project: project, user: user, tasks_to_be_done: [:ci, :code]) }
|
||||
|
||||
it 'contains the assigned tasks to be done' do
|
||||
is_expected.to have_body_text 'You were assigned the following tasks:'
|
||||
is_expected.to have_body_text localized_tasks_to_be_done_choices[:ci]
|
||||
is_expected.to have_body_text localized_tasks_to_be_done_choices[:code]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def invite_to_project(project, inviter:, user: nil)
|
||||
def invite_to_project(project, inviter:, user: nil, tasks_to_be_done: [])
|
||||
create(
|
||||
:project_member,
|
||||
:developer,
|
||||
|
@ -772,7 +784,8 @@ RSpec.describe Notify do
|
|||
invite_token: '1234',
|
||||
invite_email: 'toto@example.com',
|
||||
user: user,
|
||||
created_by: inviter
|
||||
created_by: inviter,
|
||||
tasks_to_be_done: tasks_to_be_done
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -804,6 +817,7 @@ RSpec.describe Notify do
|
|||
is_expected.to have_content("#{inviter.name} invited you to join the")
|
||||
is_expected.to have_content('Project details')
|
||||
is_expected.to have_content("What's it about?")
|
||||
is_expected.not_to have_body_text 'and has assigned you the following tasks:'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -890,6 +904,16 @@ RSpec.describe Notify do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with tasks to be done present', :aggregate_failures do
|
||||
let(:project_member) { invite_to_project(project, inviter: inviter, tasks_to_be_done: [:ci, :code]) }
|
||||
|
||||
it 'contains the assigned tasks to be done' do
|
||||
is_expected.to have_body_text 'and has assigned you the following tasks:'
|
||||
is_expected.to have_body_text localized_tasks_to_be_done_choices[:ci]
|
||||
is_expected.to have_body_text localized_tasks_to_be_done_choices[:code]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'project invitation accepted' do
|
||||
|
@ -1398,7 +1422,7 @@ RSpec.describe Notify do
|
|||
end
|
||||
end
|
||||
|
||||
def invite_to_group(group, inviter:, user: nil)
|
||||
def invite_to_group(group, inviter:, user: nil, tasks_to_be_done: [])
|
||||
create(
|
||||
:group_member,
|
||||
:developer,
|
||||
|
@ -1406,7 +1430,8 @@ RSpec.describe Notify do
|
|||
invite_token: '1234',
|
||||
invite_email: 'toto@example.com',
|
||||
user: user,
|
||||
created_by: inviter
|
||||
created_by: inviter,
|
||||
tasks_to_be_done: tasks_to_be_done
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -1431,6 +1456,7 @@ RSpec.describe Notify do
|
|||
is_expected.to have_body_text group.name
|
||||
is_expected.to have_body_text group_member.human_access.downcase
|
||||
is_expected.to have_body_text group_member.invite_token
|
||||
is_expected.not_to have_body_text 'and has assigned you the following tasks:'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1444,6 +1470,24 @@ RSpec.describe Notify do
|
|||
is_expected.to have_body_text group_member.invite_token
|
||||
end
|
||||
end
|
||||
|
||||
context 'with tasks to be done present', :aggregate_failures do
|
||||
let(:group_member) { invite_to_group(group, inviter: inviter, tasks_to_be_done: [:ci, :code]) }
|
||||
|
||||
it 'contains the assigned tasks to be done' do
|
||||
is_expected.to have_body_text 'and has assigned you the following tasks:'
|
||||
is_expected.to have_body_text localized_tasks_to_be_done_choices[:ci]
|
||||
is_expected.to have_body_text localized_tasks_to_be_done_choices[:code]
|
||||
end
|
||||
|
||||
context 'when there is no inviter' do
|
||||
let(:inviter) { nil }
|
||||
|
||||
it 'does not contain the assigned tasks to be done' do
|
||||
is_expected.not_to have_body_text 'and has assigned you the following tasks:'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'group invitation reminders' do
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration! 'clean_up_migrate_merge_request_diff_commit_users'
|
||||
|
||||
RSpec.describe CleanUpMigrateMergeRequestDiffCommitUsers, :migration do
|
||||
describe '#up' do
|
||||
context 'when there are pending jobs' do
|
||||
it 'processes the jobs immediately' do
|
||||
Gitlab::Database::BackgroundMigrationJob.create!(
|
||||
class_name: 'MigrateMergeRequestDiffCommitUsers',
|
||||
status: :pending,
|
||||
arguments: [10, 20]
|
||||
)
|
||||
|
||||
spy = Gitlab::BackgroundMigration::MigrateMergeRequestDiffCommitUsers
|
||||
migration = described_class.new
|
||||
|
||||
allow(Gitlab::BackgroundMigration::MigrateMergeRequestDiffCommitUsers)
|
||||
.to receive(:new)
|
||||
.and_return(spy)
|
||||
|
||||
expect(migration).to receive(:say)
|
||||
expect(spy).to receive(:perform).with(10, 20)
|
||||
|
||||
migration.up
|
||||
end
|
||||
end
|
||||
|
||||
context 'when all jobs are completed' do
|
||||
it 'does nothing' do
|
||||
Gitlab::Database::BackgroundMigrationJob.create!(
|
||||
class_name: 'MigrateMergeRequestDiffCommitUsers',
|
||||
status: :succeeded,
|
||||
arguments: [10, 20]
|
||||
)
|
||||
|
||||
migration = described_class.new
|
||||
|
||||
expect(migration).not_to receive(:say)
|
||||
expect(Gitlab::BackgroundMigration::MigrateMergeRequestDiffCommitUsers)
|
||||
.not_to receive(:new)
|
||||
|
||||
migration.up
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1710,4 +1710,36 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
|
|||
subject
|
||||
end
|
||||
end
|
||||
|
||||
describe '#should_link_to_merge_requests?' do
|
||||
subject { environment.should_link_to_merge_requests? }
|
||||
|
||||
context 'when environment is foldered' do
|
||||
context 'when environment is production tier' do
|
||||
let(:environment) { create(:environment, project: project, name: 'production/aws') }
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
|
||||
context 'when environment is development tier' do
|
||||
let(:environment) { create(:environment, project: project, name: 'review/feature') }
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when environment is unfoldered' do
|
||||
context 'when environment is production tier' do
|
||||
let(:environment) { create(:environment, project: project, name: 'production') }
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
|
||||
context 'when environment is development tier' do
|
||||
let(:environment) { create(:environment, project: project, name: 'development') }
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -563,6 +563,25 @@ RSpec.describe Group do
|
|||
it { expect(group.ancestors.to_sql).not_to include 'traversal_ids <@' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project namespace exists in the group' do
|
||||
let!(:project) { create(:project, group: group) }
|
||||
let!(:project_namespace) { create(:project_namespace, project: project) }
|
||||
|
||||
it 'filters out project namespace' do
|
||||
expect(group.descendants.find_by_id(project_namespace.id)).to be_nil
|
||||
end
|
||||
|
||||
context 'when include_sti_condition is disabled' do
|
||||
before do
|
||||
stub_feature_flags(include_sti_condition: false)
|
||||
end
|
||||
|
||||
it 'raises an exception' do
|
||||
expect { group.descendants.find_by_id(project_namespace.id)}.to raise_error(ActiveRecord::SubclassNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -718,6 +737,22 @@ RSpec.describe Group do
|
|||
expect(group.group_members.developers.map(&:user)).to include(user)
|
||||
expect(group.group_members.guests.map(&:user)).not_to include(user)
|
||||
end
|
||||
|
||||
context 'when `tasks_to_be_done` and `tasks_project_id` are passed' do
|
||||
let!(:project) { create(:project, group: group) }
|
||||
|
||||
before do
|
||||
stub_experiments(invite_members_for_task: true)
|
||||
group.add_users([create(:user)], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: project.id)
|
||||
end
|
||||
|
||||
it 'creates a member_task with the correct attributes', :aggregate_failures do
|
||||
member = group.group_members.last
|
||||
|
||||
expect(member.tasks_to_be_done).to match_array([:ci, :code])
|
||||
expect(member.member_task.project).to eq(project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#avatar_type' do
|
||||
|
|
|
@ -9,6 +9,7 @@ RSpec.describe Member do
|
|||
|
||||
describe 'Associations' do
|
||||
it { is_expected.to belong_to(:user) }
|
||||
it { is_expected.to have_one(:member_task) }
|
||||
end
|
||||
|
||||
describe 'Validation' do
|
||||
|
@ -678,6 +679,19 @@ RSpec.describe Member do
|
|||
expect(member.invite_token).not_to be_nil
|
||||
expect_any_instance_of(Member).not_to receive(:after_accept_invite)
|
||||
end
|
||||
|
||||
it 'schedules a TasksToBeDone::CreateWorker task' do
|
||||
stub_experiments(invite_members_for_task: true)
|
||||
|
||||
member_task = create(:member_task, member: member, project: member.project)
|
||||
|
||||
expect(TasksToBeDone::CreateWorker)
|
||||
.to receive(:perform_async)
|
||||
.with(member_task.id, member.created_by_id, [user.id])
|
||||
.once
|
||||
|
||||
member.accept_invite!(user)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#decline_invite!' do
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe MemberTask do
|
||||
describe 'Associations' do
|
||||
it { is_expected.to belong_to(:member) }
|
||||
it { is_expected.to belong_to(:project) }
|
||||
end
|
||||
|
||||
describe 'Validations' do
|
||||
it { is_expected.to validate_presence_of(:member) }
|
||||
it { is_expected.to validate_presence_of(:project) }
|
||||
it { is_expected.to validate_inclusion_of(:tasks).in_array(MemberTask::TASKS.values) }
|
||||
|
||||
describe 'unique tasks validation' do
|
||||
subject do
|
||||
build(:member_task, tasks: [0, 0])
|
||||
end
|
||||
|
||||
it 'expects the task values to be unique' do
|
||||
expect(subject).to be_invalid
|
||||
expect(subject.errors[:tasks]).to include('are not unique')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'project validations' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
subject do
|
||||
build(:member_task, member: member, project: project, tasks_to_be_done: [:ci, :code])
|
||||
end
|
||||
|
||||
context 'when the member source is a group' do
|
||||
let_it_be(:member) { create(:group_member) }
|
||||
|
||||
it "expects the project to be part of the member's group projects" do
|
||||
expect(subject).to be_invalid
|
||||
expect(subject.errors[:project]).to include('is not in the member group')
|
||||
end
|
||||
|
||||
context "when the project is part of the member's group projects" do
|
||||
let_it_be(:project) { create(:project, namespace: member.source) }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the member source is a project' do
|
||||
let_it_be(:member) { create(:project_member) }
|
||||
|
||||
it "expects the project to be the member's project" do
|
||||
expect(subject).to be_invalid
|
||||
expect(subject.errors[:project]).to include('is not the member project')
|
||||
end
|
||||
|
||||
context "when the project is the member's project" do
|
||||
let_it_be(:project) { member.source }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.for_members' do
|
||||
it 'returns the member_tasks for multiple members' do
|
||||
member1 = create(:group_member)
|
||||
member_task1 = create(:member_task, member: member1)
|
||||
create(:member_task)
|
||||
expect(described_class.for_members([member1])).to match_array([member_task1])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#tasks_to_be_done' do
|
||||
subject { member_task.tasks_to_be_done }
|
||||
|
||||
let_it_be(:member_task) { build(:member_task) }
|
||||
|
||||
before do
|
||||
member_task[:tasks] = [0, 1]
|
||||
end
|
||||
|
||||
it 'returns an array of symbols for the corresponding integers' do
|
||||
expect(subject).to match_array([:ci, :code])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#tasks_to_be_done=' do
|
||||
let_it_be(:member_task) { build(:member_task) }
|
||||
|
||||
context 'when passing valid values' do
|
||||
subject { member_task[:tasks] }
|
||||
|
||||
before do
|
||||
member_task.tasks_to_be_done = tasks
|
||||
end
|
||||
|
||||
context 'when passing tasks as strings' do
|
||||
let_it_be(:tasks) { %w(ci code) }
|
||||
|
||||
it 'sets an array of integers for the corresponding tasks' do
|
||||
expect(subject).to match_array([0, 1])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when passing a single task' do
|
||||
let_it_be(:tasks) { :ci }
|
||||
|
||||
it 'sets an array of integers for the corresponding tasks' do
|
||||
expect(subject).to match_array([1])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when passing a task twice' do
|
||||
let_it_be(:tasks) { %w(ci ci) }
|
||||
|
||||
it 'is set only once' do
|
||||
expect(subject).to match_array([1])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -46,11 +46,7 @@ RSpec.describe MergeRequestDiffCommit do
|
|||
{
|
||||
"message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
|
||||
"authored_date": "2014-02-27T10:01:38.000+01:00".to_time,
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T10:01:38.000+01:00".to_time,
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author_id": an_instance_of(Integer),
|
||||
"committer_id": an_instance_of(Integer),
|
||||
"merge_request_diff_id": merge_request_diff_id,
|
||||
|
@ -61,11 +57,7 @@ RSpec.describe MergeRequestDiffCommit do
|
|||
{
|
||||
"message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
|
||||
"authored_date": "2014-02-27T09:57:31.000+01:00".to_time,
|
||||
"author_name": "Dmitriy Zaporozhets",
|
||||
"author_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"committed_date": "2014-02-27T09:57:31.000+01:00".to_time,
|
||||
"committer_name": "Dmitriy Zaporozhets",
|
||||
"committer_email": "dmitriy.zaporozhets@gmail.com",
|
||||
"commit_author_id": an_instance_of(Integer),
|
||||
"committer_id": an_instance_of(Integer),
|
||||
"merge_request_diff_id": merge_request_diff_id,
|
||||
|
@ -111,11 +103,7 @@ RSpec.describe MergeRequestDiffCommit do
|
|||
[{
|
||||
"message": "Weird commit date\n",
|
||||
"authored_date": timestamp,
|
||||
"author_name": "Alejandro Rodríguez",
|
||||
"author_email": "alejorro70@gmail.com",
|
||||
"committed_date": timestamp,
|
||||
"committer_name": "Alejandro Rodríguez",
|
||||
"committer_email": "alejorro70@gmail.com",
|
||||
"commit_author_id": an_instance_of(Integer),
|
||||
"committer_id": an_instance_of(Integer),
|
||||
"merge_request_diff_id": merge_request_diff_id,
|
||||
|
|
|
@ -234,6 +234,20 @@ RSpec.describe ProjectTeam do
|
|||
expect(project.team.reporter?(user1)).to be(true)
|
||||
expect(project.team.reporter?(user2)).to be(true)
|
||||
end
|
||||
|
||||
context 'when `tasks_to_be_done` and `tasks_project_id` are passed' do
|
||||
before do
|
||||
stub_experiments(invite_members_for_task: true)
|
||||
project.team.add_users([user1], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: project.id)
|
||||
end
|
||||
|
||||
it 'creates a member_task with the correct attributes', :aggregate_failures do
|
||||
member = project.project_members.last
|
||||
|
||||
expect(member.tasks_to_be_done).to match_array([:ci, :code])
|
||||
expect(member.member_task.project).to eq(project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#add_user' do
|
||||
|
|
|
@ -166,6 +166,38 @@ RSpec.describe API::Invitations do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with tasks_to_be_done and tasks_project_id in the params' do
|
||||
before do
|
||||
stub_experiments(invite_members_for_task: true)
|
||||
end
|
||||
|
||||
let(:project_id) { source_type == 'project' ? source.id : create(:project, namespace: source).id }
|
||||
|
||||
context 'when there is 1 invitation' do
|
||||
it 'creates a member_task with the tasks_to_be_done and the project' do
|
||||
post invitations_url(source, maintainer),
|
||||
params: { email: email, access_level: Member::DEVELOPER, tasks_to_be_done: %w(code ci), tasks_project_id: project_id }
|
||||
|
||||
member = source.members.find_by(invite_email: email)
|
||||
expect(member.tasks_to_be_done).to match_array([:code, :ci])
|
||||
expect(member.member_task.project_id).to eq(project_id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are multiple invitations' do
|
||||
it 'creates a member_task with the tasks_to_be_done and the project' do
|
||||
post invitations_url(source, maintainer),
|
||||
params: { email: [email, email2].join(','), access_level: Member::DEVELOPER, tasks_to_be_done: %w(code ci), tasks_project_id: project_id }
|
||||
|
||||
members = source.members.where(invite_email: [email, email2])
|
||||
members.each do |member|
|
||||
expect(member.tasks_to_be_done).to match_array([:code, :ci])
|
||||
expect(member.member_task.project_id).to eq(project_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invite_source considerations', :snowplow do
|
||||
let(:params) { { email: email, access_level: Member::DEVELOPER } }
|
||||
|
||||
|
|
|
@ -81,14 +81,22 @@ RSpec.describe API::Members do
|
|||
expect(json_response.map { |u| u['id'] }).to match_array [maintainer.id, developer.id]
|
||||
end
|
||||
|
||||
it 'finds members with query string' do
|
||||
get api(members_url, developer), params: { query: maintainer.username }
|
||||
context 'with cross db check disabled' do
|
||||
around do |example|
|
||||
allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/343305') do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.count).to eq(1)
|
||||
expect(json_response.first['username']).to eq(maintainer.username)
|
||||
it 'finds members with query string' do
|
||||
get api(members_url, developer), params: { query: maintainer.username }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.count).to eq(1)
|
||||
expect(json_response.first['username']).to eq(maintainer.username)
|
||||
end
|
||||
end
|
||||
|
||||
it 'finds members with the given user_ids' do
|
||||
|
@ -406,6 +414,38 @@ RSpec.describe API::Members do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with tasks_to_be_done and tasks_project_id in the params' do
|
||||
before do
|
||||
stub_experiments(invite_members_for_task: true)
|
||||
end
|
||||
|
||||
let(:project_id) { source_type == 'project' ? source.id : create(:project, namespace: source).id }
|
||||
|
||||
context 'when there is 1 user to add' do
|
||||
it 'creates a member_task with the correct attributes' do
|
||||
post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
|
||||
params: { user_id: stranger.id, access_level: Member::DEVELOPER, tasks_to_be_done: %w(code ci), tasks_project_id: project_id }
|
||||
|
||||
member = source.members.find_by(user_id: stranger.id)
|
||||
expect(member.tasks_to_be_done).to match_array([:code, :ci])
|
||||
expect(member.member_task.project_id).to eq(project_id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are multiple users to add' do
|
||||
it 'creates a member_task with the correct attributes' do
|
||||
post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
|
||||
params: { user_id: [developer.id, stranger.id].join(','), access_level: Member::DEVELOPER, tasks_to_be_done: %w(code ci), tasks_project_id: project_id }
|
||||
|
||||
members = source.members.where(user_id: [developer.id, stranger.id])
|
||||
members.each do |member|
|
||||
expect(member.tasks_to_be_done).to match_array([:code, :ci])
|
||||
expect(member.member_task.project_id).to eq(project_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "returns 409 if member already exists" do
|
||||
post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
|
||||
params: { user_id: maintainer.id, access_level: Member::MAINTAINER }
|
||||
|
|
|
@ -32,6 +32,19 @@ RSpec.describe Deployments::LinkMergeRequestsService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the deployment is for one of the production environments' do
|
||||
it 'links merge requests' do
|
||||
environment =
|
||||
create(:environment, environment_type: 'production', name: 'production/gcp')
|
||||
|
||||
deploy = create(:deployment, :success, environment: environment)
|
||||
|
||||
expect(deploy).to receive(:link_merge_requests).once
|
||||
|
||||
described_class.new(deploy).execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the deployment failed' do
|
||||
it 'does nothing' do
|
||||
environment = create(:environment, name: 'foo')
|
||||
|
|
|
@ -196,4 +196,110 @@ RSpec.describe Members::CreateService, :aggregate_failures, :clean_gitlab_redis_
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assigning tasks to be done' do
|
||||
let(:additional_params) do
|
||||
{ invite_source: '_invite_source_', tasks_to_be_done: %w(ci code), tasks_project_id: source.id }
|
||||
end
|
||||
|
||||
before do
|
||||
stub_experiments(invite_members_for_task: true)
|
||||
end
|
||||
|
||||
it 'creates 2 task issues', :aggregate_failures do
|
||||
expect(TasksToBeDone::CreateWorker)
|
||||
.to receive(:perform_async)
|
||||
.with(anything, user.id, [member.id])
|
||||
.once
|
||||
.and_call_original
|
||||
expect { execute_service }.to change { source.issues.count }.by(2)
|
||||
|
||||
expect(source.issues).to all have_attributes(
|
||||
project: source,
|
||||
author: user,
|
||||
assignees: array_including(member)
|
||||
)
|
||||
end
|
||||
|
||||
context 'when passing many user ids' do
|
||||
before do
|
||||
stub_licensed_features(multiple_issue_assignees: false)
|
||||
end
|
||||
|
||||
let(:another_user) { create(:user) }
|
||||
let(:user_ids) { [member.id, another_user.id].join(',') }
|
||||
|
||||
it 'still creates 2 task issues', :aggregate_failures do
|
||||
expect(TasksToBeDone::CreateWorker)
|
||||
.to receive(:perform_async)
|
||||
.with(anything, user.id, array_including(member.id, another_user.id))
|
||||
.once
|
||||
.and_call_original
|
||||
expect { execute_service }.to change { source.issues.count }.by(2)
|
||||
|
||||
expect(source.issues).to all have_attributes(
|
||||
project: source,
|
||||
author: user,
|
||||
assignees: array_including(member)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a `tasks_project_id` is missing' do
|
||||
let(:additional_params) do
|
||||
{ invite_source: '_invite_source_', tasks_to_be_done: %w(ci code) }
|
||||
end
|
||||
|
||||
it 'does not create task issues' do
|
||||
expect(TasksToBeDone::CreateWorker).not_to receive(:perform_async)
|
||||
execute_service
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `tasks_to_be_done` are missing' do
|
||||
let(:additional_params) do
|
||||
{ invite_source: '_invite_source_', tasks_project_id: source.id }
|
||||
end
|
||||
|
||||
it 'does not create task issues' do
|
||||
expect(TasksToBeDone::CreateWorker).not_to receive(:perform_async)
|
||||
execute_service
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid `tasks_to_be_done` are passed' do
|
||||
let(:additional_params) do
|
||||
{ invite_source: '_invite_source_', tasks_project_id: source.id, tasks_to_be_done: %w(invalid_task) }
|
||||
end
|
||||
|
||||
it 'does not create task issues' do
|
||||
expect(TasksToBeDone::CreateWorker).not_to receive(:perform_async)
|
||||
execute_service
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid `tasks_project_id` is passed' do
|
||||
let(:another_project) { create(:project) }
|
||||
let(:additional_params) do
|
||||
{ invite_source: '_invite_source_', tasks_project_id: another_project.id, tasks_to_be_done: %w(ci code) }
|
||||
end
|
||||
|
||||
it 'does not create task issues' do
|
||||
expect(TasksToBeDone::CreateWorker).not_to receive(:perform_async)
|
||||
execute_service
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a member was already invited' do
|
||||
let(:user_ids) { create(:project_member, :invited, project: source).invite_email }
|
||||
let(:additional_params) do
|
||||
{ invite_source: '_invite_source_', tasks_project_id: source.id, tasks_to_be_done: %w(ci code) }
|
||||
end
|
||||
|
||||
it 'does not create task issues' do
|
||||
expect(TasksToBeDone::CreateWorker).not_to receive(:perform_async)
|
||||
execute_service
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,6 +22,11 @@ RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_
|
|||
end
|
||||
|
||||
it_behaves_like 'records an onboarding progress action', :user_added
|
||||
|
||||
it 'does not create task issues' do
|
||||
expect(TasksToBeDone::CreateWorker).not_to receive(:perform_async)
|
||||
expect { result }.not_to change { project.issues.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when email belongs to an existing user as a secondary email' do
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe TasksToBeDone::BaseService do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:assignee_one) { create(:user) }
|
||||
let_it_be(:assignee_two) { create(:user) }
|
||||
let_it_be(:assignee_ids) { [assignee_one.id] }
|
||||
let_it_be(:label) { create(:label, title: 'tasks to be done:ci', project: project) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(current_user)
|
||||
project.add_developer(assignee_one)
|
||||
project.add_developer(assignee_two)
|
||||
end
|
||||
|
||||
subject(:service) do
|
||||
TasksToBeDone::CreateCiTaskService.new(
|
||||
project: project,
|
||||
current_user: current_user,
|
||||
assignee_ids: assignee_ids
|
||||
)
|
||||
end
|
||||
|
||||
context 'no existing task issue', :aggregate_failures do
|
||||
it 'creates an issue' do
|
||||
params = {
|
||||
assignee_ids: assignee_ids,
|
||||
title: 'Set up CI/CD',
|
||||
description: anything,
|
||||
add_labels: label.title
|
||||
}
|
||||
|
||||
expect(Issues::BuildService)
|
||||
.to receive(:new)
|
||||
.with(project: project, current_user: current_user, params: params)
|
||||
.and_call_original
|
||||
|
||||
expect { service.execute }.to change(Issue, :count).by(1)
|
||||
|
||||
expect(project.issues.last).to have_attributes(
|
||||
author: current_user,
|
||||
title: params[:title],
|
||||
assignees: [assignee_one],
|
||||
labels: [label]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'an open issue with the same label already exists', :aggregate_failures do
|
||||
let_it_be(:assignee_ids) { [assignee_two.id] }
|
||||
|
||||
it 'assigns the user to the existing issue' do
|
||||
issue = create(:labeled_issue, project: project, labels: [label], assignees: [assignee_one])
|
||||
params = { add_assignee_ids: assignee_ids }
|
||||
|
||||
expect(Issues::UpdateService)
|
||||
.to receive(:new)
|
||||
.with(project: project, current_user: current_user, params: params)
|
||||
.and_call_original
|
||||
|
||||
expect { service.execute }.not_to change(Issue, :count)
|
||||
|
||||
expect(issue.reload.assignees).to match_array([assignee_one, assignee_two])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -286,6 +286,7 @@ licenses: :gitlab_main
|
|||
lists: :gitlab_main
|
||||
list_user_preferences: :gitlab_main
|
||||
loose_foreign_keys_deleted_records: :gitlab_main
|
||||
member_tasks: :gitlab_main
|
||||
members: :gitlab_main
|
||||
merge_request_assignees: :gitlab_main
|
||||
merge_request_blocks: :gitlab_main
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue