Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-12-06 12:08:19 +00:00
parent 5bc6fcec0e
commit eaf41d710d
132 changed files with 1020 additions and 979 deletions

View File

@ -49,7 +49,6 @@
/lib/registry/
/lib/policy/
/lib/feature/
/lib/flowdock/
/lib/generators/
/lib/gitaly/
/lib/api/

View File

@ -565,7 +565,6 @@ Gitlab/StrongMemoizeAttr:
- 'lib/container_registry/client.rb'
- 'lib/container_registry/gitlab_api_client.rb'
- 'lib/container_registry/tag.rb'
- 'lib/flowdock/git/builder.rb'
- 'lib/gitlab/alert_management/alert_status_counts.rb'
- 'lib/gitlab/alert_management/payload/base.rb'
- 'lib/gitlab/alert_management/payload/managed_prometheus.rb'

View File

@ -391,7 +391,6 @@ Layout/LineLength:
- 'app/models/integrations/emails_on_push.rb'
- 'app/models/integrations/ewm.rb'
- 'app/models/integrations/external_wiki.rb'
- 'app/models/integrations/flowdock.rb'
- 'app/models/integrations/hangouts_chat.rb'
- 'app/models/integrations/harbor.rb'
- 'app/models/integrations/jenkins.rb'

View File

@ -3,23 +3,6 @@
Layout/SpaceInLambdaLiteral:
Details: grace period
Exclude:
- 'app/controllers/concerns/issuable_actions.rb'
- 'app/controllers/projects/ci/daily_build_group_report_results_controller.rb'
- 'app/controllers/projects/merge_requests_controller.rb'
- 'app/finders/releases/group_releases_finder.rb'
- 'app/graphql/mutations/ci/runner/update.rb'
- 'app/models/abuse_report.rb'
- 'app/models/alert_management/alert.rb'
- 'app/models/alert_management/http_integration.rb'
- 'app/models/analytics/cycle_analytics/aggregation.rb'
- 'app/models/analytics/usage_trends/measurement.rb'
- 'app/models/application_setting.rb'
- 'app/models/audit_event.rb'
- 'app/models/award_emoji.rb'
- 'app/models/board_group_recent_visit.rb'
- 'app/models/board_project_recent_visit.rb'
- 'app/models/bulk_import.rb'
- 'app/models/bulk_imports/entity.rb'
- 'app/models/bulk_imports/tracker.rb'
- 'app/models/ci/build.rb'
- 'app/models/ci/daily_build_group_report_result.rb'

View File

@ -105,7 +105,6 @@ Style/FormatString:
- 'app/models/integrations/emails_on_push.rb'
- 'app/models/integrations/ewm.rb'
- 'app/models/integrations/external_wiki.rb'
- 'app/models/integrations/flowdock.rb'
- 'app/models/integrations/hangouts_chat.rb'
- 'app/models/integrations/irker.rb'
- 'app/models/integrations/jenkins.rb'
@ -281,7 +280,6 @@ Style/FormatString:
- 'lib/api/helpers/packages/conan/api_helpers.rb'
- 'lib/bulk_imports/network_error.rb'
- 'lib/bulk_imports/users_mapper.rb'
- 'lib/flowdock/git/builder.rb'
- 'lib/gitlab/bitbucket_server_import/importer.rb'
- 'lib/gitlab/checks/push_file_count_check.rb'
- 'lib/gitlab/ci/ansi2json/line.rb'

View File

@ -87,7 +87,6 @@ Style/PercentLiteralDelimiters:
- 'app/models/integrations/emails_on_push.rb'
- 'app/models/integrations/external_wiki.rb'
- 'app/models/integrations/field.rb'
- 'app/models/integrations/flowdock.rb'
- 'app/models/integrations/jenkins.rb'
- 'app/models/integrations/jira.rb'
- 'app/models/integrations/packagist.rb'
@ -485,7 +484,6 @@ Style/PercentLiteralDelimiters:
- 'lib/bitbucket/representation/issue.rb'
- 'lib/container_registry/path.rb'
- 'lib/feature.rb'
- 'lib/flowdock/git/builder.rb'
- 'lib/generators/gitlab/usage_metric_definition_generator.rb'
- 'lib/generators/gitlab/usage_metric_generator.rb'
- 'lib/gitlab.rb'

View File

@ -264,9 +264,6 @@ gem 'discordrb-webhooks', '~> 3.4', require: false
gem 'jira-ruby', '~> 2.1.4'
gem 'atlassian-jwt', '~> 0.2.0'
# Flowdock integration
gem 'flowdock', '~> 0.7'
# Slack integration
gem 'slack-messenger', '~> 2.3.4'

View File

@ -178,7 +178,6 @@
{"name":"flipper","version":"0.25.0","platform":"ruby","checksum":"ccb2776752b8378bc994c9d873ccde290c090341940761b873494695ee697add"},
{"name":"flipper-active_record","version":"0.25.0","platform":"ruby","checksum":"85a5c99465e2cc6a09e91931a9998b0dbd463cd6c80dd513129377132e3eb67f"},
{"name":"flipper-active_support_cache_store","version":"0.25.0","platform":"ruby","checksum":"7282bf994b08d1a076b65c6f3b51e3dc04fcb00fa6e7b20089e60db25c7b531b"},
{"name":"flowdock","version":"0.7.1","platform":"ruby","checksum":"cfa95b2ac96e5f883f6e419d7a891f76cfcc17a28c416b6b714bbdffc8dbd912"},
{"name":"fog-aliyun","version":"0.3.3","platform":"ruby","checksum":"d0aa317f7c1473a1d684fff51699f216bb9cb78b9ee9ce55a81c9bcc93fb85ee"},
{"name":"fog-aws","version":"3.15.0","platform":"ruby","checksum":"09752931ea0c6165b018e1a89253248d86b246645086ccf19bc44fabe3381e8c"},
{"name":"fog-core","version":"2.1.0","platform":"ruby","checksum":"53e5d793554d7080d015ef13cd44b54027e421d924d9dba4ce3d83f95f37eda9"},

View File

@ -484,9 +484,6 @@ GEM
flipper-active_support_cache_store (0.25.0)
activesupport (>= 4.2, < 8)
flipper (~> 0.25.0)
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
fog-aliyun (0.3.3)
fog-core
fog-json
@ -1649,7 +1646,6 @@ DEPENDENCIES
flipper (~> 0.25.0)
flipper-active_record (~> 0.25.0)
flipper-active_support_cache_store (~> 0.25.0)
flowdock (~> 0.7)
fog-aliyun (~> 0.3)
fog-aws (~> 3.15)
fog-core (= 2.1.0)

View File

