Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1816056039
commit
5f95234f7b
|
|
@ -2369,11 +2369,9 @@
|
|||
- <<: *if-merge-request-labels-run-review-app
|
||||
- <<: *if-dot-com-gitlab-org-merge-request
|
||||
changes: *ci-review-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-dot-com-gitlab-org-merge-request
|
||||
changes: *frontend-build-patterns
|
||||
variables: *review-change-pattern
|
||||
allow_failure: true
|
||||
- <<: *if-dot-com-gitlab-org-merge-request
|
||||
changes: *controllers-patterns
|
||||
variables: *review-change-pattern
|
||||
|
|
@ -2391,7 +2389,6 @@
|
|||
allow_failure: true
|
||||
- <<: *if-dot-com-gitlab-org-merge-request
|
||||
changes: *qa-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-dot-com-gitlab-org-merge-request
|
||||
changes: *code-patterns
|
||||
when: manual
|
||||
|
|
|
|||
|
|
@ -78,6 +78,9 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapState(['isShowingLabels', 'allowSubEpics']),
|
||||
isLoading() {
|
||||
return this.item.isLoading || this.item.iid === '-1';
|
||||
},
|
||||
cappedAssignees() {
|
||||
// e.g. maxRender is 4,
|
||||
// Render up to all 4 assignees if there are only 4 assigness
|
||||
|
|
@ -243,7 +246,7 @@ export default {
|
|||
<a
|
||||
:href="item.path || item.webUrl || ''"
|
||||
:title="item.title"
|
||||
:class="{ 'gl-text-gray-400!': item.isLoading }"
|
||||
:class="{ 'gl-text-gray-400!': isLoading }"
|
||||
class="js-no-trigger gl-text-body gl-hover-text-gray-900"
|
||||
@mousemove.stop
|
||||
>{{ item.title }}</a
|
||||
|
|
@ -272,9 +275,9 @@ export default {
|
|||
<div
|
||||
class="gl-display-flex align-items-start flex-wrap-reverse board-card-number-container gl-overflow-hidden"
|
||||
>
|
||||
<gl-loading-icon v-if="item.isLoading" size="lg" class="gl-mt-5" />
|
||||
<gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-5" />
|
||||
<span
|
||||
v-if="item.referencePath"
|
||||
v-if="item.referencePath && !isLoading"
|
||||
class="board-card-number gl-overflow-hidden gl-display-flex gl-mr-3 gl-mt-3 gl-font-sm gl-text-secondary"
|
||||
:class="{ 'gl-font-base': isEpicBoard }"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
removeItemFromList,
|
||||
updateEpicsCount,
|
||||
updateIssueCountAndWeight,
|
||||
setError,
|
||||
} from '../graphql/cache_updates';
|
||||
import { shouldCloneCard, moveItemVariables } from '../boards_util';
|
||||
import eventHub from '../eventhub';
|
||||
|
|
@ -33,7 +34,7 @@ export default {
|
|||
name: 'BoardList',
|
||||
i18n: {
|
||||
loading: __('Loading'),
|
||||
loadingMoreboardItems: __('Loading more'),
|
||||
loadingMoreBoardItems: __('Loading more'),
|
||||
showingAllIssues: __('Showing all issues'),
|
||||
showingAllEpics: __('Showing all epics'),
|
||||
},
|
||||
|
|
@ -83,6 +84,7 @@ export default {
|
|||
isLoadingMore: false,
|
||||
toListId: null,
|
||||
toList: {},
|
||||
addItemToListInProgress: false,
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
|
|
@ -213,7 +215,8 @@ export default {
|
|||
return !this.disabled;
|
||||
},
|
||||
treeRootWrapper() {
|
||||
return this.canMoveIssue && !this.listsFlags[this.list.id]?.addItemToListInProgress
|
||||
return this.canMoveIssue &&
|
||||
(!this.listsFlags[this.list.id]?.addItemToListInProgress || this.addItemToListInProgress)
|
||||
? Draggable
|
||||
: 'ul';
|
||||
},
|
||||
|
|
@ -468,14 +471,14 @@ export default {
|
|||
|
||||
this.updateCountAndWeight({ fromListId, toListId, issuable, cache });
|
||||
},
|
||||
updateCountAndWeight({ fromListId, toListId, issuable, isAddingIssue, cache }) {
|
||||
updateCountAndWeight({ fromListId, toListId, issuable, isAddingItem, cache }) {
|
||||
if (!this.isEpicBoard) {
|
||||
updateIssueCountAndWeight({
|
||||
fromListId,
|
||||
toListId,
|
||||
filterParams: this.filterParams,
|
||||
issuable,
|
||||
shouldClone: isAddingIssue || this.shouldCloneCard,
|
||||
shouldClone: isAddingItem || this.shouldCloneCard,
|
||||
cache,
|
||||
});
|
||||
} else {
|
||||
|
|
@ -486,7 +489,7 @@ export default {
|
|||
fromListId,
|
||||
filterParams,
|
||||
issuable,
|
||||
shouldClone: this.shouldCloneCard,
|
||||
shouldClone: isAddingItem || this.shouldCloneCard,
|
||||
cache,
|
||||
});
|
||||
}
|
||||
|
|
@ -538,6 +541,59 @@ export default {
|
|||
},
|
||||
});
|
||||
},
|
||||
async addListItem(input) {
|
||||
this.toggleForm();
|
||||
this.addItemToListInProgress = true;
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: listIssuablesQueries[this.issuableType].createMutation,
|
||||
variables: {
|
||||
input: this.isEpicBoard ? input : { ...input, moveAfterId: this.boardListItems[0]?.id },
|
||||
withColor: this.isEpicBoard && this.glFeatures.epicColorHighlight,
|
||||
},
|
||||
update: (cache, { data: { createIssuable } }) => {
|
||||
const { issuable } = createIssuable;
|
||||
addItemToList({
|
||||
query: listIssuablesQueries[this.issuableType].query,
|
||||
variables: { ...this.listQueryVariables, id: this.currentList.id },
|
||||
issuable,
|
||||
newIndex: 0,
|
||||
boardType: this.boardType,
|
||||
issuableType: this.issuableType,
|
||||
cache,
|
||||
});
|
||||
this.updateCountAndWeight({
|
||||
fromListId: null,
|
||||
toListId: this.list.id,
|
||||
issuable,
|
||||
isAddingItem: true,
|
||||
cache,
|
||||
});
|
||||
},
|
||||
optimisticResponse: {
|
||||
createIssuable: {
|
||||
errors: [],
|
||||
issuable: {
|
||||
...listIssuablesQueries[this.issuableType].optimisticResponse,
|
||||
title: input.title,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
setError({
|
||||
message: sprintf(
|
||||
__('An error occurred while creating the %{issuableType}. Please try again.'),
|
||||
{
|
||||
issuableType: this.isEpicBoard ? 'epic' : 'issue',
|
||||
},
|
||||
),
|
||||
error,
|
||||
});
|
||||
} finally {
|
||||
this.addItemToListInProgress = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -556,8 +612,18 @@ export default {
|
|||
>
|
||||
<gl-loading-icon size="sm" />
|
||||
</div>
|
||||
<board-new-issue v-if="issueCreateFormVisible" :list="list" />
|
||||
<board-new-epic v-if="epicCreateFormVisible" :list="list" />
|
||||
<board-new-issue
|
||||
v-if="issueCreateFormVisible"
|
||||
:list="list"
|
||||
:board-id="boardId"
|
||||
@addNewIssue="addListItem"
|
||||
/>
|
||||
<board-new-epic
|
||||
v-if="epicCreateFormVisible"
|
||||
:list="list"
|
||||
:board-id="boardId"
|
||||
@addNewEpic="addListItem"
|
||||
/>
|
||||
<component
|
||||
:is="treeRootWrapper"
|
||||
v-show="!loading"
|
||||
|
|
@ -610,7 +676,7 @@ export default {
|
|||
<gl-loading-icon
|
||||
v-if="loadingMore"
|
||||
size="sm"
|
||||
:label="$options.i18n.loadingMoreboardItems"
|
||||
:label="$options.i18n.loadingMoreBoardItems"
|
||||
/>
|
||||
<span v-if="showingAllItems">{{ showingAllItemsText }}</span>
|
||||
<span v-else>{{ paginatedIssueText }}</span>
|
||||
|
|
|
|||
|
|
@ -1,33 +1,72 @@
|
|||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { getMilestone } from 'ee_else_ce/boards/boards_util';
|
||||
import { s__ } from '~/locale';
|
||||
import { getMilestone, formatIssueInput, getBoardQuery } from 'ee_else_ce/boards/boards_util';
|
||||
import BoardNewIssueMixin from 'ee_else_ce/boards/mixins/board_new_issue';
|
||||
|
||||
import { toggleFormEventPrefix } from '../constants';
|
||||
import eventHub from '../eventhub';
|
||||
import { setError } from '../graphql/cache_updates';
|
||||
|
||||
import BoardNewItem from './board_new_item.vue';
|
||||
import ProjectSelect from './project_select.vue';
|
||||
|
||||
export default {
|
||||
name: 'BoardNewIssue',
|
||||
i18n: {
|
||||
errorFetchingBoard: s__('Boards|An error occurred while fetching board. Please try again.'),
|
||||
},
|
||||
components: {
|
||||
BoardNewItem,
|
||||
ProjectSelect,
|
||||
},
|
||||
mixins: [BoardNewIssueMixin],
|
||||
inject: ['fullPath', 'isGroupBoard'],
|
||||
inject: ['boardType', 'groupId', 'fullPath', 'isGroupBoard', 'isEpicBoard', 'isApolloBoard'],
|
||||
props: {
|
||||
list: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
boardId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedProject: {},
|
||||
board: {},
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
board: {
|
||||
query() {
|
||||
return getBoardQuery(this.boardType, this.isEpicBoard);
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
boardId: this.boardId,
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
return !this.isApolloBoard;
|
||||
},
|
||||
update(data) {
|
||||
const { board } = data.workspace;
|
||||
return {
|
||||
...board,
|
||||
labels: board.labels?.nodes,
|
||||
};
|
||||
},
|
||||
error(error) {
|
||||
setError({
|
||||
error,
|
||||
message: this.$options.i18n.errorFetchingBoard,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['getBoardItemsByList']),
|
||||
formEventPrefix() {
|
||||
|
|
@ -46,8 +85,20 @@ export default {
|
|||
const labels = this.list.label ? [this.list.label] : [];
|
||||
const assignees = this.list.assignee ? [this.list.assignee] : [];
|
||||
const milestone = getMilestone(this.list);
|
||||
const firstItemId = this.getBoardItemsByList(this.list.id)[0]?.id;
|
||||
|
||||
if (this.isApolloBoard) {
|
||||
return this.addNewIssueToList({
|
||||
issueInput: {
|
||||
title,
|
||||
labelIds: labels?.map((l) => l.id),
|
||||
assigneeIds: assignees?.map((a) => a?.id),
|
||||
milestoneId: milestone?.id,
|
||||
projectPath: this.projectPath,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const firstItemId = this.getBoardItemsByList(this.list.id)[0]?.id;
|
||||
return this.addListNewIssue({
|
||||
list: this.list,
|
||||
issueInput: {
|
||||
|
|
@ -62,6 +113,22 @@ export default {
|
|||
this.cancel();
|
||||
});
|
||||
},
|
||||
addNewIssueToList({ issueInput }) {
|
||||
const { labels, assignee, milestone, weight } = this.board;
|
||||
const config = {
|
||||
labels,
|
||||
assigneeId: assignee?.id || null,
|
||||
milestoneId: milestone?.id || null,
|
||||
weight,
|
||||
};
|
||||
const input = formatIssueInput(issueInput, config);
|
||||
|
||||
if (!this.isGroupBoard) {
|
||||
input.projectPath = this.fullPath;
|
||||
}
|
||||
|
||||
this.$emit('addNewIssue', input);
|
||||
},
|
||||
cancel() {
|
||||
eventHub.$emit(`${this.formEventPrefix}${this.list.id}`);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
|
||||
import { TYPE_EPIC, TYPE_ISSUE, WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
|
||||
import { s__, __ } from '~/locale';
|
||||
import { TYPENAME_ISSUE } from '~/graphql_shared/constants';
|
||||
import updateEpicSubscriptionMutation from '~/sidebar/queries/update_epic_subscription.mutation.graphql';
|
||||
import updateEpicTitleMutation from '~/sidebar/queries/update_epic_title.mutation.graphql';
|
||||
import createBoardListMutation from './graphql/board_list_create.mutation.graphql';
|
||||
|
|
@ -11,6 +12,7 @@ import toggleListCollapsedMutation from './graphql/client/board_toggle_collapsed
|
|||
import issueSetSubscriptionMutation from './graphql/issue_set_subscription.mutation.graphql';
|
||||
import issueSetTitleMutation from './graphql/issue_set_title.mutation.graphql';
|
||||
import issueMoveListMutation from './graphql/issue_move_list.mutation.graphql';
|
||||
import issueCreateMutation from './graphql/issue_create.mutation.graphql';
|
||||
import groupBoardQuery from './graphql/group_board.query.graphql';
|
||||
import projectBoardQuery from './graphql/project_board.query.graphql';
|
||||
import listIssuesQuery from './graphql/lists_issues.query.graphql';
|
||||
|
|
@ -126,6 +128,30 @@ export const listIssuablesQueries = {
|
|||
[TYPE_ISSUE]: {
|
||||
query: listIssuesQuery,
|
||||
moveMutation: issueMoveListMutation,
|
||||
createMutation: issueCreateMutation,
|
||||
optimisticResponse: {
|
||||
assignees: { nodes: [], __typename: 'UserCoreConnection' },
|
||||
confidential: false,
|
||||
dueDate: null,
|
||||
emailsDisabled: false,
|
||||
hidden: false,
|
||||
humanTimeEstimate: null,
|
||||
humanTotalTimeSpent: null,
|
||||
id: 'gid://gitlab/Issue/-1',
|
||||
iid: '-1',
|
||||
labels: { nodes: [], __typename: 'LabelConnection' },
|
||||
milestone: null,
|
||||
referencePath: '',
|
||||
relativePosition: null,
|
||||
severity: 'UNKNOWN',
|
||||
timeEstimate: 0,
|
||||
title: '',
|
||||
totalTimeSpent: 0,
|
||||
type: 'ISSUE',
|
||||
webUrl: '',
|
||||
weight: null,
|
||||
__typename: TYPENAME_ISSUE,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#import "ee_else_ce/boards/graphql/issue.fragment.graphql"
|
||||
|
||||
mutation CreateIssue($input: CreateIssueInput!) {
|
||||
createIssue(input: $input) {
|
||||
issue {
|
||||
createIssuable: createIssue(input: $input) {
|
||||
issuable: issue {
|
||||
...Issue
|
||||
}
|
||||
errors
|
||||
|
|
|
|||
|
|
@ -743,11 +743,11 @@ export default {
|
|||
},
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.createIssue.errors.length) {
|
||||
if (data.createIssuable.errors.length) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const rawIssue = data.createIssue?.issue;
|
||||
const rawIssue = data.createIssuable?.issuable;
|
||||
const formattedIssue = formatIssue(rawIssue);
|
||||
dispatch('removeListItem', { listId: list.id, itemId: placeholderId });
|
||||
dispatch('addListItem', { list, item: formattedIssue, position: 0 });
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ module MembershipActions
|
|||
member_data = if member.expires?
|
||||
{
|
||||
expires_soon: member.expires_soon?,
|
||||
expires_at_formatted: member.expires_at.to_time.in_time_zone.to_s(:medium)
|
||||
expires_at_formatted: member.expires_at.to_time.in_time_zone.to_fs(:medium)
|
||||
}
|
||||
else
|
||||
{}
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class EmptyRepoUploadExperiment < ApplicationExperiment
|
||||
include ProjectCommitCount
|
||||
|
||||
TRACKING_START_DATE = DateTime.parse('2021/4/20')
|
||||
INITIAL_COMMIT_COUNT = 1
|
||||
|
||||
def track_initial_write
|
||||
return unless should_track? # early return if we don't need to ask for commit counts
|
||||
return unless context.project.created_at > TRACKING_START_DATE # early return for older projects
|
||||
return unless commit_count == INITIAL_COMMIT_COUNT
|
||||
|
||||
track(:initial_write, project: context.project)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def commit_count
|
||||
commit_count_for(context.project, max_count: INITIAL_COMMIT_COUNT, experiment: name)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ForceCompanyTrialExperiment < ApplicationExperiment
|
||||
exclude :setup_for_personal
|
||||
|
||||
private
|
||||
|
||||
def setup_for_personal
|
||||
!context.user.setup_for_company
|
||||
end
|
||||
end
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class LoggedOutMarketingHeaderExperiment < ApplicationExperiment
|
||||
# These default behaviors are overriden in ApplicationHelper and header
|
||||
# template partial
|
||||
control {}
|
||||
candidate {}
|
||||
variant(:trial_focused) {}
|
||||
end
|
||||
|
|
@ -17,7 +17,7 @@ module Mutations
|
|||
description: 'Whether the integration is receiving alerts.'
|
||||
|
||||
argument :api_url, GraphQL::Types::String,
|
||||
required: true,
|
||||
required: false,
|
||||
description: 'Endpoint at which Prometheus can be queried.'
|
||||
|
||||
def resolve(args)
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ module IssuablesHelper
|
|||
def due_date_with_remaining_days(due_date, start_date = nil)
|
||||
return unless due_date
|
||||
|
||||
"#{due_date.to_s(:medium)} (#{remaining_days_in_words(due_date, start_date)})"
|
||||
"#{due_date.to_fs(:medium)} (#{remaining_days_in_words(due_date, start_date)})"
|
||||
end
|
||||
|
||||
def multi_label_name(current_labels, default_label)
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ module TimeboxesHelper
|
|||
content = [
|
||||
title,
|
||||
"<br />",
|
||||
date.to_s(:medium),
|
||||
date.to_fs(:medium),
|
||||
"(#{time_ago} #{state})"
|
||||
].join(" ")
|
||||
|
||||
|
|
@ -172,7 +172,7 @@ module TimeboxesHelper
|
|||
|
||||
def milestone_tooltip_due_date(milestone)
|
||||
if milestone.due_date
|
||||
"#{milestone.due_date.to_s(:medium)} (#{remaining_days_in_words(milestone.due_date, milestone.start_date)})"
|
||||
"#{milestone.due_date.to_fs(:medium)} (#{remaining_days_in_words(milestone.due_date, milestone.start_date)})"
|
||||
else
|
||||
_('Milestone')
|
||||
end
|
||||
|
|
|
|||
|
|
@ -90,9 +90,9 @@ module Milestoneish
|
|||
def expires_at
|
||||
if due_date
|
||||
if due_date.past?
|
||||
"expired on #{due_date.to_s(:medium)}"
|
||||
"expired on #{due_date.to_fs(:medium)}"
|
||||
else
|
||||
"expires on #{due_date.to_s(:medium)}"
|
||||
"expires on #{due_date.to_fs(:medium)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@ class Deployment < ApplicationRecord
|
|||
end
|
||||
|
||||
def formatted_deployment_time
|
||||
deployed_at&.to_time&.in_time_zone&.to_s(:medium)
|
||||
deployed_at&.to_time&.in_time_zone&.to_fs(:medium)
|
||||
end
|
||||
|
||||
def deployed_by
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ module Integrations
|
|||
title: 'API URL',
|
||||
placeholder: -> { s_('PrometheusService|https://prometheus.example.com/') },
|
||||
help: -> { s_('PrometheusService|The Prometheus API base URL.') },
|
||||
required: true
|
||||
required: false
|
||||
|
||||
field :google_iap_audience_client_id,
|
||||
title: 'Google IAP Audience Client ID',
|
||||
|
|
@ -34,8 +34,8 @@ module Integrations
|
|||
# to allow localhost URLs when the following conditions are true:
|
||||
# 1. api_url is the internal Prometheus URL.
|
||||
with_options presence: true do
|
||||
validates :api_url, public_url: true, if: ->(object) { object.manual_configuration? && !object.allow_local_api_url? }
|
||||
validates :api_url, url: true, if: ->(object) { object.manual_configuration? && object.allow_local_api_url? }
|
||||
validates :api_url, public_url: true, if: ->(object) { object.api_url.present? && object.manual_configuration? && !object.allow_local_api_url? }
|
||||
validates :api_url, url: true, if: ->(object) { object.api_url.present? && object.manual_configuration? && object.allow_local_api_url? }
|
||||
end
|
||||
|
||||
before_save :synchronize_service_state
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
%li
|
||||
%span.light= _('Created on:')
|
||||
%strong
|
||||
= @group.created_at.to_s(:medium)
|
||||
= @group.created_at.to_fs(:medium)
|
||||
|
||||
%li
|
||||
%span.light= _('ID:')
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
%span.light
|
||||
= _('Created on:')
|
||||
%strong
|
||||
= @project.created_at.to_s(:medium)
|
||||
= @project.created_at.to_fs(:medium)
|
||||
|
||||
%li{ class: 'gl-px-5!' }
|
||||
%span.light
|
||||
|
|
@ -158,10 +158,10 @@
|
|||
= _("This repository has never been checked.")
|
||||
- elsif @project.last_repository_check_failed?
|
||||
- failed_message = _("This repository was last checked %{last_check_timestamp}. The check %{strong_start}failed.%{strong_end} See the 'repocheck.log' file for error messages.")
|
||||
- failed_message = failed_message % { last_check_timestamp: @project.last_repository_check_at.to_s(:medium), strong_start: "<strong class='cred'>", strong_end: "</strong>" }
|
||||
- failed_message = failed_message % { last_check_timestamp: @project.last_repository_check_at.to_fs(:medium), strong_start: "<strong class='cred'>", strong_end: "</strong>" }
|
||||
= failed_message.html_safe
|
||||
- else
|
||||
= _("This repository was last checked %{last_check_timestamp}. The check passed.") % { last_check_timestamp: @project.last_repository_check_at.to_s(:medium) }
|
||||
= _("This repository was last checked %{last_check_timestamp}. The check passed.") % { last_check_timestamp: @project.last_repository_check_at.to_fs(:medium) }
|
||||
|
||||
= link_to sprite_icon('question-o'), help_page_path('administration/repository_checks')
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
%ul.content-list
|
||||
%li
|
||||
%span.light= _('Member since')
|
||||
%strong= user.created_at.to_s(:medium)
|
||||
%strong= user.created_at.to_fs(:medium)
|
||||
- unless user.public_email.blank?
|
||||
%li
|
||||
%span.light= _('E-mail:')
|
||||
|
|
|
|||
|
|
@ -86,12 +86,12 @@
|
|||
%li
|
||||
%span.light= _('Member since:')
|
||||
%strong
|
||||
= @user.created_at.to_s(:medium)
|
||||
= @user.created_at.to_fs(:medium)
|
||||
- if @user.confirmed_at
|
||||
%li
|
||||
%span.light= _('Confirmed at:')
|
||||
%strong
|
||||
= @user.confirmed_at.to_s(:medium)
|
||||
= @user.confirmed_at.to_fs(:medium)
|
||||
- else
|
||||
%li
|
||||
%span.ligh= _('Confirmed:')
|
||||
|
|
@ -106,7 +106,7 @@
|
|||
%li
|
||||
%span.light= _('Current sign-in at:')
|
||||
%strong
|
||||
= @user.current_sign_in_at&.to_s(:medium) || _('never')
|
||||
= @user.current_sign_in_at&.to_fs(:medium) || _('never')
|
||||
|
||||
%li
|
||||
%span.light= _('Last sign-in IP:')
|
||||
|
|
@ -116,7 +116,7 @@
|
|||
%li
|
||||
%span.light= _('Last sign-in at:')
|
||||
%strong
|
||||
= @user.last_sign_in_at&.to_s(:medium) || _('never')
|
||||
= @user.last_sign_in_at&.to_fs(:medium) || _('never')
|
||||
|
||||
%li
|
||||
%span.light= _('Sign-in count:')
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
%p
|
||||
= assignees_label(@issue)
|
||||
%p
|
||||
= sprintf(s_('Notify|This issue is due on: %{issue_due_date}'), { issue_due_date: @issue.due_date.to_s(:medium) }).html_safe
|
||||
= sprintf(s_('Notify|This issue is due on: %{issue_due_date}'), { issue_due_date: @issue.due_date.to_fs(:medium) }).html_safe
|
||||
|
||||
- if @issue.description
|
||||
.md
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
%strong= ssh_key_usage_types.invert[@key.usage_type]
|
||||
%li
|
||||
%span.light= _('Created on:')
|
||||
%strong= @key.created_at.to_s(:medium)
|
||||
%strong= @key.created_at.to_fs(:medium)
|
||||
%li
|
||||
%span.light= _('Expires:')
|
||||
%strong= @key.expires_at.try(:to_s, :medium) || _('Never')
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@
|
|||
- else
|
||||
%span.gl-text-gray-500
|
||||
= _("no name set")
|
||||
%td= registration[:created_at].to_date.to_s(:medium)
|
||||
%td= registration[:created_at].to_date.to_fs(:medium)
|
||||
%td
|
||||
= render Pajamas::ButtonComponent.new(variant: :danger,
|
||||
href: registration[:delete_path],
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
%span.issuable-due-date.d-none.d-sm-inline-block.has-tooltip{ class: "#{'cred' if issue.overdue? && !issue.closed?}", title: _('Due date') }
|
||||
|
||||
= sprite_icon('calendar')
|
||||
= issue.due_date.to_s(:medium)
|
||||
= issue.due_date.to_fs(:medium)
|
||||
|
||||
= render_if_exists "projects/issues/issue_weight", issue: issue
|
||||
= render_if_exists "projects/issues/health_status", issue: issue
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
%tr
|
||||
%td= token.name
|
||||
%td= token.username
|
||||
%td= token.created_at.to_date.to_s(:medium)
|
||||
%td= token.created_at.to_date.to_fs(:medium)
|
||||
%td
|
||||
- if token.expires?
|
||||
%span{ class: ('text-warning' if token.expires_soon?) }
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
= _("Given access %{time_ago}").html_safe % { time_ago: time_ago_with_tooltip(member.created_at) }
|
||||
%span.js-expires-in{ class: ('gl-display-none' unless member.expires?) }
|
||||
·
|
||||
%span.js-expires-in-text{ class: "has-tooltip#{' text-warning' if member.expires_soon?}", title: (member.expires_at.to_time.in_time_zone.to_s(:medium) if member.expires?) }
|
||||
%span.js-expires-in-text{ class: "has-tooltip#{' text-warning' if member.expires_soon?}", title: (member.expires_at.to_time.in_time_zone.to_fs(:medium) if member.expires?) }
|
||||
- if member.expires?
|
||||
- preposition = current_user.time_display_relative ? '' : 'on'
|
||||
= _("Expires %{preposition} %{expires_at}").html_safe % { expires_at: time_ago_with_tooltip(member.expires_at), preposition: preposition }
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
.value
|
||||
%span.value-content{ data: { qa_selector: 'start_date_content' } }
|
||||
- if milestone.start_date
|
||||
%span.bold= milestone.start_date.to_s(:medium)
|
||||
%span.bold= milestone.start_date.to_fs(:medium)
|
||||
- else
|
||||
%span.no-value= s_('MilestoneSidebar|No start date')
|
||||
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
.value.hide-collapsed
|
||||
%span.value-content{ data: { qa_selector: 'due_date_content' } }
|
||||
- if milestone.due_date
|
||||
%span.bold= milestone.due_date.to_s(:medium)
|
||||
%span.bold= milestone.due_date.to_fs(:medium)
|
||||
- else
|
||||
%span.no-value= s_('MilestoneSidebar|No due date')
|
||||
- remaining_days = remaining_days_in_words(milestone.due_date, milestone.start_date)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
%h4.prepend-top-20
|
||||
= html_escape(_("Contributions for %{calendar_date}")) % { calendar_date: tag.strong(@calendar_date.to_s(:medium)) }
|
||||
= html_escape(_("Contributions for %{calendar_date}")) % { calendar_date: tag.strong(@calendar_date.to_fs(:medium)) }
|
||||
|
||||
- if @events.any?
|
||||
%ul.bordered-list
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
unless experiments.class_files_removed?
|
||||
msg = "This merge request removes experiment: `#{experiments.removed_experiments.join(',')}`" \
|
||||
", please also remove the class file."
|
||||
fail msg # rubocop:disable Style/SignalException
|
||||
end
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../tooling/danger/experiments'
|
||||
|
||||
module Danger
|
||||
class Experiments < ::Danger::Plugin
|
||||
# Put the helper code somewhere it can be tested
|
||||
include Tooling::Danger::Experiments
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
- title: "Deprecate `CiRunner` GraphQL fields duplicated in `CiRunnerManager`" # (required) The name of the feature to be deprecated
|
||||
announcement_milestone: "16.2" # (required) The milestone when this feature was first announced as deprecated.
|
||||
announcement_date: "2023-07-22" # (required) The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
|
||||
removal_milestone: "17.0" # (required) The milestone when this feature is planned to be removed
|
||||
removal_date: "2024-05-22" # (required) The date of the milestone release when this feature is planned to be removed. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
|
||||
breaking_change: true # (required) If this deprecation is a breaking change, set this value to true
|
||||
reporter: DarrenEastman # (required) GitLab username of the person reporting the deprecation
|
||||
stage: Verify # (required) String value of the stage that the feature was created in. e.g., Growth
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/41518 # (required) Link to the deprecation issue in GitLab
|
||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||
These fields (`architectureName`, `ipAddress`, `platformName`, `revision`, `version`) are now deprecated from the [GraphQL `CiRunner`](https://docs.gitlab.com/ee/api/graphql/reference/#cirunner) type as they are duplicated with the introduction of runner managers grouped within a runner configuration.
|
||||
end_of_support_milestone: "17.0" # (optional) Use "XX.YY" format. The milestone when support for this feature will end.
|
||||
end_of_support_date: "2024-05-22" # (optional) The date of the milestone release when support for this feature will end.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateFkMlCandidatesOnUserId < Gitlab::Database::Migration[2.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
NEW_CONSTRAINT_NAME = 'fk_ml_candidates_on_user_id'
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key(
|
||||
:ml_candidates,
|
||||
:users,
|
||||
column: :user_id,
|
||||
on_delete: :nullify,
|
||||
validate: false,
|
||||
name: NEW_CONSTRAINT_NAME
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(
|
||||
:ml_candidates,
|
||||
column: :user_id,
|
||||
on_delete: :nullify,
|
||||
name: NEW_CONSTRAINT_NAME
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ValidateFkMlCandidatesOnUserId < Gitlab::Database::Migration[2.1]
|
||||
NEW_CONSTRAINT_NAME = 'fk_ml_candidates_on_user_id'
|
||||
|
||||
def up
|
||||
validate_foreign_key(:ml_candidates, :user_id, name: NEW_CONSTRAINT_NAME)
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveOldFkMlCandidatesOnUserId < Gitlab::Database::Migration[2.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
OLD_CONSTRAINT_NAME = 'fk_rails_1b37441fe5'
|
||||
|
||||
def up
|
||||
remove_foreign_key_if_exists(:ml_candidates, column: :user_id, name: OLD_CONSTRAINT_NAME)
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_foreign_key(
|
||||
:ml_candidates,
|
||||
:users,
|
||||
column: :user_id,
|
||||
validate: false,
|
||||
name: OLD_CONSTRAINT_NAME
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
90b19651bc3f69a2e104a94ec4b9c4a758b4e258b49dad19b9795d1574c51946
|
||||
|
|
@ -0,0 +1 @@
|
|||
1cca0c19cc117465e14bf52ad8aadbf61c4c6e4c3fbc17a77138dd1f40fad902
|
||||
|
|
@ -0,0 +1 @@
|
|||
fc7195a78541583e95a007594b393d0cd67f942c275efb8a43c6953128e8b4ec
|
||||
|
|
@ -36336,6 +36336,9 @@ ALTER TABLE ONLY ml_candidate_metrics
|
|||
ALTER TABLE ONLY ml_candidate_params
|
||||
ADD CONSTRAINT fk_ml_candidate_params_on_candidate_id FOREIGN KEY (candidate_id) REFERENCES ml_candidates(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ml_candidates
|
||||
ADD CONSTRAINT fk_ml_candidates_on_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY path_locks
|
||||
ADD CONSTRAINT fk_path_locks_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -36531,9 +36534,6 @@ ALTER TABLE ONLY vulnerability_user_mentions
|
|||
ALTER TABLE ONLY packages_debian_file_metadata
|
||||
ADD CONSTRAINT fk_rails_1ae85be112 FOREIGN KEY (package_file_id) REFERENCES packages_package_files(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ml_candidates
|
||||
ADD CONSTRAINT fk_rails_1b37441fe5 FOREIGN KEY (user_id) REFERENCES users(id);
|
||||
|
||||
ALTER TABLE ONLY issuable_slas
|
||||
ADD CONSTRAINT fk_rails_1b8768cd63 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -5416,7 +5416,7 @@ Input type: `PrometheusIntegrationCreateInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationprometheusintegrationcreateactive"></a>`active` | [`Boolean!`](#boolean) | Whether the integration is receiving alerts. |
|
||||
| <a id="mutationprometheusintegrationcreateapiurl"></a>`apiUrl` | [`String!`](#string) | Endpoint at which Prometheus can be queried. |
|
||||
| <a id="mutationprometheusintegrationcreateapiurl"></a>`apiUrl` | [`String`](#string) | Endpoint at which Prometheus can be queried. |
|
||||
| <a id="mutationprometheusintegrationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationprometheusintegrationcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Project to create the integration in. |
|
||||
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ listed in the descriptions of the relevant settings.
|
|||
| `authorized_keys_enabled` | boolean | no | By default, we write to the `authorized_keys` file to support Git over SSH without additional configuration. GitLab can be optimized to authenticate SSH keys via the database file. Only disable this if you have configured your OpenSSH server to use the AuthorizedKeysCommand. |
|
||||
| `auto_devops_domain` | string | no | Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages. |
|
||||
| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for projects by default. It automatically builds, tests, and deploys applications based on a predefined CI/CD configuration. |
|
||||
| `automatic_purchased_storage_allocation` | boolean | no | Enabling this permits automatic allocation of purchased storage in a namespace. |
|
||||
| `automatic_purchased_storage_allocation` | boolean | no | Enabling this permits automatic allocation of purchased storage in a namespace. Relevant only to EE distributions. |
|
||||
| `bulk_import_enabled` | boolean | no | Enable migrating GitLab groups by direct transfer. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/383268) in GitLab 15.8. Setting also [available](../user/admin_area/settings/visibility_and_access_controls.md#enable-migration-of-groups-and-projects-by-direct-transfer) in the Admin Area. |
|
||||
| `can_create_group` | boolean | no | Indicates whether users can create top-level groups. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367754) in GitLab 15.5. Defaults to `true`. |
|
||||
| `check_namespace_plan` **(PREMIUM)** | boolean | no | Enabling this makes only licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public. |
|
||||
|
|
|
|||
|
|
@ -30,11 +30,17 @@ Base type for issue, requirement, test case, incident and task (this list is pla
|
|||
|
||||
A set of predefined types for different categories of work items. Currently, the available types are:
|
||||
|
||||
- Issue
|
||||
- Incident
|
||||
- Test case
|
||||
- Requirement
|
||||
- Task
|
||||
- [Incident](/ee/operations/incident_management/incidents.md)
|
||||
- [Test case](/ee/ci/test_cases/index.md)
|
||||
- [Requirement](/ee/user/project/requirements/index.md)
|
||||
- [Task](/ee/user/tasks.md)
|
||||
- [OKRs](/ee/user/okrs.md)
|
||||
|
||||
Work is underway to convert existing objects to Work Item Types or add new ones:
|
||||
|
||||
- [Issue](https://gitlab.com/groups/gitlab-org/-/epics/9584)
|
||||
- [Epic](https://gitlab.com/groups/gitlab-org/-/epics/9290)
|
||||
- [Ticket](https://gitlab.com/groups/gitlab-org/-/epics/10419)
|
||||
|
||||
#### Work Item properties
|
||||
|
||||
|
|
@ -58,7 +64,7 @@ All Work Item types share the same pool of predefined widgets and are customized
|
|||
### Work Item widget types (updating)
|
||||
|
||||
| widget type | feature flag |
|
||||
|---|---|---|
|
||||
|---|---|
|
||||
| assignees | |
|
||||
| description | |
|
||||
| hierarchy | |
|
||||
|
|
|
|||
|
|
@ -882,3 +882,22 @@ WARNING:
|
|||
If you add `CI_DEBUG_TRACE` as a local variable to runners, debug logs generate and are visible
|
||||
to all users with access to job logs. The permission levels are not checked by the runner,
|
||||
so you should only use the variable in GitLab itself.
|
||||
|
||||
## Known issues and workarounds
|
||||
|
||||
These are some know issues with CI/CD variables, and where applicable, known workarounds.
|
||||
|
||||
### "argument list too long"
|
||||
|
||||
This issue occurs when the combined length of all CI/CD variables defined for a job exceeds the limit imposed by the
|
||||
shell where the job executes. This includes the names and values of pre-defined and user defined variables. This limit
|
||||
is typically referred to as `ARG_MAX`, and is shell and operating system dependent. This issue also occurs when the
|
||||
content of a single [File-type](#use-file-type-cicd-variables) variable exceeds `ARG_MAX`.
|
||||
|
||||
For more information, see [issue 392406](https://gitlab.com/gitlab-org/gitlab/-/issues/392406#note_1414219596).
|
||||
|
||||
As a workaround you can either:
|
||||
|
||||
- Use [File-type](#use-file-type-cicd-variables) CI/CD variables for large environment variables where possible.
|
||||
- If a single large variable is larger than `ARG_MAX`, try using [Secure Files](../secure_files/index.md), or
|
||||
bring the file to the job through some other mechanism.
|
||||
|
|
|
|||
|
|
@ -1575,7 +1575,7 @@ Helpers should follow the Rails naming / namespacing convention, where
|
|||
module Features
|
||||
module IterationHelpers
|
||||
def iteration_period(iteration)
|
||||
"#{iteration.start_date.to_s(:medium)} - #{iteration.due_date.to_s(:medium)}"
|
||||
"#{iteration.start_date.to_fs(:medium)} - #{iteration.due_date.to_fs(:medium)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -90,6 +90,9 @@ For more information, see the [Atlassian documentation](https://confluence.atlas
|
|||
|
||||
### Use a prefix
|
||||
|
||||
You can define a prefix for GitLab to match Jira issue keys. For example, if your Jira issue ID is `ALPHA-1`
|
||||
and you've set a `JIRA#` prefix, GitLab matches `JIRA#ALPHA-1` rather than `ALPHA-1`.
|
||||
|
||||
To define a prefix for Jira issue keys:
|
||||
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
|
|
|
|||
|
|
@ -144,6 +144,21 @@ In GitLab 11.11 the Windows Batch executor, the CMD shell was deprecated in GitL
|
|||
|
||||
<div class="deprecation breaking-change" data-milestone="17.0">
|
||||
|
||||
### Deprecate `CiRunner` GraphQL fields duplicated in `CiRunnerManager`
|
||||
|
||||
<div class="deprecation-notes">
|
||||
- Announced in: GitLab <span class="milestone">16.2</span>
|
||||
- End of Support: GitLab <span class="milestone">17.0</span>
|
||||
- This is a [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/41518).
|
||||
</div>
|
||||
|
||||
These fields (`architectureName`, `ipAddress`, `platformName`, `revision`, `version`) are now deprecated from the [GraphQL `CiRunner`](https://docs.gitlab.com/ee/api/graphql/reference/#cirunner) type as they are duplicated with the introduction of runner managers grouped within a runner configuration.
|
||||
|
||||
</div>
|
||||
|
||||
<div class="deprecation breaking-change" data-milestone="17.0">
|
||||
|
||||
### Deprecate `message` field from Vulnerability Management features
|
||||
|
||||
<div class="deprecation-notes">
|
||||
|
|
|
|||
|
|
@ -4857,6 +4857,9 @@ msgstr ""
|
|||
msgid "An error occurred while checking group path. Please refresh and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred while creating the %{issuableType}. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred while decoding the file."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -7767,6 +7770,9 @@ msgstr ""
|
|||
msgid "Boards|An error occurred while creating the list. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Boards|An error occurred while fetching board. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Boards|An error occurred while fetching child groups. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ RSpec.describe Groups::GroupMembersController do
|
|||
it 'returns correct json response' do
|
||||
expect(json_response).to eq({
|
||||
"expires_soon" => false,
|
||||
"expires_at_formatted" => expiry_date.to_time.in_time_zone.to_s(:medium)
|
||||
"expires_at_formatted" => expiry_date.to_time.in_time_zone.to_fs(:medium)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ RSpec.describe Projects::ProjectMembersController do
|
|||
it 'returns correct json response' do
|
||||
expect(json_response).to eq({
|
||||
"expires_soon" => false,
|
||||
"expires_at_formatted" => expiry_date.to_time.in_time_zone.to_s(:medium)
|
||||
"expires_at_formatted" => expiry_date.to_time.in_time_zone.to_fs(:medium)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ForceCompanyTrialExperiment, :experiment do
|
||||
subject { described_class.new(current_user: user) }
|
||||
|
||||
let(:user) { create(:user, setup_for_company: setup_for_company) }
|
||||
let(:setup_for_company) { true }
|
||||
|
||||
context 'when a user is setup_for_company' do
|
||||
it 'is not excluded' do
|
||||
expect(subject).not_to exclude(user: user)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user is not setup_for_company' do
|
||||
let(:setup_for_company) { nil }
|
||||
|
||||
it 'is excluded' do
|
||||
expect(subject).to exclude(user: user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -159,7 +159,7 @@ RSpec.describe "User creates issue", feature_category: :team_planning do
|
|||
click_button 'Create issue'
|
||||
|
||||
page.within '.issuable-sidebar' do
|
||||
expect(page).to have_content date.to_s(:medium)
|
||||
expect(page).to have_content date.to_fs(:medium)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin
|
|||
click_button _('Save changes')
|
||||
|
||||
page.within '.issuable-sidebar' do
|
||||
expect(page).to have_content date.to_s(:medium)
|
||||
expect(page).to have_content date.to_fs(:medium)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ RSpec.describe 'Merge request > User sees deployment widget', :js, feature_categ
|
|||
wait_for_requests
|
||||
|
||||
assert_env_widget("Deployed to", environment.name)
|
||||
expect(find('.js-deploy-time')['title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
|
||||
expect(find('.js-deploy-time')['title']).to eq(deployment.created_at.to_time.in_time_zone.to_fs(:medium))
|
||||
end
|
||||
|
||||
context 'when a user created a new merge request with the same SHA' do
|
||||
|
|
|
|||
|
|
@ -1,26 +1,49 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import BoardNewIssue from '~/boards/components/board_new_issue.vue';
|
||||
import BoardNewItem from '~/boards/components/board_new_item.vue';
|
||||
import ProjectSelect from '~/boards/components/project_select.vue';
|
||||
import eventHub from '~/boards/eventhub';
|
||||
import groupBoardQuery from '~/boards/graphql/group_board.query.graphql';
|
||||
import projectBoardQuery from '~/boards/graphql/project_board.query.graphql';
|
||||
import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
|
||||
|
||||
import { mockList, mockGroupProjects, mockIssue, mockIssue2 } from '../mock_data';
|
||||
import {
|
||||
mockList,
|
||||
mockGroupProjects,
|
||||
mockIssue,
|
||||
mockIssue2,
|
||||
mockProjectBoardResponse,
|
||||
mockGroupBoardResponse,
|
||||
} from '../mock_data';
|
||||
|
||||
Vue.use(Vuex);
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const addListNewIssuesSpy = jest.fn().mockResolvedValue();
|
||||
const mockActions = { addListNewIssue: addListNewIssuesSpy };
|
||||
|
||||
const projectBoardQueryHandlerSuccess = jest.fn().mockResolvedValue(mockProjectBoardResponse);
|
||||
const groupBoardQueryHandlerSuccess = jest.fn().mockResolvedValue(mockGroupBoardResponse);
|
||||
|
||||
const mockApollo = createMockApollo([
|
||||
[projectBoardQuery, projectBoardQueryHandlerSuccess],
|
||||
[groupBoardQuery, groupBoardQueryHandlerSuccess],
|
||||
]);
|
||||
|
||||
const createComponent = ({
|
||||
state = {},
|
||||
actions = mockActions,
|
||||
getters = { getBoardItemsByList: () => () => [] },
|
||||
isGroupBoard = true,
|
||||
data = { selectedProject: mockGroupProjects[0] },
|
||||
provide = {},
|
||||
} = {}) =>
|
||||
shallowMount(BoardNewIssue, {
|
||||
apolloProvider: mockApollo,
|
||||
store: new Vuex.Store({
|
||||
state,
|
||||
actions,
|
||||
|
|
@ -28,6 +51,7 @@ const createComponent = ({
|
|||
}),
|
||||
propsData: {
|
||||
list: mockList,
|
||||
boardId: 'gid://gitlab/Board/1',
|
||||
},
|
||||
data: () => data,
|
||||
provide: {
|
||||
|
|
@ -36,6 +60,10 @@ const createComponent = ({
|
|||
weightFeatureAvailable: false,
|
||||
boardWeight: null,
|
||||
isGroupBoard,
|
||||
boardType: 'group',
|
||||
isEpicBoard: false,
|
||||
isApolloBoard: false,
|
||||
...provide,
|
||||
},
|
||||
stubs: {
|
||||
BoardNewItem,
|
||||
|
|
@ -139,4 +167,33 @@ describe('Issue boards new issue form', () => {
|
|||
expect(projectSelect.exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Apollo boards', () => {
|
||||
it.each`
|
||||
boardType | queryHandler | notCalledHandler
|
||||
${WORKSPACE_GROUP} | ${groupBoardQueryHandlerSuccess} | ${projectBoardQueryHandlerSuccess}
|
||||
${WORKSPACE_PROJECT} | ${projectBoardQueryHandlerSuccess} | ${groupBoardQueryHandlerSuccess}
|
||||
`(
|
||||
'fetches $boardType board and emits addNewIssue event',
|
||||
async ({ boardType, queryHandler, notCalledHandler }) => {
|
||||
wrapper = createComponent({
|
||||
provide: {
|
||||
boardType,
|
||||
isProjectBoard: boardType === WORKSPACE_PROJECT,
|
||||
isGroupBoard: boardType === WORKSPACE_GROUP,
|
||||
isApolloBoard: true,
|
||||
},
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
findBoardNewItem().vm.$emit('form-submit', { title: 'Foo' });
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(queryHandler).toHaveBeenCalled();
|
||||
expect(notCalledHandler).not.toHaveBeenCalled();
|
||||
expect(wrapper.emitted('addNewIssue')[0][0]).toMatchObject({ title: 'Foo' });
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1541,8 +1541,8 @@ describe('addListNewIssue', () => {
|
|||
it('should add board scope to the issue being created', async () => {
|
||||
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
|
||||
data: {
|
||||
createIssue: {
|
||||
issue: mockIssue,
|
||||
createIssuable: {
|
||||
issuable: mockIssue,
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
|
|
@ -1600,8 +1600,8 @@ describe('addListNewIssue', () => {
|
|||
it('dispatches a correct set of mutations', () => {
|
||||
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
|
||||
data: {
|
||||
createIssue: {
|
||||
issue: mockIssue,
|
||||
createIssuable: {
|
||||
issuable: mockIssue,
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Create do
|
|||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
let(:args) { { project_path: project.full_path, active: true, api_url: 'http://prometheus.com/' } }
|
||||
let(:api_url) { 'http://prometheus.com/' }
|
||||
let(:args) { { project_path: project.full_path, active: true, api_url: api_url } }
|
||||
|
||||
specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
|
||||
|
||||
|
|
@ -29,6 +30,14 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Create do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when api_url is nil' do
|
||||
let(:api_url) { nil }
|
||||
|
||||
it 'creates the integration' do
|
||||
expect { resolve }.to change(::Alerting::ProjectAlertingSetting, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when UpdateService responds with success' do
|
||||
it 'returns the integration with no errors' do
|
||||
expect(resolve).to eq(
|
||||
|
|
@ -38,7 +47,7 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Create do
|
|||
end
|
||||
|
||||
it 'creates a corresponding token' do
|
||||
expect { resolve }.to change(::Alerting::ProjectAlertingSetting, :count).by(1)
|
||||
expect { resolve }.to change(::Integrations::Prometheus, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ require 'spec_helper'
|
|||
|
||||
require 'googleauth'
|
||||
|
||||
RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching, :snowplow do
|
||||
RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching, :snowplow, feature_category: :metrics do
|
||||
include PrometheusHelpers
|
||||
include ReactiveCachingHelpers
|
||||
|
||||
|
|
@ -37,8 +37,8 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching,
|
|||
integration.manual_configuration = true
|
||||
end
|
||||
|
||||
it 'validates presence of api_url' do
|
||||
expect(integration).to validate_presence_of(:api_url)
|
||||
it 'does not validates presence of api_url' do
|
||||
expect(integration).not_to validate_presence_of(:api_url)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching,
|
|||
|
||||
context 'when configuration is not valid' do
|
||||
before do
|
||||
integration.api_url = nil
|
||||
integration.manual_configuration = nil
|
||||
end
|
||||
|
||||
it 'returns failure message' do
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ RSpec.describe 'Creating a new Prometheus Integration', feature_category: :incid
|
|||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
let(:api_url) { 'https://prometheus-url.com' }
|
||||
|
||||
let(:variables) do
|
||||
{
|
||||
project_path: project.full_path,
|
||||
active: false,
|
||||
api_url: 'https://prometheus-url.com'
|
||||
api_url: api_url
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -56,7 +58,20 @@ RSpec.describe 'Creating a new Prometheus Integration', feature_category: :incid
|
|||
expect(integration_response['apiUrl']).to eq(new_integration.api_url)
|
||||
end
|
||||
|
||||
[:project_path, :active, :api_url].each do |argument|
|
||||
context 'without api url' do
|
||||
let(:api_url) { nil }
|
||||
|
||||
it 'creates a new integration' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
integration_response = mutation_response['integration']
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(integration_response['apiUrl']).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
[:project_path, :active].each do |argument|
|
||||
context "without required argument #{argument}" do
|
||||
before do
|
||||
variables.delete(argument)
|
||||
|
|
|
|||
|
|
@ -690,7 +690,7 @@ RSpec.describe Projects::UpdateService, feature_category: :groups_and_projects d
|
|||
attributes_for(
|
||||
:prometheus_integration,
|
||||
project: project,
|
||||
properties: { api_url: nil, manual_configuration: "1" }
|
||||
properties: { api_url: 'invalid-url', manual_configuration: "1" }
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
it 'returns due_date message: Date.new(2016, 8, 28) if content contains /due 2016-08-28' do
|
||||
_, _, message = service.execute(content, issuable)
|
||||
|
||||
expect(message).to eq("Set the due date to #{expected_date.to_s(:medium)}.")
|
||||
expect(message).to eq("Set the due date to #{expected_date.to_fs(:medium)}.")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
module Features
|
||||
module IterationHelpers
|
||||
def iteration_period(iteration)
|
||||
"#{iteration.start_date.to_s(:medium)} - #{iteration.due_date.to_s(:medium)}"
|
||||
"#{iteration.start_date.to_fs(:medium)} - #{iteration.due_date.to_fs(:medium)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ RSpec.shared_examples 'date sidebar widget' do
|
|||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content(today.to_s(:medium))
|
||||
expect(page).to have_content(today.to_fs(:medium))
|
||||
expect(due_date_value.text).to have_content Time.current.strftime('%b %-d, %Y')
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'gitlab-dangerfiles'
|
||||
require 'gitlab/dangerfiles/spec_helper'
|
||||
|
||||
require_relative '../../../tooling/danger/experiments'
|
||||
|
||||
RSpec.describe Tooling::Danger::Experiments, feature_category: :tooling do
|
||||
include_context "with dangerfile"
|
||||
|
||||
let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) }
|
||||
|
||||
subject(:experiments) { fake_danger.new(helper: fake_helper) }
|
||||
|
||||
describe '#removed_experiments' do
|
||||
let(:removed_experiments_yml_files) do
|
||||
[
|
||||
'config/feature_flags/experiment/tier_badge.yml',
|
||||
'ee/config/feature_flags/experiment/direct_to_trial.yml'
|
||||
]
|
||||
end
|
||||
|
||||
let(:deleted_files) do
|
||||
[
|
||||
'app/models/model.rb',
|
||||
'app/assets/javascripts/file.js'
|
||||
] + removed_experiments_yml_files
|
||||
end
|
||||
|
||||
it 'returns names of removed experiments' do
|
||||
expect(experiments.removed_experiments).to eq(%w[tier_badge direct_to_trial])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#class_files_removed?' do
|
||||
let(:removed_experiments_name) { current_experiment_with_class_files_example }
|
||||
|
||||
context 'when yml file is deleted but not class file' do
|
||||
let(:deleted_files) { ["config/feature_flags/experiment/#{removed_experiments_name}.yml"] }
|
||||
|
||||
it 'returns false' do
|
||||
expect(experiments.class_files_removed?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when yml file is deleted but no corresponding class file exists' do
|
||||
let(:deleted_files) { ["config/feature_flags/experiment/fake_experiment.yml"] }
|
||||
|
||||
it 'returns true' do
|
||||
expect(experiments.class_files_removed?).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def current_experiment_with_class_files_example
|
||||
path = Dir.glob("app/experiments/*.rb").last
|
||||
File.basename(path).chomp('_experiment.rb')
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Tooling
|
||||
module Danger
|
||||
module Experiments
|
||||
EXPERIMENTS_YML_REGEX = %r{\A(ee/)?config/feature_flags/experiment/}
|
||||
CLASS_FILES_DIR = %w[app/experiments/ ee/app/experiments/].freeze
|
||||
|
||||
def class_files_removed?
|
||||
(removed_experiments & current_experiments_with_class_files).empty?
|
||||
end
|
||||
|
||||
def removed_experiments
|
||||
yml_files_paths = helper.deleted_files
|
||||
|
||||
yml_files_paths.select { |path| path =~ EXPERIMENTS_YML_REGEX }.map { |path| File.basename(path).chomp('.yml') }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_experiments_with_class_files
|
||||
experiment_names = []
|
||||
|
||||
CLASS_FILES_DIR.each do |directory_path|
|
||||
experiment_names += Dir.glob("#{directory_path}*.rb").map do |path|
|
||||
File.basename(path).chomp('_experiment.rb')
|
||||
end
|
||||
end
|
||||
|
||||
experiment_names
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue