Add latest changes from gitlab-org/gitlab@master
|
@ -91,20 +91,15 @@ export default {
|
|||
data-testid="dropzone-area"
|
||||
>
|
||||
<gl-icon name="upload" :size="24" :class="hasDesigns ? 'gl-mb-2' : 'gl-mr-4'" />
|
||||
<gl-sprintf
|
||||
:message="
|
||||
__(
|
||||
'%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}',
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #content="{ content }">
|
||||
<span class="gl-font-weight-bold">{{ content }} </span>
|
||||
</template>
|
||||
<template #link="{ content }">
|
||||
<gl-link @click.stop="openFileUpload">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<p class="gl-font-weight-bold gl-mb-0">
|
||||
<gl-sprintf :message="__('Drop or %{linkStart}upload%{linkEnd} Designs to attach')">
|
||||
<template #link="{ content }">
|
||||
<gl-link class="gl-font-weight-normal" @click.stop="openFileUpload">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
|
|
@ -267,7 +267,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div data-testid="designs-root" class="gl-mt-2">
|
||||
<div data-testid="designs-root" class="gl-mt-5">
|
||||
<header v-if="showToolbar" class="row-content-block border-top-0 p-2 d-flex">
|
||||
<div class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-w-full">
|
||||
<div>
|
||||
|
|
|
@ -7,6 +7,7 @@ export default [
|
|||
name: DESIGNS_ROUTE_NAME,
|
||||
path: '/',
|
||||
component: Home,
|
||||
alias: '/designs',
|
||||
},
|
||||
{
|
||||
name: DESIGN_ROUTE_NAME,
|
||||
|
@ -16,7 +17,7 @@ export default [
|
|||
{
|
||||
params: { id },
|
||||
},
|
||||
from,
|
||||
_,
|
||||
next,
|
||||
) {
|
||||
if (typeof id === 'string') {
|
||||
|
|
|
@ -3,6 +3,7 @@ import { GlAlert, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
|
|||
import { last } from 'lodash';
|
||||
import { __ } from '~/locale';
|
||||
import getJiraImportDetailsQuery from '../queries/get_jira_import_details.query.graphql';
|
||||
import getJiraUserMappingMutation from '../queries/get_jira_user_mapping.mutation.graphql';
|
||||
import initiateJiraImportMutation from '../queries/initiate_jira_import.mutation.graphql';
|
||||
import { addInProgressImportToStore } from '../utils/cache_update';
|
||||
import { isInProgress, extractJiraProjectsOptions } from '../utils/jira_import_utils';
|
||||
|
@ -37,6 +38,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -48,10 +53,12 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
isSubmitting: false,
|
||||
jiraImportDetails: {},
|
||||
selectedProject: undefined,
|
||||
userMappings: [],
|
||||
errorMessage: '',
|
||||
showAlert: false,
|
||||
selectedProject: undefined,
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
|
@ -89,16 +96,43 @@ export default {
|
|||
: 'jira-import::KEY-1';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.isJiraConfigured) {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: getJiraUserMappingMutation,
|
||||
variables: {
|
||||
input: {
|
||||
projectPath: this.projectPath,
|
||||
startAt: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.jiraImportUsers.errors.length) {
|
||||
this.setAlertMessage(data.jiraImportUsers.errors.join('. '));
|
||||
} else {
|
||||
this.userMappings = data.jiraImportUsers.jiraUsers;
|
||||
}
|
||||
})
|
||||
.catch(() => this.setAlertMessage(__('There was an error retrieving the Jira users.')));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initiateJiraImport(project) {
|
||||
this.isSubmitting = true;
|
||||
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: initiateJiraImportMutation,
|
||||
variables: {
|
||||
input: {
|
||||
projectPath: this.projectPath,
|
||||
jiraProjectKey: project,
|
||||
usersMapping: [],
|
||||
projectPath: this.projectPath,
|
||||
usersMapping: this.userMappings.map(({ gitlabId, jiraAccountId }) => ({
|
||||
gitlabId,
|
||||
jiraAccountId,
|
||||
})),
|
||||
},
|
||||
},
|
||||
update: (store, { data }) =>
|
||||
|
@ -111,7 +145,21 @@ export default {
|
|||
this.selectedProject = undefined;
|
||||
}
|
||||
})
|
||||
.catch(() => this.setAlertMessage(__('There was an error importing the Jira project.')));
|
||||
.catch(() => this.setAlertMessage(__('There was an error importing the Jira project.')))
|
||||
.finally(() => {
|
||||
this.isSubmitting = false;
|
||||
});
|
||||
},
|
||||
updateMapping(jiraAccountId, gitlabId, gitlabUsername) {
|
||||
this.userMappings = this.userMappings.map(userMapping =>
|
||||
userMapping.jiraAccountId === jiraAccountId
|
||||
? {
|
||||
...userMapping,
|
||||
gitlabId,
|
||||
gitlabUsername,
|
||||
}
|
||||
: userMapping,
|
||||
);
|
||||
},
|
||||
setAlertMessage(message) {
|
||||
this.errorMessage = message;
|
||||
|
@ -156,9 +204,13 @@ export default {
|
|||
v-else
|
||||
v-model="selectedProject"
|
||||
:import-label="importLabel"
|
||||
:is-submitting="isSubmitting"
|
||||
:issues-path="issuesPath"
|
||||
:jira-projects="jiraImportDetails.projects"
|
||||
:project-id="projectId"
|
||||
:user-mappings="userMappings"
|
||||
@initiateJiraImport="initiateJiraImport"
|
||||
@updateMapping="updateMapping"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,22 +1,61 @@
|
|||
<script>
|
||||
import { GlAvatar, GlButton, GlFormGroup, GlFormSelect, GlLabel } from '@gitlab/ui';
|
||||
import {
|
||||
GlButton,
|
||||
GlNewDropdown,
|
||||
GlNewDropdownItem,
|
||||
GlNewDropdownText,
|
||||
GlFormGroup,
|
||||
GlFormSelect,
|
||||
GlIcon,
|
||||
GlLabel,
|
||||
GlLoadingIcon,
|
||||
GlSearchBoxByType,
|
||||
GlTable,
|
||||
} from '@gitlab/ui';
|
||||
import { debounce } from 'lodash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
name: 'JiraImportForm',
|
||||
components: {
|
||||
GlAvatar,
|
||||
GlButton,
|
||||
GlNewDropdown,
|
||||
GlNewDropdownItem,
|
||||
GlNewDropdownText,
|
||||
GlFormGroup,
|
||||
GlFormSelect,
|
||||
GlIcon,
|
||||
GlLabel,
|
||||
GlLoadingIcon,
|
||||
GlSearchBoxByType,
|
||||
GlTable,
|
||||
},
|
||||
currentUserAvatarUrl: gon.current_user_avatar_url,
|
||||
currentUsername: gon.current_username,
|
||||
dropdownLabel: __('The GitLab user to which the Jira user %{jiraDisplayName} will be mapped'),
|
||||
tableConfig: [
|
||||
{
|
||||
key: 'jiraDisplayName',
|
||||
label: __('Jira display name'),
|
||||
},
|
||||
{
|
||||
key: 'arrow',
|
||||
label: '',
|
||||
},
|
||||
{
|
||||
key: 'gitlabUsername',
|
||||
label: __('GitLab username'),
|
||||
},
|
||||
],
|
||||
props: {
|
||||
importLabel: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isSubmitting: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
issuesPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -25,6 +64,14 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
projectId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
userMappings: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -33,10 +80,53 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
isFetching: false,
|
||||
searchTerm: '',
|
||||
selectState: null,
|
||||
users: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
shouldShowNoMatchesFoundText() {
|
||||
return !this.isFetching && this.users.length === 0;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
searchTerm: debounce(function debouncedUserSearch() {
|
||||
this.searchUsers();
|
||||
}, 500),
|
||||
},
|
||||
mounted() {
|
||||
this.searchUsers()
|
||||
.then(data => {
|
||||
this.initialUsers = data;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
methods: {
|
||||
searchUsers() {
|
||||
const params = {
|
||||
active: true,
|
||||
project_id: this.projectId,
|
||||
search: this.searchTerm,
|
||||
};
|
||||
|
||||
this.isFetching = true;
|
||||
|
||||
return axios
|
||||
.get('/-/autocomplete/users.json', { params })
|
||||
.then(({ data }) => {
|
||||
this.users = data;
|
||||
return data;
|
||||
})
|
||||
.finally(() => {
|
||||
this.isFetching = false;
|
||||
});
|
||||
},
|
||||
resetDropdown() {
|
||||
this.searchTerm = '';
|
||||
this.users = this.initialUsers;
|
||||
},
|
||||
initiateJiraImport(event) {
|
||||
event.preventDefault();
|
||||
if (this.value) {
|
||||
|
@ -80,7 +170,7 @@ export default {
|
|||
</gl-form-group>
|
||||
|
||||
<gl-form-group
|
||||
class="row align-items-center"
|
||||
class="row gl-align-items-center gl-mb-6"
|
||||
:label="__('Issue label')"
|
||||
label-cols-sm="2"
|
||||
label-for="jira-project-label"
|
||||
|
@ -94,46 +184,54 @@ export default {
|
|||
/>
|
||||
</gl-form-group>
|
||||
|
||||
<hr />
|
||||
<h4 class="gl-mb-4">{{ __('Jira-GitLab user mapping template') }}</h4>
|
||||
|
||||
<p class="offset-md-1">
|
||||
<p>
|
||||
{{
|
||||
__(
|
||||
"For each Jira issue successfully imported, we'll create a new GitLab issue with the following data:",
|
||||
`Jira users have been matched with similar GitLab users.
|
||||
This can be overwritten by selecting a GitLab user from the dropdown in the "GitLab
|
||||
username" column.
|
||||
If it wasn't possible to match a Jira user with a GitLab user, the dropdown defaults to
|
||||
the user conducting the import.`,
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
|
||||
<gl-form-group
|
||||
class="row align-items-center mb-1"
|
||||
:label="__('Title')"
|
||||
label-cols-sm="2"
|
||||
label-for="jira-project-title"
|
||||
>
|
||||
<p id="jira-project-title" class="mb-2">{{ __('jira.issue.summary') }}</p>
|
||||
</gl-form-group>
|
||||
<gl-form-group
|
||||
class="row align-items-center mb-1"
|
||||
:label="__('Reporter')"
|
||||
label-cols-sm="2"
|
||||
label-for="jira-project-reporter"
|
||||
>
|
||||
<gl-avatar
|
||||
id="jira-project-reporter"
|
||||
class="mb-2"
|
||||
:src="$options.currentUserAvatarUrl"
|
||||
:size="24"
|
||||
:aria-label="$options.currentUsername"
|
||||
/>
|
||||
</gl-form-group>
|
||||
<gl-form-group
|
||||
class="row align-items-center mb-1"
|
||||
:label="__('Description')"
|
||||
label-cols-sm="2"
|
||||
label-for="jira-project-description"
|
||||
>
|
||||
<p id="jira-project-description" class="mb-2">{{ __('jira.issue.description.content') }}</p>
|
||||
</gl-form-group>
|
||||
<gl-table :fields="$options.tableConfig" :items="userMappings" fixed>
|
||||
<template #cell(arrow)>
|
||||
<gl-icon name="arrow-right" :aria-label="__('Will be mapped to')" />
|
||||
</template>
|
||||
<template #cell(gitlabUsername)="data">
|
||||
<gl-new-dropdown
|
||||
:text="data.value || $options.currentUsername"
|
||||
class="w-100"
|
||||
:aria-label="
|
||||
sprintf($options.dropdownLabel, { jiraDisplayName: data.item.jiraDisplayName })
|
||||
"
|
||||
@hide="resetDropdown"
|
||||
>
|
||||
<gl-search-box-by-type v-model.trim="searchTerm" class="m-2" />
|
||||
|
||||
<div v-if="isFetching" class="gl-text-center">
|
||||
<gl-loading-icon />
|
||||
</div>
|
||||
|
||||
<gl-new-dropdown-item
|
||||
v-for="user in users"
|
||||
v-else
|
||||
:key="user.id"
|
||||
@click="$emit('updateMapping', data.item.jiraAccountId, user.id, user.username)"
|
||||
>
|
||||
{{ user.username }} ({{ user.name }})
|
||||
</gl-new-dropdown-item>
|
||||
|
||||
<gl-new-dropdown-text v-show="shouldShowNoMatchesFoundText" class="text-secondary">
|
||||
{{ __('No matches found') }}
|
||||
</gl-new-dropdown-text>
|
||||
</gl-new-dropdown>
|
||||
</template>
|
||||
</gl-table>
|
||||
|
||||
<div class="footer-block row-content-block d-flex justify-content-between">
|
||||
<gl-button
|
||||
|
@ -141,9 +239,10 @@ export default {
|
|||
category="primary"
|
||||
variant="success"
|
||||
class="js-no-auto-disable"
|
||||
:loading="isSubmitting"
|
||||
data-qa-selector="jira_issues_import_button"
|
||||
>
|
||||
{{ __('Next') }}
|
||||
{{ __('Continue') }}
|
||||
</gl-button>
|
||||
<gl-button :href="issuesPath">{{ __('Cancel') }}</gl-button>
|
||||
</div>
|
||||
|
|
|
@ -28,6 +28,7 @@ export default function mountJiraImportApp() {
|
|||
isJiraConfigured: parseBoolean(el.dataset.isJiraConfigured),
|
||||
issuesPath: el.dataset.issuesPath,
|
||||
jiraIntegrationPath: el.dataset.jiraIntegrationPath,
|
||||
projectId: el.dataset.projectId,
|
||||
projectPath: el.dataset.projectPath,
|
||||
setupIllustration: el.dataset.setupIllustration,
|
||||
},
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
mutation($input: JiraImportUsersInput!) {
|
||||
jiraImportUsers(input: $input) {
|
||||
jiraUsers {
|
||||
jiraAccountId
|
||||
jiraDisplayName
|
||||
jiraEmail
|
||||
gitlabId
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
|
@ -202,7 +202,7 @@ export default {
|
|||
|
||||
<log-control-buttons
|
||||
ref="scrollButtons"
|
||||
class="flex-grow-0 pr-2 mb-2 controllers"
|
||||
class="flex-grow-0 pr-2 mb-2 controllers gl-display-inline-flex"
|
||||
:scroll-down-button-disabled="scrollDownButtonDisabled"
|
||||
@refresh="refreshPodLogs()"
|
||||
@scrollDown="scrollDown"
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
<script>
|
||||
import { GlDeprecatedButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
GlDeprecatedButton,
|
||||
GlButton,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
@ -51,14 +49,16 @@ export default {
|
|||
:title="__('Scroll to top')"
|
||||
aria-labelledby="scroll-to-top"
|
||||
>
|
||||
<gl-deprecated-button
|
||||
<gl-button
|
||||
id="scroll-to-top"
|
||||
class="btn-blank js-scroll-to-top"
|
||||
class="js-scroll-to-top gl-mr-2 btn-blank"
|
||||
:aria-label="__('Scroll to top')"
|
||||
:disabled="scrollUpButtonDisabled"
|
||||
icon="scroll_up"
|
||||
category="primary"
|
||||
variant="default"
|
||||
@click="handleScrollUp()"
|
||||
><icon name="scroll_up"
|
||||
/></gl-deprecated-button>
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="scrollDownAvailable"
|
||||
|
@ -68,25 +68,28 @@ export default {
|
|||
:title="__('Scroll to bottom')"
|
||||
aria-labelledby="scroll-to-bottom"
|
||||
>
|
||||
<gl-deprecated-button
|
||||
<gl-button
|
||||
id="scroll-to-bottom"
|
||||
class="btn-blank js-scroll-to-bottom"
|
||||
class="js-scroll-to-bottom gl-mr-2 btn-blank"
|
||||
:aria-label="__('Scroll to bottom')"
|
||||
:v-if="scrollDownAvailable"
|
||||
:disabled="scrollDownButtonDisabled"
|
||||
icon="scroll_down"
|
||||
category="primary"
|
||||
variant="default"
|
||||
@click="handleScrollDown()"
|
||||
><icon name="scroll_down"
|
||||
/></gl-deprecated-button>
|
||||
/>
|
||||
</div>
|
||||
<gl-deprecated-button
|
||||
<gl-button
|
||||
id="refresh-log"
|
||||
v-gl-tooltip
|
||||
class="ml-1 px-2 js-refresh-log"
|
||||
class="js-refresh-log"
|
||||
:title="__('Refresh')"
|
||||
:aria-label="__('Refresh')"
|
||||
icon="retry"
|
||||
category="primary"
|
||||
variant="default"
|
||||
@click="handleRefreshClick"
|
||||
>
|
||||
<icon name="retry" />
|
||||
</gl-deprecated-button>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -31,10 +31,6 @@
|
|||
width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
.controllers {
|
||||
@include build-controllers(16px, flex-end, false, 2, inline);
|
||||
}
|
||||
}
|
||||
|
||||
.log-lines,
|
||||
|
|
|
@ -440,3 +440,5 @@ class JiraService < IssueTrackerService
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
JiraService.prepend_if_ee('EE::JiraService')
|
||||
|
|
|
@ -16,6 +16,7 @@ class AuditEventService
|
|||
@author = build_author(author)
|
||||
@entity = entity
|
||||
@details = details
|
||||
@ip_address = (@details[:ip_address].presence || @author.current_sign_in_ip)
|
||||
end
|
||||
|
||||
# Builds the @details attribute for authentication
|
||||
|
@ -49,6 +50,8 @@ class AuditEventService
|
|||
|
||||
private
|
||||
|
||||
attr_reader :ip_address
|
||||
|
||||
def build_author(author)
|
||||
case author
|
||||
when User
|
||||
|
|
|
@ -119,6 +119,8 @@ class EventCreateService
|
|||
event.update_columns(updated_at: time_stamp, created_at: time_stamp)
|
||||
end
|
||||
|
||||
Gitlab::UsageDataCounters::TrackUniqueActions.track_action(event_action: action, event_target: wiki_page_meta.class, author_id: author.id)
|
||||
|
||||
event
|
||||
end
|
||||
|
||||
|
@ -163,7 +165,13 @@ class EventCreateService
|
|||
.merge(action: action, target_id: record.id, target_type: record.class.name)
|
||||
end
|
||||
|
||||
Event.insert_all(attribute_sets, returning: %w[id])
|
||||
result = Event.insert_all(attribute_sets, returning: %w[id])
|
||||
|
||||
pairs.each do |record, status|
|
||||
Gitlab::UsageDataCounters::TrackUniqueActions.track_action(event_action: status, event_target: record.class, author_id: current_user.id)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def create_push_event(service_class, project, current_user, push_data)
|
||||
|
@ -178,6 +186,8 @@ class EventCreateService
|
|||
new_event
|
||||
end
|
||||
|
||||
Gitlab::UsageDataCounters::TrackUniqueActions.track_action(event_action: :pushed, event_target: Project, author_id: current_user.id)
|
||||
|
||||
Users::LastPushEventService.new(current_user)
|
||||
.cache_last_push_event(event)
|
||||
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
jira_integration_path: edit_project_service_path(@project, :jira),
|
||||
is_jira_configured: @project.jira_service&.active? && @project.jira_service&.valid_connection?.to_s,
|
||||
in_progress_illustration: image_path('illustrations/export-import.svg'),
|
||||
project_id: @project.id,
|
||||
setup_illustration: image_path('illustrations/manual_action.svg') } }
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
- if @project.design_management_enabled?
|
||||
- if Feature.enabled?(:design_management_moved, @project)
|
||||
- if Feature.enabled?(:design_management_moved, @project, default_enabled: true)
|
||||
.js-design-management-new{ data: { project_path: @project.full_path, issue_iid: @issue.iid, issue_path: project_issue_path(@project, @issue) } }
|
||||
- else
|
||||
.js-design-management{ data: { project_path: @project.full_path, issue_iid: @issue.iid, issue_path: project_issue_path(@project, @issue) } }
|
||||
- else
|
||||
- if Feature.enabled?(:design_management_moved, @project)
|
||||
- if Feature.enabled?(:design_management_moved, @project, default_enabled: true)
|
||||
.row.empty-state.design-dropzone-border.gl-mt-5
|
||||
.text-content.center.gl-font-weight-bold
|
||||
- requirements_link_url = help_page_path('user/project/issues/design_management', anchor: 'requirements')
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
- if @issue.sentry_issue.present?
|
||||
#js-sentry-error-stack-trace{ data: error_details_data(@project, @issue.sentry_issue.sentry_issue_identifier) }
|
||||
|
||||
- if Feature.enabled?(:design_management_moved, @project)
|
||||
- if Feature.enabled?(:design_management_moved, @project, default_enabled: true)
|
||||
= render 'projects/issues/design_management'
|
||||
|
||||
= render_if_exists 'projects/issues/related_issues'
|
||||
|
@ -94,7 +94,7 @@
|
|||
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@issue), notes_filters: UserPreference.notes_filters.to_json } }
|
||||
= render 'new_branch' if show_new_branch_button?
|
||||
|
||||
- if Feature.enabled?(:design_management_moved, @project)
|
||||
- if Feature.enabled?(:design_management_moved, @project, default_enabled: true)
|
||||
= render 'projects/issues/discussion'
|
||||
- else
|
||||
= render 'projects/issues/tabs'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add confidential attribute to public API for notes creation
|
||||
merge_request: 36793
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add Jira Importer user mapping form
|
||||
merge_request: 33320
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Track the number of unique users who push, change wikis and change design managerment
|
||||
merge_request:
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Make the Design Collection more visible in the Issue UI
|
||||
merge_request: 36681
|
||||
author:
|
||||
type: changed
|
|
@ -360,7 +360,7 @@ production: &base
|
|||
# storage_path: shared/terraform_state
|
||||
object_store:
|
||||
enabled: false
|
||||
remote_directory: terraform_state # The bucket name
|
||||
remote_directory: terraform # The bucket name
|
||||
connection:
|
||||
provider: AWS
|
||||
aws_access_key_id: AWS_ACCESS_KEY_ID
|
||||
|
@ -1253,7 +1253,7 @@ test:
|
|||
storage_path: tmp/tests/terraform_state
|
||||
object_store:
|
||||
enabled: false
|
||||
remote_directory: terraform_state
|
||||
remote_directory: terraform
|
||||
connection:
|
||||
provider: AWS # Only AWS supported at the moment
|
||||
aws_access_key_id: AWS_ACCESS_KEY_ID
|
||||
|
|
|
@ -78,7 +78,7 @@ See [the available connection settings for different providers](object_storage.m
|
|||
```ruby
|
||||
gitlab_rails['terraform_state_enabled'] = true
|
||||
gitlab_rails['terraform_state_object_store_enabled'] = true
|
||||
gitlab_rails['terraform_state_object_store_remote_directory'] = "terraform_state"
|
||||
gitlab_rails['terraform_state_object_store_remote_directory'] = "terraform"
|
||||
gitlab_rails['terraform_state_object_store_connection'] = {
|
||||
'provider' => 'AWS',
|
||||
'region' => 'eu-central-1',
|
||||
|
@ -110,7 +110,7 @@ See [the available connection settings for different providers](object_storage.m
|
|||
enabled: true
|
||||
object_store:
|
||||
enabled: true
|
||||
remote_directory: "terraform_state" # The bucket name
|
||||
remote_directory: "terraform" # The bucket name
|
||||
connection:
|
||||
provider: AWS # Only AWS supported at the moment
|
||||
aws_access_key_id: AWS_ACESS_KEY_ID
|
||||
|
|
|
@ -116,6 +116,7 @@ Parameters:
|
|||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
|
||||
- `issue_iid` (required) - The IID of an issue
|
||||
- `body` (required) - The content of a note. Limited to 1,000,000 characters.
|
||||
- `confidential` (optional) - The confidential flag of a note. Default is false.
|
||||
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights)
|
||||
|
||||
```shell
|
||||
|
|
|
@ -525,6 +525,7 @@ appear to be associated to any of the services running, since they all appear to
|
|||
| `projects_jira_cloud_active` | `counts` | | | | |
|
||||
| `projects_jira_dvcs_cloud_active` | `counts` | | | | |
|
||||
| `projects_jira_dvcs_server_active` | `counts` | | | | |
|
||||
| `projects_jira_issuelist_active` | `counts` | `create` | | EE | Total Jira Issue feature enabled |
|
||||
| `labels` | `counts` | | | | |
|
||||
| `merge_requests` | `counts` | | | | |
|
||||
| `merge_requests_users` | `counts` | | | | |
|
||||
|
@ -658,6 +659,9 @@ appear to be associated to any of the services running, since they all appear to
|
|||
| `remote_mirrors` | `usage_activity_by_stage` | `create` | | CE+EE | |
|
||||
| `snippets` | `usage_activity_by_stage` | `create` | | CE+EE | |
|
||||
| `merge_requests_users` | `usage_activity_by_stage_monthly` | `create` | | CE+EE | Unique count of users who used a merge request |
|
||||
| `action_monthly_active_users_project_repo` | `usage_activity_by_stage_monthly` | `create` | | CE+EE | Unique count of users who pushed to a project repo |
|
||||
| `action_monthly_active_users_design_management` | `usage_activity_by_stage_monthly` | `create` | | CE+EE | Unique count of users who interacted with the design system management |
|
||||
| `action_monthly_active_users_wiki_repo` | `usage_activity_by_stage_monthly` | `create` | | CE+EE | Unique count of users who created or updated a wiki repo |
|
||||
| `projects_enforcing_code_owner_approval` | `usage_activity_by_stage` | `create` | | EE | |
|
||||
| `merge_requests_with_optional_codeowners` | `usage_activity_by_stage` | `create` | | EE | |
|
||||
| `merge_requests_with_required_codeowners` | `usage_activity_by_stage` | `create` | | EE | |
|
||||
|
|
|
@ -5,12 +5,16 @@ type: concepts
|
|||
# GitLab release and maintenance policy
|
||||
|
||||
GitLab has strict policies governing version naming, as well as release pace for major, minor,
|
||||
patch, and security releases. New releases are usually announced on the [GitLab blog](https://about.gitlab.com/releases/categories/releases/).
|
||||
patch, and security releases. New releases are announced on the [GitLab blog](https://about.gitlab.com/releases/categories/releases/).
|
||||
|
||||
Our current policy is:
|
||||
|
||||
- Backporting bug fixes for **only the current stable release** at any given time. (See [patch releases](#patch-releases).)
|
||||
- Backporting **to the previous two monthly releases in addition to the current stable release**. (See [security releases](#security-releases).)
|
||||
- Backporting security fixes **to the previous two monthly releases in addition to the current stable release**. (See [security releases](#security-releases).)
|
||||
|
||||
In rare cases, release managers may make an exception and backport to more than
|
||||
the last two monthly releases. See [Backporting to older
|
||||
releases](#backporting-to-older-releases) for more information.
|
||||
|
||||
## Versioning
|
||||
|
||||
|
@ -177,7 +181,7 @@ accessible.
|
|||
|
||||
### Backporting to older releases
|
||||
|
||||
Backporting to more than one stable release is reserved for [security releases](#security-releases).
|
||||
Backporting to more than one stable release is normally reserved for [security releases](#security-releases).
|
||||
In some cases, however, we may need to backport *a bug fix* to more than one stable
|
||||
release, depending on the severity of the bug.
|
||||
|
||||
|
@ -188,16 +192,13 @@ based on *all* of the following:
|
|||
|
||||
1. Estimated [severity](../development/contributing/issue_workflow.md#severity-labels) of the bug:
|
||||
Highest possible impact to users based on the current definition of severity.
|
||||
|
||||
1. Estimated [priority](../development/contributing/issue_workflow.md#priority-labels) of the bug:
|
||||
Immediate impact on all impacted users based on the above estimated severity.
|
||||
|
||||
1. Potentially incurring data loss and/or security breach.
|
||||
|
||||
1. Potentially affecting one or more strategic accounts due to a proven inability by the user to upgrade to the current stable version.
|
||||
|
||||
If *all* of the above are satisfied, the backport releases can be created for
|
||||
the current stable release, and two previous monthly releases.
|
||||
the current stable release, and two previous monthly releases. In rare cases a release manager may grant an exception to backport to more than two previous monthly releases.
|
||||
For instance, if we release `11.2.1` with a fix for a severe bug introduced in
|
||||
`11.0.0`, we could backport the fix to a new `11.0.x`, and `11.1.x` patch release.
|
||||
|
||||
|
|
|
@ -37,12 +37,12 @@ Merge request approval rules that can be set at an instance level are:
|
|||
|
||||
## Scope rules to compliance-labeled projects
|
||||
|
||||
> Introduced in [GitLab Premium](https://gitlab.com/groups/gitlab-org/-/epics/3432) 13.1.
|
||||
> Introduced in [GitLab Premium](https://gitlab.com/groups/gitlab-org/-/epics/3432) 13.2.
|
||||
|
||||
Merge request approval rules can be further scoped to specific compliance frameworks.
|
||||
|
||||
When the compliance framework label is selected and the project is assigned the compliance
|
||||
label, the instance-level MR approval settings will take effect and
|
||||
label, the instance-level MR approval settings will take effect and the
|
||||
[project-level settings](../project/merge_requests/merge_request_approvals.md#adding--editing-a-default-approval-rule)
|
||||
is locked for modification.
|
||||
|
||||
|
@ -53,18 +53,3 @@ Maintainer role and above can modify these.
|
|||
| Instance-level | Project-level |
|
||||
| -------------- | ------------- |
|
||||
| ![Scope MR approval settings to compliance frameworks](img/scope_mr_approval_settings_v13_1.png) | ![MR approval settings on compliance projects](img/mr_approval_settings_compliance_project_v13_1.png) |
|
||||
|
||||
### Enabling the feature
|
||||
|
||||
This feature comes with two feature flags which are disabled by default.
|
||||
|
||||
- The configuration in Admin area is controlled via `admin_compliance_merge_request_approval_settings`.
|
||||
- The application of these rules is controlled via `project_compliance_merge_request_approval_settings`.
|
||||
|
||||
These feature flags can be managed by feature flag [API endpoint](../../api/features.md#set-or-create-a-feature) or
|
||||
by [GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) with the following commands:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:admin_compliance_merge_request_approval_settings)
|
||||
Feature.enable(:project_compliance_merge_request_approval_settings)
|
||||
```
|
||||
|
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 71 KiB |
|
@ -510,6 +510,29 @@ license_scanning:
|
|||
GOFLAGS: '-insecure'
|
||||
```
|
||||
|
||||
#### Using private NuGet registries
|
||||
|
||||
If you have a private NuGet registry you can add it as a source
|
||||
by adding it to the [`packageSources`](https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file#package-source-sections)
|
||||
section of a [`nuget.config`](https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file) file.
|
||||
|
||||
For example:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="custom" value="https://nuget.example.com/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
#### Custom root certificates for NuGet
|
||||
|
||||
You can supply a custom root certificate to complete TLS verification by using the
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [environment variable](#available-variables).
|
||||
|
||||
### Migration from `license_management` to `license_scanning`
|
||||
|
||||
In GitLab 12.8 a new name for `license_management` job was introduced. This change was made to improve clarity around the purpose of the scan, which is to scan and collect the types of licenses present in a projects dependencies.
|
||||
|
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 510 KiB |
|
@ -40,6 +40,8 @@ Make sure you have the integration set up before trying to import Jira issues.
|
|||
|
||||
## Import Jira issues to GitLab
|
||||
|
||||
> New import form [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216145) in GitLab 13.2.
|
||||
|
||||
To import Jira issues to a GitLab project, follow the steps below.
|
||||
|
||||
NOTE: **Note:**
|
||||
|
@ -47,27 +49,34 @@ Importing Jira issues is done as an asynchronous background job, which
|
|||
may result in delays based on import queues load, system load, or other factors.
|
||||
Importing large projects may take several minutes depending on the size of the import.
|
||||
|
||||
1. On the **{issues}** **Issues** page, click the **Import Issues** (**{import}**) button.
|
||||
1. Select **Import from Jira**.
|
||||
This option is only visible if you have the [correct permissions](#permissions).
|
||||
1. On the **{issues}** **Issues** page, click **Import Issues** (**{import}**) **> Import from Jira**.
|
||||
|
||||
![Import issues from Jira button](img/jira/import_issues_from_jira_button_v12_10.png)
|
||||
|
||||
The **Import from Jira** option is only visible if you have the [correct permissions](#permissions).
|
||||
|
||||
The following form appears.
|
||||
If you've previously set up the [Jira integration](../integrations/jira.md), you can now see
|
||||
the Jira projects that you have access to in the dropdown.
|
||||
|
||||
![Import issues from Jira form](img/jira/import_issues_from_jira_form_v12_10.png)
|
||||
![Import issues from Jira form](img/jira/import_issues_from_jira_form_v13_2.png)
|
||||
|
||||
If you've previously set up the [Jira integration](../integrations/jira.md), you now see the Jira
|
||||
projects that you have access to in the dropdown.
|
||||
1. Click the **Import from** dropdown and select the Jira project that you wish to import issues from.
|
||||
|
||||
1. Select the Jira project that you wish to import issues from.
|
||||
In the **Jira-GitLab user mapping template** section, the table shows to which GitLab users your Jira
|
||||
users will be mapped.
|
||||
If it wasn't possible to match a Jira user with a GitLab user, the dropdown defaults to the user
|
||||
conducting the import.
|
||||
|
||||
![Import issues from Jira form](img/jira/import_issues_from_jira_projects_v12_10.png)
|
||||
1. To change any of the suggested mappings, click the dropdown in the **GitLab username** column and
|
||||
select the user you want to map to each Jira user.
|
||||
|
||||
The dropdown may not show all the users, so use the search bar to find a specific
|
||||
user in this GitLab project.
|
||||
|
||||
1. Click **Continue**. You're presented with a confirmation that import has started.
|
||||
|
||||
1. Click **Import Issues**. You're presented with a confirmation that import has started.
|
||||
While the import is running in the background, you can navigate away from the import status page
|
||||
to the issues page, and you'll see the new issues appearing in the issues list.
|
||||
|
||||
1. To check the status of your import, go back to the Jira import page.
|
||||
|
||||
![Import issues from Jira button](img/jira/import_issues_from_jira_button_v12_10.png)
|
||||
1. To check the status of your import, go to the Jira import page again.
|
||||
|
|
|
@ -60,20 +60,25 @@ and [PDFs](https://gitlab.com/gitlab-org/gitlab/-/issues/32811) is planned for a
|
|||
- Only the latest version of the designs can be deleted.
|
||||
- Deleted designs cannot be recovered but you can see them on previous designs versions.
|
||||
|
||||
## The Design Management page
|
||||
## The Design Management section
|
||||
|
||||
Navigate to the **Design Management** page from any issue by clicking the **Designs** tab:
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223193) in GitLab 13.2, Designs are displayed directly on the issue description rather than on a separate tab.
|
||||
|
||||
![Designs tab](img/design_management_v12_3.png)
|
||||
You can find to the **Design Management** section in the issue description:
|
||||
|
||||
![Designs section](img/design_management_v13_2.png)
|
||||
|
||||
## Adding designs
|
||||
|
||||
To upload design images, click the **Upload Designs** button and select images to upload.
|
||||
To upload Design images, drag files from your computer and drop them in the Design Management section,
|
||||
or click **upload** to select images from your file browser:
|
||||
|
||||
![Designs empty state](img/design_management_upload_v13.2.png)
|
||||
|
||||
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34353) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.9,
|
||||
you can drag and drop designs onto the dedicated drop zone to upload them.
|
||||
|
||||
![Drag and drop design uploads](img/design_drag_and_drop_uploads_v12_9.png)
|
||||
![Drag and drop design uploads](img/design_drag_and_drop_uploads_v13_2.png)
|
||||
|
||||
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202634)
|
||||
in GitLab 12.10, you can also copy images from your file system and
|
||||
|
|
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 994 KiB |
|
@ -68,6 +68,7 @@ module API
|
|||
params do
|
||||
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
|
||||
requires :body, type: String, desc: 'The content of a note'
|
||||
optional :confidential, type: Boolean, desc: 'Confidentiality note flag, default is false'
|
||||
optional :created_at, type: String, desc: 'The creation date of the note'
|
||||
end
|
||||
post ":id/#{noteables_str}/:noteable_id/notes" do
|
||||
|
@ -77,6 +78,7 @@ module API
|
|||
note: params[:body],
|
||||
noteable_type: noteables_str.classify,
|
||||
noteable_id: noteable.id,
|
||||
confidential: params[:confidential],
|
||||
created_at: params[:created_at]
|
||||
}
|
||||
|
||||
|
|
|
@ -490,7 +490,10 @@ module Gitlab
|
|||
remote_mirrors: distinct_count(::Project.with_remote_mirrors.where(time_period), :creator_id),
|
||||
snippets: distinct_count(::Snippet.where(time_period), :author_id)
|
||||
}.tap do |h|
|
||||
h[:merge_requests_users] = merge_requests_users(time_period) if time_period.present?
|
||||
if time_period.present?
|
||||
h[:merge_requests_users] = merge_requests_users(time_period)
|
||||
h.merge!(action_monthly_active_users(time_period))
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
@ -582,6 +585,42 @@ module Gitlab
|
|||
{ analytics_unique_visits: results }
|
||||
end
|
||||
|
||||
def action_monthly_active_users(time_period)
|
||||
return {} unless Feature.enabled?(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG)
|
||||
|
||||
counter = Gitlab::UsageDataCounters::TrackUniqueActions
|
||||
|
||||
project_count = redis_usage_data do
|
||||
counter.count_unique_events(
|
||||
event_action: Gitlab::UsageDataCounters::TrackUniqueActions::PUSH_ACTION,
|
||||
date_from: time_period[:created_at].first,
|
||||
date_to: time_period[:created_at].last
|
||||
)
|
||||
end
|
||||
|
||||
design_count = redis_usage_data do
|
||||
counter.count_unique_events(
|
||||
event_action: Gitlab::UsageDataCounters::TrackUniqueActions::DESIGN_ACTION,
|
||||
date_from: time_period[:created_at].first,
|
||||
date_to: time_period[:created_at].last
|
||||
)
|
||||
end
|
||||
|
||||
wiki_count = redis_usage_data do
|
||||
counter.count_unique_events(
|
||||
event_action: Gitlab::UsageDataCounters::TrackUniqueActions::WIKI_ACTION,
|
||||
date_from: time_period[:created_at].first,
|
||||
date_to: time_period[:created_at].last
|
||||
)
|
||||
end
|
||||
|
||||
{
|
||||
action_monthly_active_users_project_repo: project_count,
|
||||
action_monthly_active_users_design_management: design_count,
|
||||
action_monthly_active_users_wiki_repo: wiki_count
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unique_visit_service
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module UsageDataCounters
|
||||
module TrackUniqueActions
|
||||
KEY_EXPIRY_LENGTH = 29.days
|
||||
FEATURE_FLAG = :track_unique_actions
|
||||
|
||||
WIKI_ACTION = :wiki_action
|
||||
DESIGN_ACTION = :design_action
|
||||
PUSH_ACTION = :project_action
|
||||
|
||||
ACTION_TRANSFORMATIONS = HashWithIndifferentAccess.new({
|
||||
wiki: {
|
||||
created: WIKI_ACTION,
|
||||
updated: WIKI_ACTION,
|
||||
destroyed: WIKI_ACTION
|
||||
},
|
||||
design: {
|
||||
created: DESIGN_ACTION,
|
||||
updated: DESIGN_ACTION,
|
||||
destroyed: DESIGN_ACTION
|
||||
},
|
||||
project: {
|
||||
pushed: PUSH_ACTION
|
||||
}
|
||||
}).freeze
|
||||
|
||||
class << self
|
||||
def track_action(event_action:, event_target:, author_id:, time: Time.zone.now)
|
||||
return unless Gitlab::CurrentSettings.usage_ping_enabled
|
||||
return unless Feature.enabled?(FEATURE_FLAG)
|
||||
return unless valid_target?(event_target)
|
||||
return unless valid_action?(event_action)
|
||||
|
||||
transformed_target = transform_target(event_target)
|
||||
transformed_action = transform_action(event_action, transformed_target)
|
||||
|
||||
add_event(transformed_action, author_id, time)
|
||||
end
|
||||
|
||||
def count_unique_events(event_action:, date_from:, date_to:)
|
||||
keys = (date_from.to_date..date_to.to_date).map { |date| key(event_action, date) }
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.pfcount(*keys)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def transform_action(event_action, event_target)
|
||||
ACTION_TRANSFORMATIONS.dig(event_target, event_action) || event_action
|
||||
end
|
||||
|
||||
def transform_target(event_target)
|
||||
Event::TARGET_TYPES.key(event_target)
|
||||
end
|
||||
|
||||
def valid_target?(target)
|
||||
Event::TARGET_TYPES.value?(target)
|
||||
end
|
||||
|
||||
def valid_action?(action)
|
||||
Event.actions.key?(action)
|
||||
end
|
||||
|
||||
def key(event_action, date)
|
||||
year_day = date.strftime('%G-%j')
|
||||
"#{year_day}-{#{event_action}}"
|
||||
end
|
||||
|
||||
def add_event(event_action, author_id, date)
|
||||
target_key = key(event_action, date)
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.multi do |multi|
|
||||
multi.pfadd(target_key, author_id)
|
||||
multi.expire(target_key, KEY_EXPIRY_LENGTH)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -304,9 +304,6 @@ msgstr ""
|
|||
msgid "%{containerScanningLinkStart}Container Scanning%{containerScanningLinkEnd} and/or %{dependencyScanningLinkStart}Dependency Scanning%{dependencyScanningLinkEnd} must be enabled. %{securityBotLinkStart}GitLab-Security-Bot%{securityBotLinkEnd} will be the author of the auto-created merge request. %{moreInfoLinkStart}More information%{moreInfoLinkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{cores} cores"
|
||||
msgstr ""
|
||||
|
||||
|
@ -8409,6 +8406,9 @@ msgstr ""
|
|||
msgid "Downvotes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Drop or %{linkStart}upload%{linkEnd} Designs to attach"
|
||||
msgstr ""
|
||||
|
||||
msgid "Drop your designs to start your upload."
|
||||
msgstr ""
|
||||
|
||||
|
@ -10410,9 +10410,6 @@ msgstr ""
|
|||
msgid "Footer message"
|
||||
msgstr ""
|
||||
|
||||
msgid "For each Jira issue successfully imported, we'll create a new GitLab issue with the following data:"
|
||||
msgstr ""
|
||||
|
||||
msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -11082,6 +11079,9 @@ msgstr ""
|
|||
msgid "GitLab single sign-on URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab username"
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab uses %{jaeger_link} to monitor distributed systems."
|
||||
msgstr ""
|
||||
|
||||
|
@ -13065,6 +13065,9 @@ msgstr ""
|
|||
msgid "Jira Issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "Jira display name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Jira import is already running."
|
||||
msgstr ""
|
||||
|
||||
|
@ -13080,6 +13083,12 @@ msgstr ""
|
|||
msgid "Jira service not configured."
|
||||
msgstr ""
|
||||
|
||||
msgid "Jira users have been matched with similar GitLab users. This can be overwritten by selecting a GitLab user from the dropdown in the \"GitLab username\" column. If it wasn't possible to match a Jira user with a GitLab user, the dropdown defaults to the user conducting the import."
|
||||
msgstr ""
|
||||
|
||||
msgid "Jira-GitLab user mapping template"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService| on branch %{branch_link}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19630,9 +19639,6 @@ msgstr ""
|
|||
msgid "Reported %{timeAgo} by %{reportedBy}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reporter"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reporting"
|
||||
msgstr ""
|
||||
|
||||
|
@ -23138,6 +23144,9 @@ msgstr ""
|
|||
msgid "The Git LFS objects will <strong>not</strong> be synced."
|
||||
msgstr ""
|
||||
|
||||
msgid "The GitLab user to which the Jira user %{jiraDisplayName} will be mapped"
|
||||
msgstr ""
|
||||
|
||||
msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
|
||||
msgstr ""
|
||||
|
||||
|
@ -23671,6 +23680,9 @@ msgstr ""
|
|||
msgid "There was an error resetting user pipeline minutes."
|
||||
msgstr ""
|
||||
|
||||
msgid "There was an error retrieving the Jira users."
|
||||
msgstr ""
|
||||
|
||||
msgid "There was an error saving this Geo Node."
|
||||
msgstr ""
|
||||
|
||||
|
@ -26594,6 +26606,9 @@ msgstr ""
|
|||
msgid "Will be created"
|
||||
msgstr ""
|
||||
|
||||
msgid "Will be mapped to"
|
||||
msgstr ""
|
||||
|
||||
msgid "Will deploy to"
|
||||
msgstr ""
|
||||
|
||||
|
@ -27959,12 +27974,6 @@ msgstr ""
|
|||
msgid "jigsaw is not defined"
|
||||
msgstr ""
|
||||
|
||||
msgid "jira.issue.description.content"
|
||||
msgstr ""
|
||||
|
||||
msgid "jira.issue.summary"
|
||||
msgstr ""
|
||||
|
||||
msgid "latest"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ RUN rm -f chromedriver_linux64.zip
|
|||
# Install K3d local cluster support
|
||||
# https://github.com/rancher/k3d
|
||||
#
|
||||
RUN curl -s https://raw.githubusercontent.com/rancher/k3d/master/install.sh | TAG="v${K3D_VERSION}" bash
|
||||
RUN curl -s https://raw.githubusercontent.com/rancher/k3d/main/install.sh | TAG="v${K3D_VERSION}" bash
|
||||
|
||||
##
|
||||
# Install gcloud and kubectl CLI used in Auto DevOps test to create K8s
|
||||
|
|
|
@ -77,13 +77,16 @@ FactoryBot.define do
|
|||
username { 'jira_username' }
|
||||
password { 'jira_password' }
|
||||
jira_issue_transition_id { '56-1' }
|
||||
issues_enabled { false }
|
||||
project_key { nil }
|
||||
end
|
||||
|
||||
after(:build) do |service, evaluator|
|
||||
if evaluator.create_data
|
||||
create(:jira_tracker_data, service: service,
|
||||
url: evaluator.url, api_url: evaluator.api_url, jira_issue_transition_id: evaluator.jira_issue_transition_id,
|
||||
username: evaluator.username, password: evaluator.password
|
||||
username: evaluator.username, password: evaluator.password, issues_enabled: evaluator.issues_enabled,
|
||||
project_key: evaluator.project_key
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,11 +5,10 @@ require 'spec_helper'
|
|||
RSpec.describe "Public Project Snippets Access" do
|
||||
include AccessMatchers
|
||||
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
let(:public_snippet) { create(:project_snippet, :public, project: project, author: project.owner) }
|
||||
let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) }
|
||||
let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
let_it_be(:public_snippet) { create(:project_snippet, :public, project: project, author: project.owner) }
|
||||
let_it_be(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) }
|
||||
let_it_be(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
|
||||
|
||||
describe "GET /:project_path/snippets" do
|
||||
subject { project_snippets_path(project) }
|
||||
|
|
|
@ -17,9 +17,13 @@ exports[`Design management dropzone component when dragging renders correct temp
|
|||
size="24"
|
||||
/>
|
||||
|
||||
<gl-sprintf-stub
|
||||
message="%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
|
||||
/>
|
||||
<p
|
||||
class="gl-font-weight-bold gl-mb-0"
|
||||
>
|
||||
<gl-sprintf-stub
|
||||
message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
@ -89,9 +93,13 @@ exports[`Design management dropzone component when dragging renders correct temp
|
|||
size="24"
|
||||
/>
|
||||
|
||||
<gl-sprintf-stub
|
||||
message="%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
|
||||
/>
|
||||
<p
|
||||
class="gl-font-weight-bold gl-mb-0"
|
||||
>
|
||||
<gl-sprintf-stub
|
||||
message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
@ -161,9 +169,13 @@ exports[`Design management dropzone component when dragging renders correct temp
|
|||
size="24"
|
||||
/>
|
||||
|
||||
<gl-sprintf-stub
|
||||
message="%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
|
||||
/>
|
||||
<p
|
||||
class="gl-font-weight-bold gl-mb-0"
|
||||
>
|
||||
<gl-sprintf-stub
|
||||
message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
@ -232,9 +244,13 @@ exports[`Design management dropzone component when dragging renders correct temp
|
|||
size="24"
|
||||
/>
|
||||
|
||||
<gl-sprintf-stub
|
||||
message="%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
|
||||
/>
|
||||
<p
|
||||
class="gl-font-weight-bold gl-mb-0"
|
||||
>
|
||||
<gl-sprintf-stub
|
||||
message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
@ -303,9 +319,13 @@ exports[`Design management dropzone component when dragging renders correct temp
|
|||
size="24"
|
||||
/>
|
||||
|
||||
<gl-sprintf-stub
|
||||
message="%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
|
||||
/>
|
||||
<p
|
||||
class="gl-font-weight-bold gl-mb-0"
|
||||
>
|
||||
<gl-sprintf-stub
|
||||
message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
@ -374,9 +394,13 @@ exports[`Design management dropzone component when no slot provided renders defa
|
|||
size="24"
|
||||
/>
|
||||
|
||||
<gl-sprintf-stub
|
||||
message="%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
|
||||
/>
|
||||
<p
|
||||
class="gl-font-weight-bold gl-mb-0"
|
||||
>
|
||||
<gl-sprintf-stub
|
||||
message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`Design management index page designs does not render toolbar when there is no permission 1`] = `
|
||||
<div
|
||||
class="gl-mt-2"
|
||||
class="gl-mt-5"
|
||||
data-testid="designs-root"
|
||||
>
|
||||
<!---->
|
||||
|
@ -87,7 +87,7 @@ exports[`Design management index page designs does not render toolbar when there
|
|||
|
||||
exports[`Design management index page designs renders designs list and header with upload button 1`] = `
|
||||
<div
|
||||
class="gl-mt-2"
|
||||
class="gl-mt-5"
|
||||
data-testid="designs-root"
|
||||
>
|
||||
<header
|
||||
|
@ -227,7 +227,7 @@ exports[`Design management index page designs renders designs list and header wi
|
|||
|
||||
exports[`Design management index page designs renders error 1`] = `
|
||||
<div
|
||||
class="gl-mt-2"
|
||||
class="gl-mt-5"
|
||||
data-testid="designs-root"
|
||||
>
|
||||
<!---->
|
||||
|
@ -258,7 +258,7 @@ exports[`Design management index page designs renders error 1`] = `
|
|||
|
||||
exports[`Design management index page designs renders loading icon 1`] = `
|
||||
<div
|
||||
class="gl-mt-2"
|
||||
class="gl-mt-5"
|
||||
data-testid="designs-root"
|
||||
>
|
||||
<!---->
|
||||
|
@ -281,7 +281,7 @@ exports[`Design management index page designs renders loading icon 1`] = `
|
|||
|
||||
exports[`Design management index page when has no designs renders design dropzone 1`] = `
|
||||
<div
|
||||
class="gl-mt-2"
|
||||
class="gl-mt-5"
|
||||
data-testid="designs-root"
|
||||
>
|
||||
<!---->
|
||||
|
|
|
@ -0,0 +1,299 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`JiraImportForm table body shows correct information in each cell 1`] = `
|
||||
<table
|
||||
aria-busy="false"
|
||||
aria-colcount="3"
|
||||
class="table b-table gl-table b-table-fixed"
|
||||
role="table"
|
||||
>
|
||||
<!---->
|
||||
<!---->
|
||||
<thead
|
||||
class=""
|
||||
role="rowgroup"
|
||||
>
|
||||
<!---->
|
||||
<tr
|
||||
class=""
|
||||
role="row"
|
||||
>
|
||||
<th
|
||||
aria-colindex="1"
|
||||
class=""
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
Jira display name
|
||||
</th>
|
||||
<th
|
||||
aria-colindex="2"
|
||||
aria-label="Arrow"
|
||||
class=""
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
/>
|
||||
<th
|
||||
aria-colindex="3"
|
||||
class=""
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
GitLab username
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
role="rowgroup"
|
||||
>
|
||||
<!---->
|
||||
<tr
|
||||
class=""
|
||||
role="row"
|
||||
>
|
||||
<td
|
||||
aria-colindex="1"
|
||||
class=""
|
||||
role="cell"
|
||||
>
|
||||
Jane Doe
|
||||
</td>
|
||||
<td
|
||||
aria-colindex="2"
|
||||
class=""
|
||||
role="cell"
|
||||
>
|
||||
<svg
|
||||
aria-label="Will be mapped to"
|
||||
class="gl-icon s16"
|
||||
>
|
||||
<use
|
||||
href="#arrow-right"
|
||||
/>
|
||||
</svg>
|
||||
</td>
|
||||
<td
|
||||
aria-colindex="3"
|
||||
class=""
|
||||
role="cell"
|
||||
>
|
||||
<div
|
||||
aria-label="The GitLab user to which the Jira user Jane Doe will be mapped"
|
||||
class="dropdown b-dropdown gl-new-dropdown w-100 btn-group"
|
||||
>
|
||||
<!---->
|
||||
<button
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="gl-new-dropdown-button-text"
|
||||
>
|
||||
janedoe
|
||||
</span>
|
||||
|
||||
<svg
|
||||
class="dropdown-chevron gl-icon s16"
|
||||
>
|
||||
<use
|
||||
href="#chevron-down"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<ul
|
||||
class="dropdown-menu"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<div
|
||||
class="gl-search-box-by-type m-2"
|
||||
>
|
||||
<svg
|
||||
class="gl-search-box-by-type-search-icon gl-icon s16"
|
||||
>
|
||||
<use
|
||||
href="#search"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<input
|
||||
aria-label="Search"
|
||||
class="gl-form-input gl-search-box-by-type-input form-control"
|
||||
placeholder="Search"
|
||||
type="text"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="gl-search-box-by-type-right-icons"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<button
|
||||
aria-hidden="true"
|
||||
class="gl-clear-icon-button gl-search-box-by-type-clear gl-clear-icon-button"
|
||||
name="clear"
|
||||
style="display: none;"
|
||||
title="Clear"
|
||||
>
|
||||
<svg
|
||||
class="gl-icon s16"
|
||||
>
|
||||
<use
|
||||
href="#clear"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<li
|
||||
class="gl-new-dropdown-text text-secondary"
|
||||
role="presentation"
|
||||
>
|
||||
<p
|
||||
class="b-dropdown-text"
|
||||
>
|
||||
|
||||
No matches found
|
||||
|
||||
</p>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
class=""
|
||||
role="row"
|
||||
>
|
||||
<td
|
||||
aria-colindex="1"
|
||||
class=""
|
||||
role="cell"
|
||||
>
|
||||
Fred Chopin
|
||||
</td>
|
||||
<td
|
||||
aria-colindex="2"
|
||||
class=""
|
||||
role="cell"
|
||||
>
|
||||
<svg
|
||||
aria-label="Will be mapped to"
|
||||
class="gl-icon s16"
|
||||
>
|
||||
<use
|
||||
href="#arrow-right"
|
||||
/>
|
||||
</svg>
|
||||
</td>
|
||||
<td
|
||||
aria-colindex="3"
|
||||
class=""
|
||||
role="cell"
|
||||
>
|
||||
<div
|
||||
aria-label="The GitLab user to which the Jira user Fred Chopin will be mapped"
|
||||
class="dropdown b-dropdown gl-new-dropdown w-100 btn-group"
|
||||
>
|
||||
<!---->
|
||||
<button
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="gl-new-dropdown-button-text"
|
||||
>
|
||||
mrgitlab
|
||||
</span>
|
||||
|
||||
<svg
|
||||
class="dropdown-chevron gl-icon s16"
|
||||
>
|
||||
<use
|
||||
href="#chevron-down"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<ul
|
||||
class="dropdown-menu"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<div
|
||||
class="gl-search-box-by-type m-2"
|
||||
>
|
||||
<svg
|
||||
class="gl-search-box-by-type-search-icon gl-icon s16"
|
||||
>
|
||||
<use
|
||||
href="#search"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<input
|
||||
aria-label="Search"
|
||||
class="gl-form-input gl-search-box-by-type-input form-control"
|
||||
placeholder="Search"
|
||||
type="text"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="gl-search-box-by-type-right-icons"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<button
|
||||
aria-hidden="true"
|
||||
class="gl-clear-icon-button gl-search-box-by-type-clear gl-clear-icon-button"
|
||||
name="clear"
|
||||
style="display: none;"
|
||||
title="Clear"
|
||||
>
|
||||
<svg
|
||||
class="gl-icon s16"
|
||||
>
|
||||
<use
|
||||
href="#clear"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<li
|
||||
class="gl-new-dropdown-text text-secondary"
|
||||
role="presentation"
|
||||
>
|
||||
<p
|
||||
class="b-dropdown-text"
|
||||
>
|
||||
|
||||
No matches found
|
||||
|
||||
</p>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!---->
|
||||
<!---->
|
||||
</tbody>
|
||||
<!---->
|
||||
</table>
|
||||
`;
|
|
@ -1,88 +1,19 @@
|
|||
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||
import Vue from 'vue';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import JiraImportApp from '~/jira_import/components/jira_import_app.vue';
|
||||
import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
|
||||
import JiraImportProgress from '~/jira_import/components/jira_import_progress.vue';
|
||||
import JiraImportSetup from '~/jira_import/components/jira_import_setup.vue';
|
||||
import initiateJiraImportMutation from '~/jira_import/queries/initiate_jira_import.mutation.graphql';
|
||||
|
||||
const mountComponent = ({
|
||||
isJiraConfigured = true,
|
||||
errorMessage = '',
|
||||
selectedProject = 'MTG',
|
||||
showAlert = false,
|
||||
isInProgress = false,
|
||||
loading = false,
|
||||
mutate = jest.fn(() => Promise.resolve()),
|
||||
mountType,
|
||||
} = {}) => {
|
||||
const mountFunction = mountType === 'mount' ? mount : shallowMount;
|
||||
|
||||
return mountFunction(JiraImportApp, {
|
||||
propsData: {
|
||||
inProgressIllustration: 'in-progress-illustration.svg',
|
||||
isJiraConfigured,
|
||||
issuesPath: 'gitlab-org/gitlab-test/-/issues',
|
||||
jiraIntegrationPath: 'gitlab-org/gitlab-test/-/services/jira/edit',
|
||||
projectPath: 'gitlab-org/gitlab-test',
|
||||
setupIllustration: 'setup-illustration.svg',
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errorMessage,
|
||||
showAlert,
|
||||
selectedProject,
|
||||
jiraImportDetails: {
|
||||
isInProgress,
|
||||
imports: [
|
||||
{
|
||||
jiraProjectKey: 'MTG',
|
||||
scheduledAt: '2020-04-08T10:11:12+00:00',
|
||||
scheduledBy: {
|
||||
name: 'John Doe',
|
||||
},
|
||||
},
|
||||
{
|
||||
jiraProjectKey: 'MSJP',
|
||||
scheduledAt: '2020-04-09T13:14:15+00:00',
|
||||
scheduledBy: {
|
||||
name: 'Jimmy Doe',
|
||||
},
|
||||
},
|
||||
{
|
||||
jiraProjectKey: 'MTG',
|
||||
scheduledAt: '2020-04-09T16:17:18+00:00',
|
||||
scheduledBy: {
|
||||
name: 'Jane Doe',
|
||||
},
|
||||
},
|
||||
],
|
||||
mostRecentImport: {
|
||||
jiraProjectKey: 'MTG',
|
||||
scheduledAt: '2020-04-09T16:17:18+00:00',
|
||||
scheduledBy: {
|
||||
name: 'Jane Doe',
|
||||
},
|
||||
},
|
||||
projects: [
|
||||
{ text: 'My Jira Project (MJP)', value: 'MJP' },
|
||||
{ text: 'My Second Jira Project (MSJP)', value: 'MSJP' },
|
||||
{ text: 'Migrate to GitLab (MTG)', value: 'MTG' },
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
mocks: {
|
||||
$apollo: {
|
||||
loading,
|
||||
mutate,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
import getJiraUserMappingMutation from '~/jira_import/queries/get_jira_user_mapping.mutation.graphql';
|
||||
import { imports, issuesPath, jiraIntegrationPath, jiraProjects, userMappings } from '../mock_data';
|
||||
|
||||
describe('JiraImportApp', () => {
|
||||
let axiosMock;
|
||||
let mutateSpy;
|
||||
let wrapper;
|
||||
|
||||
const getFormComponent = () => wrapper.find(JiraImportForm);
|
||||
|
@ -95,7 +26,64 @@ describe('JiraImportApp', () => {
|
|||
|
||||
const getLoadingIcon = () => wrapper.find(GlLoadingIcon);
|
||||
|
||||
const mountComponent = ({
|
||||
isJiraConfigured = true,
|
||||
errorMessage = '',
|
||||
selectedProject = 'MTG',
|
||||
showAlert = false,
|
||||
isInProgress = false,
|
||||
loading = false,
|
||||
mutate = mutateSpy,
|
||||
mountFunction = shallowMount,
|
||||
} = {}) =>
|
||||
mountFunction(JiraImportApp, {
|
||||
propsData: {
|
||||
inProgressIllustration: 'in-progress-illustration.svg',
|
||||
isJiraConfigured,
|
||||
issuesPath,
|
||||
jiraIntegrationPath,
|
||||
projectId: '5',
|
||||
projectPath: 'gitlab-org/gitlab-test',
|
||||
setupIllustration: 'setup-illustration.svg',
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isSubmitting: false,
|
||||
selectedProject,
|
||||
userMappings,
|
||||
errorMessage,
|
||||
showAlert,
|
||||
jiraImportDetails: {
|
||||
isInProgress,
|
||||
imports,
|
||||
mostRecentImport: imports[imports.length - 1],
|
||||
projects: jiraProjects,
|
||||
},
|
||||
};
|
||||
},
|
||||
mocks: {
|
||||
$apollo: {
|
||||
loading,
|
||||
mutate,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = new AxiosMockAdapter(axios);
|
||||
mutateSpy = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
jiraImportStart: { errors: [] },
|
||||
jiraImportUsers: { jiraUsers: [], errors: [] },
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
axiosMock.restore();
|
||||
mutateSpy.mockRestore();
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
@ -223,7 +211,7 @@ describe('JiraImportApp', () => {
|
|||
});
|
||||
|
||||
it('shows warning alert to explain project MTG has been imported 2 times before', () => {
|
||||
wrapper = mountComponent({ mountType: 'mount' });
|
||||
wrapper = mountComponent({ mountFunction: mount });
|
||||
|
||||
expect(getAlert().text()).toBe(
|
||||
'You have imported from this project 2 times before. Each new import will create duplicate issues.',
|
||||
|
@ -248,9 +236,7 @@ describe('JiraImportApp', () => {
|
|||
|
||||
describe('initiating a Jira import', () => {
|
||||
it('calls the mutation with the expected arguments', () => {
|
||||
const mutate = jest.fn(() => Promise.resolve());
|
||||
|
||||
wrapper = mountComponent({ mutate });
|
||||
wrapper = mountComponent();
|
||||
|
||||
const mutationArguments = {
|
||||
mutation: initiateJiraImportMutation,
|
||||
|
@ -258,14 +244,23 @@ describe('JiraImportApp', () => {
|
|||
input: {
|
||||
jiraProjectKey: 'MTG',
|
||||
projectPath: 'gitlab-org/gitlab-test',
|
||||
usersMapping: [],
|
||||
usersMapping: [
|
||||
{
|
||||
jiraAccountId: 'aei23f98f-q23fj98qfj',
|
||||
gitlabId: 15,
|
||||
},
|
||||
{
|
||||
jiraAccountId: 'fu39y8t34w-rq3u289t3h4i',
|
||||
gitlabId: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
getFormComponent().vm.$emit('initiateJiraImport', 'MTG');
|
||||
|
||||
expect(mutate).toHaveBeenCalledWith(expect.objectContaining(mutationArguments));
|
||||
expect(mutateSpy).toHaveBeenCalledWith(expect.objectContaining(mutationArguments));
|
||||
});
|
||||
|
||||
it('shows alert message with error message on error', () => {
|
||||
|
@ -284,19 +279,53 @@ describe('JiraImportApp', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('can dismiss alert message', () => {
|
||||
wrapper = mountComponent({
|
||||
errorMessage: 'There was an error importing the Jira project.',
|
||||
showAlert: true,
|
||||
selectedProject: null,
|
||||
describe('alert', () => {
|
||||
it('can be dismissed', () => {
|
||||
wrapper = mountComponent({
|
||||
errorMessage: 'There was an error importing the Jira project.',
|
||||
showAlert: true,
|
||||
selectedProject: null,
|
||||
});
|
||||
|
||||
expect(getAlert().exists()).toBe(true);
|
||||
|
||||
getAlert().vm.$emit('dismiss');
|
||||
|
||||
return Vue.nextTick().then(() => {
|
||||
expect(getAlert().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('on mount', () => {
|
||||
it('makes a GraphQL mutation call to get user mappings', () => {
|
||||
wrapper = mountComponent();
|
||||
|
||||
const mutationArguments = {
|
||||
mutation: getJiraUserMappingMutation,
|
||||
variables: {
|
||||
input: {
|
||||
projectPath: 'gitlab-org/gitlab-test',
|
||||
startAt: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(mutateSpy).toHaveBeenCalledWith(expect.objectContaining(mutationArguments));
|
||||
});
|
||||
|
||||
expect(getAlert().exists()).toBe(true);
|
||||
it('does not make a GraphQL mutation call to get user mappings when Jira is not configured', () => {
|
||||
wrapper = mountComponent({ isJiraConfigured: false });
|
||||
|
||||
getAlert().vm.$emit('dismiss');
|
||||
expect(mutateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
return Vue.nextTick().then(() => {
|
||||
expect(getAlert().exists()).toBe(false);
|
||||
it('shows error message when there is an error with the GraphQL mutation call', () => {
|
||||
const mutate = jest.fn(() => Promise.reject());
|
||||
|
||||
wrapper = mountComponent({ mutate });
|
||||
|
||||
expect(getAlert().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,44 +1,51 @@
|
|||
import { GlAvatar, GlButton, GlFormSelect, GlLabel } from '@gitlab/ui';
|
||||
import { GlButton, GlFormSelect, GlLabel, GlTable } from '@gitlab/ui';
|
||||
import { getByRole } from '@testing-library/dom';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
|
||||
|
||||
const importLabel = 'jira-import::MTG-1';
|
||||
const value = 'MTG';
|
||||
|
||||
const mountComponent = ({ mountType } = {}) => {
|
||||
const mountFunction = mountType === 'mount' ? mount : shallowMount;
|
||||
|
||||
return mountFunction(JiraImportForm, {
|
||||
propsData: {
|
||||
importLabel,
|
||||
issuesPath: 'gitlab-org/gitlab-test/-/issues',
|
||||
jiraProjects: [
|
||||
{
|
||||
text: 'My Jira Project',
|
||||
value: 'MJP',
|
||||
},
|
||||
{
|
||||
text: 'My Second Jira Project',
|
||||
value: 'MSJP',
|
||||
},
|
||||
{
|
||||
text: 'Migrate to GitLab',
|
||||
value: 'MTG',
|
||||
},
|
||||
],
|
||||
value,
|
||||
},
|
||||
});
|
||||
};
|
||||
import { issuesPath, jiraProjects, userMappings } from '../mock_data';
|
||||
|
||||
describe('JiraImportForm', () => {
|
||||
let axiosMock;
|
||||
let wrapper;
|
||||
|
||||
const currentUsername = 'mrgitlab';
|
||||
const importLabel = 'jira-import::MTG-1';
|
||||
const value = 'MTG';
|
||||
|
||||
const getSelectDropdown = () => wrapper.find(GlFormSelect);
|
||||
|
||||
const getCancelButton = () => wrapper.findAll(GlButton).at(1);
|
||||
|
||||
const getHeader = name => getByRole(wrapper.element, 'columnheader', { name });
|
||||
|
||||
const mountComponent = ({ isSubmitting = false, mountFunction = shallowMount } = {}) =>
|
||||
mountFunction(JiraImportForm, {
|
||||
propsData: {
|
||||
importLabel,
|
||||
isSubmitting,
|
||||
issuesPath,
|
||||
jiraProjects,
|
||||
projectId: '5',
|
||||
userMappings,
|
||||
value,
|
||||
},
|
||||
data: () => ({
|
||||
isFetching: false,
|
||||
searchTerm: '',
|
||||
selectState: null,
|
||||
users: [],
|
||||
}),
|
||||
currentUsername,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = new AxiosMockAdapter(axios);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
axiosMock.restore();
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
@ -51,16 +58,22 @@ describe('JiraImportForm', () => {
|
|||
});
|
||||
|
||||
it('contains a list of Jira projects to select from', () => {
|
||||
wrapper = mountComponent({ mountType: 'mount' });
|
||||
|
||||
const optionItems = ['My Jira Project', 'My Second Jira Project', 'Migrate to GitLab'];
|
||||
wrapper = mountComponent({ mountFunction: mount });
|
||||
|
||||
getSelectDropdown()
|
||||
.findAll('option')
|
||||
.wrappers.forEach((optionEl, index) => {
|
||||
expect(optionEl.text()).toBe(optionItems[index]);
|
||||
expect(optionEl.text()).toBe(jiraProjects[index].text);
|
||||
});
|
||||
});
|
||||
|
||||
it('emits an "input" event when the input select value changes', () => {
|
||||
wrapper = mountComponent();
|
||||
|
||||
getSelectDropdown().vm.$emit('change', value);
|
||||
|
||||
expect(wrapper.emitted('input')[0]).toEqual([value]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('form information', () => {
|
||||
|
@ -72,64 +85,90 @@ describe('JiraImportForm', () => {
|
|||
expect(wrapper.find(GlLabel).props('title')).toBe(importLabel);
|
||||
});
|
||||
|
||||
it('shows a heading for the user mapping section', () => {
|
||||
expect(
|
||||
getByRole(wrapper.element, 'heading', { name: 'Jira-GitLab user mapping template' }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows information to the user', () => {
|
||||
expect(wrapper.find('p').text()).toBe(
|
||||
"For each Jira issue successfully imported, we'll create a new GitLab issue with the following data:",
|
||||
);
|
||||
});
|
||||
|
||||
it('shows jira.issue.summary for the Title', () => {
|
||||
expect(wrapper.find('[id="jira-project-title"]').text()).toBe('jira.issue.summary');
|
||||
});
|
||||
|
||||
it('shows an avatar for the Reporter', () => {
|
||||
expect(wrapper.contains(GlAvatar)).toBe(true);
|
||||
});
|
||||
|
||||
it('shows jira.issue.description.content for the Description', () => {
|
||||
expect(wrapper.find('[id="jira-project-description"]').text()).toBe(
|
||||
'jira.issue.description.content',
|
||||
'Jira users have been matched with similar GitLab users. This can be overwritten by selecting a GitLab user from the dropdown in the "GitLab username" column. If it wasn\'t possible to match a Jira user with a GitLab user, the dropdown defaults to the user conducting the import.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Next button', () => {
|
||||
beforeEach(() => {
|
||||
describe('table', () => {
|
||||
describe('headers', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mountComponent({ mountFunction: mount });
|
||||
});
|
||||
|
||||
it('has a "Jira display name" column', () => {
|
||||
expect(getHeader('Jira display name')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('has an "arrow" column', () => {
|
||||
expect(getHeader('Arrow')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('has a "GitLab username" column', () => {
|
||||
expect(getHeader('GitLab username')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('body', () => {
|
||||
it('shows all user mappings', () => {
|
||||
wrapper = mountComponent({ mountFunction: mount });
|
||||
|
||||
expect(wrapper.find(GlTable).findAll('tbody tr').length).toBe(userMappings.length);
|
||||
});
|
||||
|
||||
it('shows correct information in each cell', () => {
|
||||
wrapper = mountComponent({ mountFunction: mount });
|
||||
|
||||
expect(wrapper.find(GlTable).element).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('buttons', () => {
|
||||
describe('"Continue" button', () => {
|
||||
it('is shown', () => {
|
||||
wrapper = mountComponent();
|
||||
|
||||
expect(wrapper.find(GlButton).text()).toBe('Continue');
|
||||
});
|
||||
|
||||
it('is in loading state when the form is submitting', async () => {
|
||||
wrapper = mountComponent({ isSubmitting: true });
|
||||
|
||||
expect(wrapper.find(GlButton).props('loading')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('"Cancel" button', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mountComponent();
|
||||
});
|
||||
|
||||
it('is shown', () => {
|
||||
expect(getCancelButton().text()).toBe('Cancel');
|
||||
});
|
||||
|
||||
it('links to the Issues page', () => {
|
||||
expect(getCancelButton().attributes('href')).toBe(issuesPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('form', () => {
|
||||
it('emits an "initiateJiraImport" event with the selected dropdown value when submitted', () => {
|
||||
wrapper = mountComponent();
|
||||
|
||||
wrapper.find('form').trigger('submit');
|
||||
|
||||
expect(wrapper.emitted('initiateJiraImport')[0]).toEqual([value]);
|
||||
});
|
||||
|
||||
it('is shown', () => {
|
||||
expect(wrapper.find(GlButton).text()).toBe('Next');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Cancel button', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mountComponent();
|
||||
});
|
||||
|
||||
it('is shown', () => {
|
||||
expect(getCancelButton().text()).toBe('Cancel');
|
||||
});
|
||||
|
||||
it('links to the Issues page', () => {
|
||||
expect(getCancelButton().attributes('href')).toBe('gitlab-org/gitlab-test/-/issues');
|
||||
});
|
||||
});
|
||||
|
||||
it('emits an "input" event when the input select value changes', () => {
|
||||
wrapper = mountComponent({ mountType: 'mount' });
|
||||
|
||||
getSelectDropdown().vm.$emit('change', value);
|
||||
|
||||
expect(wrapper.emitted('input')[0]).toEqual([value]);
|
||||
});
|
||||
|
||||
it('emits an "initiateJiraImport" event with the selected dropdown value when submitted', () => {
|
||||
wrapper = mountComponent();
|
||||
|
||||
wrapper.find('form').trigger('submit');
|
||||
|
||||
expect(wrapper.emitted('initiateJiraImport')[0]).toEqual([value]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { GlEmptyState } from '@gitlab/ui';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import JiraImportProgress from '~/jira_import/components/jira_import_progress.vue';
|
||||
|
||||
const illustration = 'illustration.svg';
|
||||
const importProject = 'JIRAPROJECT';
|
||||
const issuesPath = 'gitlab-org/gitlab-test/-/issues';
|
||||
import { illustration, issuesPath } from '../mock_data';
|
||||
|
||||
describe('JiraImportProgress', () => {
|
||||
let wrapper;
|
||||
|
||||
const importProject = 'JIRAPROJECT';
|
||||
|
||||
const getGlEmptyStateProp = attribute => wrapper.find(GlEmptyState).props(attribute);
|
||||
|
||||
const getParagraphText = () => wrapper.find('p').text();
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { GlEmptyState } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import JiraImportSetup from '~/jira_import/components/jira_import_setup.vue';
|
||||
|
||||
const illustration = 'illustration.svg';
|
||||
const jiraIntegrationPath = 'gitlab-org/gitlab-test/-/services/jira/edit';
|
||||
import { illustration, jiraIntegrationPath } from '../mock_data';
|
||||
|
||||
describe('JiraImportSetup', () => {
|
||||
let wrapper;
|
||||
|
|
|
@ -70,3 +70,56 @@ export const jiraImportMutationResponse = {
|
|||
__typename: 'JiraImportStartPayload',
|
||||
},
|
||||
};
|
||||
|
||||
export const issuesPath = 'gitlab-org/gitlab-test/-/issues';
|
||||
|
||||
export const jiraIntegrationPath = 'gitlab-org/gitlab-test/-/services/jira/edit';
|
||||
|
||||
export const illustration = 'illustration.svg';
|
||||
|
||||
export const jiraProjects = [
|
||||
{ text: 'My Jira Project (MJP)', value: 'MJP' },
|
||||
{ text: 'My Second Jira Project (MSJP)', value: 'MSJP' },
|
||||
{ text: 'Migrate to GitLab (MTG)', value: 'MTG' },
|
||||
];
|
||||
|
||||
export const imports = [
|
||||
{
|
||||
jiraProjectKey: 'MTG',
|
||||
scheduledAt: '2020-04-08T10:11:12+00:00',
|
||||
scheduledBy: {
|
||||
name: 'John Doe',
|
||||
},
|
||||
},
|
||||
{
|
||||
jiraProjectKey: 'MSJP',
|
||||
scheduledAt: '2020-04-09T13:14:15+00:00',
|
||||
scheduledBy: {
|
||||
name: 'Jimmy Doe',
|
||||
},
|
||||
},
|
||||
{
|
||||
jiraProjectKey: 'MTG',
|
||||
scheduledAt: '2020-04-09T16:17:18+00:00',
|
||||
scheduledBy: {
|
||||
name: 'Jane Doe',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const userMappings = [
|
||||
{
|
||||
jiraAccountId: 'aei23f98f-q23fj98qfj',
|
||||
jiraDisplayName: 'Jane Doe',
|
||||
jiraEmail: 'janedoe@example.com',
|
||||
gitlabId: 15,
|
||||
gitlabUsername: 'janedoe',
|
||||
},
|
||||
{
|
||||
jiraAccountId: 'fu39y8t34w-rq3u289t3h4i',
|
||||
jiraDisplayName: 'Fred Chopin',
|
||||
jiraEmail: 'fredchopin@example.com',
|
||||
gitlabId: undefined,
|
||||
gitlabUsername: undefined,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlDeprecatedButton } from '@gitlab/ui';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import LogControlButtons from '~/logs/components/log_control_buttons.vue';
|
||||
|
||||
describe('LogControlButtons', () => {
|
||||
|
@ -31,9 +31,9 @@ describe('LogControlButtons', () => {
|
|||
expect(wrapper.isVueInstance()).toBe(true);
|
||||
expect(wrapper.isEmpty()).toBe(false);
|
||||
|
||||
expect(findScrollToTop().is(GlDeprecatedButton)).toBe(true);
|
||||
expect(findScrollToBottom().is(GlDeprecatedButton)).toBe(true);
|
||||
expect(findRefreshBtn().is(GlDeprecatedButton)).toBe(true);
|
||||
expect(findScrollToTop().is(GlButton)).toBe(true);
|
||||
expect(findScrollToBottom().is(GlButton)).toBe(true);
|
||||
expect(findRefreshBtn().is(GlButton)).toBe(true);
|
||||
});
|
||||
|
||||
it('emits a `refresh` event on click on `refresh` button', () => {
|
||||
|
|
|
@ -229,7 +229,6 @@ RSpec.describe Gitlab::Profiler do
|
|||
.map { |(total)| total.to_f }
|
||||
|
||||
expect(total_times).to eq(total_times.sort.reverse)
|
||||
expect(total_times).not_to eq(total_times.uniq)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::UsageDataCounters::TrackUniqueActions, :clean_gitlab_redis_shared_state do
|
||||
subject(:track_unique_events) { described_class }
|
||||
|
||||
let(:time) { Time.zone.now }
|
||||
|
||||
def track_action(params)
|
||||
track_unique_events.track_action(params)
|
||||
end
|
||||
|
||||
def count_unique_events(params)
|
||||
track_unique_events.count_unique_events(params)
|
||||
end
|
||||
|
||||
context 'tracking an event' do
|
||||
context 'when tracking successfully' do
|
||||
context 'when the feature flag and the application setting is enabled' do
|
||||
context 'when the target and the action is valid' do
|
||||
before do
|
||||
stub_feature_flags(described_class::FEATURE_FLAG => true)
|
||||
stub_application_setting(usage_ping_enabled: true)
|
||||
end
|
||||
|
||||
it 'tracks and counts the events as expected' do
|
||||
project = Event::TARGET_TYPES[:project]
|
||||
design = Event::TARGET_TYPES[:design]
|
||||
wiki = Event::TARGET_TYPES[:wiki]
|
||||
|
||||
expect(track_action(event_action: :pushed, event_target: project, author_id: 1)).to be_truthy
|
||||
expect(track_action(event_action: :pushed, event_target: project, author_id: 1)).to be_truthy
|
||||
expect(track_action(event_action: :pushed, event_target: project, author_id: 2)).to be_truthy
|
||||
expect(track_action(event_action: :pushed, event_target: project, author_id: 3)).to be_truthy
|
||||
expect(track_action(event_action: :pushed, event_target: project, author_id: 4, time: time - 3.days)).to be_truthy
|
||||
expect(track_action(event_action: :created, event_target: project, author_id: 5, time: time - 3.days)).to be_truthy
|
||||
|
||||
expect(track_action(event_action: :destroyed, event_target: design, author_id: 3)).to be_truthy
|
||||
expect(track_action(event_action: :created, event_target: design, author_id: 4)).to be_truthy
|
||||
expect(track_action(event_action: :updated, event_target: design, author_id: 5)).to be_truthy
|
||||
expect(track_action(event_action: :pushed, event_target: design, author_id: 6)).to be_truthy
|
||||
|
||||
expect(track_action(event_action: :destroyed, event_target: wiki, author_id: 5)).to be_truthy
|
||||
expect(track_action(event_action: :created, event_target: wiki, author_id: 3)).to be_truthy
|
||||
expect(track_action(event_action: :updated, event_target: wiki, author_id: 4)).to be_truthy
|
||||
expect(track_action(event_action: :pushed, event_target: wiki, author_id: 6)).to be_truthy
|
||||
|
||||
expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time, date_to: Date.today)).to eq(3)
|
||||
expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time - 5.days, date_to: Date.tomorrow)).to eq(4)
|
||||
expect(count_unique_events(event_action: described_class::DESIGN_ACTION, date_from: time - 5.days, date_to: Date.today)).to eq(3)
|
||||
expect(count_unique_events(event_action: described_class::WIKI_ACTION, date_from: time - 5.days, date_to: Date.today)).to eq(3)
|
||||
expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time - 5.days, date_to: time - 2.days)).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tracking unsuccessfully' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:feature_flag, :application_setting, :target, :action) do
|
||||
true | true | Project | :invalid_action
|
||||
false | true | Project | :pushed
|
||||
true | false | Project | :pushed
|
||||
true | true | :invalid_target | :pushed
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
stub_application_setting(usage_ping_enabled: application_setting)
|
||||
stub_feature_flags(described_class::FEATURE_FLAG => feature_flag)
|
||||
end
|
||||
|
||||
it 'returns the expected values' do
|
||||
expect(track_action(event_action: action, event_target: target, author_id: 2)).to be_nil
|
||||
expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time, date_to: Date.today)).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -919,6 +919,53 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#action_monthly_active_users', :clean_gitlab_redis_shared_state do
|
||||
let(:time_period) { { created_at: 2.days.ago..time } }
|
||||
let(:time) { Time.zone.now }
|
||||
|
||||
before do
|
||||
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => feature_flag)
|
||||
end
|
||||
|
||||
context 'when the feature flag is enabled' do
|
||||
let(:feature_flag) { true }
|
||||
|
||||
before do
|
||||
counter = Gitlab::UsageDataCounters::TrackUniqueActions
|
||||
project = Event::TARGET_TYPES[:project]
|
||||
wiki = Event::TARGET_TYPES[:wiki]
|
||||
design = Event::TARGET_TYPES[:design]
|
||||
|
||||
counter.track_action(event_action: :pushed, event_target: project, author_id: 1)
|
||||
counter.track_action(event_action: :pushed, event_target: project, author_id: 1)
|
||||
counter.track_action(event_action: :pushed, event_target: project, author_id: 2)
|
||||
counter.track_action(event_action: :pushed, event_target: project, author_id: 3)
|
||||
counter.track_action(event_action: :pushed, event_target: project, author_id: 4, time: time - 3.days)
|
||||
counter.track_action(event_action: :created, event_target: project, author_id: 5, time: time - 3.days)
|
||||
counter.track_action(event_action: :created, event_target: wiki, author_id: 3)
|
||||
counter.track_action(event_action: :created, event_target: design, author_id: 3)
|
||||
end
|
||||
|
||||
it 'returns the distinct count of user actions within the specified time period' do
|
||||
expect(described_class.action_monthly_active_users(time_period)).to eq(
|
||||
{
|
||||
action_monthly_active_users_design_management: 1,
|
||||
action_monthly_active_users_project_repo: 3,
|
||||
action_monthly_active_users_wiki_repo: 1
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the feature flag is disabled' do
|
||||
let(:feature_flag) { false }
|
||||
|
||||
it 'returns an empty hash' do
|
||||
expect(described_class.action_monthly_active_users(time_period)).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.analytics_unique_visits_data' do
|
||||
subject { described_class.analytics_unique_visits_data }
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe JiraTrackerData do
|
||||
let(:service) { create(:jira_service, active: false) }
|
||||
let(:service) { build(:jira_service) }
|
||||
|
||||
describe 'Associations' do
|
||||
it { is_expected.to belong_to(:service) }
|
||||
|
|
|
@ -166,7 +166,7 @@ RSpec.describe EventCreateService do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#wiki_event' do
|
||||
describe '#wiki_event', :clean_gitlab_redis_shared_state do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:wiki_page) { create(:wiki_page) }
|
||||
let_it_be(:meta) { create(:wiki_page_meta, :for_wiki_page, wiki_page: wiki_page) }
|
||||
|
@ -186,6 +186,16 @@ RSpec.describe EventCreateService do
|
|||
)
|
||||
end
|
||||
|
||||
it 'records the event in the event counter' do
|
||||
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
|
||||
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
|
||||
tracking_params = { event_action: counter_class::WIKI_ACTION, date_from: Date.yesterday, date_to: Date.today }
|
||||
|
||||
expect { event }
|
||||
.to change { counter_class.count_unique_events(tracking_params) }
|
||||
.from(0).to(1)
|
||||
end
|
||||
|
||||
it 'is idempotent', :aggregate_failures do
|
||||
expect { event }.to change(Event, :count).by(1)
|
||||
duplicate = nil
|
||||
|
@ -224,6 +234,16 @@ RSpec.describe EventCreateService do
|
|||
subject { service.push(project, user, push_data) }
|
||||
|
||||
it_behaves_like 'service for creating a push event', PushEventPayloadService
|
||||
|
||||
it 'records the event in the event counter' do
|
||||
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
|
||||
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
|
||||
tracking_params = { event_action: counter_class::PUSH_ACTION, date_from: Date.yesterday, date_to: Date.today }
|
||||
|
||||
expect { subject }
|
||||
.to change { counter_class.count_unique_events(tracking_params) }
|
||||
.from(0).to(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#bulk_push', :clean_gitlab_redis_shared_state do
|
||||
|
@ -238,6 +258,16 @@ RSpec.describe EventCreateService do
|
|||
subject { service.bulk_push(project, user, push_data) }
|
||||
|
||||
it_behaves_like 'service for creating a push event', BulkPushEventPayloadService
|
||||
|
||||
it 'records the event in the event counter' do
|
||||
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
|
||||
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
|
||||
tracking_params = { event_action: counter_class::PUSH_ACTION, date_from: Date.yesterday, date_to: Date.today }
|
||||
|
||||
expect { subject }
|
||||
.to change { counter_class.count_unique_events(tracking_params) }
|
||||
.from(0).to(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Project' do
|
||||
|
@ -256,7 +286,7 @@ RSpec.describe EventCreateService do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'design events' do
|
||||
describe 'design events', :clean_gitlab_redis_shared_state do
|
||||
let_it_be(:design) { create(:design, project: project) }
|
||||
let_it_be(:author) { user }
|
||||
|
||||
|
@ -297,6 +327,16 @@ RSpec.describe EventCreateService do
|
|||
end
|
||||
|
||||
it_behaves_like 'feature flag gated multiple event creation'
|
||||
|
||||
it 'records the event in the event counter' do
|
||||
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
|
||||
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
|
||||
tracking_params = { event_action: counter_class::DESIGN_ACTION, date_from: Date.yesterday, date_to: Date.today }
|
||||
|
||||
expect { result }
|
||||
.to change { counter_class.count_unique_events(tracking_params) }
|
||||
.from(0).to(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy_designs' do
|
||||
|
@ -317,6 +357,16 @@ RSpec.describe EventCreateService do
|
|||
end
|
||||
|
||||
it_behaves_like 'feature flag gated multiple event creation'
|
||||
|
||||
it 'records the event in the event counter' do
|
||||
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
|
||||
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
|
||||
tracking_params = { event_action: counter_class::DESIGN_ACTION, date_from: Date.yesterday, date_to: Date.today }
|
||||
|
||||
expect { result }
|
||||
.to change { counter_class.count_unique_events(tracking_params) }
|
||||
.from(0).to(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe Notes::CreateService do
|
|||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let(:opts) do
|
||||
{ note: 'Awesome comment', noteable_type: 'Issue', noteable_id: issue.id }
|
||||
{ note: 'Awesome comment', noteable_type: 'Issue', noteable_id: issue.id, confidential: true }
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
|
|
|
@ -92,7 +92,7 @@ module StubObjectStorage
|
|||
def stub_terraform_state_object_storage(uploader = described_class, **params)
|
||||
stub_object_storage_uploader(config: Gitlab.config.terraform_state.object_store,
|
||||
uploader: uploader,
|
||||
remote_directory: 'terraform_state',
|
||||
remote_directory: 'terraform',
|
||||
**params)
|
||||
end
|
||||
|
||||
|
|
|
@ -132,6 +132,16 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
|
|||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response['body']).to eq('hi!')
|
||||
expect(json_response['confidential']).to be_falsey
|
||||
expect(json_response['author']['username']).to eq(user.username)
|
||||
end
|
||||
|
||||
it "creates a confidential note if confidential is set to true" do
|
||||
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: 'hi!', confidential: true }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response['body']).to eq('hi!')
|
||||
expect(json_response['confidential']).to be_truthy
|
||||
expect(json_response['author']['username']).to eq(user.username)
|
||||
end
|
||||
|
||||
|
|