@ -85,32 +85,25 @@ export default {
};
</script>
<template>
<article
class="draft-note-component note-wrapper"
@mouseenter="handleMouseEnter(draft)"
@mouseleave="handleMouseLeave(draft)"
<noteable-note
:note="draft"
:line="line"
:discussion-root="true"
:class="{ 'gl-mb-0!': glFeatures.mrReviewSubmitComment }"
class="draft-note-component draft-note"
@handleEdit="handleEditing"
@cancelForm="handleNotEditing"
@updateSuccess="handleNotEditing"
@handleDeleteNote="deleteDraft"
@handleUpdateNote="update"
@toggleResolveStatus="toggleResolveDiscussion(draft.id)"
@mouseenter.native="handleMouseEnter(draft)"
@mouseleave.native="handleMouseLeave(draft)"
>
<ul class="notes draft-notes">
<noteable-note
:note="draft"
:line="line"
:discussion-root="true"
:class="{ 'gl-mb-0!': glFeatures.mrReviewSubmitComment }"
class="draft-note"
@handleEdit="handleEditing"
@cancelForm="handleNotEditing"
@updateSuccess="handleNotEditing"
@handleDeleteNote="deleteDraft"
@handleUpdateNote="update"
@toggleResolveStatus="toggleResolveDiscussion(draft.id)"
>
<template #note-header-info>
<gl-badge variant="warning" class="gl-mr-2">{{ __('Pending') }}</gl-badge>
</template>
</noteable-note>
</ul>
<template v-if="!isEditingDraft">
<template #note-header-info>
<gl-badge variant="warning" class="gl-mr-2">{{ __('Pending') }}</gl-badge>
</template>
<template v-if="!isEditingDraft" #after-note-body>
<div
v-if="draftCommands"
v-safe-html:[$options.safeHtmlConfig]="draftCommands"
@ -134,5 +127,5 @@ export default {
</gl-button>
</p>
</template>
</article>
</noteable-note>
</template>

View File

@ -6,6 +6,7 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import RunnerName from '../runner_name.vue';
import RunnerTags from '../runner_tags.vue';
import RunnerTypeBadge from '../runner_type_badge.vue';
import RunnerJobStatusBadge from '../runner_job_status_badge.vue';
import { formatJobCount } from '../../utils';
import {
@ -25,6 +26,7 @@ export default {
RunnerName,
RunnerTags,
RunnerTypeBadge,
RunnerJobStatusBadge,
RunnerUpgradeStatusIcon: () =>
import('ee_component/ci/runner/components/runner_upgrade_status_icon.vue'),
TooltipOnTruncate,
@ -81,6 +83,8 @@ export default {
</div>
<div>
<runner-job-status-badge :job-status="runner.jobExecutionStatus" />
<runner-summary-field icon="clock">
<gl-sprintf :message="$options.i18n.I18N_LAST_CONTACT_LABEL">
<template #timeAgo>

View File

@ -0,0 +1,54 @@
<script>
import { GlBadge, GlTooltipDirective } from '@gitlab/ui';
import {
I18N_JOB_STATUS_RUNNING,
I18N_JOB_STATUS_IDLE,
JOB_STATUS_RUNNING,
JOB_STATUS_IDLE,
} from '../constants';
export default {
components: {
GlBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
jobStatus: {
required: false,
default: null,
type: String,
},
},
computed: {
badge() {
switch (this.jobStatus) {
case JOB_STATUS_RUNNING:
return {
classes: 'gl-text-blue-600! gl-border gl-border-blue-600!',
label: I18N_JOB_STATUS_RUNNING,
};
case JOB_STATUS_IDLE:
return {
classes: 'gl-text-gray-700! gl-border gl-border-gray-500!',
label: I18N_JOB_STATUS_IDLE,
};
default:
return null;
}
},
},
};
</script>
<template>
<gl-badge
v-if="badge"
size="sm"
class="gl-mr-3 gl-bg-transparent!"
variant="muted"
:class="badge.classes"
>
{{ badge.label }}
</gl-badge>
</template>

View File

@ -32,6 +32,10 @@ export const I18N_STATUS_NEVER_CONTACTED = s__('Runners|Never contacted');
export const I18N_STATUS_OFFLINE = s__('Runners|Offline');
export const I18N_STATUS_STALE = s__('Runners|Stale');
// Executor Status
export const I18N_JOB_STATUS_RUNNING = s__('Runners|Running');
export const I18N_JOB_STATUS_IDLE = s__('Runners|Idle');
// Status help popover
export const I18N_STATUS_POPOVER_TITLE = s__('Runners|Runner statuses');
@ -134,6 +138,11 @@ export const STATUS_NEVER_CONTACTED = 'NEVER_CONTACTED';
export const STATUS_OFFLINE = 'OFFLINE';
export const STATUS_STALE = 'STALE';
// CiRunnerJobExecutionStatus
export const JOB_STATUS_RUNNING = 'RUNNING';
export const JOB_STATUS_IDLE = 'IDLE';
// CiRunnerAccessLevel
export const ACCESS_LEVEL_NOT_PROTECTED = 'NOT_PROTECTED';

View File

@ -12,6 +12,7 @@ fragment ListItemShared on CiRunner {
createdAt
contactedAt
status(legacyMode: null)
jobExecutionStatus
userPermissions {
updateRunner
deleteRunner

View File

@ -1,22 +1,24 @@
<script>
import { GlAlert, GlFormInputGroup, GlLink, GlSprintf } from '@gitlab/ui';
import { GlAlert, GlFormInputGroup, GlLink, GlSprintf, GlIcon } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import { generateAgentRegistrationCommand } from '../clusters_util';
import { I18N_AGENT_TOKEN } from '../constants';
import { I18N_AGENT_TOKEN, HELM_VERSION_POLICY_URL } from '../constants';
export default {
i18n: I18N_AGENT_TOKEN,
advancedInstallPath: helpPagePath('user/clusters/agent/install/index', {
anchor: 'advanced-installation-method',
}),
HELM_VERSION_POLICY_URL,
components: {
GlAlert,
CodeBlock,
GlFormInputGroup,
GlLink,
GlSprintf,
GlIcon,
ModalCopyButton,
},
inject: ['kasAddress', 'kasVersion'],
@ -77,6 +79,11 @@ export default {
<p>
{{ $options.i18n.basicInstallBody }}
<gl-sprintf :message="$options.i18n.helmVersionText">
<template #link="{ content }"
><gl-link :href="$options.HELM_VERSION_POLICY_URL" target="_blank"
>{{ content }} <gl-icon name="external-link" :size="12" /></gl-link></template
></gl-sprintf>
</p>
<p class="gl-display-flex gl-align-items-flex-start">

View File

@ -1,23 +1,13 @@
<script>
import {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
GlDropdownText,
GlSearchBoxByType,
GlSprintf,
} from '@gitlab/ui';
import { GlCollapsibleListbox, GlButton, GlSprintf } from '@gitlab/ui';
import { I18N_AVAILABLE_AGENTS_DROPDOWN } from '../constants';
export default {
name: 'AvailableAgentsDropdown',
i18n: I18N_AVAILABLE_AGENTS_DROPDOWN,
components: {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
GlDropdownText,
GlSearchBoxByType,
GlCollapsibleListbox,
GlButton,
GlSprintf,
},
props: {
@ -46,13 +36,21 @@ export default {
return this.selectedAgent;
},
dropdownItems() {
return this.availableAgents.map((agent) => {
return {
value: agent,
text: agent,
};
});
},
shouldRenderCreateButton() {
return this.searchTerm && !this.availableAgents.includes(this.searchTerm);
},
filteredResults() {
const lowerCasedSearchTerm = this.searchTerm.toLowerCase();
return this.availableAgents.filter((resultString) =>
resultString.toLowerCase().includes(lowerCasedSearchTerm),
return this.dropdownItems.filter((item) =>
item.value.toLowerCase().includes(lowerCasedSearchTerm),
);
},
},
@ -60,59 +58,48 @@ export default {
selectAgent(agent) {
this.$emit('agentSelected', agent);
this.selectedAgent = agent;
this.clearSearch();
},
isSelected(agent) {
return this.selectedAgent === agent;
},
clearSearch() {
this.searchTerm = '';
},
focusSearch() {
this.$refs.searchInput.focusInput();
},
handleShow() {
this.clearSearch();
this.focusSearch();
this.$refs.dropdown.closeAndFocus();
},
onKeyEnter() {
if (!this.searchTerm?.length) {
return;
}
this.$refs.dropdown.hide();
this.selectAgent(this.searchTerm);
},
searchAgent(searchQuery) {
this.searchTerm = searchQuery;
},
},
};
</script>
<template>
<gl-dropdown ref="dropdown" :text="dropdownText" :loading="isRegistering" @shown="handleShow">
<template #header>
<gl-search-box-by-type
ref="searchInput"
v-model.trim="searchTerm"
@keydown.enter.stop.prevent="onKeyEnter"
/>
</template>
<gl-dropdown-item
v-for="agent in filteredResults"
:key="agent"
:is-checked="isSelected(agent)"
is-check-item
@click="selectAgent(agent)"
<div @keydown.enter.stop.prevent="onKeyEnter">
<gl-collapsible-listbox
ref="dropdown"
v-model="selectedAgent"
class="gl-w-full"
toggle-class="select-agent-dropdown"
:items="filteredResults"
:toggle-text="dropdownText"
:loading="isRegistering"
:searchable="true"
:no-results-text="$options.i18n.noResults"
@search="searchAgent"
@select="selectAgent"
>
{{ agent }}
</gl-dropdown-item>
<gl-dropdown-text v-if="!filteredResults.length" ref="noMatchingResults">{{
$options.i18n.noResults
}}</gl-dropdown-text>
<template v-if="shouldRenderCreateButton">
<gl-dropdown-divider />
<gl-dropdown-item data-testid="create-config-button" @click="selectAgent(searchTerm)">
<gl-sprintf :message="$options.i18n.createButton">
<template #searchTerm>{{ searchTerm }}</template>
</gl-sprintf>
</gl-dropdown-item>
</template>
</gl-dropdown>
<template v-if="shouldRenderCreateButton" #footer>
<gl-button
category="tertiary"
class="gl-justify-content-start! gl-border-t-1! gl-border-t-solid gl-border-t-gray-200 gl-pl-7! gl-rounded-top-left-none! gl-rounded-top-right-none!"
:class="{ 'gl-mt-3': !filteredResults.length }"
@click="selectAgent(searchTerm)"
>
<gl-sprintf :message="$options.i18n.createButton">
<template #searchTerm>{{ searchTerm }}</template>
</gl-sprintf>
</gl-button>
</template>
</gl-collapsible-listbox>
</div>
</template>

View File

@ -101,6 +101,9 @@ export const I18N_AGENT_TOKEN = {
basicInstallBody: s__(
'ClusterAgents|From a terminal, connect to your cluster and run this command. The token is included in the command.',
),
helmVersionText: s__(
'ClusterAgents|Use a Helm version compatible with your Kubernetes version (see %{linkStart}Helm version support policy%{linkEnd}).',
),
advancedInstallTitle: s__('ClusterAgents|Advanced installation methods'),
advancedInstallBody: s__(
@ -108,6 +111,8 @@ export const I18N_AGENT_TOKEN = {
),
};
export const HELM_VERSION_POLICY_URL = 'https://helm.sh/docs/topics/version_skew/';
export const I18N_AGENT_MODAL = {
registerAgentButton: s__('ClusterAgents|Register'),
close: __('Close'),

View File

@ -1,8 +1,12 @@
<script>
import { GlButton, GlIcon } from '@gitlab/ui';
import { SEVERITY_CLASSES, SEVERITY_ICONS } from '~/ci/reports/codequality_report/constants';
import { NEW_CODE_QUALITY_FINDINGS } from '../i18n';
export default {
i18n: {
newFindings: NEW_CODE_QUALITY_FINDINGS,
},
components: { GlButton, GlIcon },
props: {
codeQuality: {
@ -22,22 +26,33 @@ export default {
</script>
<template>
<div data-testid="diff-codequality" class="gl-relative">
<ul
class="gl-list-style-none gl-mb-0 gl-p-0 codequality-findings-list gl-border-top-1 gl-border-bottom-1 gl-bg-gray-10"
<div
data-testid="diff-codequality"
class="gl-relative codequality-findings-list gl-border-top-1 gl-border-bottom-1 gl-bg-gray-10 gl-pl-5 gl-pt-4 gl-pb-4"
>
<h4
data-testid="diff-codequality-findings-heading"
class="gl-mt-0 gl-mb-0 gl-font-base gl-font-regular"
>
{{ $options.i18n.newFindings }}
</h4>
<ul class="gl-list-style-none gl-mb-0 gl-p-0">
<li
v-for="finding in codeQuality"
:key="finding.description"
class="gl-pt-1 gl-pb-1 gl-pl-3 gl-border-solid gl-border-bottom-0 gl-border-right-0 gl-border-1 gl-border-gray-100 gl-font-regular"
class="gl-pt-1 gl-pb-1 gl-font-regular gl-display-flex"
>
<gl-icon
:size="12"
:name="severityIcon(finding.severity)"
:class="severityClass(finding.severity)"
class="codequality-severity-icon"
/>
{{ finding.description }}
<span class="gl-mr-3">
<gl-icon
:size="12"
:name="severityIcon(finding.severity)"
:class="severityClass(finding.severity)"
class="codequality-severity-icon"
/>
</span>
<span>
<span class="severity-copy">{{ finding.severity }}</span> - {{ finding.description }}
</span>
</li>
</ul>
<gl-button

View File

@ -303,7 +303,11 @@ export default {
class="diff-td notes-content parallel old"
>
<div v-for="draft in lineDrafts(line, 'left')" :key="draft.id" class="content">
<draft-note :draft="draft" :line="line.left" />
<article class="note-wrapper">
<ul class="notes draft-notes">
<draft-note :draft="draft" :line="line.left" />
</ul>
</article>
</div>
</div>
<div
@ -311,7 +315,11 @@ export default {
class="diff-td notes-content parallel new"
>
<div v-for="draft in lineDrafts(line, 'right')" :key="draft.id" class="content">
<draft-note :draft="draft" :line="line.right" />
<article class="note-wrapper">
<ul class="notes draft-notes">
<draft-note :draft="draft" :line="line.right" />
</ul>
</article>
</div>
</div>
</div>

View File

@ -49,3 +49,5 @@ export const CONFLICT_TEXT = {
};
export const HIDE_COMMENTS = __('Hide comments');
export const NEW_CODE_QUALITY_FINDINGS = __('New code quality findings');

View File

@ -307,7 +307,7 @@ export default {
:draft="draftForDiscussion(discussion.reply_id)"
:line="line"
/>
<div
<li
v-else-if="canShowReplyActions && showReplies"
:class="{ 'is-replying': isReplying }"
class="discussion-reply-holder gl-border-t-0! clearfix"
@ -334,7 +334,7 @@ export default {
@cancelForm="cancelReplyForm"
/>
<note-signed-out-widget v-if="!isLoggedIn" />
</div>
</li>
</template>
</discussion-notes>
</component>

View File

@ -443,7 +443,7 @@ export default {
</gl-avatar-link>
</div>
<div v-else-if="!isDraft" class="timeline-avatar gl-float-left">
<div v-else class="timeline-avatar gl-float-left">
<gl-avatar-link :href="author.path">
<gl-avatar
:src="author.avatar_url"
@ -516,6 +516,9 @@ export default {
@handleFormUpdate="formUpdateHandler"
@cancelForm="formCancelHandler"
/>
<div class="timeline-discussion-body-footer">
<slot name="after-note-body"></slot>
</div>
</div>
</div>
</timeline-entry-item>

View File

@ -58,11 +58,21 @@ export default {
<collapsed-reviewer-list :users="sortedReviewers" :issuable-type="issuableType" />
<div class="value hide-collapsed">
<template v-if="hasNoUsers">
<span class="no-value">
{{ __('None') }}
</span>
</template>
<span v-if="hasNoUsers" class="no-value" data-testid="no-value">
{{ __('None') }}
<template v-if="editable">
-
<button
type="button"
class="gl-button btn-link gl-reset-color!"
data-testid="assign-yourself"
data-qa-selector="assign_yourself_button"
@click="assignSelf"
>
{{ __('assign yourself') }}
</button>
</template>
</span>
<uncollapsed-reviewer-list
v-else

View File

@ -143,6 +143,13 @@ export default {
eventHub.$off('sidebar.saveReviewers', this.saveReviewers);
},
methods: {
reviewBySelf() {
// Notify gl dropdown that we are now assigning to current user
this.$el.parentElement.dispatchEvent(new Event('assignYourself'));
this.mediator.addSelfReview();
this.saveReviewers();
},
saveReviewers() {
this.loading = true;
@ -181,6 +188,7 @@ export default {
:editable="canUpdate"
:issuable-type="issuableType"
@request-review="requestReview"
@assign-self="reviewBySelf"
/>
</div>
</template>

View File

@ -31,6 +31,9 @@ export default class SidebarMediator {
assignYourself() {
this.store.addAssignee(this.store.currentUser);
}
addSelfReview() {
this.store.addReviewer(this.store.currentUser);
}
async saveAssignees(field) {
const selected = this.store.assignees.map((u) => u.id);

View File

@ -15,7 +15,6 @@ import { s__ } from '~/locale';
import { parseBoolean } from '~/lib/utils/common_utils';
import { getParameterByName } from '~/lib/utils/url_utility';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import {
i18n,
@ -25,7 +24,6 @@ import {
WIDGET_TYPE_START_AND_DUE_DATE,
WIDGET_TYPE_WEIGHT,
WIDGET_TYPE_HIERARCHY,
WORK_ITEM_VIEWED_STORAGE_KEY,
WIDGET_TYPE_MILESTONE,
WIDGET_TYPE_ITERATION,
WORK_ITEM_TYPE_VALUE_ISSUE,
@ -49,7 +47,6 @@ import WorkItemDueDate from './work_item_due_date.vue';
import WorkItemAssignees from './work_item_assignees.vue';
import WorkItemLabels from './work_item_labels.vue';
import WorkItemMilestone from './work_item_milestone.vue';
import WorkItemInformation from './work_item_information.vue';
export default {
i18n,
@ -72,8 +69,6 @@ export default {
WorkItemTitle,
WorkItemState,
WorkItemWeight: () => import('ee_component/work_items/components/work_item_weight.vue'),
WorkItemInformation,
LocalStorageSync,
WorkItemTypeIcon,
WorkItemIteration: () => import('ee_component/work_items/components/work_item_iteration.vue'),
WorkItemMilestone,
@ -108,7 +103,6 @@ export default {
error: undefined,
updateError: undefined,
workItem: {},
showInfoBanner: true,
updateInProgress: false,
};
},
@ -276,18 +270,10 @@ export default {
};
},
},
beforeDestroy() {
/** make sure that if the user has not even dismissed the alert ,
* should no be able to see the information next time and update the local storage * */
this.dismissBanner();
},
methods: {
isWidgetPresent(type) {
return this.workItem?.widgets?.find((widget) => widget.type === type);
},
dismissBanner() {
this.showInfoBanner = false;
},
toggleConfidentiality(confidentialStatus) {
this.updateInProgress = true;
let updateMutation = updateWorkItemMutation;
@ -341,7 +327,6 @@ export default {
document.title = s__('404|Not found');
},
},
WORK_ITEM_VIEWED_STORAGE_KEY,
WORK_ITEM_TYPE_VALUE_OBJECTIVE,
};
</script>
@ -431,16 +416,6 @@ export default {
@click="$emit('close')"
/>
</div>
<local-storage-sync
v-model="showInfoBanner"
:storage-key="$options.WORK_ITEM_VIEWED_STORAGE_KEY"
>
<work-item-information
v-if="showInfoBanner && !error"
:show-info-banner="showInfoBanner"
@work-item-banner-dismissed="dismissBanner"
/>
</local-storage-sync>
<work-item-title
v-if="workItem.title"
:work-item-id="workItem.id"
@ -485,7 +460,7 @@ export default {
:work-item-type="workItemType"
@error="updateError = $event"
/>
<template v-if="workItemsMvc2Enabled">
<template v-if="workItemsMvcEnabled">
<work-item-milestone
v-if="workItemMilestone"
:work-item-id="workItem.id"

View File

@ -1,53 +0,0 @@
<script>
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import { s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
export default {
i18n: {
learnTasksLinkText: s__('WorkItem|Learn about tasks.'),
tasksInformationTitle: s__('WorkItem|Introducing tasks'),
tasksInformationBody: s__(
'WorkItem|Use tasks to break down your work in an issue into smaller pieces. %{learnMoreLink}',
),
},
helpPageLinks: {
tasksDocLinkPath: helpPagePath('user/tasks'),
},
components: {
GlAlert,
GlSprintf,
GlLink,
},
props: {
showInfoBanner: {
type: Boolean,
required: false,
default: true,
},
},
emits: ['work-item-banner-dismissed'],
};
</script>
<template>
<section class="gl-display-block gl-mb-2">
<gl-alert
v-if="showInfoBanner"
variant="tip"
:title="$options.i18n.tasksInformationTitle"
data-testid="work-item-information"
class="gl-mt-3"
@dismiss="$emit('work-item-banner-dismissed')"
>
<gl-sprintf :message="$options.i18n.tasksInformationBody">
<template #learnMoreLink>
<gl-link :href="$options.helpPageLinks.tasksDocLinkPath">{{
$options.i18n.learnTasksLinkText
}}</gl-link>
</template>
></gl-sprintf
>
</gl-alert>
</section>
</template>

View File

@ -168,7 +168,7 @@ export default {
return this.parentMilestone?.id;
},
associateMilestone() {
return this.parentMilestoneId && this.workItemsMvc2Enabled;
return this.parentMilestoneId && this.workItemsMvcEnabled;
},
isSubmitButtonDisabled() {
return this.isCreateForm ? this.search.length === 0 : this.workItemsToAdd.length === 0;

View File

@ -20,8 +20,6 @@ export const WIDGET_TYPE_HIERARCHY = 'HIERARCHY';
export const WIDGET_TYPE_MILESTONE = 'MILESTONE';
export const WIDGET_TYPE_ITERATION = 'ITERATION';
export const WORK_ITEM_VIEWED_STORAGE_KEY = 'gl-show-work-item-banner';
export const WORK_ITEM_TYPE_ENUM_INCIDENT = 'INCIDENT';
export const WORK_ITEM_TYPE_ENUM_ISSUE = 'ISSUE';
export const WORK_ITEM_TYPE_ENUM_TASK = 'TASK';

View File

@ -24,3 +24,9 @@
.cluster-button-container:focus-within {
@include gl-focus;
}
.select-agent-dropdown {
.gl-button-text {
@include gl-flex-grow-1;
}
}

View File

@ -60,6 +60,10 @@ $system-note-svg-size: 1rem;
padding: $gl-padding-4 $gl-padding-8;
}
&.draft-note .timeline-content:not(.flash-container) {
border: 0;
}
.note-header-info {
min-height: 2rem;
display: flex;
@ -94,6 +98,7 @@ $system-note-svg-size: 1rem;
}
&.draft-note .timeline-content:not(.flash-container) {
margin-left: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
@ -104,10 +109,14 @@ $system-note-svg-size: 1rem;
border-right: 1px solid $border-color;
background-color: $white;
.timeline-content {
.timeline-content:not(.flash-container) {
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 $gl-padding;
}
.timeline-discussion-body-footer {
padding: 0 $gl-padding-8 0;
}
.timeline-avatar {
margin: $gl-padding-8 0 0 $gl-padding;
}
@ -116,6 +125,12 @@ $system-note-svg-size: 1rem;
margin-left: 2rem;
}
}
&:last-of-type .timeline-entry-inner {
border-bottom: 1px solid $border-color;
border-bottom-left-radius: $gl-border-radius-base;
border-bottom-right-radius: $gl-border-radius-base;
}
}
.diff-content {
@ -1055,7 +1070,7 @@ $system-note-svg-size: 1rem;
padding-left: 0;
ul.notes li.note-wrapper {
.timeline-content {
.timeline-content:not(.flash-container) {
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 $gl-padding;
}
@ -1071,7 +1086,7 @@ $system-note-svg-size: 1rem;
border-right: 0;
}
div.discussion-reply-holder {
.discussion-reply-holder {
margin-left: 0;
}
}
@ -1102,7 +1117,7 @@ $system-note-svg-size: 1rem;
}
}
.draft-note-component .draft-note.timeline-entry {
.draft-note-component.draft-note.timeline-entry {
.timeline-content:not(.flash-container) {
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 $gl-padding;
}

View File

@ -247,7 +247,7 @@
.repository-languages-bar {
height: 8px;
margin-bottom: $gl-padding-8;
margin-bottom: $gl-padding;
background-color: $white;
border-radius: $border-radius-default;

View File

@ -173,7 +173,7 @@ module IssuableActions
def render_cached_discussions(discussions, serializer, cache_context)
render_cached(discussions,
with: serializer,
cache_context: -> (_) { cache_context },
cache_context: ->(_) { cache_context },
context: self)
end

View File

@ -25,7 +25,7 @@ class Projects::Ci::DailyBuildGroupReportResultsController < Projects::Applicati
{
date: 'date',
group_name: 'group_name',
param_type => -> (record) { record.data[param_type] }
param_type => ->(record) { record.data[param_type] }
}
).render
end

View File

@ -147,7 +147,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
render_cached(@merge_request,
with: serializer,
cache_context: -> (_) { [Digest::SHA256.hexdigest(cache_context.to_s)] },
cache_context: ->(_) { [Digest::SHA256.hexdigest(cache_context.to_s)] },
serializer: params[:serializer])
else
render json: serializer.represent(@merge_request, serializer: params[:serializer])

View File

@ -33,8 +33,8 @@ module Releases
Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder.new(
scope: releases_scope,
array_scope: Project.for_group_and_its_subgroups(parent).select(:id),
array_mapping_scope: -> (project_id_expression) { Release.where(Release.arel_table[:project_id].eq(project_id_expression)) },
finder_query: -> (order_by, id_expression) { Release.where(Release.arel_table[:id].eq(id_expression)) }
array_mapping_scope: ->(project_id_expression) { Release.where(Release.arel_table[:project_id].eq(project_id_expression)) },
finder_query: ->(order_by, id_expression) { Release.where(Release.arel_table[:id].eq(id_expression)) }
)
.execute
end

View File

@ -54,7 +54,7 @@ module Mutations
argument :associated_projects, [::Types::GlobalIDType[::Project]],
required: false,
description: 'Projects associated with the runner. Available only for project runners.',
prepare: -> (global_ids, ctx) { global_ids&.filter_map { |gid| gid.model_id.to_i } }
prepare: ->(global_ids, ctx) { global_ids&.filter_map { |gid| gid.model_id.to_i } }
field :runner,
Types::Ci::RunnerType,

View File

@ -142,6 +142,8 @@ module ProjectsHelper
end
def project_search_tabs?(tab)
return false unless @project.present?
abilities = Array(search_tab_ability_map[tab])
abilities.any? { |ability| can?(current_user, ability, @project) }

View File

@ -12,6 +12,7 @@ module Routing
tab
glm_source
glm_content
_gl
].freeze
def initialize(request_object, group, project)

View File

@ -447,20 +447,38 @@ module SearchHelper
result
end
def code_tab_condition
return true if project_search_tabs?(:blobs)
@project.nil? && search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_code_tab)
end
def wiki_tab_condition
return true if project_search_tabs?(:wiki)
@project.nil? && search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_wiki_tab)
end
def commits_tab_condition
return true if project_search_tabs?(:commits)
@project.nil? && search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_commits_tab)
end
# search page scope navigation
def search_navigation
{
projects: { sort: 1, label: _("Projects"), data: { qa_selector: 'projects_tab' }, condition: @project.nil? },
blobs: { sort: 2, label: _("Code"), data: { qa_selector: 'code_tab' }, condition: project_search_tabs?(:blobs) || (search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_code_tab)) },
blobs: { sort: 2, label: _("Code"), data: { qa_selector: 'code_tab' }, condition: code_tab_condition },
# sort: 3 is reserved for EE items
issues: { sort: 4, label: _("Issues"), condition: project_search_tabs?(:issues) || feature_flag_tab_enabled?(:global_search_issues_tab) },
merge_requests: { sort: 5, label: _("Merge requests"), condition: project_search_tabs?(:merge_requests) || feature_flag_tab_enabled?(:global_search_merge_requests_tab) },
wiki_blobs: { sort: 6, label: _("Wiki"), condition: project_search_tabs?(:wiki) || search_service.show_elasticsearch_tabs? },
commits: { sort: 7, label: _("Commits"), condition: project_search_tabs?(:commits) || (search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_commits_tab)) },
wiki_blobs: { sort: 6, label: _("Wiki"), condition: wiki_tab_condition },
commits: { sort: 7, label: _("Commits"), condition: commits_tab_condition },
notes: { sort: 8, label: _("Comments"), condition: project_search_tabs?(:notes) || search_service.show_elasticsearch_tabs? },
milestones: { sort: 9, label: _("Milestones"), condition: project_search_tabs?(:milestones) || @project.nil? },
users: { sort: 10, label: _("Users"), condition: show_user_search_tab? },
snippet_titles: { sort: 11, label: _("Titles and Descriptions"), search: { snippets: true, group_id: nil, project_id: nil }, condition: @show_snippets.present? && @project.nil? }
users: { sort: 10, label: _("Users"), condition: show_user_search_tab? },
snippet_titles: { sort: 11, label: _("Titles and Descriptions"), search: { snippets: true, group_id: nil, project_id: nil }, condition: @show_snippets.present? && @project.nil? }
}
end
@ -584,7 +602,7 @@ module SearchHelper
end
def feature_flag_tab_enabled?(flag)
@group || Feature.enabled?(flag, current_user, type: :ops)
@group.present? || Feature.enabled?(flag, current_user, type: :ops)
end
def sanitized_search_params

View File

@ -14,7 +14,7 @@ class AbuseReport < ApplicationRecord
validates :message, presence: true
validates :user_id, uniqueness: { message: 'has already been reported' }
scope :by_user, -> (user) { where(user_id: user) }
scope :by_user, ->(user) { where(user_id: user) }
scope :with_users, -> { includes(:reporter, :user) }
# For CacheMarkdownField

View File

@ -53,7 +53,7 @@ module AlertManagement
validates :fingerprint, allow_blank: true, uniqueness: {
scope: :project,
conditions: -> { not_resolved },
message: -> (object, data) { _('Cannot have multiple unresolved alerts') }
message: ->(object, data) { _('Cannot have multiple unresolved alerts') }
}, unless: :resolved?
validate :hosts_format
@ -74,23 +74,23 @@ module AlertManagement
delegate :iid, to: :issue, prefix: true, allow_nil: true
delegate :details_url, to: :present
scope :for_iid, -> (iid) { where(iid: iid) }
scope :for_fingerprint, -> (project, fingerprint) { where(project: project, fingerprint: fingerprint) }
scope :for_environment, -> (environment) { where(environment: environment) }
scope :for_assignee_username, -> (assignee_username) { joins(:assignees).merge(User.by_username(assignee_username)) }
scope :search, -> (query) { fuzzy_search(query, [:title, :description, :monitoring_tool, :service]) }
scope :for_iid, ->(iid) { where(iid: iid) }
scope :for_fingerprint, ->(project, fingerprint) { where(project: project, fingerprint: fingerprint) }
scope :for_environment, ->(environment) { where(environment: environment) }
scope :for_assignee_username, ->(assignee_username) { joins(:assignees).merge(User.by_username(assignee_username)) }
scope :search, ->(query) { fuzzy_search(query, [:title, :description, :monitoring_tool, :service]) }
scope :not_resolved, -> { without_status(:resolved) }
scope :with_prometheus_alert, -> { includes(:prometheus_alert) }
scope :with_operations_alerts, -> { where(domain: :operations) }
scope :order_start_time, -> (sort_order) { order(started_at: sort_order) }
scope :order_end_time, -> (sort_order) { order(ended_at: sort_order) }
scope :order_event_count, -> (sort_order) { order(events: sort_order) }
scope :order_start_time, ->(sort_order) { order(started_at: sort_order) }
scope :order_end_time, ->(sort_order) { order(ended_at: sort_order) }
scope :order_event_count, ->(sort_order) { order(events: sort_order) }
# Ascending sort order sorts severity from less critical to more critical.
# Descending sort order sorts severity from more critical to less critical.
# https://gitlab.com/gitlab-org/gitlab/-/issues/221242#what-is-the-expected-correct-behavior
scope :order_severity, -> (sort_order) { order(severity: sort_order == :asc ? :desc : :asc) }
scope :order_severity, ->(sort_order) { order(severity: sort_order == :asc ? :desc : :asc) }
scope :order_severity_with_open_prometheus_alert, -> { open.with_prometheus_alert.order(severity: :asc, started_at: :desc) }
scope :counts_by_project_id, -> { group(:project_id).count }

View File

@ -28,7 +28,7 @@ module AlertManagement
before_validation :ensure_token
before_validation :ensure_payload_example_not_nil
scope :for_endpoint_identifier, -> (endpoint_identifier) { where(endpoint_identifier: endpoint_identifier) }
scope :for_endpoint_identifier, ->(endpoint_identifier) { where(endpoint_identifier: endpoint_identifier) }
scope :active, -> { where(active: true) }
scope :ordered_by_id, -> { order(:id) }

View File

@ -7,7 +7,7 @@ class Analytics::CycleAnalytics::Aggregation < ApplicationRecord
validates :incremental_runtimes_in_seconds, :incremental_processed_records, :full_runtimes_in_seconds, :full_processed_records, presence: true, length: { maximum: 10 }, allow_blank: true
scope :priority_order, -> (column_to_sort = :last_incremental_run_at) { order(arel_table[column_to_sort].asc.nulls_first) }
scope :priority_order, ->(column_to_sort = :last_incremental_run_at) { order(arel_table[column_to_sort].asc.nulls_first) }
scope :enabled, -> { where('enabled IS TRUE') }
def cursor_for(mode, model)

View File

@ -23,9 +23,9 @@ module Analytics
validates :recorded_at, uniqueness: { scope: :identifier }
scope :order_by_latest, -> { order(recorded_at: :desc) }
scope :with_identifier, -> (identifier) { where(identifier: identifier) }
scope :recorded_after, -> (date) { where(self.model.arel_table[:recorded_at].gteq(date)) if date.present? }
scope :recorded_before, -> (date) { where(self.model.arel_table[:recorded_at].lteq(date)) if date.present? }
scope :with_identifier, ->(identifier) { where(identifier: identifier) }
scope :recorded_after, ->(date) { where(self.model.arel_table[:recorded_at].gteq(date)) if date.present? }
scope :recorded_before, ->(date) { where(self.model.arel_table[:recorded_at].lteq(date)) if date.present? }
def self.identifier_query_mapping
{

View File

@ -464,7 +464,7 @@ class ApplicationSetting < ApplicationRecord
validates :external_auth_client_key,
presence: true,
if: -> (setting) { setting.external_auth_client_cert.present? }
if: ->(setting) { setting.external_auth_client_cert.present? }
validates :lets_encrypt_notification_email,
devise_email: true,
@ -486,17 +486,17 @@ class ApplicationSetting < ApplicationRecord
validates :eks_access_key_id,
length: { in: 16..128 },
if: -> (setting) { setting.eks_integration_enabled? && setting.eks_access_key_id.present? }
if: ->(setting) { setting.eks_integration_enabled? && setting.eks_access_key_id.present? }
validates :eks_secret_access_key,
presence: true,
if: -> (setting) { setting.eks_integration_enabled? && setting.eks_access_key_id.present? }
if: ->(setting) { setting.eks_integration_enabled? && setting.eks_access_key_id.present? }
validates_with X509CertificateCredentialsValidator,
certificate: :external_auth_client_cert,
pkey: :external_auth_client_key,
pass: :external_auth_client_key_pass,
if: -> (setting) { setting.external_auth_client_cert.present? }
if: ->(setting) { setting.external_auth_client_cert.present? }
validates :default_ci_config_path,
format: { without: %r{(\.{2}|\A/)},

View File

@ -28,11 +28,11 @@ class AuditEvent < ApplicationRecord
validates :entity_type, presence: true
validates :ip_address, ip_address: true
scope :by_entity_type, -> (entity_type) { where(entity_type: entity_type) }
scope :by_entity_id, -> (entity_id) { where(entity_id: entity_id) }
scope :by_author_id, -> (author_id) { where(author_id: author_id) }
scope :by_entity_username, -> (username) { where(entity_id: find_user_id(username)) }
scope :by_author_username, -> (username) { where(author_id: find_user_id(username)) }
scope :by_entity_type, ->(entity_type) { where(entity_type: entity_type) }
scope :by_entity_id, ->(entity_id) { where(entity_id: entity_id) }
scope :by_author_id, ->(author_id) { where(author_id: author_id) }
scope :by_entity_username, ->(username) { where(entity_id: find_user_id(username)) }
scope :by_author_username, ->(username) { where(author_id: find_user_id(username)) }
after_initialize :initialize_details

View File

@ -23,8 +23,8 @@ class AwardEmoji < ApplicationRecord
scope :downvotes, -> { named(DOWNVOTE_NAME) }
scope :upvotes, -> { named(UPVOTE_NAME) }
scope :named, -> (names) { where(name: names) }
scope :awarded_by, -> (users) { where(user: users) }
scope :named, ->(names) { where(name: names) }
scope :awarded_by, ->(users) { where(user: users) }
after_save :expire_cache
after_destroy :expire_cache

View File

@ -12,7 +12,7 @@ class BoardGroupRecentVisit < ApplicationRecord
validates :group, presence: true
validates :board, presence: true
scope :by_user_parent, -> (user, group) { where(user: user, group: group) }
scope :by_user_parent, ->(user, group) { where(user: user, group: group) }
def self.board_parent_relation
:group

View File

@ -12,7 +12,7 @@ class BoardProjectRecentVisit < ApplicationRecord
validates :project, presence: true
validates :board, presence: true
scope :by_user_parent, -> (user, project) { where(user: user, project: project) }
scope :by_user_parent, ->(user, project) { where(user: user, project: project) }
def self.board_parent_relation
:project

View File

@ -17,7 +17,7 @@ class BulkImport < ApplicationRecord
enum source_type: { gitlab: 0 }
scope :stale, -> { where('created_at < ?', 8.hours.ago).where(status: [0, 1]) }
scope :order_by_created_at, -> (direction) { order(created_at: direction) }
scope :order_by_created_at, ->(direction) { order(created_at: direction) }
state_machine :status, initial: :created do
state :created, value: 0

View File

@ -53,7 +53,7 @@ class BulkImports::Entity < ApplicationRecord
scope :by_user_id, ->(user_id) { joins(:bulk_import).where(bulk_imports: { user_id: user_id }) }
scope :stale, -> { where('created_at < ?', 8.hours.ago).where(status: [0, 1]) }
scope :by_bulk_import_id, ->(bulk_import_id) { where(bulk_import_id: bulk_import_id) }
scope :order_by_created_at, -> (direction) { order(created_at: direction) }
scope :order_by_created_at, ->(direction) { order(created_at: direction) }
alias_attribute :destination_slug, :destination_name

View File

@ -1,6 +1,13 @@
# frozen_string_literal: true
module Ci
# This model represents metadata for a running build.
# Despite the generic RunningBuild name, in this first iteration it applies only to shared runners
# (see Ci::RunningBuild.upsert_shared_runner_build!).
# The decision to insert all of the running builds here was deferred to avoid the pressure on the database as
# at this time that was not necessary.
# We can reconsider the decision to limit this only to shared runners when there is more evidence that inserting all
# of the running builds there is worth the additional pressure.
class RunningBuild < Ci::ApplicationRecord
include Ci::Partitionable

View File

@ -19,7 +19,7 @@ class Integration < ApplicationRecord
INTEGRATION_NAMES = %w[
asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord
drone_ci emails_on_push ewm external_wiki flowdock hangouts_chat harbor irker jira
drone_ci emails_on_push ewm external_wiki hangouts_chat harbor irker jira
mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email
pivotaltracker prometheus pumble pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack zentao
].freeze

View File

@ -1,28 +1,12 @@
# frozen_string_literal: true
# This integration is scheduled for removal.
# All records must be deleted before the class can be removed.
# https://gitlab.com/gitlab-org/gitlab/-/issues/379197
module Integrations
class Flowdock < Integration
validates :token, presence: true, if: :activated?
field :token,
type: 'password',
help: -> { s_('FlowdockService|Enter your Flowdock token.') },
non_empty_password_title: -> { s_('ProjectService|Enter new token') },
non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current token.') },
placeholder: '1b609b52537...',
required: true
def title
'Flowdock'
end
def description
s_('FlowdockService|Send event notifications from GitLab to Flowdock flows.')
end
def help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('api/services', anchor: 'flowdock'), target: '_blank', rel: 'noopener noreferrer'
s_('FlowdockService|Send event notifications from GitLab to Flowdock flows. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
def readonly?
true
end
def self.to_param
@ -30,22 +14,7 @@ module Integrations
end
def self.supported_events
%w(push)
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
::Flowdock::Git.post(
data[:ref],
data[:before],
data[:after],
token: token,
repo: project.repository,
repo_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}",
commit_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/-/commit/%s",
diff_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/compare/%s...%s"
)
%w[]
end
end
end

View File

@ -196,7 +196,6 @@ class Project < ApplicationRecord
has_one :emails_on_push_integration, class_name: 'Integrations::EmailsOnPush'
has_one :ewm_integration, class_name: 'Integrations::Ewm'
has_one :external_wiki_integration, class_name: 'Integrations::ExternalWiki'
has_one :flowdock_integration, class_name: 'Integrations::Flowdock'
has_one :hangouts_chat_integration, class_name: 'Integrations::HangoutsChat'
has_one :harbor_integration, class_name: 'Integrations::Harbor'
has_one :irker_integration, class_name: 'Integrations::Irker'

View File

@ -68,6 +68,8 @@ class BasePolicy < DeclarativePolicy::Base
rule { default }.enable :read_cross_project
condition(:is_gitlab_com, score: 0, scope: :global) { ::Gitlab.com? }
condition(:is_bot?) { @user&.bot? }
end
BasePolicy.prepend_mod_with('BasePolicy')

View File

@ -18,6 +18,10 @@ class MergeRequestPolicy < IssuablePolicy
enable :approve_merge_request
end
rule { can?(:approve_merge_request) & is_bot? }.policy do
enable :reset_merge_request_approvals
end
rule { ~anonymous & can?(:read_merge_request) }.policy do
enable :create_todo
enable :update_subscription

View File

@ -6,6 +6,8 @@ module Projects
private
def filter_out_latest!(tags)
return unless keep_latest
tags.reject!(&:latest?)
end
@ -84,6 +86,10 @@ module Projects
params['keep_n']
end
def keep_latest
params.fetch('keep_latest', true)
end
def project
container_repository.project
end

View File

@ -5,7 +5,8 @@ module Projects
class DestroyService < BaseService
CLEANUP_TAGS_SERVICE_PARAMS = {
'name_regex_delete' => '.*',
'container_expiration_policy' => true # to avoid permissions checks
'container_expiration_policy' => true, # to avoid permissions checks
'keep_latest' => false
}.freeze
def execute(container_repository, disable_timeout: true)

View File

@ -1,4 +1,4 @@
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-terminal-settings'), html: { class: 'fieldset-form', id: 'terminal-settings' } do |f|
= gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-terminal-settings'), html: { class: 'fieldset-form', id: 'terminal-settings' } do |f|
= form_errors(@application_setting)
%fieldset
@ -7,4 +7,4 @@
= f.number_field :terminal_max_session_time, class: 'form-control gl-form-input'
.form-text.text-muted
= _('Maximum time, in seconds, for a web terminal websocket connection. 0 for unlimited.')
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
= f.submit _('Save changes'), pajamas_button: true

View File

@ -6,5 +6,5 @@
= c.body do
= s_('ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab\'s Google Kubernetes Engine Integration.').html_safe % { sign_up_link: link }
= c.actions do
%a.gl-button.btn-confirm.text-decoration-none{ href: 'https://cloud.google.com/partners/partnercredit/?pcn_code=0014M00001h35gDQAQ#contact-form', target: '_blank', rel: 'noopener noreferrer' }
= render Pajamas::ButtonComponent.new(variant: :confirm, href: 'https://cloud.google.com/partners/partnercredit/?pcn_code=0014M00001h35gDQAQ#contact-form', target: '_blank', button_options: { rel: 'noopener noreferrer' }) do
= s_("ClusterIntegration|Apply for credit")

View File

@ -20,6 +20,17 @@
'wait_for_update': 500
});
window.geofeed = (options) => {
dataLayer.push({
'event' : 'OneTrustCountryLoad',
'oneTrustCountryId': options.country.toString()
})
}
const json = document.createElement('script');
json.setAttribute('src', 'https://geolocation.onetrust.com/cookieconsentpub/v1/geo/location/geofeed');
document.head.appendChild(json);
- if Feature.enabled?(:gtm_nonce, type: :ops)
= javascript_tag nonce: content_security_policy_nonce do
:plain

View File

@ -18,22 +18,24 @@
%p.gl-text-center= html_escape(_('%{gitlab_experience_text}. Don\'t worry, this information isn\'t shared outside of your self-managed GitLab instance.')) % { gitlab_experience_text: gitlab_experience_text }
= gitlab_ui_form_for(current_user,
url: users_sign_up_welcome_path(glm_tracking_params),
html: { class: 'card gl-w-full! gl-p-5 js-users-signup-welcome',
html: { class: 'gl-w-full! gl-p-5 js-users-signup-welcome',
'aria-live' => 'assertive',
data: { testid: 'welcome-form' } }) do |f|
.devise-errors
= render 'devise/shared/error_messages', resource: current_user
.row
.form-group.col-sm-12
= f.label :role, _('Role'), class: 'label-bold'
= f.select :role, ::User.roles.keys.map { |role| [role.titleize, role] }, { include_blank: _('Select a role') }, class: 'form-control js-user-role-dropdown', autofocus: true, required: true, data: { qa_selector: 'role_dropdown' }
= render_if_exists "registrations/welcome/jobs_to_be_done", f: f
= render_if_exists "registrations/welcome/setup_for_company", f: f
= render_if_exists "registrations/welcome/joining_project"
= render 'devise/shared/email_opted_in', f: f
.row
.form-group.col-sm-12.gl-mb-0
- if partial_exists? "registrations/welcome/button"
= render "registrations/welcome/button"
- else
= f.submit _('Get started!'), class: 'btn-confirm gl-button btn btn-block gl-mb-0 gl-p-3', data: { qa_selector: 'get_started_button' }
= render Pajamas::CardComponent.new do |c|
- c.body do
.devise-errors
= render 'devise/shared/error_messages', resource: current_user
.row
.form-group.col-sm-12
= f.label :role, _('Role'), class: 'label-bold'
= f.select :role, ::User.roles.keys.map { |role| [role.titleize, role] }, { include_blank: _('Select a role') }, class: 'form-control js-user-role-dropdown', autofocus: true, required: true, data: { qa_selector: 'role_dropdown' }
= render_if_exists "registrations/welcome/jobs_to_be_done", f: f
= render_if_exists "registrations/welcome/setup_for_company", f: f
= render_if_exists "registrations/welcome/joining_project"
= render 'devise/shared/email_opted_in', f: f
.row
.form-group.col-sm-12.gl-mb-0
- if partial_exists? "registrations/welcome/button"
= render "registrations/welcome/button"
- else
= f.submit _('Get started!'), class: 'btn-confirm gl-button btn btn-block gl-mb-0 gl-p-3', data: { qa_selector: 'get_started_button' }

View File

@ -20,7 +20,7 @@
.js-sidebar-todo-widget-root{ data: { project_path: issuable_sidebar[:project_full_path], iid: issuable_sidebar[:iid], id: issuable_sidebar[:id] } }
= form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f|
.block.assignee{ class: "#{'gl-mt-3' if !signed_in && moved_sidebar_enabled}", data: { qa_selector: 'assignee_block_container' } }
.block.assignee{ class: "#{'gl-mt-3' if !signed_in && moved_sidebar_enabled}", data: { qa_selector: 'assignee_block_container', testid: 'assignee-block-container' } }
= render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees, signed_in: signed_in
- if issuable_sidebar[:supports_severity]

View File

@ -17,10 +17,12 @@ module ContainerRegistry
MAX_CAPACITY = 2
CLEANUP_TAGS_SERVICE_PARAMS = {
'name_regex_delete' => '.*',
'keep_latest' => false,
'container_expiration_policy' => true # to avoid permissions checks
}.freeze
def perform_work
return unless Feature.enabled?(:container_registry_delete_repository_with_cron_worker)
return unless next_container_repository
result = delete_tags
@ -38,6 +40,8 @@ module ContainerRegistry
end
def remaining_work_count
return 0 unless Feature.enabled?(:container_registry_delete_repository_with_cron_worker)
::ContainerRepository.delete_scheduled.limit(max_running_jobs + 1).count
end

View File

@ -7,7 +7,9 @@ product_stage: manage
product_group: integrations
product_category: integrations
value_type: number
status: active
status: removed
removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102394
milestone_removed: '15.7'
time_frame: all
data_source: database
distribution:

View File

@ -7,7 +7,9 @@ product_stage: manage
product_group: integrations
product_category: integrations
value_type: number
status: active
status: removed
removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102394
milestone_removed: '15.7'
time_frame: all
data_source: database
distribution:

View File

@ -7,7 +7,9 @@ product_stage: manage
product_group: integrations
product_category: integrations
value_type: number
status: active
status: removed
removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102394
milestone_removed: '15.7'
time_frame: all
data_source: database
distribution:

View File

@ -7,7 +7,9 @@ product_stage: manage
product_group: integrations
product_category: integrations
value_type: number
status: active
status: removed
removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102394
milestone_removed: '15.7'
time_frame: all
data_source: database
distribution:

View File

@ -7,7 +7,9 @@ product_stage: manage
product_group: integrations
product_category: integrations
value_type: number
status: active
status: removed
removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102394
milestone_removed: '15.7'
time_frame: all
data_source: database
distribution:

View File

@ -0,0 +1,18 @@
- title: "Flowdock integration" # (required) Actionable title. e.g., The `confidential` field for a `Note` is deprecated. Use `internal` instead.
announcement_milestone: "15.7" # (required) The milestone when this feature was deprecated.
announcement_date: "2022-12-22" # (required) The date of the milestone release when this feature was deprecated. This should almost always be the 22nd of a month (YYYY-MM-DD), unless you did an out of band blog post.
removal_milestone: "15.7" # (required) The milestone when this feature is being removed.
removal_date: "2022-12-22" # (required) This should almost always be the 22nd of a month (YYYY-MM-DD), the date of the milestone release when this feature will be removed.
breaking_change: false # (required) Change to true if this removal is a breaking change.
reporter: arturoherrero # (required) GitLab username of the person reporting the removal
stage: manage # (required) String value of the stage that the feature was created in. e.g., Growth
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/379197 # (required) Link to the deprecation issue in GitLab
body: | # (required) Do not modify this line, instead modify the lines below.
As of December 22, 2022, we are removing the Flowdock integration because the service was shut down on August 15, 2022.
#
# OPTIONAL FIELDS
#
tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
documentation_url: # (optional) This is a link to the current documentation page
image_url: # (optional) This is a link to a thumbnail image depicting the feature
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg

View File

@ -4,7 +4,7 @@ classes:
- Ci::PendingBuild
feature_categories:
- continuous_integration
description: TODO
description: Pending builds metadata
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61581
milestone: '14.0'
gitlab_schema: gitlab_ci

View File

@ -4,7 +4,13 @@ classes:
- Ci::RunningBuild
feature_categories:
- continuous_integration
description: TODO
description: >
Running builds metadata.
Despite the generic `RunningBuild` name, in this first iteration it applies only to shared runners.
The decision to insert all of the running builds here was deferred to avoid the pressure on the database as
at this time that was not necessary.
We can reconsider the decision to limit this only to shared runners when there is more evidence that inserting all
of the running builds there is worth the additional pressure.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62912
milestone: '14.0'
gitlab_schema: gitlab_ci

View File

@ -21,7 +21,6 @@ classes:
- Integrations::EmailsOnPush
- Integrations::Ewm
- Integrations::ExternalWiki
- Integrations::Flowdock
- Integrations::Github
- Integrations::GitlabSlackApplication
- Integrations::HangoutsChat

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddLastSeatRefreshAtToGitlabSubscriptions < Gitlab::Database::Migration[2.0]
enable_lock_retries!
TABLE_NAME = 'gitlab_subscriptions'
COLUMN_NAME = 'last_seat_refresh_at'
def up
add_column(TABLE_NAME, COLUMN_NAME, :datetime_with_timezone)
end
def down
remove_column(TABLE_NAME, COLUMN_NAME)
end
end

View File

@ -0,0 +1 @@
1621f0ac141f24c15beef34f5f411158c1eb8a89f5022dd426533d705aa859fe

View File

@ -15982,6 +15982,7 @@ CREATE TABLE gitlab_subscriptions (
seats_owed integer DEFAULT 0 NOT NULL,
trial_extension_type smallint,
max_seats_used_changed_at timestamp with time zone,
last_seat_refresh_at timestamp with time zone,
CONSTRAINT check_77fea3f0e7 CHECK ((namespace_id IS NOT NULL))
);

View File

@ -348,7 +348,6 @@ Flamegraph
flamegraphs
Flawfinder
Flickr
Flowdock
Fluentd
Flutterwave
Flycheck

View File

@ -16885,7 +16885,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="pipelinesecurityreportfindingsolution"></a>`solution` | [`String`](#string) | URL to the vulnerability's details page. |
| <a id="pipelinesecurityreportfindingstate"></a>`state` | [`VulnerabilityState`](#vulnerabilitystate) | Finding status. |
| <a id="pipelinesecurityreportfindingtitle"></a>`title` | [`String`](#string) | Title of the vulnerability finding. |
| <a id="pipelinesecurityreportfindinguuid"></a>`uuid` | [`String`](#string) | Name of the vulnerability finding. |
| <a id="pipelinesecurityreportfindinguuid"></a>`uuid` | [`String`](#string) | UUIDv5 digest based on the vulnerability's report type, primary identifier, location, fingerprint, project identifier. |
### `PreviewBillableUserChange`
@ -22509,7 +22509,6 @@ State of a Sentry error.
| <a id="servicetypeemails_on_push_service"></a>`EMAILS_ON_PUSH_SERVICE` | EmailsOnPushService type. |
| <a id="servicetypeewm_service"></a>`EWM_SERVICE` | EwmService type. |
| <a id="servicetypeexternal_wiki_service"></a>`EXTERNAL_WIKI_SERVICE` | ExternalWikiService type. |
| <a id="servicetypeflowdock_service"></a>`FLOWDOCK_SERVICE` | FlowdockService type. |
| <a id="servicetypegithub_service"></a>`GITHUB_SERVICE` | GithubService type. |
| <a id="servicetypegitlab_slack_application_service"></a>`GITLAB_SLACK_APPLICATION_SERVICE` | GitlabSlackApplicationService type (Gitlab.com only). |
| <a id="servicetypehangouts_chat_service"></a>`HANGOUTS_CHAT_SERVICE` | HangoutsChatService type. |

View File

@ -755,42 +755,6 @@ Get External wiki integration settings for a project.
GET /projects/:id/integrations/external-wiki
```
## Flowdock
Flowdock is a ChatOps application for collaboration in software engineering
companies. You can send notifications from GitLab events to Flowdock flows.
For integration instructions, see the [Flowdock documentation](https://www.flowdock.com/help/gitlab).
### Create/Edit Flowdock integration
Set Flowdock integration for a project.
```plaintext
PUT /projects/:id/integrations/flowdock
```
Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `token` | string | true | Flowdock Git source token |
### Disable Flowdock integration
Disable the Flowdock integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/flowdock
```
### Get Flowdock integration settings
Get Flowdock integration settings for a project.
```plaintext
GET /projects/:id/integrations/flowdock
```
## GitHub **(PREMIUM)**
Code collaboration software.

View File

@ -61,7 +61,7 @@ All Work Item types share the same pool of predefined widgets and are customized
| description | |
| hierarchy | |
| [iteration](https://gitlab.com/gitlab-org/gitlab/-/issues/367456) | |
| [milestone](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) | work_items_mvc_2 |
| [milestone](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) | work_items_mvc |
| labels | |
| start and due date | |
| status\* | |

View File

@ -11,7 +11,7 @@ You can integrate GitLab with external services for enhanced functionality.
## Services
Services such as Campfire, Flowdock, Jira, Pivotal Tracker, and Slack
Services such as Campfire, Jira, Pivotal Tracker, and Slack
are available as [integrations](../user/project/integrations/index.md).
## Issue trackers

View File

@ -57,6 +57,12 @@ If you want to preserve this functionality, you can follow one of these two path
1. Fork the [GitLab Auto Deploy Helm chart](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image/-/tree/master/assets/auto-deploy-app) into the `chart/` path within your project
1. Set `AUTO_DEPLOY_IMAGE_VERSION` and `DAST_AUTO_DEPLOY_IMAGE_VERSION` to the most recent version of the image that included the CiliumNetworkPolicy
## Removed in 15.7
### Flowdock integration
As of December 22, 2022, we are removing the Flowdock integration because the service was shut down on August 15, 2022.
## Removed in 15.6
### NFS as Git repository storage is no longer supported. Migrate to Gitaly Cluster as soon as possible

View File

@ -58,7 +58,6 @@ You can configure the following integrations.
| [Emails on push](emails_on_push.md) | Send commits and diff of each push by email. | **{dotted-circle}** No |
| [EWM](ewm.md) | Use IBM Engineering Workflow Management as the issue tracker. | **{dotted-circle}** No |
| [External wiki](../wiki/index.md#link-an-external-wiki) | Link an external wiki. | **{dotted-circle}** No |
| [Flowdock](../../../api/integrations.md#flowdock) | Send notifications from GitLab to Flowdock flows. | **{dotted-circle}** No |
| [GitHub](github.md) | Obtain statuses for commits and pull requests. | **{dotted-circle}** No |
| [Google Chat](hangouts_chat.md) | Send notifications from your GitLab project to a room in Google Chat. | **{dotted-circle}** No |
| [Harbor](harbor.md) | Use Harbor as the container registry. | **{dotted-circle}** No |

View File

@ -207,10 +207,12 @@ To set a start date:
## Add a task to a milestone
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) in GitLab 15.5 [with a flag](../administration/feature_flags.md) named `work_items_mvc_2`. Disabled by default.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) in GitLab 15.5 [with a flag](../administration/feature_flags.md) named `work_items_mvc_2`. Disabled by default.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) to feature flag named `work_items_mvc` in GitLab 15.7. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available per group, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `work_items_mvc_2`. On GitLab.com, this feature is not available. The feature is not ready for production use.
On self-managed GitLab, by default this feature is not available. To make it available per group, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `work_items_mvc`.
On GitLab.com, this feature is not available. The feature is not ready for production use.
You can add a task to a [milestone](project/milestones/index.md).
You can see the milestone title when you view a task.

View File

@ -415,14 +415,6 @@ module API
desc: 'The URL of the external wiki'
}
],
'flowdock' => [
{
required: true,
name: :token,
type: String,
desc: 'Flowdock token'
}
],
'hangouts-chat' => [
{
required: true,
@ -893,7 +885,6 @@ module API
::Integrations::EmailsOnPush,
::Integrations::Ewm,
::Integrations::ExternalWiki,
::Integrations::Flowdock,
::Integrations::HangoutsChat,
::Integrations::Harbor,
::Integrations::Irker,

View File

@ -88,6 +88,23 @@ module API
present_approval(merge_request)
end
desc 'Remove all merge request approvals' do
detail 'Clear all approvals of merge request. This feature was added in GitLab 15.4'
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not found' }
]
tags %w[merge_requests]
end
put 'reset_approvals', urgency: :low do
merge_request = find_project_merge_request(params[:merge_request_iid])
unauthorized! unless current_user.can?(:reset_merge_request_approvals, merge_request)
merge_request.approvals.delete_all
status :accepted
end
end
end
end

View File

@ -733,25 +733,6 @@ module API
rescue ::MergeRequest::RebaseLockTimeout => e
render_api_error!(e.message, 409)
end
desc 'Reset approvals of a merge request' do
detail 'Clear all approvals of merge request. This feature was added in GitLab 15.4'
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not found' }
]
tags %w[merge_requests]
end
put ':id/merge_requests/:merge_request_iid/reset_approvals', feature_category: :code_review, urgency: :low do
merge_request = find_project_merge_request(params[:merge_request_iid])
unauthorized! unless current_user.bot? && merge_request.eligible_for_approval_by?(current_user)
merge_request.approvals.delete_all
status :accepted
end
desc 'List issues that close on merge' do
detail 'Get all the issues that would be closed by merging the provided merge request.'
success Entities::MRNote

View File

@ -1,67 +0,0 @@
# frozen_string_literal: true
require 'flowdock'
require 'flowdock/git/builder'
module Flowdock
class Git
TokenError = Class.new(StandardError)
DEFAULT_PERMANENT_REFS = [
Regexp.new('refs/heads/master')
].freeze
class << self
def post(ref, from, to, options = {})
Git.new(ref, from, to, options).post
end
end
def initialize(ref, from, to, options = {})
raise TokenError, "Flowdock API token not found" unless options[:token]
@ref = ref
@from = from
@to = to
@options = options
@token = options[:token]
@commit_url = options[:commit_url]
@diff_url = options[:diff_url]
@repo_url = options[:repo_url]
@repo_name = options[:repo_name]
@permanent_refs = options.fetch(:permanent_refs, DEFAULT_PERMANENT_REFS)
end
# Send git push notification to Flowdock
def post
messages.each do |message|
::Flowdock::Client.new(flow_token: @token).post_to_thread(message)
end
end
def repo
@options[:repo]
end
private
def messages
Git::Builder.new(repo: repo,
ref: @ref,
before: @from,
after: @to,
commit_url: @commit_url,
branch_url: @branch_url,
diff_url: @diff_url,
repo_url: @repo_url,
repo_name: @repo_name,
permanent_refs: @permanent_refs,
tags: tags
).to_hashes
end
# Flowdock tags attached to the push notification
def tags
Array(@options[:tags]).map { |tag| CGI.escape(tag) }
end
end
end

View File

@ -1,145 +0,0 @@
# frozen_string_literal: true
module Flowdock
class Git
class Commit
def initialize(external_thread_id, thread, tags, commit)
@commit = commit
@external_thread_id = external_thread_id
@thread = thread
@tags = tags
end
def to_hash
hash = {
external_thread_id: @external_thread_id,
event: "activity",
author: {
name: @commit[:author][:name],
email: @commit[:author][:email]
},
title: title,
thread: @thread,
body: body
}
hash[:tags] = @tags if @tags
encode(hash)
end
private
def encode(hash)
return hash unless "".respond_to?(:encode)
encode_as_utf8(hash)
end
# This only works on Ruby 1.9
def encode_as_utf8(obj)
if obj.is_a? Hash
obj.each_pair do |key, val|
encode_as_utf8(val)
end
elsif obj.is_a?(Array)
obj.each do |val|
encode_as_utf8(val)
end
elsif obj.is_a?(String) && obj.encoding != Encoding::UTF_8
unless obj.force_encoding("UTF-8").valid_encoding?
obj.force_encoding("ISO-8859-1").encode!(Encoding::UTF_8, invalid: :replace, undef: :replace)
end
end
end
def body
content = @commit[:message][first_line.size..]
content.strip! if content
"<pre>#{content}</pre>" unless content.empty?
end
def first_line
@first_line ||= (@commit[:message].split("\n")[0] || @commit[:message])
end
def title
commit_id = @commit[:id][0, 7]
if @commit[:url]
"<a href=\"#{@commit[:url]}\">#{commit_id}</a> #{message_title}"
else
"#{commit_id} #{message_title}"
end
end
def message_title
CGI.escape_html(first_line.strip)
end
end
# Class used to build Git payload
class Builder
include ::Gitlab::Utils::StrongMemoize
def initialize(opts)
@repo = opts[:repo]
@ref = opts[:ref]
@before = opts[:before]
@after = opts[:after]
@opts = opts
end
def commits
@repo.commits_between(@before, @after).map do |commit|
{
url: @opts[:commit_url] ? @opts[:commit_url] % [commit.sha] : nil,
id: commit.sha,
message: commit.message,
author: {
name: commit.author_name,
email: commit.author_email
}
}
end
end
def ref_name
@ref.to_s.sub(%r{\Arefs/(heads|tags)/}, '')
end
def to_hashes
commits.map do |commit|
Commit.new(external_thread_id, thread, @opts[:tags], commit).to_hash
end
end
private
def thread
@thread ||= {
title: thread_title,
external_url: @opts[:repo_url]
}
end
def permanent?
strong_memoize(:permanent) do
@opts[:permanent_refs].any? { |regex| regex.match(@ref) }
end
end
def thread_title
action = "updated" if permanent?
type = @ref =~ %r(^refs/heads/) ? "branch" : "tag"
[@opts[:repo_name], type, ref_name, action].compact.join(" ")
end
def external_thread_id
@external_thread_id ||=
if permanent?
SecureRandom.hex
else
@ref
end
end
end
end
end

View File

@ -50,5 +50,3 @@ module Gitlab
end
end
end
Gitlab::Ci::Build::Context::Build.prepend_mod_with('Gitlab::Ci::Build::Context::Build')

View File

@ -40,15 +40,6 @@ module Gitlab
pids.each { |pid| signal(pid, signal) }
end
# Waits for the given process to complete using a separate thread.
def self.wait_async(pid)
Thread.new do
Process.wait(pid)
rescue StandardError
nil # There is no reason to return `Errno::ECHILD` if it catches a `TypeError`
end
end
# Returns true if all the processes are alive.
def self.all_alive?(pids)
pids.each do |pid|

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
require_relative './daemon'
module Gitlab
# Given a set of process IDs, the supervisor can monitor processes
# for being alive and invoke a callback if some or all should go away.

View File

@ -18,11 +18,16 @@ module Gitlab
def verification_status
strong_memoize(:verification_status) do
next :unverified unless all_attributes_present?
next :unverified unless valid_signature_blob? && committer
next :unverified unless valid_signature_blob?
next :unknown_key unless signed_by_key
next :other_user unless committer
next :other_user unless signed_by_key.user == committer
:verified
if signed_by_user_email_verified?
:verified
else
:unverified
end
end
end
@ -55,7 +60,11 @@ module Gitlab
def committer
# Lookup by email because users can push verified commits that were made
# by someone else. For example: Doing a rebase.
strong_memoize(:committer) { User.find_by_any_email(@committer_email, confirmed: true) }
strong_memoize(:committer) { User.find_by_any_email(@committer_email) }
end
def signed_by_user_email_verified?
signed_by_key.user.verified_emails.include?(@committer_email)
end
def signature

View File

@ -1279,12 +1279,18 @@ msgstr ""
msgid "(Group Managed Account)"
msgstr ""
msgid "(Limited to %{quota} pipeline minutes per month)"
msgstr ""
msgid "(No changes)"
msgstr ""
msgid "(UTC %{offset}) %{timezone}"
msgstr ""
msgid "(Unlimited pipeline minutes)"
msgstr ""
msgid "(check progress)"
msgstr ""
@ -9256,6 +9262,9 @@ msgstr ""
msgid "ClusterAgents|Unknown user"
msgstr ""
msgid "ClusterAgents|Use a Helm version compatible with your Kubernetes version (see %{linkStart}Helm version support policy%{linkEnd})."
msgstr ""
msgid "ClusterAgents|Valid access token"
msgstr ""
@ -17295,15 +17304,6 @@ msgstr ""
msgid "FloC|Participate in FLoC"
msgstr ""
msgid "FlowdockService|Enter your Flowdock token."
msgstr ""
msgid "FlowdockService|Send event notifications from GitLab to Flowdock flows."
msgstr ""
msgid "FlowdockService|Send event notifications from GitLab to Flowdock flows. %{docs_link}"
msgstr ""
msgid "Focus filter bar"
msgstr ""
@ -27117,6 +27117,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
msgid "New code quality findings"
msgstr ""
msgid "New confidential epic title "
msgstr ""
@ -31336,6 +31339,9 @@ msgstr ""
msgid "Proceed"
msgstr ""
msgid "Product Analytics|Onboarding view"
msgstr ""
msgid "Product analytics"
msgstr ""
@ -35502,6 +35508,9 @@ msgstr ""
msgid "Runners|IP Address"
msgstr ""
msgid "Runners|Idle"
msgstr ""
msgid "Runners|Install a runner"
msgstr ""
@ -35708,6 +35717,9 @@ msgstr ""
msgid "Runners|Runners are the agents that run your CI/CD jobs. To register new runners, please contact your administrator."
msgstr ""
msgid "Runners|Running"
msgstr ""
msgid "Runners|Runs untagged jobs"
msgstr ""
@ -38056,6 +38068,9 @@ msgstr ""
msgid "Shared Runners"
msgstr ""
msgid "Shared Runners:"
msgstr ""
msgid "Shared projects"
msgstr ""
@ -46704,9 +46719,6 @@ msgstr ""
msgid "WorkItem|Incident"
msgstr ""
msgid "WorkItem|Introducing tasks"
msgstr ""
msgid "WorkItem|Issue"
msgstr ""
@ -46716,9 +46728,6 @@ msgstr ""
msgid "WorkItem|Key result"
msgstr ""
msgid "WorkItem|Learn about tasks."
msgstr ""
msgid "WorkItem|Milestone"
msgstr ""
@ -46836,9 +46845,6 @@ msgstr ""
msgid "WorkItem|Undo"
msgstr ""
msgid "WorkItem|Use tasks to break down your work in an issue into smaller pieces. %{learnMoreLink}"
msgstr ""
msgid "WorkItem|View current version"
msgstr ""

View File

@ -112,7 +112,7 @@ module Gitlab
end
def start_and_supervise_workers(queue_groups)
worker_pids = SidekiqCluster.start(
wait_threads = SidekiqCluster.start(
queue_groups,
env: @environment,
directory: @rails_path,
@ -135,6 +135,7 @@ module Gitlab
)
metrics_server_pid = start_metrics_server
worker_pids = wait_threads.map(&:pid)
supervisor.supervise(worker_pids + Array(metrics_server_pid)) do |dead_pids|
# If we're not in the process of shutting down the cluster,
# and the metrics server died, restart it.
@ -149,6 +150,13 @@ module Gitlab
[]
end
end
exit_statuses = wait_threads.map do |thread|
thread.join
thread.value
end
exit 1 unless exit_statuses.compact.all?(&:success?)
end
def start_metrics_server

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative '../lib/gitlab/process_management'
require_relative '../lib/gitlab/process_supervisor'
module Gitlab
module SidekiqCluster
@ -33,7 +34,8 @@ module Gitlab
#
# directory - The directory of the Rails application.
#
# Returns an Array containing the PIDs of the started processes.
# Returns an Array containing the waiter threads (from Process.detach) of
# the started processes.
def self.start(queues, env: :development, directory: Dir.pwd, max_concurrency: 20, min_concurrency: 0, timeout: DEFAULT_SOFT_TIMEOUT_SECONDS, dryrun: false)
queues.map.with_index do |pair, index|
start_sidekiq(pair, env: env,
@ -82,9 +84,7 @@ module Gitlab
)
end
ProcessManagement.wait_async(pid)
pid
Process.detach(pid)
end
def self.count_by_queue(queues)

View File

@ -299,11 +299,11 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
end
context 'starting the server' do
context 'without --dryrun' do
before do
allow(Gitlab::SidekiqCluster).to receive(:start).and_return([])
end
before do
allow(Gitlab::SidekiqCluster).to receive(:start).and_return([])
end
context 'without --dryrun' do
it 'wipes the metrics directory before starting workers' do
expect(metrics_cleanup_service).to receive(:execute).ordered
expect(Gitlab::SidekiqCluster).to receive(:start).ordered.and_return([])
@ -403,9 +403,42 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
let(:sidekiq_exporter_enabled) { true }
let(:metrics_server_pid) { 99 }
let(:sidekiq_worker_pids) { [2, 42] }
let(:waiter_threads) { [instance_double('Process::Waiter'), instance_double('Process::Waiter')] }
let(:process_status) { instance_double('Process::Status') }
before do
allow(Gitlab::SidekiqCluster).to receive(:start).and_return(sidekiq_worker_pids)
allow(Gitlab::SidekiqCluster).to receive(:start).and_return(waiter_threads)
allow(process_status).to receive(:success?).and_return(true)
allow(cli).to receive(:exit)
waiter_threads.each.with_index do |thread, i|
allow(thread).to receive(:join)
allow(thread).to receive(:pid).and_return(sidekiq_worker_pids[i])
allow(thread).to receive(:value).and_return(process_status)
end
end
context 'when one of the workers has been terminated gracefully' do
it 'stops the entire process cluster' do
expect(MetricsServer).to receive(:start_for_sidekiq).once.and_return(metrics_server_pid)
expect(supervisor).to receive(:supervise).and_yield([2, 99])
expect(supervisor).to receive(:shutdown)
expect(cli).not_to receive(:exit).with(1)
cli.run(%w(foo))
end
end
context 'when one of the workers has failed' do
it 'stops the entire process cluster and exits with a non-zero code' do
expect(MetricsServer).to receive(:start_for_sidekiq).once.and_return(metrics_server_pid)
expect(supervisor).to receive(:supervise).and_yield([2, 99])
expect(supervisor).to receive(:shutdown)
expect(process_status).to receive(:success?).and_return(false)
expect(cli).to receive(:exit).with(1)
cli.run(%w(foo))
end
end
it 'stops the entire process cluster if one of the workers has been terminated' do

View File

@ -2,16 +2,31 @@
require 'spec_helper'
# Flaky spec warning: the queries in this file routinely exceed the defined GraphQL query limit of 100.
# Until those queries are optimized, we need to disable query limit checking in order for these tests
# to pass consistently. Note that removing the disabling code can lead to flaky failures locally and in CI.
#
# In addition, it seems as though the use of `let_it_be` might be causing some of the
# flakiness, as discussed in https://github.com/test-prof/test-prof/blob/master/docs/recipes/let_it_be.md#modifiers.
# `reload: true` has been added to all `let_it_be` statements.
#
# See:
# - https://gitlab.com/gitlab-org/gitlab/-/issues/323426
# - https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56458#note_535900110
# - https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102719
# - https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105849
# - https://gitlab.com/gitlab-org/gitlab/-/issues/383970
#
RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do
include DragTo
include MobileHelpers
include BoardHelpers
let_it_be(:group) { create(:group, :nested) }
let_it_be(:project) { create(:project, :public, namespace: group) }
let_it_be(:board) { create(:board, project: project) }
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
let_it_be(:group, reload: true) { create(:group, :nested) }
let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
let_it_be(:board, reload: true) { create(:board, project: project) }
let_it_be(:user, reload: true) { create(:user) }
let_it_be(:user2, reload: true) { create(:user) }
let(:filtered_search) { find('[data-testid="issue-board-filtered-search"]') }
let(:filter_input) { find('.gl-filtered-search-term-input') }
@ -47,31 +62,31 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do
end
context 'with lists' do
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:milestone, reload: true) { create(:milestone, project: project) }
let_it_be(:planning) { create(:label, project: project, name: 'Planning', description: 'Test') }
let_it_be(:development) { create(:label, project: project, name: 'Development') }
let_it_be(:testing) { create(:label, project: project, name: 'Testing') }
let_it_be(:bug) { create(:label, project: project, name: 'Bug') }
let_it_be(:backlog) { create(:label, project: project, name: 'Backlog') }
let_it_be(:closed) { create(:label, project: project, name: 'Closed') }
let_it_be(:accepting) { create(:label, project: project, name: 'Accepting Merge Requests') }
let_it_be(:a_plus) { create(:label, project: project, name: 'A+') }
let_it_be(:list1) { create(:list, board: board, label: planning, position: 0) }
let_it_be(:list2) { create(:list, board: board, label: development, position: 1) }
let_it_be(:backlog_list) { create(:backlog_list, board: board) }
let_it_be(:planning, reload: true) { create(:label, project: project, name: 'Planning', description: 'Test') }
let_it_be(:development, reload: true) { create(:label, project: project, name: 'Development') }
let_it_be(:testing, reload: true) { create(:label, project: project, name: 'Testing') }
let_it_be(:bug, reload: true) { create(:label, project: project, name: 'Bug') }
let_it_be(:backlog, reload: true) { create(:label, project: project, name: 'Backlog') }
let_it_be(:closed, reload: true) { create(:label, project: project, name: 'Closed') }
let_it_be(:accepting, reload: true) { create(:label, project: project, name: 'Accepting Merge Requests') }
let_it_be(:a_plus, reload: true) { create(:label, project: project, name: 'A+') }
let_it_be(:list1, reload: true) { create(:list, board: board, label: planning, position: 0) }
let_it_be(:list2, reload: true) { create(:list, board: board, label: development, position: 1) }
let_it_be(:backlog_list, reload: true) { create(:backlog_list, board: board) }
let_it_be(:confidential_issue) { create(:labeled_issue, :confidential, project: project, author: user, labels: [planning], relative_position: 9) }
let_it_be(:issue1) { create(:labeled_issue, project: project, title: 'aaa', description: '111', assignees: [user], labels: [planning], relative_position: 8) }
let_it_be(:issue2) { create(:labeled_issue, project: project, title: 'bbb', description: '222', author: user2, labels: [planning], relative_position: 7) }
let_it_be(:issue3) { create(:labeled_issue, project: project, title: 'ccc', description: '333', labels: [planning], relative_position: 6) }
let_it_be(:issue4) { create(:labeled_issue, project: project, title: 'ddd', description: '444', labels: [planning], relative_position: 5) }
let_it_be(:issue5) { create(:labeled_issue, project: project, title: 'eee', description: '555', labels: [planning], milestone: milestone, relative_position: 4) }
let_it_be(:issue6) { create(:labeled_issue, project: project, title: 'fff', description: '666', labels: [planning, development], relative_position: 3) }
let_it_be(:issue7) { create(:labeled_issue, project: project, title: 'ggg', description: '777', labels: [development], relative_position: 2) }
let_it_be(:issue8) { create(:closed_issue, project: project, title: 'hhh', description: '888') }
let_it_be(:issue9) { create(:labeled_issue, project: project, title: 'iii', description: '999', labels: [planning, testing, bug, accepting], relative_position: 1) }
let_it_be(:issue10) { create(:labeled_issue, project: project, title: 'issue +', description: 'A+ great issue', labels: [a_plus]) }
let_it_be(:confidential_issue, reload: true) { create(:labeled_issue, :confidential, project: project, author: user, labels: [planning], relative_position: 9) }
let_it_be(:issue1, reload: true) { create(:labeled_issue, project: project, title: 'aaa', description: '111', assignees: [user], labels: [planning], relative_position: 8) }
let_it_be(:issue2, reload: true) { create(:labeled_issue, project: project, title: 'bbb', description: '222', author: user2, labels: [planning], relative_position: 7) }
let_it_be(:issue3, reload: true) { create(:labeled_issue, project: project, title: 'ccc', description: '333', labels: [planning], relative_position: 6) }
let_it_be(:issue4, reload: true) { create(:labeled_issue, project: project, title: 'ddd', description: '444', labels: [planning], relative_position: 5) }
let_it_be(:issue5, reload: true) { create(:labeled_issue, project: project, title: 'eee', description: '555', labels: [planning], milestone: milestone, relative_position: 4) }
let_it_be(:issue6, reload: true) { create(:labeled_issue, project: project, title: 'fff', description: '666', labels: [planning, development], relative_position: 3) }
let_it_be(:issue7, reload: true) { create(:labeled_issue, project: project, title: 'ggg', description: '777', labels: [development], relative_position: 2) }
let_it_be(:issue8, reload: true) { create(:closed_issue, project: project, title: 'hhh', description: '888') }
let_it_be(:issue9, reload: true) { create(:labeled_issue, project: project, title: 'iii', description: '999', labels: [planning, testing, bug, accepting], relative_position: 1) }
let_it_be(:issue10, reload: true) { create(:labeled_issue, project: project, title: 'issue +', description: 'A+ great issue', labels: [a_plus]) }
before do
visit_project_board(project, board)
@ -125,7 +140,7 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do
it 'infinite scrolls list' do
create_list(:labeled_issue, 30, project: project, labels: [planning])
visit_project_board(project, board)
visit_project_board_path_without_query_limit(project, board)
page.within(find('.board:nth-child(2)')) do
expect(page.find('.board-header')).to have_content('38')
@ -204,31 +219,26 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do
expect(find('.board:nth-child(3) [data-testid="board-list-header"]')).to have_content(planning.title)
# Make sure list positions are preserved after a reload
visit_project_board(project, board)
visit_project_board_path_without_query_limit(project, board)
expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(development.title)
expect(find('.board:nth-child(3) [data-testid="board-list-header"]')).to have_content(planning.title)
end
context 'without backlog and closed lists' do
let_it_be(:board) { create(:board, project: project, hide_backlog_list: true, hide_closed_list: true) }
let_it_be(:list1) { create(:list, board: board, label: planning, position: 0) }
let_it_be(:list2) { create(:list, board: board, label: development, position: 1) }
let_it_be(:board, reload: true) { create(:board, project: project, hide_backlog_list: true, hide_closed_list: true) }
let_it_be(:list1, reload: true) { create(:list, board: board, label: planning, position: 0) }
let_it_be(:list2, reload: true) { create(:list, board: board, label: development, position: 1) }
it 'changes position of list' do
inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
visit_project_board(project, board)
end
visit_project_board_path_without_query_limit(project, board)
drag(list_from_index: 0, list_to_index: 1, selector: '.board-header')
expect(find('.board:nth-child(1) [data-testid="board-list-header"]')).to have_content(development.title)
expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(planning.title)
inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
# Make sure list positions are preserved after a reload
visit_project_board(project, board)
end
visit_project_board_path_without_query_limit(project, board)
expect(find('.board:nth-child(1) [data-testid="board-list-header"]')).to have_content(development.title)
expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(planning.title)
@ -531,7 +541,7 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do
end
context 'as guest user' do
let_it_be(:user_guest) { create(:user) }
let_it_be(:user_guest, reload: true) { create(:user) }
before do
stub_feature_flags(apollo_boards: false)
@ -601,4 +611,10 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do
wait_for_requests
end
def visit_project_board_path_without_query_limit(project, board)
inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
visit_project_board(project, board)
end
end
end

Some files were not shown because too many files have changed in this diff Show More