Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-11-12 06:25:57 +00:00
parent 8ba468e30c
commit bd84e48794
46 changed files with 1477 additions and 372 deletions

View File

@ -29,7 +29,7 @@ function isCommentPopover(target) {
const targetUrl = new URL(target.href);
const noteId = targetUrl.hash;
return window?.gon?.features?.commentTooltips && noteId && noteId.startsWith('#note_');
return noteId && noteId.startsWith('#note_');
}
export function handleCommentPopoverMount({ target, apolloProvider }) {

View File

@ -81,6 +81,9 @@ export const typePolicies = {
ComplianceFrameworkConnection: {
merge: true,
},
OrganizationUserConnection: {
merge: true,
},
};
export const stripWhitespaceFromQuery = (url, path) => {

View File

@ -1,8 +1,12 @@
<script>
import { isEqual } from 'lodash';
import { __, s__ } from '~/locale';
import { createAlert } from '~/alert';
import { fetchPolicies } from '~/lib/graphql';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import organizationUsersQuery from '../graphql/queries/organization_users.query.graphql';
import organizationUsersIsLastOwnerQuery from '../graphql/queries/organization_users_is_last_owner.query.graphql';
import { ORGANIZATION_USERS_PER_PAGE } from '../constants';
import UsersView from './users_view.vue';
@ -28,40 +32,21 @@ export default {
inject: ['organizationGid'],
data() {
return {
users: [],
organization: {},
paginationHistory: [defaultPagination],
pagination: {
...defaultPagination,
},
pageInfo: {},
};
},
apollo: {
users: {
organization: {
query: organizationUsersQuery,
variables() {
return {
id: this.organizationGid,
first: this.pagination.first,
last: this.pagination.last,
before: this.pagination.before,
after: this.pagination.after,
};
return this.apolloQueryVariables;
},
update(data) {
const { nodes, pageInfo } = data.organization.organizationUsers;
this.pageInfo = pageInfo;
return nodes.map(({ id, badges, accessLevel, userPermissions, user }) => {
return {
...user,
gid: id,
id: getIdFromGraphQLId(user.id),
badges,
accessLevel,
userPermissions,
email: user.publicEmail,
};
});
return data.organization;
},
error(error) {
createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
@ -70,17 +55,87 @@ export default {
},
computed: {
loading() {
return this.$apollo.queries.users.loading;
return this.$apollo.queries.organization.loading;
},
users() {
return this.organization?.organizationUsers || { nodes: [], pageInfo: {} };
},
nodes() {
return this.users.nodes.map(
({ id, badges, accessLevel, userPermissions, user, isLastOwner }) => ({
...user,
gid: id,
id: getIdFromGraphQLId(user.id),
badges,
accessLevel,
userPermissions,
email: user.publicEmail,
isLastOwner,
}),
);
},
pageInfo() {
return this.users.pageInfo;
},
apolloCache() {
return this.$apollo.provider.defaultClient.cache;
},
apolloQueryVariables() {
return {
id: this.organizationGid,
...this.pagination,
};
},
},
methods: {
setPagination(pagination) {
this.pagination = pagination;
this.paginationHistory.push(pagination);
},
handlePrevPage() {
this.pagination.before = this.pageInfo.startCursor;
this.pagination.after = '';
this.setPagination({
first: null,
after: '',
last: ORGANIZATION_USERS_PER_PAGE,
before: this.pageInfo.startCursor,
});
},
handleNextPage() {
this.pagination.before = '';
this.pagination.after = this.pageInfo.endCursor;
this.setPagination({
first: ORGANIZATION_USERS_PER_PAGE,
after: this.pageInfo.endCursor,
last: null,
before: '',
});
},
async onRoleChange() {
try {
await this.$apollo.query({
query: organizationUsersIsLastOwnerQuery,
variables: this.apolloQueryVariables,
fetchPolicy: fetchPolicies.NETWORK_ONLY,
});
// isLastOwner may have changed on other pages so we need to
// evict the cache for all cached pages except for current page.
const cacheId = this.apolloCache.identify(this.organization);
new Set(this.paginationHistory).forEach((pagination) => {
if (isEqual(pagination, this.pagination)) {
return;
}
this.apolloCache.evict({
id: cacheId,
fieldName: 'organizationUsers',
args: pagination,
});
});
this.apolloCache.gc();
} catch (error) {
Sentry.captureException(error);
// Failed fetching new data async. Reload entire page to hopefully get fresh results and reset Apollo caches.
window.location.reload();
}
},
},
};
@ -90,11 +145,12 @@ export default {
<section>
<h1 class="gl-my-4 gl-text-size-h-display">{{ $options.i18n.users }}</h1>
<users-view
:users="users"
:users="nodes"
:loading="loading"
:page-info="pageInfo"
@prev="handlePrevPage"
@next="handleNextPage"
@role-change="onRoleChange"
/>
</section>
</template>

View File

@ -1,5 +1,10 @@
<script>
import { GlLoadingIcon, GlKeysetPagination, GlCollapsibleListbox } from '@gitlab/ui';
import {
GlLoadingIcon,
GlKeysetPagination,
GlCollapsibleListbox,
GlTooltipDirective,
} from '@gitlab/ui';
import { createAlert } from '~/alert';
import UsersTable from '~/vue_shared/components/users_table/users_table.vue';
import {
@ -19,6 +24,10 @@ export default {
'Organization|An error occurred updating the organization role. Please try again.',
),
successMessage: s__('Organization|Organization role was updated successfully.'),
disabledRoleListboxTooltipText: s__('Organization|Organizations must have at least one owner.'),
},
directives: {
GlTooltip: GlTooltipDirective,
},
components: {
GlLoadingIcon,
@ -90,6 +99,7 @@ export default {
}
this.$toast.show(this.$options.i18n.successMessage);
this.$emit('role-change');
} catch (error) {
createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
} finally {
@ -99,6 +109,14 @@ export default {
roleListboxItemText(accessLevel) {
return this.$options.roleListboxItems.find((item) => item.value === accessLevel).text;
},
isRoleListboxDisabled(user) {
return user.isLastOwner;
},
roleListboxTooltip(user) {
return this.isRoleListboxDisabled(user)
? this.$options.i18n.disabledRoleListboxTooltipText
: null;
},
},
};
</script>
@ -114,14 +132,21 @@ export default {
:column-widths="$options.usersTable.columnWidths"
>
<template #organization-role="{ user }">
<gl-collapsible-listbox
:selected="user.accessLevel.stringValue"
block
toggle-class="gl-form-input-xl"
:items="$options.roleListboxItems"
:loading="roleListboxLoadingStates.includes(user.gid)"
@select="onRoleSelect($event, user)"
/>
<div
v-gl-tooltip="{ disabled: !roleListboxTooltip(user), title: roleListboxTooltip(user) }"
class="gl-rounded-base focus:gl-focus"
:tabindex="isRoleListboxDisabled(user) && 0"
>
<gl-collapsible-listbox
:disabled="isRoleListboxDisabled(user)"
:selected="user.accessLevel.stringValue"
block
toggle-class="gl-form-input-xl"
:items="$options.roleListboxItems"
:loading="roleListboxLoadingStates.includes(user.gid)"
@select="onRoleSelect($event, user)"
/>
</div>
</template>
</users-table>
<div class="gl-flex gl-justify-center">

View File

@ -4,8 +4,8 @@ query getOrganizationUsers(
$id: OrganizationsOrganizationID!
$first: Int
$last: Int
$before: String!
$after: String!
$before: String
$after: String
) {
organization(id: $id) {
id
@ -28,6 +28,7 @@ query getOrganizationUsers(
accessLevel {
stringValue
}
isLastOwner
}
pageInfo {
...PageInfo

View File

@ -0,0 +1,17 @@
query getOrganizationUsersIsLastOwner(
$id: OrganizationsOrganizationID!
$first: Int
$last: Int
$before: String
$after: String
) {
organization(id: $id) {
id
organizationUsers(first: $first, last: $last, before: $before, after: $after) {
nodes {
id
isLastOwner
}
}
}
}

View File

@ -5,7 +5,6 @@ module Groups
feature_category :team_planning
before_action do
push_frontend_feature_flag(:comment_tooltips)
push_frontend_feature_flag(:notifications_todos_buttons)
push_force_frontend_feature_flag(:work_items, group&.work_items_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_beta, group&.work_items_beta_feature_flag_enabled?)

View File

@ -49,7 +49,6 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:service_desk_ticket)
push_frontend_feature_flag(:issues_list_drawer, project)
push_frontend_feature_flag(:notifications_todos_buttons, current_user)
push_frontend_feature_flag(:comment_tooltips, current_user)
push_force_frontend_feature_flag(:glql_integration, project&.glql_integration_feature_flag_enabled?)
end

View File

@ -14,7 +14,6 @@ class Projects::WorkItemsController < Projects::ApplicationController
push_force_frontend_feature_flag(:work_items_alpha, project&.work_items_alpha_feature_flag_enabled?)
push_force_frontend_feature_flag(:glql_integration, project&.glql_integration_feature_flag_enabled?)
push_frontend_feature_flag(:namespace_level_work_items, project&.group)
push_frontend_feature_flag(:comment_tooltips)
end
feature_category :team_planning

View File

@ -1,8 +0,0 @@
---
name: comment_tooltips
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158020
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/476395
milestone: '17.3'
type: development
group: group::project management
default_enabled: false

View File

@ -18,7 +18,7 @@
- title: "`workflow:rules` templates"
# The milestones for the deprecation announcement, and the removal.
removal_milestone: "18.0"
removal_milestone: "19.0"
announcement_milestone: "17.0"
# Change breaking_change to false if needed.
breaking_change: true

View File

@ -7,7 +7,7 @@ feature_categories:
description: Stores pre-calculated vulnerability statistics for projects
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34289
milestone: '13.2'
gitlab_schema: gitlab_main_cell
gitlab_schema: gitlab_sec
allow_cross_foreign_keys:
- gitlab_main_clusterwide
sharding_key:

View File

@ -1488,7 +1488,8 @@ job:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145206) in GitLab 16.11.
Use `artifacts:access` to determine who can access the job artifacts.
Use `artifacts:access` to determine who can access the job artifacts from the GitLab UI
or API. This option does not prevent you from forwarding artifacts to downstream pipelines.
You cannot use [`artifacts:public`](#artifactspublic) and `artifacts:access` in the same job.

View File

@ -146,6 +146,24 @@ to [add the `ci:` section](https://docs.gitlab.com/ee/install/installation.html#
Omnibus, the Helm chart, and Operator will handle this configuration
automatically from GitLab 16.0 onwards.
</div>
<div class="deprecation breaking-change" data-milestone="19.0">
### `workflow:rules` templates
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">17.0</span>
- Removal in GitLab <span class="milestone">19.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/456394).
</div>
The [`workflow:rules`](https://docs.gitlab.com/ee/ci/yaml/workflow.html#workflowrules-templates) templates are deprecated and no longer recommended for use. Using these templates greatly limits the flexibility of your pipelines and makes it hard to use new `workflow` features.
This is one small step towards moving away from CI/CD templates in preference of [CI/CD components](https://docs.gitlab.com/ee/ci/components/). You can search the [CI/CD Catalog](https://docs.gitlab.com/ee/ci/components/#cicd-catalog) for a replacement, or [add `workflow:rules`](https://docs.gitlab.com/ee/ci/yaml/workflow.html) to your pipeline explicitly.
</div>
</div>
@ -1133,24 +1151,6 @@ We encourage GitLab administrators to switch to the webhook delivery method for
[Issue 393157](https://gitlab.com/gitlab-org/gitlab/-/issues/393157) tracks improving email ingestion in general.
We hope this will simplify infrastructure setup and add several improvements to how you manage GitLab in the near future.
</div>
<div class="deprecation breaking-change" data-milestone="18.0">
### `workflow:rules` templates
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">17.0</span>
- Removal in GitLab <span class="milestone">18.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/456394).
</div>
The [`workflow:rules`](https://docs.gitlab.com/ee/ci/yaml/workflow.html#workflowrules-templates) templates are deprecated and no longer recommended for use. Using these templates greatly limits the flexibility of your pipelines and makes it hard to use new `workflow` features.
This is one small step towards moving away from CI/CD templates in preference of [CI/CD components](https://docs.gitlab.com/ee/ci/components/). You can search the [CI/CD Catalog](https://docs.gitlab.com/ee/ci/components/#cicd-catalog) for a replacement, or [add `workflow:rules`](https://docs.gitlab.com/ee/ci/yaml/workflow.html) to your pipeline explicitly.
</div>
</div>

View File

@ -2055,17 +2055,9 @@ references refresh.
### Show comment preview when hovering on a link
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/29663) in GitLab 17.3 [with a flag](../administration/feature_flags.md) named `comment_tooltips`. Disabled by default.
> - Feature flag removed in GitLab 17.6
FLAG:
The availability of this feature is controlled by a feature flag.
For more information, see the history.
This feature is available for testing, but not ready for production use.
When this feature is enabled, hovering over a link to a comment shows the author and
part of the comment.
When this feature is disabled, hovering over a link to a comment shows information about the item,
such as issue or epic.
Hovering over a link to a comment shows the author and first line of the comment.
### Embedding Observability dashboards

View File

@ -108,10 +108,26 @@ To switch organizations:
1. Select the [**Visibility level**](../public_access.md) of the group.
1. Select **Create group**.
## Manage users
## View users
1. On the left sidebar, select **Organizations** and find the organization you want to view.
1. Select **Manage > Users**.
## Change a user's role
Prerequisites:
- You must have the Owner role for the organization.
To change a user's role:
1. On the left sidebar, select **Organizations** and find the organization you want to manage.
1. Select **Manage > Users**.
1. Find the user whose role you want to update.
1. From the **Organization role** dropdown list, select a role.
NOTE:
If you cannot select from the **Organization role** dropdown list, this user is the organization's only Owner. To change this user's role, first assign the Owner role to another user.
## Supported Markdown for Organization description

View File

@ -84,6 +84,9 @@ If the above requirements are not met, the importer can't map the particular use
describing that non-existent users were added as reviewers and approvers. However, the actual reviewer status and
approval are not applied to the merge request in GitLab.
[In GitLab 17.5 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/477553), GitLab adds backticks to username mentions in issues, merge requests, and notes.
These backticks prevent linking to an incorrect user with the same username on the GitLab instance.
## Known issues
- GitHub pull request comments (known as diff notes in GitLab) created before 2017 are imported in separate threads.

View File

@ -6,12 +6,15 @@ module Gitlab
module Events
# Base class for importing issue events during project import from GitHub
class BaseImporter
include Gitlab::GithubImport::PushPlaceholderReferences
# project - An instance of `Project`.
# client - An instance of `Gitlab::GithubImport::Client`.
def initialize(project, client)
@project = project
@client = client
@user_finder = UserFinder.new(project, client)
@mapper = Gitlab::GithubImport::ContributionsMapper.new(project)
end
# issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
@ -21,7 +24,7 @@ module Gitlab
private
attr_reader :project, :user_finder, :client
attr_reader :project, :user_finder, :client, :mapper
def author_id(issue_event, author_key: :actor)
user_finder.author_id_for(issue_event, author_key: author_key).first
@ -45,11 +48,7 @@ module Gitlab
end
def imported_from
::Import::HasImportSource::IMPORT_SOURCES[:github]
end
def import_settings
@import_settings ||= Gitlab::GithubImport::Settings.new(project)
::Import::SOURCE_GITHUB
end
end
end

View File

@ -6,12 +6,15 @@ module Gitlab
module Events
class ChangedAssignee < BaseImporter
def execute(issue_event)
assignee_id = author_id(issue_event, author_key: :assignee)
author_id = author_id(issue_event, author_key: :actor)
note_body = parse_body(issue_event, assignee_id)
note_body = parse_body(issue_event)
create_note(issue_event, note_body, author_id)
created_note = create_note(issue_event, note_body, author_id)
return unless mapper.user_mapping_enabled?
push_with_record(created_note, :author_id, issue_event[:actor].id, mapper.user_mapper)
end
private
@ -38,13 +41,11 @@ module Gitlab
)
end
def parse_body(issue_event, assignee_id)
assignee = User.find(assignee_id).to_reference
def parse_body(issue_event)
if issue_event.event == 'unassigned'
"#{SystemNotes::IssuablesService.issuable_events[:unassigned]} #{assignee}"
"#{SystemNotes::IssuablesService.issuable_events[:unassigned]} `@#{issue_event[:assignee].login}`"
else
"#{SystemNotes::IssuablesService.issuable_events[:assigned]} #{assignee}"
"#{SystemNotes::IssuablesService.issuable_events[:assigned]} `@#{issue_event[:assignee].login}`"
end
end
end

View File

@ -21,7 +21,11 @@ module Gitlab
imported_from: imported_from
}.merge(resource_event_belongs_to(issue_event))
ResourceLabelEvent.create!(attrs)
created_event = ResourceLabelEvent.create!(attrs)
return unless mapper.user_mapping_enabled?
push_with_record(created_event, :user_id, issue_event[:actor].id, mapper.user_mapper)
end
def label_finder

View File

@ -30,7 +30,11 @@ module Gitlab
imported_from: imported_from
}.merge(resource_event_belongs_to(issue_event))
ResourceMilestoneEvent.create!(attrs)
created_event = ResourceMilestoneEvent.create!(attrs)
return unless mapper.user_mapping_enabled?
push_with_record(created_event, :user_id, issue_event[:actor].id, mapper.user_mapper)
end
def action(event_type)

View File

@ -6,10 +6,9 @@ module Gitlab
module Events
class ChangedReviewer < BaseImporter
def execute(issue_event)
requested_reviewer_id = author_id(issue_event, author_key: :requested_reviewer)
review_requester_id = author_id(issue_event, author_key: :review_requester)
note_body = parse_body(issue_event, requested_reviewer_id)
note_body = parse_body(issue_event)
create_note(issue_event, note_body, review_requester_id)
end
@ -17,7 +16,7 @@ module Gitlab
private
def create_note(issue_event, note_body, review_requester_id)
Note.create!(
created_note = Note.create!(
importing: true,
system: true,
noteable_type: issuable_type(issue_event),
@ -36,17 +35,19 @@ module Gitlab
updated_at: issue_event.created_at,
imported_from: imported_from
)
return unless mapper.user_mapping_enabled?
push_with_record(created_note, :author_id, issue_event[:review_requester].id, mapper.user_mapper)
end
def parse_body(issue_event, requested_reviewer_id)
requested_reviewer = User.find(requested_reviewer_id).to_reference
def parse_body(issue_event)
if issue_event.event == 'review_request_removed'
"#{SystemNotes::IssuablesService.issuable_events[:review_request_removed]} " \
"#{requested_reviewer}"
"`@#{issue_event[:requested_reviewer].login}`"
else
"#{SystemNotes::IssuablesService.issuable_events[:review_requested]} " \
"#{requested_reviewer}"
"`@#{issue_event[:requested_reviewer].login}`"
end
end
end

View File

@ -13,7 +13,7 @@ module Gitlab
private
def create_event(issue_event)
Event.create!(
created_event = Event.create!(
project_id: project.id,
author_id: author_id(issue_event),
action: 'closed',
@ -23,6 +23,10 @@ module Gitlab
updated_at: issue_event.created_at,
imported_from: imported_from
)
return unless mapper.user_mapping_enabled?
push_with_record(created_event, :author_id, issue_event[:actor].id, mapper.user_mapper)
end
def create_state_event(issue_event)
@ -37,7 +41,11 @@ module Gitlab
imported_from: imported_from
}.merge(resource_event_belongs_to(issue_event))
ResourceStateEvent.create!(attrs)
state_event = ResourceStateEvent.create!(attrs)
return unless mapper.user_mapping_enabled?
push_with_record(state_event, :user_id, issue_event[:actor].id, mapper.user_mapper)
end
end
end

View File

@ -33,7 +33,7 @@ module Gitlab
end
def create_note(issue_event, note_body, user_id)
Note.create!(
created_note = Note.create!(
importing: true,
system: true,
noteable_type: issuable_type(issue_event),
@ -45,6 +45,12 @@ module Gitlab
created_at: issue_event.created_at,
imported_from: imported_from
)
return created_note unless mapper.user_mapping_enabled?
push_with_record(created_note, :author_id, issue_event[:actor].id, mapper.user_mapper)
created_note
end
def mentioned_in_type(issue_event)

View File

@ -14,7 +14,7 @@ module Gitlab
private
def create_event(issue_event)
Event.create!(
event = Event.create!(
project_id: project.id,
author_id: author_id(issue_event),
action: 'merged',
@ -24,6 +24,9 @@ module Gitlab
updated_at: issue_event.created_at,
imported_from: imported_from
)
return unless mapper.user_mapping_enabled?
push_with_record(event, :author_id, issue_event[:actor].id, mapper.user_mapper)
end
def create_state_event(issue_event)
@ -38,7 +41,11 @@ module Gitlab
imported_from: imported_from
}.merge(resource_event_belongs_to(issue_event))
ResourceStateEvent.create!(attrs)
state_event = ResourceStateEvent.create!(attrs)
return unless mapper.user_mapping_enabled?
push_with_record(state_event, :user_id, issue_event[:actor].id, mapper.user_mapper)
end
def create_note(issue_event)

View File

@ -6,7 +6,11 @@ module Gitlab
module Events
class Renamed < BaseImporter
def execute(issue_event)
Note.create!(note_params(issue_event))
created_note = Note.create!(note_params(issue_event))
return unless mapper.user_mapping_enabled?
push_with_record(created_note, :author_id, issue_event[:actor].id, mapper.user_mapper)
end
private

View File

@ -13,7 +13,7 @@ module Gitlab
private
def create_event(issue_event)
Event.create!(
created_event = Event.create!(
project_id: project.id,
author_id: author_id(issue_event),
action: 'reopened',
@ -23,6 +23,10 @@ module Gitlab
updated_at: issue_event.created_at,
imported_from: imported_from
)
return unless mapper.user_mapping_enabled?
push_with_record(created_event, :author_id, issue_event[:actor].id, mapper.user_mapper)
end
def create_state_event(issue_event)
@ -33,7 +37,11 @@ module Gitlab
imported_from: imported_from
}.merge(resource_event_belongs_to(issue_event))
ResourceStateEvent.create!(attrs)
state_event = ResourceStateEvent.create!(attrs)
return unless mapper.user_mapping_enabled?
push_with_record(state_event, :user_id, issue_event[:actor].id, mapper.user_mapper)
end
end
end

View File

@ -47,7 +47,7 @@ namespace :gitlab do
def add_merge_request_shas(project, csv)
logger.info "Checking merge request shas..."
merge_requests = MergeRequest.from_and_to_forks(project).select(:id, :merge_commit_sha)
merge_requests = MergeRequest.of_projects(project).select(:id, :merge_commit_sha)
merge_requests.find_each do |merge_request|
add_match(csv, merge_request.merge_commit_sha)
end
@ -55,12 +55,12 @@ namespace :gitlab do
def add_merge_request_diff_shas(project, csv)
logger.info "Checking merge request diff shas..."
merge_requests = MergeRequest.from_and_to_forks(project)
merge_request_diffs = MergeRequestDiff
.joins(:merge_request).merge(merge_requests)
.select(:id, :start_commit_sha, :head_commit_sha)
.joins(:merge_request).merge(MergeRequest.of_projects([project, project.forked_from_project].compact))
.select(:id, :start_commit_sha, :head_commit_sha, :diff_type)
merge_request_diffs.find_each do |diff|
next if diff.merge_head?
merge_request_diffs.where.not(diff_type: :merge_head).find_each do |diff|
add_match(csv, diff.start_commit_sha)
add_match(csv, diff.head_commit_sha)
end

View File

@ -38576,6 +38576,9 @@ msgstr ""
msgid "Organization|Organizations"
msgstr ""
msgid "Organization|Organizations must have at least one owner."
msgstr ""
msgid "Organization|Organizations must have at least one owner. To delete the user, first assign a new owner to %{lastLink}."
msgid_plural "Organization|Organizations must have at least one owner. To delete the user, first assign a new owner to %{links} %{boldStart}and%{boldEnd} %{lastLink}."
msgstr[0] ""

View File

@ -32,4 +32,4 @@ tracer:
enabled: false
# https://gitlab.com/gitlab-org/gitlab/-/issues/471172
gitlab_http_router:
enabled: true
enabled: false

View File

@ -60,9 +60,9 @@ class Server
se_category: query['se_ca'],
se_action: query['se_ac'],
collector_tstamp: query['dtm'],
label: query['se_la'],
property: query['se_pr'],
value: query['se_va'],
se_label: query['se_la'],
se_property: query['se_pr'],
se_value: query['se_va'],
contexts: (JSON.parse(Base64.decode64(query['cx'])) if query['cx'])
},
rawEvent: { parameters: query }

View File

@ -99,14 +99,6 @@ describe('initIssuablePopovers', () => {
});
describe('comment tooltips', () => {
beforeEach(() => {
window.gon = {
features: {
commentTooltips: true,
},
};
});
it('calls popover mount function for comments', async () => {
jest.spyOn(popover, 'handleIssuablePopoverMount').mockImplementation(jest.fn());

View File

@ -4,15 +4,21 @@ import organizationUsersResponse from 'test_fixtures/graphql/organizations/organ
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import { createAlert } from '~/alert';
import organizationUsersQuery from '~/organizations/users/graphql/queries/organization_users.query.graphql';
import organizationUsersIsLastOwnerQuery from '~/organizations/users/graphql/queries/organization_users_is_last_owner.query.graphql';
import OrganizationsUsersApp from '~/organizations/users/components/app.vue';
import OrganizationsUsersView from '~/organizations/users/components/users_view.vue';
import { ORGANIZATION_USERS_PER_PAGE } from '~/organizations/users/constants';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { pageInfoMultiplePages, pageInfoEmpty } from 'jest/organizations/mock_data';
import { MOCK_USERS_FORMATTED } from '../mock_data';
jest.mock('~/alert');
jest.mock('~/sentry/sentry_browser_wrapper');
useMockLocationHelper();
Vue.use(VueApollo);
@ -53,12 +59,32 @@ const successfulResponseHandlerNoResults = jest.fn().mockResolvedValue({
const errorResponseHandler = jest.fn().mockRejectedValue(mockError);
const loadingResponseHandler = jest.fn().mockReturnValue(new Promise(() => {}));
const successfulIsLastOwnerResponseHandler = jest.fn().mockResolvedValue({
data: {
organization: {
...organizationUsersResponse.data.organization,
organizationUsers: {
// eslint-disable-next-line no-underscore-dangle
__typename: organizationUsersResponse.data.organization.organizationUsers.__typename,
nodes: nodes.map((node) => ({ id: node.id, isLastOwner: node.isLastOwner })),
},
},
},
});
describe('OrganizationsUsersApp', () => {
let wrapper;
let mockApollo;
let apolloCache;
const createComponent = ({ handler = successfulResponseHandler } = {}) => {
mockApollo = createMockApollo([[organizationUsersQuery, handler]]);
const createComponent = ({
handler = successfulResponseHandler,
isLastOwnerHandler = successfulIsLastOwnerResponseHandler,
} = {}) => {
mockApollo = createMockApollo([
[organizationUsersQuery, handler],
[organizationUsersIsLastOwnerQuery, isLastOwnerHandler],
]);
wrapper = shallowMountExtended(OrganizationsUsersApp, {
apolloProvider: mockApollo,
@ -66,10 +92,16 @@ describe('OrganizationsUsersApp', () => {
organizationGid,
},
});
apolloCache = mockApollo.defaultClient.cache;
jest.spyOn(apolloCache, 'identify').mockImplementation((object) => object.id);
jest.spyOn(apolloCache, 'evict');
jest.spyOn(apolloCache, 'gc');
};
afterEach(() => {
mockApollo = null;
apolloCache = null;
});
const findOrganizationUsersView = () => wrapper.findComponent(OrganizationsUsersView);
@ -138,9 +170,77 @@ describe('OrganizationsUsersApp', () => {
id: organizationGid,
before: pageInfoMultiplePages.startCursor,
after: '',
first: null,
last: ORGANIZATION_USERS_PER_PAGE,
});
});
});
describe('when role-change event is emitted', () => {
it('calls isLastOwner query with correct variables', async () => {
createComponent();
await waitForPromises();
findOrganizationUsersView().vm.$emit('role-change');
await waitForPromises();
expect(successfulIsLastOwnerResponseHandler).toHaveBeenCalledWith({
id: organizationGid,
before: '',
after: '',
first: ORGANIZATION_USERS_PER_PAGE,
last: null,
});
});
describe('when isLastOwner query is successful', () => {
beforeEach(async () => {
createComponent({
handler: successfulResponseHandlerMultiplePages,
});
await waitForPromises();
findOrganizationUsersView().vm.$emit('next');
await waitForPromises();
findOrganizationUsersView().vm.$emit('role-change');
await waitForPromises();
});
it('evicts cache for all pages except current one', () => {
expect(apolloCache.evict).toHaveBeenCalledWith({
id: organizationGid,
fieldName: 'organizationUsers',
args: {
before: '',
after: '',
first: ORGANIZATION_USERS_PER_PAGE,
last: null,
},
});
});
it('calls gc on apollo cache', () => {
expect(apolloCache.gc).toHaveBeenCalled();
});
});
describe('when isLastOwner query is not successful', () => {
const error = new Error();
beforeEach(async () => {
createComponent({
isLastOwnerHandler: jest.fn().mockRejectedValueOnce(error),
});
await waitForPromises();
findOrganizationUsersView().vm.$emit('role-change');
await waitForPromises();
});
it('reloads the page to get fresh user list', () => {
expect(window.location.reload).toHaveBeenCalled();
});
it('reports error to sentry', () => {
expect(Sentry.captureException).toHaveBeenCalledWith(error);
});
});
});
});

View File

@ -10,6 +10,7 @@ import organizationUserUpdateMutation from '~/organizations/users/graphql/mutati
import createMockApollo from 'helpers/mock_apollo_helper';
import { createAlert } from '~/alert';
import waitForPromises from 'helpers/wait_for_promises';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { pageInfoMultiplePages } from 'jest/organizations/mock_data';
import { MOCK_PATHS, MOCK_USERS_FORMATTED } from '../mock_data';
@ -43,6 +44,9 @@ describe('UsersView', () => {
show: mockToastShow,
},
},
directives: {
GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
@ -113,6 +117,45 @@ describe('UsersView', () => {
},
],
selected: MOCK_USERS_FORMATTED[0].accessLevel.stringValue,
disabled: false,
});
});
it('does not render tooltip', () => {
createComponent();
const tooltipContainer = findListbox().element.parentNode;
const tooltip = getBinding(tooltipContainer, 'gl-tooltip');
expect(tooltip.value.disabled).toBe(true);
expect(tooltipContainer.getAttribute('tabindex')).toBe(null);
});
describe('when user is last owner of organization', () => {
const [firstUser] = MOCK_USERS_FORMATTED;
beforeEach(() => {
createComponent({
propsData: {
loading: false,
users: [{ ...firstUser, isLastOwner: true }],
},
});
});
it('renders listbox as disabled', () => {
expect(findListbox().props('disabled')).toBe(true);
});
it('renders tooltip and makes element focusable', () => {
const tooltipContainer = findListbox().element.parentNode;
const tooltip = getBinding(tooltipContainer, 'gl-tooltip');
expect(tooltip.value).toEqual({
title: 'Organizations must have at least one owner.',
disabled: false,
});
expect(tooltipContainer.getAttribute('tabindex')).toBe('0');
});
});
@ -152,6 +195,15 @@ describe('UsersView', () => {
expect(mockToastShow).toHaveBeenCalledWith('Organization role was updated successfully.');
});
it('emits role-change event when GraphQL mutation is successful', async () => {
createComponent();
listboxSelectOwner();
await waitForPromises();
expect(wrapper.emitted('role-change')).toEqual([[]]);
});
it('calls createAlert when GraphQL mutation has validation error', async () => {
const errorResponseHandler = jest
.fn()

View File

@ -14,7 +14,7 @@ export const MOCK_PATHS = {
};
export const MOCK_USERS_FORMATTED = users.map(
({ id, badges, user, accessLevel, userPermissions }) => {
({ id, badges, user, accessLevel, userPermissions, isLastOwner }) => {
return {
...user,
gid: id,
@ -23,6 +23,7 @@ export const MOCK_USERS_FORMATTED = users.map(
accessLevel,
userPermissions,
email: user.publicEmail,
isLastOwner,
};
},
);

View File

@ -5,9 +5,8 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedAssignee, feature_category: :importers do
subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project) { create(:project, :repository, :with_import_url) }
let_it_be(:author) { create(:user) }
let_it_be(:assignee) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
let(:issuable) { create(:issue, project: project) }
@ -15,11 +14,11 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedAssignee, feature_
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
'actor' => { 'id' => author.id, 'login' => author.username },
'actor' => { 'id' => 1000, 'login' => 'github_author' },
'event' => event_type,
'commit_id' => nil,
'created_at' => '2022-04-26 18:30:53 UTC',
'assignee' => { 'id' => assignee.id, 'login' => assignee.username },
'assignee' => { 'id' => 2000, 'login' => 'github_assignee' },
'issue' => { 'number' => issuable.iid, pull_request: issuable.is_a?(MergeRequest) }
)
end
@ -70,38 +69,103 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedAssignee, feature_
shared_examples 'process assigned & unassigned events' do
context 'when importing an assigned event' do
let(:event_type) { 'assigned' }
let(:expected_note_attrs) { note_attrs.merge(note: "assigned to @#{assignee.username}") }
let(:expected_note_attrs) { note_attrs.merge(note: "assigned to `@github_assignee`") }
it_behaves_like 'create expected notes'
end
context 'when importing an unassigned event' do
let(:event_type) { 'unassigned' }
let(:expected_note_attrs) { note_attrs.merge(note: "unassigned @#{assignee.username}") }
let(:expected_note_attrs) { note_attrs.merge(note: "unassigned `@github_assignee`") }
it_behaves_like 'create expected notes'
end
end
shared_examples 'push a placeholder reference' do
let(:event_type) { 'assigned' }
it 'pushes the reference' do
expect(subject)
.to receive(:push_with_record)
.with(
an_instance_of(Note),
:author_id,
issue_event[:actor].id,
an_instance_of(Gitlab::Import::SourceUserMapper)
)
importer.execute(issue_event)
end
end
shared_examples 'do not push placeholder reference' do
let(:event_type) { 'assigned' }
it 'does not push any reference' do
expect(subject)
.not_to receive(:push_with_record)
importer.execute(issue_event)
end
end
describe '#execute' do
before do
allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
allow(finder).to receive(:database_id).and_return(issuable.id)
end
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(author.id, author.username).and_return(author.id)
allow(finder).to receive(:find).with(assignee.id, assignee.username).and_return(assignee.id)
allow(finder).to receive(:author_id_for).with(issue_event, author_key: :actor).and_return([author.id, true])
end
end
context 'with Issue' do
it_behaves_like 'process assigned & unassigned events'
context 'when user mapping is enabled' do
let_it_be(:source_user) do
create(
:import_source_user,
placeholder_user_id: author.id,
source_user_identifier: 1000,
source_username: 'github_author',
source_hostname: project.import_url,
namespace_id: project.root_ancestor.id
)
end
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: true })
end
context 'with Issue' do
it_behaves_like 'process assigned & unassigned events'
it_behaves_like 'push a placeholder reference'
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
it_behaves_like 'process assigned & unassigned events'
it_behaves_like 'push a placeholder reference'
end
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
context 'when user mapping is disabled' do
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: false })
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(1000, 'github_author').and_return(author.id)
end
end
it_behaves_like 'process assigned & unassigned events'
context 'with Issue' do
it_behaves_like 'process assigned & unassigned events'
it_behaves_like 'do not push placeholder reference'
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
it_behaves_like 'process assigned & unassigned events'
it_behaves_like 'do not push placeholder reference'
end
end
end
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel, feature_category: :importers do
subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project) { create(:project, :repository, :with_import_url) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
@ -44,84 +44,216 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel, feature_cat
end
end
shared_examples 'push placeholder reference' do
it 'pushes the reference' do
expect(subject)
.to receive(:push_with_record)
.with(
an_instance_of(ResourceLabelEvent),
:user_id,
user.id,
an_instance_of(Gitlab::Import::SourceUserMapper)
)
importer.execute(issue_event)
end
end
shared_examples 'do not push placeholder reference' do
it 'does not push any reference' do
expect(subject)
.not_to receive(:push_with_record)
importer.execute(issue_event)
end
end
before do
allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
allow(finder).to receive(:database_id).and_return(issuable.id)
end
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
end
context 'with Issue' do
context 'when importing event with associated label' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id)
context 'when user mapping is enabled' do
let_it_be(:source_user) do
create(
:import_source_user,
placeholder_user_id: user.id,
source_user_identifier: user.id,
source_username: user.username,
source_hostname: project.import_url,
namespace_id: project.root_ancestor.id
)
end
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: true })
end
context 'with Issue' do
context 'when importing event with associated label' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id)
end
context 'when importing a labeled event' do
let(:event_type) { 'labeled' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
it_behaves_like 'push placeholder reference'
end
context 'when importing an unlabeled event' do
let(:event_type) { 'unlabeled' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'remove') }
it_behaves_like 'new event'
it_behaves_like 'push placeholder reference'
end
end
context 'when importing a labeled event' do
context 'when importing event without associated label' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(nil)
end
let(:label_title) { 'deleted_label' }
let(:label_id) { nil }
let(:event_type) { 'labeled' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
end
context 'when importing an unlabeled event' do
let(:event_type) { 'unlabeled' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'remove') }
it_behaves_like 'new event'
it_behaves_like 'push placeholder reference'
end
end
context 'when importing event without associated label' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(nil)
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
context 'when importing event with associated label' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id)
end
context 'when importing a labeled event' do
let(:event_type) { 'labeled' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
it_behaves_like 'push placeholder reference'
end
context 'when importing an unlabeled event' do
let(:event_type) { 'unlabeled' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'remove') }
it_behaves_like 'new event'
it_behaves_like 'push placeholder reference'
end
end
let(:label_title) { 'deleted_label' }
let(:label_id) { nil }
let(:event_type) { 'labeled' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'add') }
context 'when importing event without associated label' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(nil)
end
it_behaves_like 'new event'
end
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
context 'when importing event with associated label' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id)
end
context 'when importing a labeled event' do
let(:label_title) { 'deleted_label' }
let(:label_id) { nil }
let(:event_type) { 'labeled' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
it_behaves_like 'push placeholder reference'
end
end
end
context 'when importing an unlabeled event' do
let(:event_type) { 'unlabeled' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'remove') }
context 'when user mapping is disabled' do
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: false })
it_behaves_like 'new event'
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
end
context 'when importing event without associated label' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(nil)
context 'with Issue' do
context 'when importing event with associated label' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id)
end
context 'when importing a labeled event' do
let(:event_type) { 'labeled' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder reference'
end
context 'when importing an unlabeled event' do
let(:event_type) { 'unlabeled' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'remove') }
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder reference'
end
end
let(:label_title) { 'deleted_label' }
let(:label_id) { nil }
let(:event_type) { 'labeled' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'add') }
context 'when importing event without associated label' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(nil)
end
it_behaves_like 'new event'
let(:label_title) { 'deleted_label' }
let(:label_id) { nil }
let(:event_type) { 'labeled' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder reference'
end
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
context 'when importing event with associated label' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id)
end
context 'when importing a labeled event' do
let(:event_type) { 'labeled' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder reference'
end
context 'when importing an unlabeled event' do
let(:event_type) { 'unlabeled' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'remove') }
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder reference'
end
end
context 'when importing event without associated label' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(nil)
end
let(:label_title) { 'deleted_label' }
let(:label_id) { nil }
let(:event_type) { 'labeled' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder reference'
end
end
end
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedMilestone, feature_category: :importers do
subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project) { create(:project, :repository, :with_import_url) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
@ -57,48 +57,141 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedMilestone, feature
end
end
shared_examples 'push placeholder reference' do
let(:milestone_title) { milestone.title }
it 'pushes the reference' do
expect(subject)
.to receive(:push_with_record)
.with(
an_instance_of(ResourceMilestoneEvent),
:user_id,
user.id,
an_instance_of(Gitlab::Import::SourceUserMapper)
)
importer.execute(issue_event)
end
end
shared_examples 'do not push placeholder reference' do
let(:milestone_title) { milestone.title }
it 'does not push any reference' do
expect(subject)
.not_to receive(:push_with_record)
importer.execute(issue_event)
end
end
describe '#execute' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(milestone.id)
allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
allow(finder).to receive(:database_id).and_return(issuable.id)
end
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
context 'when user mapping is enabled' do
let_it_be(:source_user) do
create(
:import_source_user,
placeholder_user_id: user.id,
source_user_identifier: user.id,
source_username: user.username,
source_hostname: project.import_url,
namespace_id: project.root_ancestor.id
)
end
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: true })
end
context 'with Issue' do
context 'when importing a milestoned event' do
let(:event_type) { 'milestoned' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
it_behaves_like 'push placeholder reference'
end
context 'when importing demilestoned event' do
let(:event_type) { 'demilestoned' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'remove') }
it_behaves_like 'new event'
it_behaves_like 'push placeholder reference'
end
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
context 'when importing a milestoned event' do
let(:event_type) { 'milestoned' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
it_behaves_like 'push placeholder reference'
end
context 'when importing demilestoned event' do
let(:event_type) { 'demilestoned' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'remove') }
it_behaves_like 'new event'
it_behaves_like 'push placeholder reference'
end
end
end
context 'with Issue' do
context 'when importing a milestoned event' do
let(:event_type) { 'milestoned' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
context 'when user mapping is disabled' do
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: false })
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
end
context 'when importing demilestoned event' do
let(:event_type) { 'demilestoned' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'remove') }
context 'with Issue' do
context 'when importing a milestoned event' do
let(:event_type) { 'milestoned' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
end
end
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder reference'
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
context 'when importing demilestoned event' do
let(:event_type) { 'demilestoned' }
let(:expected_event_attrs) { event_attrs.merge(issue_id: issuable.id, action: 'remove') }
context 'when importing a milestoned event' do
let(:event_type) { 'milestoned' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder reference'
end
end
context 'when importing demilestoned event' do
let(:event_type) { 'demilestoned' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'remove') }
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
it_behaves_like 'new event'
context 'when importing a milestoned event' do
let(:event_type) { 'milestoned' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'add') }
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder reference'
end
context 'when importing demilestoned event' do
let(:event_type) { 'demilestoned' }
let(:expected_event_attrs) { event_attrs.merge(merge_request_id: issuable.id, action: 'remove') }
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder reference'
end
end
end
end

