Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1ae73e5bfc
commit
3bdc719293
44 changed files with 554 additions and 315 deletions
|
@ -11,36 +11,9 @@
|
|||
metrics: "${METRICS_FILE}"
|
||||
expire_in: 31d
|
||||
|
||||
# Disabled since it causes intermittent SIGSEGV in CI:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/350296
|
||||
.memory-static:
|
||||
extends: .only-code-memory-job-base
|
||||
stage: test
|
||||
needs: ["setup-test-env"]
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
MEMORY_BUNDLE_MEM_FILE: "tmp/memory_bundle_mem.txt"
|
||||
MEMORY_BUNDLE_OBJECTS_FILE: "tmp/memory_bundle_objects.txt"
|
||||
script:
|
||||
# Uses two different reports from the 'derailed_benchmars' gem.
|
||||
|
||||
# Loads each of gems in the Gemfile and checks how much memory they consume when they are required.
|
||||
# 'derailed_benchmarks' internally uses 'get_process_mem'
|
||||
- bundle exec derailed bundle:mem > "${MEMORY_BUNDLE_MEM_FILE}"
|
||||
- scripts/generate-gems-size-metrics-static "${MEMORY_BUNDLE_MEM_FILE}" >> "${METRICS_FILE}"
|
||||
|
||||
# Outputs detailed information about objects created while gems are loaded.
|
||||
# 'derailed_benchmarks' internally uses 'memory_profiler'
|
||||
- bundle exec derailed bundle:objects > "${MEMORY_BUNDLE_OBJECTS_FILE}"
|
||||
- scripts/generate-gems-memory-metrics-static "${MEMORY_BUNDLE_OBJECTS_FILE}" >> "${METRICS_FILE}"
|
||||
artifacts:
|
||||
paths:
|
||||
- "${METRICS_FILE}"
|
||||
- "${MEMORY_BUNDLE_MEM_FILE}"
|
||||
- "${MEMORY_BUNDLE_OBJECTS_FILE}"
|
||||
|
||||
# Show memory usage caused by invoking require per gem.
|
||||
# Unlike `memory-static`, it hits the app with one request to ensure that any last minute require-s have been called.
|
||||
# Hits the app with one request to ensure that any last minute require-s have been called.
|
||||
# The application is booted in `production` environment.
|
||||
# All tests are run without a webserver (directly using Rack::Mock by default).
|
||||
memory-on-boot:
|
||||
|
|
|
@ -97,14 +97,8 @@ RSpec/FilePath:
|
|||
|
||||
# Configuration parameters: AllowSubject.
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 28
|
||||
Max: 25
|
||||
AllowSubject: true
|
||||
Exclude:
|
||||
- 'spec/migrations/**/*.rb'
|
||||
- 'spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb'
|
||||
- 'spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb'
|
||||
- 'ee/spec/lib/ee/gitlab/background_migration/populate_uuids_for_security_findings_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb'
|
||||
|
||||
Naming/FileName:
|
||||
ExpectMatchingDefinition: true
|
||||
|
|
11
.rubocop_todo/rspec/multiple_memoized_helpers.yml
Normal file
11
.rubocop_todo/rspec/multiple_memoized_helpers.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Exclude:
|
||||
- spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb
|
||||
- spec/lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid_spec.rb
|
||||
- spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
|
||||
- spec/requests/api/ci/runner/jobs_artifacts_spec.rb
|
||||
- ee/spec/lib/ee/gitlab/background_migration/populate_latest_pipeline_ids_spec.rb
|
||||
- ee/spec/lib/ee/gitlab/background_migration/populate_uuids_for_security_findings_spec.rb
|
||||
- ee/spec/services/ee/boards/issues/move_service_spec.rb
|
||||
- ee/spec/services/security/store_report_service_spec.rb
|
|
@ -17,7 +17,6 @@ Style/OpenStructUse:
|
|||
- spec/factories/go_module_versions.rb
|
||||
- spec/factories/wiki_pages.rb
|
||||
- spec/features/projects/clusters_spec.rb
|
||||
- spec/finders/template_finder_spec.rb
|
||||
- spec/graphql/mutations/branches/create_spec.rb
|
||||
- spec/graphql/mutations/clusters/agent_tokens/create_spec.rb
|
||||
- spec/graphql/mutations/clusters/agents/create_spec.rb
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.52.0
|
||||
1.53.0
|
||||
|
|
|
@ -15,7 +15,7 @@ import getAlertsQuery from '~/graphql_shared/queries/get_alerts.query.graphql';
|
|||
import { fetchPolicies } from '~/lib/graphql';
|
||||
import { convertToSnakeCase } from '~/lib/utils/text_utility';
|
||||
import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
|
||||
import { s__, __ } from '~/locale';
|
||||
import { s__, __, n__ } from '~/locale';
|
||||
import AlertStatus from '~/vue_shared/alert_details/components/alert_status.vue';
|
||||
import {
|
||||
tdClass,
|
||||
|
@ -32,8 +32,11 @@ const TH_TEST_ID = { 'data-testid': 'alert-management-severity-sort' };
|
|||
|
||||
const TWELVE_HOURS_IN_MS = 12 * 60 * 60 * 1000;
|
||||
|
||||
const MAX_VISIBLE_ASSIGNEES = 4;
|
||||
|
||||
export default {
|
||||
trackAlertListViewsOptions,
|
||||
MAX_VISIBLE_ASSIGNEES,
|
||||
i18n: {
|
||||
noAlertsMsg: s__(
|
||||
'AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list.',
|
||||
|
@ -258,6 +261,13 @@ export default {
|
|||
this.serverErrorMessage = '';
|
||||
this.isErrorAlertDismissed = true;
|
||||
},
|
||||
assigneesBadgeSrOnlyText(item) {
|
||||
return n__(
|
||||
'%d additional assignee',
|
||||
'%d additional assignees',
|
||||
item.assignees.nodes.length - MAX_VISIBLE_ASSIGNEES,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -365,10 +375,11 @@ export default {
|
|||
<gl-avatars-inline
|
||||
:avatars="item.assignees.nodes"
|
||||
:collapsed="true"
|
||||
:max-visible="4"
|
||||
:max-visible="$options.MAX_VISIBLE_ASSIGNEES"
|
||||
:avatar-size="24"
|
||||
badge-tooltip-prop="name"
|
||||
:badge-tooltip-max-chars="100"
|
||||
:badge-sr-only-text="assigneesBadgeSrOnlyText(item)"
|
||||
>
|
||||
<template #avatar="{ avatar }">
|
||||
<gl-avatar-link
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from '@gitlab/ui';
|
||||
import { isValidSlaDueAt } from 'ee_else_ce/vue_shared/components/incidents/utils';
|
||||
import { visitUrl, mergeUrlParams, joinPaths } from '~/lib/utils/url_utility';
|
||||
import { s__ } from '~/locale';
|
||||
import { s__, n__ } from '~/locale';
|
||||
import { INCIDENT_SEVERITY } from '~/sidebar/components/severity/constants';
|
||||
import SeverityToken from '~/sidebar/components/severity/severity.vue';
|
||||
import Tracking from '~/tracking';
|
||||
|
@ -38,6 +38,8 @@ import {
|
|||
import getIncidentsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql';
|
||||
import getIncidents from '../graphql/queries/get_incidents.query.graphql';
|
||||
|
||||
const MAX_VISIBLE_ASSIGNEES = 4;
|
||||
|
||||
export default {
|
||||
trackIncidentCreateNewOptions,
|
||||
trackIncidentListViewsOptions,
|
||||
|
@ -94,6 +96,7 @@ export default {
|
|||
thAttr: TH_PUBLISHED_TEST_ID,
|
||||
},
|
||||
],
|
||||
MAX_VISIBLE_ASSIGNEES,
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
GlTable,
|
||||
|
@ -295,6 +298,13 @@ export default {
|
|||
errorAlertDismissed() {
|
||||
this.isErrorAlertDismissed = true;
|
||||
},
|
||||
assigneesBadgeSrOnlyText(item) {
|
||||
return n__(
|
||||
'%d additional assignee',
|
||||
'%d additional assignees',
|
||||
item.assignees.nodes.length - MAX_VISIBLE_ASSIGNEES,
|
||||
);
|
||||
},
|
||||
isValidSlaDueAt,
|
||||
},
|
||||
};
|
||||
|
@ -391,10 +401,11 @@ export default {
|
|||
<gl-avatars-inline
|
||||
:avatars="item.assignees.nodes"
|
||||
:collapsed="true"
|
||||
:max-visible="4"
|
||||
:max-visible="$options.MAX_VISIBLE_ASSIGNEES"
|
||||
:avatar-size="24"
|
||||
badge-tooltip-prop="name"
|
||||
:badge-tooltip-max-chars="100"
|
||||
:badge-sr-only-text="assigneesBadgeSrOnlyText(item)"
|
||||
>
|
||||
<template #avatar="{ avatar }">
|
||||
<gl-avatar-link
|
||||
|
|
|
@ -69,11 +69,11 @@ export default class CreateMergeRequestDropdown {
|
|||
this.regexps = {
|
||||
branch: {
|
||||
createBranchPath: new RegExp('(branch_name=)(.+?)(?=&issue)'),
|
||||
createMrPath: new RegExp('(branch_name=)(.+?)(?=&ref)'),
|
||||
createMrPath: new RegExp('(source_branch%5D=)(.+?)(?=&)'),
|
||||
},
|
||||
ref: {
|
||||
createBranchPath: new RegExp('(ref=)(.+?)$'),
|
||||
createMrPath: new RegExp('(ref=)(.+?)$'),
|
||||
createMrPath: new RegExp('(target_branch%5D=)(.+?)$'),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -167,23 +167,18 @@ export default class CreateMergeRequestDropdown {
|
|||
}
|
||||
|
||||
createMergeRequest() {
|
||||
this.isCreatingMergeRequest = true;
|
||||
return new Promise(() => {
|
||||
this.isCreatingMergeRequest = true;
|
||||
|
||||
return axios
|
||||
.post(this.createMrPath, {
|
||||
target_project_id: canCreateConfidentialMergeRequest()
|
||||
? confidentialMergeRequestState.selectedProject.id
|
||||
: null,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
this.mergeRequestCreated = true;
|
||||
window.location.href = data.url;
|
||||
})
|
||||
.catch(() =>
|
||||
createFlash({
|
||||
message: __('Failed to create merge request. Please try again.'),
|
||||
}),
|
||||
);
|
||||
return this.createBranch().then(() => {
|
||||
window.location.href = canCreateConfidentialMergeRequest()
|
||||
? this.createMrPath.replace(
|
||||
this.projectPath,
|
||||
confidentialMergeRequestState.selectedProject.pathWithNamespace,
|
||||
)
|
||||
: this.createMrPath;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
disable() {
|
||||
|
@ -562,5 +557,7 @@ export default class CreateMergeRequestDropdown {
|
|||
this.regexps[target].createMrPath,
|
||||
pathReplacement,
|
||||
);
|
||||
|
||||
this.wrapperEl.dataset.createMrPath = this.createMrPath;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
<script>
|
||||
import { GlAlert, GlLink, GlSprintf, GlEmptyState } from '@gitlab/ui';
|
||||
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { mapState, mapMutations } from 'vuex';
|
||||
import { retrieveAlert } from '~/jira_connect/subscriptions/utils';
|
||||
import { SET_ALERT } from '../store/mutation_types';
|
||||
import SubscriptionsList from './subscriptions_list.vue';
|
||||
import AddNamespaceButton from './add_namespace_button.vue';
|
||||
import SignInButton from './sign_in_button.vue';
|
||||
import SignInPage from '../pages/sign_in.vue';
|
||||
import SubscriptionsPage from '../pages/subscriptions.vue';
|
||||
import UserLink from './user_link.vue';
|
||||
import CompatibilityAlert from './compatibility_alert.vue';
|
||||
|
||||
|
@ -16,12 +15,10 @@ export default {
|
|||
GlAlert,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
GlEmptyState,
|
||||
SubscriptionsList,
|
||||
AddNamespaceButton,
|
||||
SignInButton,
|
||||
UserLink,
|
||||
CompatibilityAlert,
|
||||
SignInPage,
|
||||
SubscriptionsPage,
|
||||
},
|
||||
inject: {
|
||||
usersPath: {
|
||||
|
@ -84,43 +81,9 @@ export default {
|
|||
<user-link :user-signed-in="userSignedIn" :has-subscriptions="hasSubscriptions" />
|
||||
|
||||
<h2 class="gl-text-center gl-mb-7">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
|
||||
<div class="jira-connect-app-body gl-mx-auto gl-px-5 gl-mb-7">
|
||||
<template v-if="hasSubscriptions">
|
||||
<div class="gl-display-flex gl-justify-content-end">
|
||||
<sign-in-button v-if="!userSignedIn" :users-path="usersPath" />
|
||||
<add-namespace-button v-else />
|
||||
</div>
|
||||
|
||||
<subscriptions-list />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="!userSignedIn" class="gl-text-center">
|
||||
<p class="gl-mb-7">{{ s__('JiraService|Sign in to GitLab.com to get started.') }}</p>
|
||||
<sign-in-button class="gl-mb-7" :users-path="usersPath">
|
||||
{{ __('Sign in to GitLab') }}
|
||||
</sign-in-button>
|
||||
<p>
|
||||
{{
|
||||
s__(
|
||||
'Integrations|Note: this integration only works with accounts on GitLab.com (SaaS).',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<gl-empty-state
|
||||
v-else
|
||||
:title="s__('Integrations|No linked namespaces')"
|
||||
:description="
|
||||
s__(
|
||||
'Integrations|Namespaces are the GitLab groups and subgroups you link to this Jira instance.',
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #actions>
|
||||
<add-namespace-button />
|
||||
</template>
|
||||
</gl-empty-state>
|
||||
</template>
|
||||
<div class="gl-layout-w-limited gl-mx-auto gl-px-5 gl-mb-7">
|
||||
<sign-in-page v-if="!userSignedIn" :has-subscriptions="hasSubscriptions" />
|
||||
<subscriptions-page v-else :has-subscriptions="hasSubscriptions" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { getGitlabSignInURL } from '~/jira_connect/subscriptions/utils';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -25,12 +26,15 @@ export default {
|
|||
this.signInURL = await getGitlabSignInURL(this.usersPath);
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
defaultButtonText: s__('Integrations|Sign in to GitLab'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-button category="primary" variant="info" :href="signInURL" target="_blank">
|
||||
<slot>
|
||||
{{ s__('Integrations|Sign in to add namespaces') }}
|
||||
{{ $options.i18n.defaultButtonText }}
|
||||
</slot>
|
||||
</gl-button>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<script>
|
||||
import { s__ } from '~/locale';
|
||||
import SubscriptionsList from '../components/subscriptions_list.vue';
|
||||
import SignInButton from '../components/sign_in_button.vue';
|
||||
|
||||
export default {
|
||||
name: 'SignInPage',
|
||||
components: {
|
||||
SubscriptionsList,
|
||||
SignInButton,
|
||||
},
|
||||
inject: ['usersPath'],
|
||||
props: {
|
||||
hasSubscriptions: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
signinButtonTextWithSubscriptions: s__('Integrations|Sign in to add namespaces'),
|
||||
signInText: s__('JiraService|Sign in to GitLab.com to get started.'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="hasSubscriptions">
|
||||
<div class="gl-display-flex gl-justify-content-end">
|
||||
<sign-in-button :users-path="usersPath">
|
||||
{{ $options.i18n.signinButtonTextWithSubscriptions }}
|
||||
</sign-in-button>
|
||||
</div>
|
||||
|
||||
<subscriptions-list />
|
||||
</div>
|
||||
<div v-else class="gl-text-center">
|
||||
<p class="gl-mb-7">{{ $options.i18n.signInText }}</p>
|
||||
<sign-in-button class="gl-mb-7" :users-path="usersPath" />
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,43 @@
|
|||
<script>
|
||||
import { GlEmptyState } from '@gitlab/ui';
|
||||
import SubscriptionsList from '../components/subscriptions_list.vue';
|
||||
import AddNamespaceButton from '../components/add_namespace_button.vue';
|
||||
|
||||
export default {
|
||||
name: 'SubscriptionsPage',
|
||||
components: {
|
||||
GlEmptyState,
|
||||
SubscriptionsList,
|
||||
AddNamespaceButton,
|
||||
},
|
||||
props: {
|
||||
hasSubscriptions: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="hasSubscriptions">
|
||||
<div class="gl-display-flex gl-justify-content-end">
|
||||
<add-namespace-button />
|
||||
</div>
|
||||
|
||||
<subscriptions-list />
|
||||
</div>
|
||||
<gl-empty-state
|
||||
v-else
|
||||
:title="s__('Integrations|No linked namespaces')"
|
||||
:description="
|
||||
s__(
|
||||
'Integrations|Namespaces are the GitLab groups and subgroups you link to this Jira instance.',
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #actions>
|
||||
<add-namespace-button />
|
||||
</template>
|
||||
</gl-empty-state>
|
||||
</template>
|
|
@ -1,10 +1,13 @@
|
|||
<script>
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
commitRef: {
|
||||
type: String,
|
||||
|
@ -41,7 +44,13 @@ export default {
|
|||
|
||||
<template>
|
||||
<tr class="tree-item">
|
||||
<td colspan="3" class="tree-item-file-name" @click.self="clickRow">
|
||||
<td
|
||||
v-gl-tooltip.left.viewport
|
||||
:title="__('Go to parent directory')"
|
||||
colspan="3"
|
||||
class="tree-item-file-name"
|
||||
@click.self="clickRow"
|
||||
>
|
||||
<gl-loading-icon
|
||||
v-if="parentPath === loadingPath"
|
||||
size="sm"
|
||||
|
|
|
@ -40,10 +40,6 @@ $header-height: 40px;
|
|||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.jira-connect-app-body {
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
// needed for external_link
|
||||
svg.s16 {
|
||||
width: 16px;
|
||||
|
|
|
@ -92,6 +92,8 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def branches
|
||||
return git_not_found! unless commit
|
||||
|
||||
# branch_names_contains/tag_names_contains can take a long time when there are thousands of
|
||||
# branches/tags - each `git branch --contains xxx` request can consume a cpu core.
|
||||
# so only do the query when there are a manageable number of branches/tags
|
||||
|
|
|
@ -178,6 +178,10 @@ class Blob < SimpleDelegator
|
|||
end
|
||||
end
|
||||
|
||||
def symlink?
|
||||
mode == MODE_SYMLINK
|
||||
end
|
||||
|
||||
def extension
|
||||
@extension ||= extname.downcase.delete('.')
|
||||
end
|
||||
|
|
|
@ -162,6 +162,23 @@ module Ci
|
|||
)
|
||||
end
|
||||
|
||||
scope :group_or_instance_wide, -> (group) do
|
||||
group_and_ancestor_runners =
|
||||
if ::Feature.enabled?(:ci_find_runners_by_ci_mirrors, group, default_enabled: :yaml)
|
||||
belonging_to_group_and_ancestors(group.id)
|
||||
else
|
||||
legacy_belonging_to_group(group.id, include_ancestors: true)
|
||||
end
|
||||
|
||||
from_union(
|
||||
[
|
||||
group_and_ancestor_runners,
|
||||
instance_type
|
||||
],
|
||||
remove_duplicates: false
|
||||
)
|
||||
end
|
||||
|
||||
scope :assignable_for, ->(project) do
|
||||
# FIXME: That `to_sql` is needed to workaround a weird Rails bug.
|
||||
# Without that, placeholders would miss one and couldn't match.
|
||||
|
|
|
@ -21,7 +21,7 @@ module Branches
|
|||
error("Failed to create branch '#{branch_name}': invalid reference name '#{ref}'")
|
||||
end
|
||||
rescue Gitlab::Git::PreReceiveError => e
|
||||
Gitlab::ErrorTracking.track_exception(e, pre_receive_message: e.raw_message, branch_name: branch_name, ref: ref)
|
||||
Gitlab::ErrorTracking.log_exception(e, pre_receive_message: e.raw_message, branch_name: branch_name, ref: ref)
|
||||
error(e.message)
|
||||
end
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
= copy_file_path_button(blob.path)
|
||||
|
||||
%small.mr-1
|
||||
- if blob.mode == Blob::MODE_SYMLINK
|
||||
- if blob.symlink?
|
||||
= _('Symbolic link') << ' ·'
|
||||
= number_to_human_size(blob.raw_size)
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
- create_mr_text = can_create_confidential_merge_request? ? _('Create confidential merge request') : _('Create merge request')
|
||||
|
||||
- can_create_path = can_create_branch_project_issue_path(@project, @issue)
|
||||
- create_mr_path = create_merge_request_project_issue_path(@project, @issue, branch_name: @issue.to_branch_name, ref: @project.default_branch)
|
||||
- create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid)
|
||||
- create_mr_path = project_new_merge_request_path(@project, merge_request: { source_branch: @issue.to_branch_name, target_branch: @project.default_branch })
|
||||
- create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid, format: :json)
|
||||
- refs_path = refs_namespace_project_path(@project.namespace, @project, search: '')
|
||||
|
||||
.create-mr-dropdown-wrap.d-inline-block.full-width-mobile.js-create-mr{ data: { project_path: @project.full_path, project_id: @project.id, can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path, is_confidential: can_create_confidential_merge_request?.to_s } }
|
||||
|
|
|
@ -448,8 +448,7 @@ Example response:
|
|||
|
||||
## List project's runners
|
||||
|
||||
List all runners (specific and shared) available in the project. Shared runners
|
||||
are listed if at least one shared runner is defined.
|
||||
List all runners available in the project, including from ancestor groups and any shared runners.
|
||||
|
||||
```plaintext
|
||||
GET /projects/:id/runners
|
||||
|
@ -567,8 +566,7 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
|
|||
|
||||
## List group's runners
|
||||
|
||||
List all runners (specific and shared) available in the group as well it's ancestor groups.
|
||||
Shared runners are listed if at least one shared runner is defined.
|
||||
List all runners available in the group as well as its ancestor groups, including any shared runners.
|
||||
|
||||
```plaintext
|
||||
GET /groups/:id/runners
|
||||
|
|
|
@ -74,13 +74,13 @@ we might want to follow these three tracks described below.
|
|||
|
||||
<!-- markdownlint-disable MD029 -->
|
||||
|
||||
1. Partition builds queuing tables
|
||||
2. Archive CI/CD data into partitioned database schema
|
||||
3. Migrate archived builds metadata out of primary database
|
||||
1. Partition CI/CD builds queuing database tables
|
||||
2. Partition CI/CD pipelines database tables
|
||||
3. Reduce the rate of builds metadata table growth
|
||||
|
||||
<!-- markdownlint-enable MD029 -->
|
||||
|
||||
### Migrate archived builds metadata out of primary database
|
||||
### Reduce the rate of builds metadata table growth
|
||||
|
||||
Once a build (or a pipeline) gets archived, it is no longer possible to resume
|
||||
pipeline processing in such pipeline. It means that all the metadata, we store
|
||||
|
@ -98,15 +98,16 @@ be able to use de-duplication of metadata entries and other normalization
|
|||
strategies to consume less storage while retaining ability to query this
|
||||
dataset. Technical evaluation will be required to find the best solution here.
|
||||
|
||||
Epic: [Migrate archived builds metadata out of primary database](https://gitlab.com/groups/gitlab-org/-/epics/7216).
|
||||
Epic: [Reduce the rate of builds metadata table growth](https://gitlab.com/groups/gitlab-org/-/epics/7434).
|
||||
|
||||
### Archive CI/CD data into partitioned database schema
|
||||
### Partition CI/CD pipelines database tables
|
||||
|
||||
After we move CI/CD metadata to a different store, the problem of having
|
||||
billions of rows describing pipelines, builds and artifacts, remains. We still
|
||||
need to keep reference to the metadata we store in object storage and we still
|
||||
do need to be able to retrieve this information reliably in bulk (or search
|
||||
through it).
|
||||
After we move CI/CD metadata to a different store, or reduce the rate of
|
||||
metadata growth in a different way, the problem of having billions of rows
|
||||
describing pipelines, builds and artifacts, remains. We still need to keep
|
||||
reference to the metadata we might store in object storage and we still do need
|
||||
to be able to retrieve this information reliably in bulk (or search through
|
||||
it).
|
||||
|
||||
It means that by moving data to object storage we might not be able to reduce
|
||||
the number of rows in CI/CD tables. Moving data to object storage should help
|
||||
|
@ -132,9 +133,9 @@ partitioning on the application level.
|
|||
Partitioning rarely accessed data should also follow the policy defined for
|
||||
builds archival, to make it consistent and reliable.
|
||||
|
||||
Epic: [Archive CI/CD data into partitioned database schema](https://gitlab.com/groups/gitlab-org/-/epics/5417).
|
||||
Epic: [Partition CI/CD pipelines database tables](https://gitlab.com/groups/gitlab-org/-/epics/5417).
|
||||
|
||||
### Partition builds queuing tables
|
||||
### Partition CI/CD builds queuing database tables
|
||||
|
||||
While working on the [CI/CD Scale](../ci_scale/index.md) blueprint, we have
|
||||
introduced a [new architecture for queuing CI/CD builds](https://gitlab.com/groups/gitlab-org/-/epics/5909#note_680407908)
|
||||
|
@ -156,7 +157,7 @@ for builds archival. Instead we should leverage a long-standing policy saying
|
|||
that builds created more 24 hours ago need to be removed from the queue. This
|
||||
business rule is present in the product since the inception of GitLab CI.
|
||||
|
||||
Epic: [Partition builds queuing tables](https://gitlab.com/gitlab-org/gitlab/-/issues/347027).
|
||||
Epic: [Partition CI/CD builds queuing database tables](https://gitlab.com/groups/gitlab-org/-/epics/7438).
|
||||
|
||||
## Principles
|
||||
|
||||
|
@ -215,9 +216,9 @@ pipelines data, although a user provided partition identifier may be required.
|
|||
|
||||
All three tracks can be worked on in parallel:
|
||||
|
||||
1. [Migrate archived build metadata to object storage](https://gitlab.com/groups/gitlab-org/-/epics/7216).
|
||||
1. [Partition CI/CD data that have been archived](https://gitlab.com/groups/gitlab-org/-/epics/5417).
|
||||
1. [Partition CI/CD queuing tables using list partitioning](https://gitlab.com/gitlab-org/gitlab/-/issues/347027)
|
||||
1. [Reduce the rate of builds metadata table growth](https://gitlab.com/groups/gitlab-org/-/epics/7434).
|
||||
1. [Partition CI/CD pipelines database tables](https://gitlab.com/groups/gitlab-org/-/epics/5417).
|
||||
1. [Partition CI/CD queuing tables using list partitioning](https://gitlab.com/groups/gitlab-org/-/epics/7438)
|
||||
|
||||
## Status
|
||||
|
||||
|
|
|
@ -316,6 +316,11 @@ Instead of:
|
|||
|
||||
Use **expand** instead of **open** when you are talking about expanding or collapsing a section in the UI.
|
||||
|
||||
## FAQ
|
||||
|
||||
We want users to find information quickly, and they rarely search for the term **FAQ**.
|
||||
Information in FAQs belongs with other similar information, under an easily searchable topic title.
|
||||
|
||||
## field
|
||||
|
||||
Use **box** instead of **field** or **text box**.
|
||||
|
@ -371,6 +376,11 @@ Use title case for **GitLab Runner**. This is the product you install. See also
|
|||
|
||||
Use **GitLab self-managed** to refer to the product license for GitLab instances managed by customers themselves.
|
||||
|
||||
## guide
|
||||
|
||||
We want to speak directly to users. On `docs.gitlab.com`, do not use **guide** as part of a page title.
|
||||
For example, **Snowplow Guide**. Instead, speak about the feature itself, and how to use it. For example, **Use Snowplow to do xyz**.
|
||||
|
||||
## Guest
|
||||
|
||||
When writing about the Guest role:
|
||||
|
|
|
@ -232,12 +232,7 @@ module API
|
|||
use :pagination
|
||||
end
|
||||
get ':id/runners' do
|
||||
runners = if ::Feature.enabled?(:ci_find_runners_by_ci_mirrors, user_group, default_enabled: :yaml)
|
||||
::Ci::Runner.belonging_to_group_and_ancestors(user_group.id)
|
||||
else
|
||||
::Ci::Runner.legacy_belonging_to_group(user_group.id, include_ancestors: true)
|
||||
end
|
||||
|
||||
runners = ::Ci::Runner.group_or_instance_wide(user_group)
|
||||
runners = apply_filter(runners, params)
|
||||
|
||||
present paginate(runners), with: Entities::Ci::Runner
|
||||
|
|
|
@ -116,6 +116,26 @@ msgid_plural "%d URLs scanned"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d additional approver"
|
||||
msgid_plural "%d additional approvers"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d additional assignee"
|
||||
msgid_plural "%d additional assignees"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d additional commenter"
|
||||
msgid_plural "%d additional commenters"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d additional committer"
|
||||
msgid_plural "%d additional committers"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d approver"
|
||||
msgid_plural "%d approvers"
|
||||
msgstr[0] ""
|
||||
|
@ -14758,9 +14778,6 @@ msgstr ""
|
|||
msgid "Failed to create import label for jira import."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to create merge request. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to create new access token: %{token_response_message}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -15780,6 +15797,9 @@ msgstr ""
|
|||
msgid "Geo|Adjust your filters/search criteria above. If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information."
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|All"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|All %{replicable_name}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -16632,6 +16652,9 @@ msgstr ""
|
|||
msgid "Go to parent"
|
||||
msgstr ""
|
||||
|
||||
msgid "Go to parent directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "Go to previous page"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19317,9 +19340,6 @@ msgstr ""
|
|||
msgid "Integrations|No linked namespaces"
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Note: this integration only works with accounts on GitLab.com (SaaS)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Projects using custom settings"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19356,6 +19376,9 @@ msgstr ""
|
|||
msgid "Integrations|Send notifications about project events to a Unify Circuit conversation. %{docs_link}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Sign in to GitLab"
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Sign in to add namespaces"
|
||||
msgstr ""
|
||||
|
||||
|
@ -23596,9 +23619,6 @@ msgstr ""
|
|||
msgid "NetworkPolicies|%{strongOpen}any%{strongClose} port"
|
||||
msgstr ""
|
||||
|
||||
msgid "NetworkPolicies|.yaml"
|
||||
msgstr ""
|
||||
|
||||
msgid "NetworkPolicies|.yaml mode"
|
||||
msgstr ""
|
||||
|
||||
|
@ -23677,9 +23697,6 @@ msgstr ""
|
|||
msgid "NetworkPolicies|Policy definition"
|
||||
msgstr ""
|
||||
|
||||
msgid "NetworkPolicies|Rule"
|
||||
msgstr ""
|
||||
|
||||
msgid "NetworkPolicies|Rule mode"
|
||||
msgstr ""
|
||||
|
||||
|
@ -31924,6 +31941,9 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|Status"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Summary"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|The %{scanners} %{severities} in an open merge request targeting %{branches}."
|
||||
msgstr ""
|
||||
|
||||
|
@ -34942,7 +34962,10 @@ msgstr ""
|
|||
msgid "SuperSonics|current subscription"
|
||||
msgstr ""
|
||||
|
||||
msgid "SuperSonics|history subscriptions"
|
||||
msgid "SuperSonics|future subscriptions"
|
||||
msgstr ""
|
||||
|
||||
msgid "SuperSonics|past subscriptions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Support"
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
"@gitlab/at.js": "1.5.7",
|
||||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/svgs": "2.2.0",
|
||||
"@gitlab/ui": "33.1.0",
|
||||
"@gitlab/ui": "34.0.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "6.1.4-1",
|
||||
"@rails/ujs": "6.1.4-1",
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
abort "usage: #{__FILE__} <memory_bundle_objects_file_name>" unless ARGV.length == 1
|
||||
memory_bundle_objects_file_name = ARGV.first
|
||||
|
||||
full_report = File.readlines(memory_bundle_objects_file_name)
|
||||
|
||||
allocated_str = full_report[1]
|
||||
retained_str = full_report[2]
|
||||
allocated_stats = /Total allocated: (?<bytes>.*) bytes \((?<objects>.*) objects\)/.match(allocated_str)
|
||||
retained_stats = /Total retained: (?<bytes>.*) bytes \((?<objects>.*) objects\)/.match(retained_str)
|
||||
|
||||
abort 'failed to process the benchmark output' unless allocated_stats && retained_stats
|
||||
|
||||
puts "memory_static_objects_allocated_mb #{(allocated_stats[:bytes].to_f / (1024 * 1024)).round(1)}"
|
||||
puts "memory_static_objects_retained_mb #{(retained_stats[:bytes].to_f / (1024 * 1024)).round(1)}"
|
||||
puts "memory_static_objects_allocated_items #{allocated_stats[:objects]}"
|
||||
puts "memory_static_objects_retained_items #{retained_stats[:objects]}"
|
|
@ -1,31 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
abort "usage: #{__FILE__} <memory_bundle_mem_file_name>" unless ARGV.length == 1
|
||||
memory_bundle_mem_file_name = ARGV.first
|
||||
|
||||
full_report = File.readlines(memory_bundle_mem_file_name)
|
||||
|
||||
def total_size(memory_bundle_mem_report)
|
||||
stats = /TOP: (?<total_mibs_str>.*) MiB/.match(memory_bundle_mem_report.first)
|
||||
abort 'failed to process the benchmark output' unless stats
|
||||
"gem_total_size_mb #{stats[:total_mibs_str].to_f.round(1)}"
|
||||
end
|
||||
|
||||
TOP_LEVEL_GEM_LOG_FORMAT = /^ (?<gem_name>\S.*):\s*(?<gem_size>\d[.\d]*)\s*MiB/.freeze
|
||||
def all_gems(memory_bundle_mem_report)
|
||||
memory_bundle_mem_report.map do |line|
|
||||
TOP_LEVEL_GEM_LOG_FORMAT.match(line)
|
||||
end.compact
|
||||
end
|
||||
|
||||
def gems_as_metrics(gems_match_data)
|
||||
gems_match_data.map do |gem|
|
||||
gem_name = gem[:gem_name]
|
||||
gem_size_mb = gem[:gem_size].to_f.round(1)
|
||||
"gem_size_mb{name=\"#{gem_name}\"} #{gem_size_mb}"
|
||||
end
|
||||
end
|
||||
|
||||
puts total_size(full_report)
|
||||
puts gems_as_metrics(all_gems(full_report)).sort(&:casecmp)
|
|
@ -183,6 +183,18 @@ RSpec.describe Projects::CommitController do
|
|||
expect(assigns(:tags)).to eq([])
|
||||
expect(assigns(:tags_limit_exceeded)).to be_truthy
|
||||
end
|
||||
|
||||
context 'when commit is not found' do
|
||||
it 'responds with 404' do
|
||||
get(:branches, params: {
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: '11111111111111111111111111111111111111'
|
||||
})
|
||||
|
||||
expect(response).to be_not_found
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST revert' do
|
||||
|
|
|
@ -71,16 +71,10 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
|
|||
perform_enqueued_jobs do
|
||||
select_dropdown_option('create-mr')
|
||||
|
||||
expect(page).to have_content('Draft: Resolve "Cherry-Coloured Funk"')
|
||||
expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first))
|
||||
|
||||
wait_for_requests
|
||||
expect(page).to have_content('New merge request')
|
||||
expect(page).to have_content("From #{issue.to_branch_name} into #{project.default_branch}")
|
||||
expect(page).to have_current_path(project_new_merge_request_path(project, merge_request: { source_branch: issue.to_branch_name, target_branch: project.default_branch }))
|
||||
end
|
||||
|
||||
visit project_issue_path(project, issue)
|
||||
|
||||
expect(page).to have_content("created merge request !1 to address this issue")
|
||||
expect(page).to have_content('mentioned in merge request !1')
|
||||
end
|
||||
|
||||
it 'creates a branch' do
|
||||
|
@ -100,17 +94,10 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
|
|||
perform_enqueued_jobs do
|
||||
select_dropdown_option('create-mr', branch_name)
|
||||
|
||||
expect(page).to have_content('Draft: Resolve "Cherry-Coloured Funk"')
|
||||
expect(page).to have_content('Request to merge custom-branch-name into')
|
||||
expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first))
|
||||
|
||||
wait_for_requests
|
||||
expect(page).to have_content('New merge request')
|
||||
expect(page).to have_content("From #{branch_name} into #{project.default_branch}")
|
||||
expect(page).to have_current_path(project_new_merge_request_path(project, merge_request: { source_branch: branch_name, target_branch: project.default_branch }))
|
||||
end
|
||||
|
||||
visit project_issue_path(project, issue)
|
||||
|
||||
expect(page).to have_content("created merge request !1 to address this issue")
|
||||
expect(page).to have_content('mentioned in merge request !1')
|
||||
end
|
||||
|
||||
it 'creates a branch' do
|
||||
|
|
|
@ -153,7 +153,12 @@ RSpec.describe TemplateFinder do
|
|||
|
||||
let(:params) { {} }
|
||||
|
||||
subject(:result) { described_class.new(type, project, params).template_names.values.flatten.map { |el| OpenStruct.new(el) } }
|
||||
let(:template_name_struct) { Struct.new(:name, :id, :key, :project_id, keyword_init: true) }
|
||||
|
||||
subject(:result) do
|
||||
described_class.new(type, project, params).template_names.values.flatten
|
||||
.map { |el| template_name_struct.new(el) }
|
||||
end
|
||||
|
||||
where(:type, :vendored_name) do
|
||||
:dockerfiles | 'Binary'
|
||||
|
|
|
@ -59,7 +59,7 @@ describe('CreateMergeRequestDropdown', () => {
|
|||
describe('updateCreatePaths', () => {
|
||||
it('escapes branch names correctly', () => {
|
||||
dropdown.createBranchPath = `${TEST_HOST}/branches?branch_name=some-branch&issue=42`;
|
||||
dropdown.createMrPath = `${TEST_HOST}/create_merge_request?branch_name=some-branch&ref=main`;
|
||||
dropdown.createMrPath = `${TEST_HOST}/create_merge_request?merge_request%5Bsource_branch%5D=test&merge_request%5Btarget_branch%5D=master`;
|
||||
|
||||
dropdown.updateCreatePaths('branch', 'contains#hash');
|
||||
|
||||
|
@ -68,7 +68,7 @@ describe('CreateMergeRequestDropdown', () => {
|
|||
);
|
||||
|
||||
expect(dropdown.createMrPath).toBe(
|
||||
`${TEST_HOST}/create_merge_request?branch_name=contains%23hash&ref=main`,
|
||||
`${TEST_HOST}/create_merge_request?merge_request%5Bsource_branch%5D=contains%23hash&merge_request%5Btarget_branch%5D=master`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { GlLink, GlEmptyState } from '@gitlab/ui';
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
import { nextTick } from 'vue';
|
||||
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
|
||||
import JiraConnectApp from '~/jira_connect/subscriptions/components/app.vue';
|
||||
import AddNamespaceButton from '~/jira_connect/subscriptions/components/add_namespace_button.vue';
|
||||
import SignInButton from '~/jira_connect/subscriptions/components/sign_in_button.vue';
|
||||
import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue';
|
||||
import SignInPage from '~/jira_connect/subscriptions/pages/sign_in.vue';
|
||||
import SubscriptionsPage from '~/jira_connect/subscriptions/pages/subscriptions.vue';
|
||||
import UserLink from '~/jira_connect/subscriptions/components/user_link.vue';
|
||||
import createStore from '~/jira_connect/subscriptions/store';
|
||||
import { SET_ALERT } from '~/jira_connect/subscriptions/store/mutation_types';
|
||||
|
@ -23,10 +22,8 @@ describe('JiraConnectApp', () => {
|
|||
|
||||
const findAlert = () => wrapper.findByTestId('jira-connect-persisted-alert');
|
||||
const findAlertLink = () => findAlert().findComponent(GlLink);
|
||||
const findSignInButton = () => wrapper.findComponent(SignInButton);
|
||||
const findAddNamespaceButton = () => wrapper.findComponent(AddNamespaceButton);
|
||||
const findSubscriptionsList = () => wrapper.findComponent(SubscriptionsList);
|
||||
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
|
||||
const findSignInPage = () => wrapper.findComponent(SignInPage);
|
||||
const findSubscriptionsPage = () => wrapper.findComponent(SubscriptionsPage);
|
||||
|
||||
const createComponent = ({ provide, mountFn = shallowMountExtended } = {}) => {
|
||||
store = createStore();
|
||||
|
@ -43,49 +40,35 @@ describe('JiraConnectApp', () => {
|
|||
|
||||
describe('template', () => {
|
||||
describe.each`
|
||||
scenario | usersPath | subscriptions | expectSignInButton | expectEmptyState | expectNamespaceButton | expectSubscriptionsList
|
||||
${'user is not signed in with subscriptions'} | ${'/users'} | ${[mockSubscription]} | ${true} | ${false} | ${false} | ${true}
|
||||
${'user is not signed in without subscriptions'} | ${'/users'} | ${undefined} | ${true} | ${false} | ${false} | ${false}
|
||||
${'user is signed in with subscriptions'} | ${undefined} | ${[mockSubscription]} | ${false} | ${false} | ${true} | ${true}
|
||||
${'user is signed in without subscriptions'} | ${undefined} | ${undefined} | ${false} | ${true} | ${false} | ${false}
|
||||
`(
|
||||
'when $scenario',
|
||||
({
|
||||
usersPath,
|
||||
expectSignInButton,
|
||||
subscriptions,
|
||||
expectEmptyState,
|
||||
expectNamespaceButton,
|
||||
expectSubscriptionsList,
|
||||
}) => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
provide: {
|
||||
usersPath,
|
||||
subscriptions,
|
||||
},
|
||||
});
|
||||
scenario | usersPath | shouldRenderSignInPage | shouldRenderSubscriptionsPage
|
||||
${'user is not signed in'} | ${'/users'} | ${true} | ${false}
|
||||
${'user is signed in'} | ${undefined} | ${false} | ${true}
|
||||
`('when $scenario', ({ usersPath, shouldRenderSignInPage, shouldRenderSubscriptionsPage }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
provide: {
|
||||
usersPath,
|
||||
subscriptions: [mockSubscription],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it(`${expectSignInButton ? 'renders' : 'does not render'} sign in button`, () => {
|
||||
expect(findSignInButton().exists()).toBe(expectSignInButton);
|
||||
});
|
||||
it(`${shouldRenderSignInPage ? 'renders' : 'does not render'} sign in page`, () => {
|
||||
expect(findSignInPage().exists()).toBe(shouldRenderSignInPage);
|
||||
if (shouldRenderSignInPage) {
|
||||
expect(findSignInPage().props('hasSubscriptions')).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it(`${expectEmptyState ? 'renders' : 'does not render'} empty state`, () => {
|
||||
expect(findEmptyState().exists()).toBe(expectEmptyState);
|
||||
});
|
||||
|
||||
it(`${
|
||||
expectNamespaceButton ? 'renders' : 'does not render'
|
||||
} button to add namespace`, () => {
|
||||
expect(findAddNamespaceButton().exists()).toBe(expectNamespaceButton);
|
||||
});
|
||||
|
||||
it(`${expectSubscriptionsList ? 'renders' : 'does not render'} subscriptions list`, () => {
|
||||
expect(findSubscriptionsList().exists()).toBe(expectSubscriptionsList);
|
||||
});
|
||||
},
|
||||
);
|
||||
it(`${
|
||||
shouldRenderSubscriptionsPage ? 'renders' : 'does not render'
|
||||
} subscriptions page`, () => {
|
||||
expect(findSubscriptionsPage().exists()).toBe(shouldRenderSubscriptionsPage);
|
||||
if (shouldRenderSubscriptionsPage) {
|
||||
expect(findSubscriptionsPage().props('hasSubscriptions')).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('renders UserLink component', () => {
|
||||
createComponent({
|
||||
|
|
|
@ -11,11 +11,12 @@ jest.mock('~/jira_connect/subscriptions/utils');
|
|||
describe('SignInButton', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
const createComponent = ({ slots } = {}) => {
|
||||
wrapper = shallowMount(SignInButton, {
|
||||
propsData: {
|
||||
usersPath: MOCK_USERS_PATH,
|
||||
},
|
||||
slots,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -29,6 +30,7 @@ describe('SignInButton', () => {
|
|||
createComponent();
|
||||
|
||||
expect(findButton().exists()).toBe(true);
|
||||
expect(findButton().text()).toBe(SignInButton.i18n.defaultButtonText);
|
||||
});
|
||||
|
||||
describe.each`
|
||||
|
@ -45,4 +47,12 @@ describe('SignInButton', () => {
|
|||
expect(findButton().attributes('href')).toBe(expectedHref);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with slot', () => {
|
||||
const mockSlotContent = 'custom button content!';
|
||||
it('renders slot content in button', () => {
|
||||
createComponent({ slots: { default: mockSlotContent } });
|
||||
expect(wrapper.text()).toMatchInterpolatedText(mockSlotContent);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import SignInPage from '~/jira_connect/subscriptions/pages/sign_in.vue';
|
||||
import SignInButton from '~/jira_connect/subscriptions/components/sign_in_button.vue';
|
||||
import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue';
|
||||
import createStore from '~/jira_connect/subscriptions/store';
|
||||
|
||||
jest.mock('~/jira_connect/subscriptions/utils');
|
||||
|
||||
describe('SignInPage', () => {
|
||||
let wrapper;
|
||||
let store;
|
||||
|
||||
const findSignInButton = () => wrapper.findComponent(SignInButton);
|
||||
const findSubscriptionsList = () => wrapper.findComponent(SubscriptionsList);
|
||||
|
||||
const createComponent = ({ provide, props } = {}) => {
|
||||
store = createStore();
|
||||
|
||||
wrapper = mount(SignInPage, {
|
||||
store,
|
||||
provide,
|
||||
propsData: props,
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
const mockUsersPath = '/test';
|
||||
describe.each`
|
||||
scenario | expectSubscriptionsList | signInButtonText
|
||||
${'with subscriptions'} | ${true} | ${SignInPage.i18n.signinButtonTextWithSubscriptions}
|
||||
${'without subscriptions'} | ${false} | ${SignInButton.i18n.defaultButtonText}
|
||||
`('$scenario', ({ expectSubscriptionsList, signInButtonText }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
provide: {
|
||||
usersPath: mockUsersPath,
|
||||
},
|
||||
props: {
|
||||
hasSubscriptions: expectSubscriptionsList,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it(`renders sign in button with text ${signInButtonText}`, () => {
|
||||
expect(findSignInButton().text()).toMatchInterpolatedText(signInButtonText);
|
||||
});
|
||||
|
||||
it('renders sign in button with `usersPath` prop', () => {
|
||||
expect(findSignInButton().props('usersPath')).toBe(mockUsersPath);
|
||||
});
|
||||
|
||||
it(`${expectSubscriptionsList ? 'renders' : 'does not render'} subscriptions list`, () => {
|
||||
expect(findSubscriptionsList().exists()).toBe(expectSubscriptionsList);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,56 @@
|
|||
import { GlEmptyState } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import SubscriptionsPage from '~/jira_connect/subscriptions/pages/subscriptions.vue';
|
||||
import AddNamespaceButton from '~/jira_connect/subscriptions/components/add_namespace_button.vue';
|
||||
import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue';
|
||||
import createStore from '~/jira_connect/subscriptions/store';
|
||||
|
||||
describe('SubscriptionsPage', () => {
|
||||
let wrapper;
|
||||
let store;
|
||||
|
||||
const findAddNamespaceButton = () => wrapper.findComponent(AddNamespaceButton);
|
||||
const findSubscriptionsList = () => wrapper.findComponent(SubscriptionsList);
|
||||
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
|
||||
|
||||
const createComponent = ({ props } = {}) => {
|
||||
store = createStore();
|
||||
|
||||
wrapper = shallowMount(SubscriptionsPage, {
|
||||
store,
|
||||
propsData: props,
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
describe.each`
|
||||
scenario | expectSubscriptionsList | expectEmptyState
|
||||
${'with subscriptions'} | ${true} | ${false}
|
||||
${'without subscriptions'} | ${false} | ${true}
|
||||
`('$scenario', ({ expectEmptyState, expectSubscriptionsList }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
props: {
|
||||
hasSubscriptions: expectSubscriptionsList,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders button to add namespace', () => {
|
||||
expect(findAddNamespaceButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it(`${expectEmptyState ? 'renders' : 'does not render'} empty state`, () => {
|
||||
expect(findEmptyState().exists()).toBe(expectEmptyState);
|
||||
});
|
||||
|
||||
it(`${expectSubscriptionsList ? 'renders' : 'does not render'} subscriptions list`, () => {
|
||||
expect(findSubscriptionsList().exists()).toBe(expectSubscriptionsList);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -215,6 +215,20 @@ RSpec.describe Blob do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#symlink?' do
|
||||
it 'is true for symlinks' do
|
||||
symlink_blob = fake_blob(path: 'file', mode: '120000')
|
||||
|
||||
expect(symlink_blob.symlink?).to eq true
|
||||
end
|
||||
|
||||
it 'is false for non-symlinks' do
|
||||
non_symlink_blob = fake_blob(path: 'file', mode: '100755')
|
||||
|
||||
expect(non_symlink_blob.symlink?).to eq false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#extension' do
|
||||
it 'returns the extension' do
|
||||
blob = fake_blob(path: 'file.md')
|
||||
|
|
|
@ -265,22 +265,37 @@ RSpec.describe Ci::Runner do
|
|||
it_behaves_like '.belonging_to_parent_group_of_project'
|
||||
end
|
||||
|
||||
describe '.owned_or_instance_wide' do
|
||||
it 'returns a globally shared, a project specific and a group specific runner' do
|
||||
# group specific
|
||||
group = create(:group)
|
||||
project = create(:project, group: group)
|
||||
group_runner = create(:ci_runner, :group, groups: [group])
|
||||
context 'with existing system wide, group and project runners' do
|
||||
# group specific
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group]) }
|
||||
|
||||
# project specific
|
||||
project_runner = create(:ci_runner, :project, projects: [project])
|
||||
# project specific
|
||||
let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project]) }
|
||||
|
||||
# globally shared
|
||||
shared_runner = create(:ci_runner, :instance)
|
||||
# globally shared
|
||||
let_it_be(:shared_runner) { create(:ci_runner, :instance) }
|
||||
|
||||
expect(described_class.owned_or_instance_wide(project.id)).to contain_exactly(
|
||||
group_runner, project_runner, shared_runner
|
||||
)
|
||||
describe '.owned_or_instance_wide' do
|
||||
subject { described_class.owned_or_instance_wide(project.id) }
|
||||
|
||||
it 'returns a globally shared, a project specific and a group specific runner' do
|
||||
is_expected.to contain_exactly(group_runner, project_runner, shared_runner)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.group_or_instance_wide' do
|
||||
subject { described_class.group_or_instance_wide(group) }
|
||||
|
||||
before do
|
||||
# Ensure the project runner is instantiated
|
||||
project_runner
|
||||
end
|
||||
|
||||
it 'returns a globally shared and a group specific runner' do
|
||||
is_expected.to contain_exactly(group_runner, shared_runner)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1026,7 +1026,8 @@ RSpec.describe API::Ci::Runners do
|
|||
get api("/groups/#{group.id}/runners", user)
|
||||
|
||||
expect(json_response).to match_array([
|
||||
a_hash_including('description' => 'Group runner A', 'active' => true, 'paused' => false)
|
||||
a_hash_including('description' => 'Group runner A', 'active' => true, 'paused' => false),
|
||||
a_hash_including('description' => 'Shared runner', 'active' => true, 'paused' => false)
|
||||
])
|
||||
end
|
||||
|
||||
|
@ -1039,6 +1040,14 @@ RSpec.describe API::Ci::Runners do
|
|||
])
|
||||
end
|
||||
|
||||
it 'returns instance runners when instance_type is specified' do
|
||||
get api("/groups/#{group.id}/runners?type=instance_type", user)
|
||||
|
||||
expect(json_response).to match_array([
|
||||
a_hash_including('description' => 'Shared runner')
|
||||
])
|
||||
end
|
||||
|
||||
it 'returns empty result when type does not match' do
|
||||
get api("/groups/#{group.id}/runners?type=project_type", user)
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ RSpec.describe Branches::CreateService do
|
|||
|
||||
allow(project.repository).to receive(:add_branch).and_raise(pre_receive_error)
|
||||
|
||||
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
|
||||
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
|
||||
pre_receive_error,
|
||||
pre_receive_message: raw_message,
|
||||
branch_name: 'new-feature',
|
||||
|
|
|
@ -4,13 +4,14 @@ module FakeBlobHelpers
|
|||
class FakeBlob
|
||||
include BlobLike
|
||||
|
||||
attr_reader :path, :size, :data, :lfs_oid, :lfs_size
|
||||
attr_reader :path, :size, :data, :lfs_oid, :lfs_size, :mode
|
||||
|
||||
def initialize(path: 'file.txt', size: 1.kilobyte, data: 'foo', binary: false, lfs: nil)
|
||||
def initialize(path: 'file.txt', size: 1.kilobyte, data: 'foo', binary: false, lfs: nil, mode: nil)
|
||||
@path = path
|
||||
@size = size
|
||||
@data = data
|
||||
@binary = binary
|
||||
@mode = mode
|
||||
|
||||
@lfs_pointer = lfs.present?
|
||||
if @lfs_pointer
|
||||
|
|
|
@ -12,7 +12,7 @@ ifdef SOURCE_DATE_EPOCH
|
|||
else
|
||||
BUILD_TIME := $(shell date -u "$(DATE_FMT)")
|
||||
endif
|
||||
GOBUILD := go build -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)"
|
||||
GO_BUILD_GENERIC_LDFLAGS := -X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)
|
||||
GITALY := tmp/tests/gitaly/_build/bin/gitaly
|
||||
GITALY_PID_FILE := gitaly.pid
|
||||
EXE_ALL := gitlab-resize-image gitlab-zip-cat gitlab-zip-metadata gitlab-workhorse
|
||||
|
@ -30,31 +30,35 @@ define message
|
|||
@echo "### $(1)"
|
||||
endef
|
||||
|
||||
# To compute a unique and deterministic value for GNU build-id, we build the Go binary a second time.
|
||||
# From the first build, we extract its unique and deterministic Go build-id, and use that to derive
|
||||
# a comparably unique and deterministic GNU build-id to inject into the final binary.
|
||||
# If we cannot extract a Go build-id, we punt and fallback to using a random 32-byte hex string.
|
||||
# This fallback is unique but non-deterministic. Uniqueness is critical, because the GNU build-id
|
||||
# can be used as a cache key in a build cache. Without the fallback, we risk cache key collisions.
|
||||
## Skip generation of the GNU build ID if set to speed up builds.
|
||||
WITHOUT_BUILD_ID ?=
|
||||
|
||||
.NOTPARALLEL:
|
||||
|
||||
.PHONY: all
|
||||
all: clean-build $(EXE_ALL)
|
||||
|
||||
.PHONY: gitlab-resize-image
|
||||
gitlab-resize-image:
|
||||
.PHONY: gitlab-resize-image gitlab-zip-cat gitlab-zip-metadata
|
||||
gitlab-resize-image gitlab-zip-cat gitlab-zip-metadata:
|
||||
$(call message,Building $@)
|
||||
$(GOBUILD) -tags "$(BUILD_TAGS)" -o $(BUILD_DIR)/$@ $(PKG)/cmd/$@
|
||||
|
||||
.PHONY: gitlab-zip-cat
|
||||
gitlab-zip-cat:
|
||||
$(call message,Building $@)
|
||||
$(GOBUILD) -tags "$(BUILD_TAGS)" -o $(BUILD_DIR)/$@ $(PKG)/cmd/$@
|
||||
|
||||
.PHONY: gitlab-zip-metadata
|
||||
gitlab-zip-metadata:
|
||||
$(call message,Building $@)
|
||||
$(GOBUILD) -tags "$(BUILD_TAGS)" -o $(BUILD_DIR)/$@ $(PKG)/cmd/$@
|
||||
go build -ldflags "$(GO_BUILD_GENERIC_LDFLAGS)" -tags "$(BUILD_TAGS)" -o $(BUILD_DIR)/$@ $(PKG)/cmd/$@
|
||||
ifndef WITHOUT_BUILD_ID
|
||||
go build -ldflags "$(GO_BUILD_GENERIC_LDFLAGS) -B 0x$$(_support/make-gnu-build-id.sh $(BUILD_DIR)/$@)" -tags "$(BUILD_TAGS)" -o $(BUILD_DIR)/$@ $(PKG)/cmd/$@
|
||||
endif
|
||||
|
||||
.PHONY: gitlab-workhorse
|
||||
gitlab-workhorse:
|
||||
$(call message,Building $@)
|
||||
$(GOBUILD) -tags "$(BUILD_TAGS)" -o $(BUILD_DIR)/$@ $(PKG)
|
||||
go build -ldflags "$(GO_BUILD_GENERIC_LDFLAGS)" -tags "$(BUILD_TAGS)" -o $(BUILD_DIR)/$@ $(PKG)
|
||||
ifndef WITHOUT_BUILD_ID
|
||||
go build -ldflags "$(GO_BUILD_GENERIC_LDFLAGS) -B 0x$$(_support/make-gnu-build-id.sh $(BUILD_DIR)/$@)" -tags "$(BUILD_TAGS)" -o $(BUILD_DIR)/$@ $(PKG)
|
||||
endif
|
||||
|
||||
.PHONY: install
|
||||
install: $(EXE_ALL)
|
||||
|
|
30
workhorse/_support/make-gnu-build-id.sh
Executable file
30
workhorse/_support/make-gnu-build-id.sh
Executable file
|
@ -0,0 +1,30 @@
|
|||
#!/bin/sh
|
||||
|
||||
main()
|
||||
{
|
||||
GO_BINARY=$1
|
||||
|
||||
if [ $# -ne 1 ] || [ ! -f $GO_BINARY ] ; then
|
||||
fail "Usage: $0 [path_to_go_binary]"
|
||||
fi
|
||||
|
||||
GO_BUILD_ID=$( go tool buildid "$GO_BINARY" || openssl rand -hex 32 )
|
||||
if [ -z "$GO_BUILD_ID" ] ; then
|
||||
fail "ERROR: Could not extract Go build-id or generate a random hex string."
|
||||
fi
|
||||
|
||||
GNU_BUILD_ID=$( echo $GO_BUILD_ID | sha1sum | cut -d' ' -f1 )
|
||||
if [ -z "$GNU_BUILD_ID" ] ; then
|
||||
fail "ERROR: Could not generate a GNU build-id"
|
||||
fi
|
||||
|
||||
echo "$GNU_BUILD_ID"
|
||||
}
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$@" 1>&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
main "$@"
|
12
yarn.lock
12
yarn.lock
|
@ -962,15 +962,15 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.2.0.tgz#95cf58d6ae634d535145159f08f5cff6241d4013"
|
||||
integrity sha512-mCwR3KfNPsxRoojtTjMIZwdd4FFlBh5DlR9AeodP+7+k8rILdWGYxTZbJMPNXoPbZx16R94nG8c5bR7toD4QBw==
|
||||
|
||||
"@gitlab/ui@33.1.0":
|
||||
version "33.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-33.1.0.tgz#45ac2e6362546530b5756b1973f97f74a9c920da"
|
||||
integrity sha512-kSpnGx7UjWxl0s6RBl9njhthD2tSyLHoVd3Q/E3WEE9gdw8VSa4tEeI2srD9eGl2npd/VUvO7tNIXzk5NpjLZg==
|
||||
"@gitlab/ui@34.0.0":
|
||||
version "34.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-34.0.0.tgz#0fe9574df2c38aeb63add94e4549ed4e65975ef8"
|
||||
integrity sha512-BFh3x+GCqWAoWhNJhJUunW3eHQLQkBOTBwZFJWSS+1+9ZtetqU3t0/OoqYjJuyTsqdra7A/e6BZsU0j7CnbY+Q==
|
||||
dependencies:
|
||||
"@babel/standalone" "^7.0.0"
|
||||
bootstrap-vue "2.20.1"
|
||||
copy-to-clipboard "^3.0.8"
|
||||
dompurify "^2.3.4"
|
||||
dompurify "^2.3.5"
|
||||
echarts "^5.2.1"
|
||||
highlight.js "^10.6.0"
|
||||
iframe-resizer "^4.3.2"
|
||||
|
@ -4890,7 +4890,7 @@ dompurify@2.3.4:
|
|||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.4.tgz#1cf5cf0105ccb4debdf6db162525bd41e6ddacc6"
|
||||
integrity sha512-6BVcgOAVFXjI0JTjEvZy901Rghm+7fDQOrNIcxB4+gdhj6Kwp6T9VBhBY/AbagKHJocRkDYGd6wvI+p4/10xtQ==
|
||||
|
||||
dompurify@^2.3.4, dompurify@^2.3.5:
|
||||
dompurify@^2.3.5:
|
||||
version "2.3.5"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.5.tgz#c83ed5a3ae5ce23e52efe654ea052ffb358dd7e3"
|
||||
integrity sha512-kD+f8qEaa42+mjdOpKeztu9Mfx5bv9gVLO6K9jRx4uGvh6Wv06Srn4jr1wPNY2OOUGGSKHNFN+A8MA3v0E0QAQ==
|
||||
|
|
Loading…
Reference in a new issue