View File

@ -2,20 +2,20 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedReviewer do
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedReviewer, feature_category: :importers do
subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project) { create(:project, :repository, :with_import_url) }
let_it_be(:requested_reviewer) { create(:user) }
let_it_be(:review_requester) { create(:user) }
let_it_be(:issuable) { create(:merge_request, source_project: project, target_project: project) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
'actor' => { 'id' => 4, 'login' => 'alice' },
'actor' => { 'id' => review_requester.id, 'login' => review_requester.username },
'event' => event_type,
'commit_id' => nil,
'created_at' => '2022-04-26 18:30:53 UTC',
@ -71,32 +71,106 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedReviewer do
shared_examples 'process review_requested & review_request_removed MR events' do
context 'when importing a review_requested event' do
let(:event_type) { 'review_requested' }
let(:expected_note_attrs) { note_attrs.merge(note: "requested review from @#{requested_reviewer.username}") }
let(:expected_note_attrs) { note_attrs.merge(note: "requested review from `@#{requested_reviewer.username}`") }
it_behaves_like 'create expected notes'
end
context 'when importing a review_request_removed event' do
let(:event_type) { 'review_request_removed' }
let(:expected_note_attrs) { note_attrs.merge(note: "removed review request for @#{requested_reviewer.username}") }
let(:expected_note_attrs) do
note_attrs.merge(note: "removed review request for `@#{requested_reviewer.username}`")
end
it_behaves_like 'create expected notes'
end
end
shared_examples 'push placeholder reference' do
let(:event_type) { 'changed' }
it 'pushes the reference' do
expect(subject)
.to receive(:push_with_record)
.with(
an_instance_of(Note),
:author_id,
review_requester.id,
an_instance_of(Gitlab::Import::SourceUserMapper)
)
importer.execute(issue_event)
end
end
shared_examples 'do not push placeholder reference' do
let(:event_type) { 'changed' }
it 'does not push any reference' do
expect(subject)
.not_to receive(:push_with_record)
importer.execute(issue_event)
end
end
describe '#execute' do
before do
allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
allow(finder).to receive(:database_id).and_return(issuable.id)
end
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(requested_reviewer.id, requested_reviewer.username)
.and_return(requested_reviewer.id)
allow(finder).to receive(:find).with(review_requester.id, review_requester.username)
.and_return(review_requester.id)
end
end
it_behaves_like 'process review_requested & review_request_removed MR events'
context 'when user mapping is enabled' do
let_it_be(:source_user_requester) do
create(
:import_source_user,
placeholder_user_id: review_requester.id,
source_user_identifier: review_requester.id,
source_username: review_requester.username,
source_hostname: project.import_url,
namespace_id: project.root_ancestor.id
)
end
let_it_be(:source_user_requested) do
create(
:import_source_user,
placeholder_user_id: requested_reviewer.id,
source_user_identifier: requested_reviewer.id,
source_username: requested_reviewer.username,
source_hostname: project.import_url,
namespace_id: project.root_ancestor.id
)
end
let_it_be(:merge_request_reviewer) do
create(
:merge_request_reviewer,
user_id: requested_reviewer.id,
merge_request_id: issuable.id
)
end
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: true })
end
it_behaves_like 'process review_requested & review_request_removed MR events'
it_behaves_like 'push placeholder reference'
end
context 'when user mapping is disabled' do
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: false })
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(requested_reviewer.id, requested_reviewer.username)
.and_return(requested_reviewer.id)
allow(finder).to receive(:find).with(review_requester.id, review_requester.username)
.and_return(review_requester.id)
end
end
it_behaves_like 'process review_requested & review_request_removed MR events'
it_behaves_like 'do not push placeholder reference'
end
end
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Closed, feature_category: :importers do
subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project) { create(:project, :repository, :with_import_url) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
@ -42,9 +42,6 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Closed, feature_category:
allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
allow(finder).to receive(:database_id).and_return(issuable.id)
end
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
end
shared_examples 'new event' do
@ -74,32 +71,124 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Closed, feature_category:
end
end
context 'with Issue' do
let(:expected_state_event_attrs) do
{
user_id: user.id,
issue_id: issuable.id,
state: 'closed',
created_at: issue_event.created_at,
imported_from: 'github'
}.stringify_keys
end
shared_examples 'push placeholder references' do
it 'pushes the references' do
expect(subject)
.to receive(:push_with_record)
.with(
an_instance_of(Event),
:author_id,
user.id,
an_instance_of(Gitlab::Import::SourceUserMapper)
)
it_behaves_like 'new event'
expect(subject)
.to receive(:push_with_record)
.with(
an_instance_of(ResourceStateEvent),
:user_id,
user.id,
an_instance_of(Gitlab::Import::SourceUserMapper)
)
importer.execute(issue_event)
end
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
let(:expected_state_event_attrs) do
{
user_id: user.id,
merge_request_id: issuable.id,
state: 'closed',
created_at: issue_event.created_at,
imported_from: 'github'
}.stringify_keys
shared_examples 'do not push placeholder references' do
it 'does not push any references' do
expect(subject)
.not_to receive(:push_with_record)
importer.execute(issue_event)
end
end
context 'when user mapping is enabled' do
let_it_be(:source_user) do
create(
:import_source_user,
placeholder_user_id: user.id,
source_user_identifier: user.id,
source_username: user.username,
source_hostname: project.import_url,
namespace_id: project.root_ancestor.id
)
end
it_behaves_like 'new event'
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: true })
end
context 'with Issue' do
let(:expected_state_event_attrs) do
{
user_id: user.id,
issue_id: issuable.id,
state: 'closed',
created_at: issue_event.created_at,
imported_from: 'github'
}.stringify_keys
end
it_behaves_like 'new event'
it_behaves_like 'push placeholder references'
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
let(:expected_state_event_attrs) do
{
user_id: user.id,
merge_request_id: issuable.id,
state: 'closed',
created_at: issue_event.created_at,
imported_from: 'github'
}.stringify_keys
end
it_behaves_like 'new event'
it_behaves_like 'push placeholder references'
end
end
context 'when user mapping is disabled' do
before do
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: false })
end
context 'with Issue' do
let(:expected_state_event_attrs) do
{
user_id: user.id,
issue_id: issuable.id,
state: 'closed',
created_at: issue_event.created_at,
imported_from: 'github'
}.stringify_keys
end
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder references'
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
let(:expected_state_event_attrs) do
{
user_id: user.id,
merge_request_id: issuable.id,
state: 'closed',
created_at: issue_event.created_at,
imported_from: 'github'
}.stringify_keys
end
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder references'
end
end
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_gitlab_redis_shared_state, feature_category: :importers do
subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project) { create(:project, :repository, :with_import_url) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
@ -56,9 +56,6 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
allow(finder).to receive(:database_id).and_return(referenced_in.iid)
allow(finder).to receive(:database_id).and_return(issuable.id)
end
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
end
it 'creates expected note' do
@ -92,9 +89,6 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
allow(finder).to receive(:database_id).and_return(referenced_in.iid)
allow(finder).to receive(:database_id).and_return(issuable.id)
end
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
end
it 'creates expected note' do
@ -121,13 +115,84 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
end
end
context 'with Issue' do
it_behaves_like 'import cross-referenced event'
shared_examples 'push a placeholder reference' do
before do
allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
allow(finder).to receive(:database_id).and_return(referenced_in.iid)
allow(finder).to receive(:database_id).and_return(issuable.id)
end
end
it 'pushes the reference' do
expect(subject)
.to receive(:push_with_record)
.with(
an_instance_of(Note),
:author_id,
issue_event[:actor].id,
an_instance_of(Gitlab::Import::SourceUserMapper)
)
importer.execute(issue_event)
end
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
shared_examples 'do not push placeholder reference' do
it 'does not push any reference' do
expect(subject)
.not_to receive(:push_with_record)
it_behaves_like 'import cross-referenced event'
importer.execute(issue_event)
end
end
context 'when user_mapping_is enabled' do
let_it_be(:source_user) do
create(
:import_source_user,
placeholder_user_id: user.id,
source_user_identifier: user.id,
source_username: user.username,
source_hostname: project.import_url,
namespace_id: project.root_ancestor.id
)
end
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: true })
end
context 'with Issue' do
it_behaves_like 'import cross-referenced event'
it_behaves_like 'push a placeholder reference'
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
it_behaves_like 'import cross-referenced event'
it_behaves_like 'push a placeholder reference'
end
end
context 'when user_mapping_is disabled' do
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: false })
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
end
context 'with Issue' do
it_behaves_like 'import cross-referenced event'
it_behaves_like 'do not push placeholder reference'
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
it_behaves_like 'import cross-referenced event'
it_behaves_like 'do not push placeholder reference'
end
end
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Merged, feature_category: :importers do
subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project) { create(:project, :repository, :with_import_url) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
@ -29,56 +29,164 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Merged, feature_category:
allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
allow(finder).to receive(:database_id).and_return(merge_request.id)
end
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
shared_examples 'push placeholder references' do
it 'pushes the references' do
expect(subject)
.to receive(:push_with_record)
.with(
an_instance_of(Event),
:author_id,
user.id,
an_instance_of(Gitlab::Import::SourceUserMapper)
)
expect(subject)
.to receive(:push_with_record)
.with(
an_instance_of(ResourceStateEvent),
:user_id,
user.id,
an_instance_of(Gitlab::Import::SourceUserMapper)
)
importer.execute(issue_event)
end
end
it 'creates expected event and state event' do
importer.execute(issue_event)
shared_examples 'do not push placeholder references' do
it 'does not push references' do
expect(subject)
.not_to receive(:push_with_record)
expect(merge_request.events.count).to eq 1
expect(merge_request.events.first).to have_attributes(
project_id: project.id,
author_id: user.id,
target_id: merge_request.id,
target_type: merge_request.class.name,
action: 'merged',
created_at: issue_event.created_at,
updated_at: issue_event.created_at,
imported_from: 'github'
)
expect(merge_request.resource_state_events.count).to eq 1
expect(merge_request.resource_state_events.first).to have_attributes(
user_id: user.id,
merge_request_id: merge_request.id,
state: 'merged',
created_at: issue_event.created_at,
close_after_error_tracking_resolve: false,
close_auto_resolve_prometheus_alert: false
)
importer.execute(issue_event)
end
end
it 'creates a merged by note' do
expect { importer.execute(issue_event) }.to change { Note.count }.by(1)
context 'when user mapping is enabled' do
let_it_be(:source_user) do
create(
:import_source_user,
placeholder_user_id: user.id,
source_user_identifier: user.id,
source_username: user.username,
source_hostname: project.import_url,
namespace_id: project.root_ancestor.id
)
end
last_note = merge_request.notes.last
expect(last_note.created_at).to eq(issue_event.created_at)
expect(last_note.author).to eq(project.owner)
expect(last_note.note).to eq("*Merged by: #{user.username} at #{issue_event.created_at}*")
end
context 'when commit ID is present' do
let!(:commit) { create(:commit, project: project) }
let(:commit_id) { commit.id }
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: true })
end
it 'creates expected event and state event' do
importer.execute(issue_event)
expect(merge_request.events.count).to eq 1
state_event = merge_request.resource_state_events.last
expect(state_event.source_commit).to eq commit_id[0..40]
expect(merge_request.events.first).to have_attributes(
project_id: project.id,
author_id: user.id,
target_id: merge_request.id,
target_type: merge_request.class.name,
action: 'merged',
created_at: issue_event.created_at,
updated_at: issue_event.created_at,
imported_from: 'github'
)
expect(merge_request.resource_state_events.count).to eq 1
expect(merge_request.resource_state_events.first).to have_attributes(
user_id: user.id,
merge_request_id: merge_request.id,
state: 'merged',
created_at: issue_event.created_at,
close_after_error_tracking_resolve: false,
close_auto_resolve_prometheus_alert: false
)
end
it 'creates a merged by note' do
expect { importer.execute(issue_event) }.to change { Note.count }.by(1)
last_note = merge_request.notes.last
expect(last_note.created_at).to eq(issue_event.created_at)
expect(last_note.author).to eq(merge_request.author)
expect(last_note.note).to eq("*Merged by: #{user.username} at #{issue_event.created_at}*")
end
context 'when commit ID is present' do
let!(:commit) { create(:commit, project: project) }
let(:commit_id) { commit.id }
it 'creates expected event and state event' do
importer.execute(issue_event)
expect(merge_request.events.count).to eq 1
state_event = merge_request.resource_state_events.last
expect(state_event.source_commit).to eq commit_id[0..40]
end
end
it_behaves_like 'push placeholder references'
end
context 'when user mapping is disabled' do
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: false })
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
end
it 'creates expected event and state event' do
importer.execute(issue_event)
expect(merge_request.events.count).to eq 1
expect(merge_request.events.first).to have_attributes(
project_id: project.id,
author_id: user.id,
target_id: merge_request.id,
target_type: merge_request.class.name,
action: 'merged',
created_at: issue_event.created_at,
updated_at: issue_event.created_at,
imported_from: 'github'
)
expect(merge_request.resource_state_events.count).to eq 1
expect(merge_request.resource_state_events.first).to have_attributes(
user_id: user.id,
merge_request_id: merge_request.id,
state: 'merged',
created_at: issue_event.created_at,
close_after_error_tracking_resolve: false,
close_auto_resolve_prometheus_alert: false
)
end
it 'creates a merged by note' do
expect { importer.execute(issue_event) }.to change { Note.count }.by(1)
last_note = merge_request.notes.last
expect(last_note.created_at).to eq(issue_event.created_at)
expect(last_note.author).to eq(merge_request.author)
expect(last_note.note).to eq("*Merged by: #{user.username} at #{issue_event.created_at}*")
end
context 'when commit ID is present' do
let!(:commit) { create(:commit, project: project) }
let(:commit_id) { commit.id }
it 'creates expected event and state event' do
importer.execute(issue_event)
expect(merge_request.events.count).to eq 1
state_event = merge_request.resource_state_events.last
expect(state_event.source_commit).to eq commit_id[0..40]
end
end
it_behaves_like 'do not push placeholder references'
end
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed, feature_category: :importers do
subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project) { create(:project, :repository, :with_import_url) }
let_it_be(:user) { create(:user) }
let(:issuable) { create(:issue, project: project) }
@ -51,9 +51,6 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed, feature_category
allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
allow(finder).to receive(:database_id).and_return(issuable.id)
end
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
end
shared_examples 'import renamed event' do
@ -78,14 +75,78 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed, feature_category
end
end
context 'with Issue' do
it_behaves_like 'import renamed event'
shared_examples 'push a placeholder reference' do
it 'pushes the reference' do
expect(subject)
.to receive(:push_with_record)
.with(
an_instance_of(Note),
:author_id,
issue_event[:actor].id,
an_instance_of(Gitlab::Import::SourceUserMapper)
)
importer.execute(issue_event)
end
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
shared_examples 'do not push placeholder reference' do
it 'does not push any reference' do
expect(subject)
.not_to receive(:push_with_record)
it_behaves_like 'import renamed event'
importer.execute(issue_event)
end
end
context 'when user mapping is enabled' do
let_it_be(:source_user) do
create(
:import_source_user,
placeholder_user_id: user.id,
source_user_identifier: user.id,
source_username: user.username,
source_hostname: project.import_url,
namespace_id: project.root_ancestor.id
)
end
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: true })
end
context 'with Issue' do
it_behaves_like 'import renamed event'
it_behaves_like 'push a placeholder reference'
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
it_behaves_like 'import renamed event'
it_behaves_like 'push a placeholder reference'
end
end
context 'when user mapping is disabled' do
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: false })
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
end
context 'with Issue' do
it_behaves_like 'import renamed event'
it_behaves_like 'do not push placeholder reference'
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
it_behaves_like 'import renamed event'
it_behaves_like 'do not push placeholder reference'
end
end
end
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_failures, feature_category: :importers do
subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project) { create(:project, :repository, :with_import_url) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
@ -59,32 +59,124 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_fail
end
end
context 'with Issue' do
let(:expected_state_event_attrs) do
{
user_id: user.id,
issue_id: issuable.id,
state: 'reopened',
created_at: issue_event.created_at,
imported_from: 'github'
}.stringify_keys
end
shared_examples 'push placeholder references' do
it 'pushes the references' do
expect(subject)
.to receive(:push_with_record)
.with(
an_instance_of(Event),
:author_id,
issue_event[:actor].id,
an_instance_of(Gitlab::Import::SourceUserMapper)
)
it_behaves_like 'new event'
expect(subject)
.to receive(:push_with_record)
.with(
an_instance_of(ResourceStateEvent),
:user_id,
issue_event[:actor].id,
an_instance_of(Gitlab::Import::SourceUserMapper)
)
importer.execute(issue_event)
end
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
let(:expected_state_event_attrs) do
{
user_id: user.id,
merge_request_id: issuable.id,
state: 'reopened',
created_at: issue_event.created_at,
imported_from: 'github'
}.stringify_keys
shared_examples 'do not push placeholder references' do
it 'does not push any reference' do
expect(subject)
.not_to receive(:push_with_record)
importer.execute(issue_event)
end
end
context 'when user mapping is enabled' do
let_it_be(:source_user) do
create(
:import_source_user,
placeholder_user_id: user.id,
source_user_identifier: user.id,
source_username: user.username,
source_hostname: project.import_url,
namespace_id: project.root_ancestor.id
)
end
it_behaves_like 'new event'
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: true })
end
context 'with Issue' do
let(:expected_state_event_attrs) do
{
user_id: user.id,
issue_id: issuable.id,
state: 'reopened',
created_at: issue_event.created_at,
imported_from: 'github'
}.stringify_keys
end
it_behaves_like 'new event'
it_behaves_like 'push placeholder references'
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
let(:expected_state_event_attrs) do
{
user_id: user.id,
merge_request_id: issuable.id,
state: 'reopened',
created_at: issue_event.created_at,
imported_from: 'github'
}.stringify_keys
end
it_behaves_like 'new event'
it_behaves_like 'push placeholder references'
end
end
context 'when user mapping is disabled' do
before do
project.build_or_assign_import_data(data: { user_contribution_mapping_enabled: false })
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
end
context 'with Issue' do
let(:expected_state_event_attrs) do
{
user_id: user.id,
issue_id: issuable.id,
state: 'reopened',
created_at: issue_event.created_at,
imported_from: 'github'
}.stringify_keys
end
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder references'
end
context 'with MergeRequest' do
let(:issuable) { create(:merge_request, source_project: project, target_project: project) }
let(:expected_state_event_attrs) do
{
user_id: user.id,
merge_request_id: issuable.id,
state: 'reopened',
created_at: issue_event.created_at,
imported_from: 'github'
}.stringify_keys
end
it_behaves_like 'new event'
it_behaves_like 'do not push placeholder references'
end
end
end

View File

@ -37,9 +37,9 @@ RSpec.describe Server, feature_category: :service_ping do
se_category: 'InternalEventTracking',
se_action: 'g_project_management_issue_created',
collector_tstamp: '1727475117074',
label: nil,
property: nil,
value: nil,
se_label: nil,
se_property: nil,
se_value: nil,
contexts: Gitlab::Json.parse(context)
},
rawEvent: { parameters: Rack::Utils.parse_query(query_params) }
@ -62,9 +62,9 @@ RSpec.describe Server, feature_category: :service_ping do
se_category: 'category',
se_action: 'super_action_thing',
collector_tstamp: '1727476712646',
label: nil,
property: nil,
value: nil,
se_label: nil,
se_property: nil,
se_value: nil,
contexts: nil
},
rawEvent: { parameters: Rack::Utils.parse_query(query_params) }
@ -90,9 +90,9 @@ RSpec.describe Server, feature_category: :service_ping do
se_category: 'projects:blob:show',
se_action: 'click_blame_control_on_blob_page',
collector_tstamp: '1727474524024',
label: nil,
property: nil,
value: nil,
se_label: nil,
se_property: nil,
se_value: nil,
contexts: Gitlab::Json.parse(context)
},
rawEvent: { parameters: Gitlab::Json.parse(body)['data'].first }
@ -116,9 +116,9 @@ RSpec.describe Server, feature_category: :service_ping do
se_category: 'admin:dashboard:index',
se_action: 'view_admin_dashboard_pageload',
collector_tstamp: '1727473513835',
label: nil,
property: nil,
value: nil,
se_label: nil,
se_property: nil,
se_value: nil,
contexts: Gitlab::Json.parse(context_1)
},
rawEvent: { parameters: Gitlab::Json.parse(body)['data'].first }
@ -128,9 +128,9 @@ RSpec.describe Server, feature_category: :service_ping do
se_category: 'admin:dashboard:index',
se_action: 'render',
collector_tstamp: '1727473513837',
label: 'version_badge',
property: 'Up to date',
value: nil,
se_label: 'version_badge',
se_property: 'Up to date',
se_value: nil,
contexts: Gitlab::Json.parse(context_2)
},
rawEvent: { parameters: Gitlab::Json.parse(body)['data'].last }
@ -153,9 +153,9 @@ RSpec.describe Server, feature_category: :service_ping do
se_category: 'admin:dashboard:index',
se_action: 'render',
collector_tstamp: '1727473512782',
label: 'version_badge',
property: 'Up to date',
value: nil,
se_label: 'version_badge',
se_property: 'Up to date',
se_value: nil,
contexts: Gitlab::Json.parse(context)
},
rawEvent: { parameters: Gitlab::Json.parse(body)['data'].first }
@ -213,9 +213,9 @@ RSpec.describe Server, feature_category: :service_ping do
se_category: 'category',
se_action: 'super_action_thing',
collector_tstamp: '1727476712646',
label: nil,
property: nil,
value: nil,
se_label: nil,
se_property: nil,
se_value: nil,
contexts: nil
},
rawEvent: { parameters: Rack::Utils.parse_query(query_params) }

View File

@ -13,7 +13,8 @@ RSpec.describe 'keep-around tasks', :silence_stdout, feature_category: :source_c
describe 'orphaned' do
subject { run_rake_task('gitlab:keep_around:orphaned') }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:original_project) { create(:project, :repository) }
let_it_be(:project) { fork_project(original_project, nil, repository: true) }
let_it_be(:keep_around_shas) do
# Keep-around references only on branch tips is not necessarily accurate,
# but this constant gives convenient access to commit IDs that actually
@ -123,8 +124,41 @@ RSpec.describe 'keep-around tasks', :silence_stdout, feature_category: :source_c
end
context "for merge request keep-arounds" do
let_it_be(:fork) { fork_project(project, nil, repository: true) }
let_it_be(:merge_request) { create(:merge_request, target_project: project, source_project: fork) }
let_it_be(:merge_request) do
create(:merge_request, :skip_diff_creation,
target_project: project,
source_project: project,
merge_commit_sha: TestEnv::BRANCH_SHA['changes-with-only-whitespace'])
end
it_behaves_like 'orphans found',
keep_around_count: 3,
orphan_count: 2
end
context "for fork merge request keep-arounds" do
let_it_be(:merge_request) do
create(:merge_request, :skip_diff_creation,
target_project: original_project,
source_project: project,
merge_commit_sha: TestEnv::BRANCH_SHA['changes-with-only-whitespace'])
end
it_behaves_like 'orphans found',
keep_around_count: 3,
orphan_count: 3
end
context "for merge request diff keep-arounds" do
let_it_be(:merge_request) { create(:merge_request, target_project: project, source_project: project) }
it_behaves_like 'orphans found',
keep_around_count: 3,
orphan_count: 2
end
context "for fork merge request diff keep-arounds" do
let_it_be(:merge_request) { create(:merge_request, target_project: original_project, source_project: project) }
it_behaves_like 'orphans found',
keep_around_count: 3,