Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5acb134776
commit
2d3e590602
|
|
@ -1079,7 +1079,6 @@ Layout/LineLength:
|
|||
- 'ee/lib/ee/api/helpers/settings_helpers.rb'
|
||||
- 'ee/lib/ee/api/helpers/users_helpers.rb'
|
||||
- 'ee/lib/ee/api/internal/base.rb'
|
||||
- 'ee/lib/ee/api/internal/kubernetes.rb'
|
||||
- 'ee/lib/ee/api/issues.rb'
|
||||
- 'ee/lib/ee/api/members.rb'
|
||||
- 'ee/lib/ee/api/merge_request_approvals.rb'
|
||||
|
|
|
|||
4
Gemfile
4
Gemfile
|
|
@ -140,7 +140,7 @@ gem 'grape-path-helpers', '~> 2.0.1', feature_category: :api
|
|||
gem 'rack-cors', '~> 2.0.1', require: 'rack/cors' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
||||
# GraphQL API
|
||||
gem 'graphql', '~> 2.3.4', feature_category: :api
|
||||
gem 'graphql', '~> 2.3.5', feature_category: :api
|
||||
gem 'graphql-docs', '~> 4.0.0', group: [:development, :test], feature_category: :api
|
||||
gem 'graphiql-rails', '~> 1.8.0', feature_category: :api
|
||||
gem 'apollo_upload_server', '~> 2.1.6', feature_category: :api
|
||||
|
|
@ -561,7 +561,7 @@ group :test do
|
|||
gem 'gitlab_quality-test_tooling', '~> 1.29.1', require: false, feature_category: :tooling
|
||||
end
|
||||
|
||||
gem 'octokit', '~> 8.1', feature_category: :importers
|
||||
gem 'octokit', '~> 9.0', feature_category: :importers
|
||||
|
||||
gem 'gitlab-mail_room', '~> 0.0.24', require: 'mail_room', feature_category: :shared
|
||||
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@
|
|||
{"name":"graphiql-rails","version":"1.8.0","platform":"ruby","checksum":"02e2c5098be2c6c29219a0e9b2910a2cd3c494301587a3199a7c4484d8038ed1"},
|
||||
{"name":"graphlient","version":"0.6.0","platform":"ruby","checksum":"b8d8664b4c8ec215012cbe3cca918a045b0a206d709712d68b6db51fd215c5c0"},
|
||||
{"name":"graphlyte","version":"1.0.0","platform":"ruby","checksum":"b5af4ab67dde6e961f00ea1c18f159f73b52ed11395bb4ece297fe628fa1804d"},
|
||||
{"name":"graphql","version":"2.3.4","platform":"ruby","checksum":"020f313608237723e576e2621ab469a54087eaac2240261f9e41478d2725c935"},
|
||||
{"name":"graphql","version":"2.3.5","platform":"ruby","checksum":"9c367835f86541660d24c3d81632267ecee553d304577aaee070f8ac05860af1"},
|
||||
{"name":"graphql-client","version":"0.19.0","platform":"ruby","checksum":"fe699d81976f916bd8f989216155326449cb8475a5d69fa1dd054012a86969c7"},
|
||||
{"name":"graphql-docs","version":"4.0.0","platform":"ruby","checksum":"f68296959263db26e1b7ba7058856d67b641cf508187222268be58f09dfa02d7"},
|
||||
{"name":"grpc","version":"1.63.0","platform":"aarch64-linux","checksum":"dc75c5fd570b819470781d9512105dddfdd11d984f38b8e60bb946f92d1f79ee"},
|
||||
|
|
@ -355,7 +355,7 @@
|
|||
{"name":"letter_opener_web","version":"2.0.0","platform":"ruby","checksum":"33860ad41e1785d75456500e8ca8bba8ed71ee6eaf08a98d06bbab67c5577b6f"},
|
||||
{"name":"libyajl2","version":"2.1.0","platform":"ruby","checksum":"aa5df6c725776fc050c8418450de0f7c129cb7200b811907c4c0b3b5c0aea0ef"},
|
||||
{"name":"license_finder","version":"7.1.0","platform":"ruby","checksum":"6d020b3639f74da1488ddff052b3c93410cbf89a82dc884d404caa5ad072c66c"},
|
||||
{"name":"licensee","version":"9.16.1","platform":"ruby","checksum":"04c0b57d20b91fa82ac9dcb3637da9cdc1c3823e217c0781e5d0514958e2e515"},
|
||||
{"name":"licensee","version":"9.17.1","platform":"ruby","checksum":"0be022c66d8853d35b08171a0f2575d5ccb5aef8a7020a1815938b4f26f4089a"},
|
||||
{"name":"listen","version":"3.7.1","platform":"ruby","checksum":"3b80caa7aa77fae836916c2f9e3fbcafbd15f5d695dd487c1f5b5e7e465efe29"},
|
||||
{"name":"llhttp-ffi","version":"0.4.0","platform":"ruby","checksum":"e5f7327db3cf8007e648342ef76347d6e0ae545a8402e519cca9c886eb37b001"},
|
||||
{"name":"locale","version":"2.1.3","platform":"ruby","checksum":"b6ddee011e157817cb98e521b3ce7cb626424d5882f1e844aafdee3e8b212725"},
|
||||
|
|
@ -426,7 +426,7 @@
|
|||
{"name":"numerizer","version":"0.2.0","platform":"ruby","checksum":"e58076d5ee5370417b7e52d9cb25836d62acd1b8d9a194c308707986c1705d7b"},
|
||||
{"name":"oauth","version":"0.5.6","platform":"ruby","checksum":"4085fe28e0c5e2434135e00a6555294fd2a4ff96a98d1bdecdcd619fc6368dff"},
|
||||
{"name":"oauth2","version":"2.0.9","platform":"ruby","checksum":"b21f9defcf52dc1610e0dfab4c868342173dcd707fd15c777d9f4f04e153f7fb"},
|
||||
{"name":"octokit","version":"8.1.0","platform":"ruby","checksum":"82229ce9b54e910e27ae75ff21e54bc97072913b5af06750999966e6817af8cd"},
|
||||
{"name":"octokit","version":"9.1.0","platform":"ruby","checksum":"7849a659d2722c629181f48d1d7e567c9539f1a85c9676144dbdbfc6ce288253"},
|
||||
{"name":"ohai","version":"18.1.3","platform":"ruby","checksum":"980cfd6a6597f897e157532ba2168d29afb83a8f5e125f682ec3248c3407df95"},
|
||||
{"name":"oj","version":"3.13.23","platform":"ruby","checksum":"206dfdc4020ad9974705037f269cfba211d61b7662a58c717cce771829ccef51"},
|
||||
{"name":"oj-introspect","version":"0.7.2","platform":"ruby","checksum":"c415a44567ed2870d8e963a69421d9322128e194fab7867e37e54d5a25d5333d"},
|
||||
|
|
@ -649,7 +649,7 @@
|
|||
{"name":"spring-commands-rspec","version":"1.0.4","platform":"ruby","checksum":"6202e54fa4767452e3641461a83347645af478bf45dddcca9737b43af0dd1a2c"},
|
||||
{"name":"sprite-factory","version":"1.7.1","platform":"ruby","checksum":"5586524a1aec003241f1abc6852b61433e988aba5ee2b55f906387bf49b01ba2"},
|
||||
{"name":"sprockets","version":"3.7.2","platform":"ruby","checksum":"5ea1d7facd09203c1aa196afd6178208cd25abdbcc2a9978810a2f0754e152a0"},
|
||||
{"name":"sprockets-rails","version":"3.4.2","platform":"ruby","checksum":"36d6327757ccf7460a00d1d52b2d5ef0019a4670503046a129fa1fb1300931ad"},
|
||||
{"name":"sprockets-rails","version":"3.5.1","platform":"ruby","checksum":"c44626cb3887a1a8b572ca258685db33b4ebd041aa73428a716eac444ee5ef48"},
|
||||
{"name":"ssh_data","version":"1.3.0","platform":"ruby","checksum":"ec7c1e95a3aebeee412147998f4c147b4b05da6ed0aafda6083f9449318eaac0"},
|
||||
{"name":"ssrf_filter","version":"1.0.8","platform":"ruby","checksum":"03f49f54837e407d43ee93ec733a8a94dc1bcf8185647ac61606e63aaedaa0db"},
|
||||
{"name":"stackprof","version":"0.2.25","platform":"ruby","checksum":"28db0e2d22b817ae35def7163822505a04a026b02ef119b6aa89d70b967b0d2e"},
|
||||
|
|
|
|||
21
Gemfile.lock
21
Gemfile.lock
|
|
@ -876,7 +876,7 @@ GEM
|
|||
faraday_middleware
|
||||
graphql-client
|
||||
graphlyte (1.0.0)
|
||||
graphql (2.3.4)
|
||||
graphql (2.3.5)
|
||||
base64
|
||||
graphql-client (0.19.0)
|
||||
activesupport (>= 3.0)
|
||||
|
|
@ -1042,9 +1042,9 @@ GEM
|
|||
tomlrb (>= 1.3, < 2.1)
|
||||
with_env (= 1.1.0)
|
||||
xml-simple (~> 1.1.9)
|
||||
licensee (9.16.1)
|
||||
dotenv (~> 2.0)
|
||||
octokit (>= 4.20, < 9.0)
|
||||
licensee (9.17.1)
|
||||
dotenv (>= 2, < 4)
|
||||
octokit (>= 4.20, < 10.0)
|
||||
reverse_markdown (>= 1, < 3)
|
||||
rugged (>= 0.24, < 2.0)
|
||||
thor (>= 0.19, < 2.0)
|
||||
|
|
@ -1156,8 +1156,7 @@ GEM
|
|||
rack (>= 1.2, < 4)
|
||||
snaky_hash (~> 2.0)
|
||||
version_gem (~> 1.1)
|
||||
octokit (8.1.0)
|
||||
base64
|
||||
octokit (9.1.0)
|
||||
faraday (>= 1, < 3)
|
||||
sawyer (~> 0.9)
|
||||
ohai (18.1.3)
|
||||
|
|
@ -1724,9 +1723,9 @@ GEM
|
|||
sprockets (3.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.4.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets-rails (3.5.1)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
sprockets (>= 3.0.0)
|
||||
ssh_data (1.3.0)
|
||||
ssrf_filter (1.0.8)
|
||||
|
|
@ -2053,7 +2052,7 @@ DEPENDENCIES
|
|||
graphiql-rails (~> 1.8.0)
|
||||
graphlient (~> 0.6.0)
|
||||
graphlyte (~> 1.0.0)
|
||||
graphql (~> 2.3.4)
|
||||
graphql (~> 2.3.5)
|
||||
graphql-docs (~> 4.0.0)
|
||||
grpc (~> 1.63)
|
||||
gssapi (~> 1.3.1)
|
||||
|
|
@ -2107,7 +2106,7 @@ DEPENDENCIES
|
|||
net-protocol (~> 0.1.3)
|
||||
nokogiri (~> 1.16)
|
||||
oauth2 (~> 2.0)
|
||||
octokit (~> 8.1)
|
||||
octokit (~> 9.0)
|
||||
ohai (~> 18.1)
|
||||
oj (~> 3.13.21)
|
||||
oj-introspect (~> 0.7)
|
||||
|
|
|
|||
|
|
@ -85,7 +85,8 @@ export default {
|
|||
},
|
||||
update({ project: { jobs: { nodes = [], pageInfo = {} } = {} } }) {
|
||||
this.pageInfo = pageInfo;
|
||||
return nodes
|
||||
|
||||
const jobNodes = nodes
|
||||
.map(mapArchivesToJobNodes)
|
||||
.map(mapBooleansToJobNodes)
|
||||
.map((jobNode) => {
|
||||
|
|
@ -96,6 +97,12 @@ export default {
|
|||
_showDetails: this.expandedJobs.includes(jobNode.id),
|
||||
};
|
||||
});
|
||||
|
||||
if (jobNodes.some((jobNode) => !jobNode.hasArtifacts)) {
|
||||
this.$apollo.queries.jobArtifacts.refetch();
|
||||
}
|
||||
|
||||
return jobNodes;
|
||||
},
|
||||
error() {
|
||||
createAlert({
|
||||
|
|
@ -367,6 +374,9 @@ export default {
|
|||
createdLabel: I18N_CREATED,
|
||||
artifactsCount: I18N_ARTIFACTS_COUNT,
|
||||
},
|
||||
TBODY_TR_ATTR: {
|
||||
'data-testid': 'job-artifact-table-row',
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
|
@ -391,6 +401,7 @@ export default {
|
|||
:busy="$apollo.queries.jobArtifacts.loading"
|
||||
stacked="sm"
|
||||
details-td-class="gl-bg-gray-10! gl-p-0! gl-overflow-auto"
|
||||
:tbody-tr-attr="$options.TBODY_TR_ATTR"
|
||||
>
|
||||
<template #table-busy>
|
||||
<gl-skeleton-loader v-for="i in 20" :key="i" :width="1000" :height="75">
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import Vue from 'vue';
|
|||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import PipelineEditorApp from 'jh_else_ce/ci/pipeline_editor/pipeline_editor_app.vue';
|
||||
import { EDITOR_APP_STATUS_LOADING } from './constants';
|
||||
import { CODE_SNIPPET_SOURCE_SETTINGS } from './components/code_snippet_alert/constants';
|
||||
import getCurrentBranch from './graphql/queries/client/current_branch.query.graphql';
|
||||
|
|
@ -11,7 +12,6 @@ import getLastCommitBranch from './graphql/queries/client/last_commit_branch.que
|
|||
import getPipelineEtag from './graphql/queries/client/pipeline_etag.query.graphql';
|
||||
import { resolvers } from './graphql/resolvers';
|
||||
import typeDefs from './graphql/typedefs.graphql';
|
||||
import PipelineEditorApp from './pipeline_editor_app.vue';
|
||||
|
||||
export const createAppOptions = (el) => {
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import deletePipelineScheduleMutation from '../graphql/mutations/delete_pipeline
|
|||
import playPipelineScheduleMutation from '../graphql/mutations/play_pipeline_schedule.mutation.graphql';
|
||||
import takeOwnershipMutation from '../graphql/mutations/take_ownership.mutation.graphql';
|
||||
import getPipelineSchedulesQuery from '../graphql/queries/get_pipeline_schedules.query.graphql';
|
||||
import { ALL_SCOPE, SCHEDULES_PER_PAGE } from '../constants';
|
||||
import { ALL_SCOPE, SCHEDULES_PER_PAGE, DEFAULT_SORT_VALUE } from '../constants';
|
||||
import PipelineSchedulesTable from './table/pipeline_schedules_table.vue';
|
||||
import TakeOwnershipModal from './take_ownership_modal.vue';
|
||||
import DeletePipelineScheduleModal from './delete_pipeline_schedule_modal.vue';
|
||||
|
|
@ -90,6 +90,7 @@ export default {
|
|||
// we need to ensure we send null to the API when
|
||||
// the scope is 'ALL'
|
||||
status: this.scope === ALL_SCOPE ? null : this.scope,
|
||||
sortValue: this.sortValue,
|
||||
first: this.pagination.first,
|
||||
last: this.pagination.last,
|
||||
prevPageCursor: this.pagination.prevPageCursor,
|
||||
|
|
@ -128,6 +129,9 @@ export default {
|
|||
playSuccess: false,
|
||||
errorMessage: '',
|
||||
scheduleId: null,
|
||||
sortValue: DEFAULT_SORT_VALUE,
|
||||
sortBy: 'ID',
|
||||
sortDesc: true,
|
||||
showDeleteModal: false,
|
||||
showTakeOwnershipModal: false,
|
||||
count: 0,
|
||||
|
|
@ -332,6 +336,13 @@ export default {
|
|||
};
|
||||
}
|
||||
},
|
||||
onUpdateSorting(sortValue, sortBy, sortDesc) {
|
||||
this.sortValue = sortValue;
|
||||
this.sortBy = sortBy;
|
||||
this.sortDesc = sortDesc;
|
||||
|
||||
this.resetPagination();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -397,9 +408,12 @@ export default {
|
|||
<pipeline-schedules-table
|
||||
:schedules="schedules.list"
|
||||
:current-user="schedules.currentUser"
|
||||
:sort-by="sortBy"
|
||||
:sort-desc="sortDesc"
|
||||
@showTakeOwnershipModal="setTakeOwnershipModal"
|
||||
@showDeleteModal="setDeleteModal"
|
||||
@playPipelineSchedule="playPipelineSchedule"
|
||||
@update-sorting="onUpdateSorting"
|
||||
/>
|
||||
|
||||
<gl-pagination
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlTable } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import { TH_DESCRIPTION_TEST_ID, TH_TARGET_TEST_ID, TH_NEXT_TEST_ID } from '../../constants';
|
||||
import PipelineScheduleActions from './cells/pipeline_schedule_actions.vue';
|
||||
import PipelineScheduleLastPipeline from './cells/pipeline_schedule_last_pipeline.vue';
|
||||
import PipelineScheduleNextRun from './cells/pipeline_schedule_next_run.vue';
|
||||
|
|
@ -14,15 +15,21 @@ export default {
|
|||
fields: [
|
||||
{
|
||||
key: 'description',
|
||||
actualSortKey: 'DESCRIPTION',
|
||||
label: s__('PipelineSchedules|Description'),
|
||||
thClass: 'gl-border-t-none!',
|
||||
columnClass: 'gl-w-8/20',
|
||||
sortable: true,
|
||||
thAttr: TH_DESCRIPTION_TEST_ID,
|
||||
},
|
||||
{
|
||||
key: 'target',
|
||||
actualSortKey: 'REF',
|
||||
sortable: true,
|
||||
label: s__('PipelineSchedules|Target'),
|
||||
thClass: 'gl-border-t-none!',
|
||||
columnClass: 'gl-w-2/20',
|
||||
thAttr: TH_TARGET_TEST_ID,
|
||||
},
|
||||
{
|
||||
key: 'pipeline',
|
||||
|
|
@ -32,9 +39,12 @@ export default {
|
|||
},
|
||||
{
|
||||
key: 'next',
|
||||
actualSortKey: 'NEXT_RUN_AT',
|
||||
label: s__('PipelineSchedules|Next Run'),
|
||||
thClass: 'gl-border-t-none!',
|
||||
columnClass: 'gl-w-3/20',
|
||||
sortable: true,
|
||||
thAttr: TH_NEXT_TEST_ID,
|
||||
},
|
||||
{
|
||||
key: 'owner',
|
||||
|
|
@ -66,6 +76,24 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
sortBy: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
sortDesc: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchSortedData({ sortBy, sortDesc }) {
|
||||
const field = this.$options.fields.find(({ key }) => key === sortBy);
|
||||
const sortingDirection = sortDesc ? 'DESC' : 'ASC';
|
||||
|
||||
if (!field?.actualSortKey) return;
|
||||
|
||||
this.$emit('update-sorting', `${field.actualSortKey}_${sortingDirection}`, sortBy, sortDesc);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -76,8 +104,11 @@ export default {
|
|||
:items="schedules"
|
||||
:tbody-tr-attr="{ 'data-testid': 'pipeline-schedule-table-row' }"
|
||||
:empty-text="$options.i18n.emptyText"
|
||||
:sort-by="sortBy"
|
||||
:sort-desc="sortDesc"
|
||||
show-empty
|
||||
stacked="md"
|
||||
@sort-changed="fetchSortedData"
|
||||
>
|
||||
<template #table-colgroup="{ fields }">
|
||||
<col v-for="field in fields" :key="field.key" :class="field.columnClass" />
|
||||
|
|
|
|||
|
|
@ -2,3 +2,8 @@ export const VARIABLE_TYPE = 'ENV_VAR';
|
|||
export const FILE_TYPE = 'FILE';
|
||||
export const ALL_SCOPE = 'ALL';
|
||||
export const SCHEDULES_PER_PAGE = 50;
|
||||
export const DEFAULT_SORT_VALUE = 'ID_DESC';
|
||||
|
||||
export const TH_DESCRIPTION_TEST_ID = { 'data-testid': 'pipeline-schedules-description-sort' };
|
||||
export const TH_TARGET_TEST_ID = { 'data-testid': 'pipeline-schedules-target-sort' };
|
||||
export const TH_NEXT_TEST_ID = { 'data-testid': 'pipeline-schedules-next-sort' };
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ query getPipelineSchedulesQuery(
|
|||
$projectPath: ID!
|
||||
$status: PipelineScheduleStatus
|
||||
$ids: [ID!] = null
|
||||
$sortValue: PipelineScheduleSort
|
||||
$first: Int
|
||||
$last: Int
|
||||
$prevPageCursor: String = ""
|
||||
|
|
@ -21,6 +22,7 @@ query getPipelineSchedulesQuery(
|
|||
pipelineSchedules(
|
||||
status: $status
|
||||
ids: $ids
|
||||
sort: $sortValue
|
||||
first: $first
|
||||
last: $last
|
||||
after: $nextPageCursor
|
||||
|
|
|
|||
|
|
@ -90,12 +90,8 @@ export default {
|
|||
|
||||
<template>
|
||||
<li :class="{ 'js-toggle-container': collapsible }" class="commit">
|
||||
<div
|
||||
class="gl-block sm:gl-flex flex-row-reverse justify-content-between align-items-start flex-lg-row-reverse"
|
||||
>
|
||||
<div
|
||||
class="commit-actions flex-row gl-hidden sm:gl-flex gl-align-items-center gl-flex-wrap justify-content-end"
|
||||
>
|
||||
<div class="gl-block sm:gl-flex gl-flex-row-reverse gl-justify-between gl-items-start">
|
||||
<div class="commit-actions gl-flex-row gl-hidden sm:gl-flex gl-items-center gl-justify-end">
|
||||
<div
|
||||
v-if="commit.signature_html"
|
||||
v-html="commit.signature_html /* eslint-disable-line vue/no-v-html */"
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ module Packages
|
|||
|
||||
def current_package_protected?
|
||||
return false if Feature.disabled?(:packages_protected_packages, project)
|
||||
return false if current_user.is_a?(DeployToken)
|
||||
|
||||
user_project_authorization_access_level = current_user.max_member_access_for_project(project.id)
|
||||
project.package_protection_rules.for_push_exists?(access_level: user_project_authorization_access_level,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
= paginate @projects, theme: 'gitlab'
|
||||
- else
|
||||
.nothing-here-block= _('No projects found')
|
||||
= render Pajamas::EmptyStateComponent.new(svg_path: 'illustrations/empty-state/empty-projects-md.svg',
|
||||
title: _('No projects found'))
|
||||
|
||||
#delete-project-modal
|
||||
|
|
|
|||
|
|
@ -7,19 +7,24 @@
|
|||
.js-jh-transition-banner{ data: { feature_name: Users::CalloutsHelper::TRANSITION_TO_JIHU_CALLOUT,
|
||||
user_preferred_language: current_user.preferred_language} }
|
||||
|
||||
.top-area.gl-flex-direction-column-reverse
|
||||
.scrolling-tabs-container.inner-page-scroll-tabs.gl-flex-grow-1.gl-min-w-0.gl-w-full
|
||||
= render ::Layouts::PageHeadingComponent.new(_('Projects')) do |c|
|
||||
- c.with_actions do
|
||||
= link_button_to new_project_path, variant: :confirm do
|
||||
= _('New Project')
|
||||
|
||||
.top-area
|
||||
.scrolling-tabs-container.inner-page-scroll-tabs.gl-grow.gl-basis-0.gl-min-w-0
|
||||
%button.fade-left{ type: 'button', title: _('Scroll left'), 'aria-label': _('Scroll left') }
|
||||
= sprite_icon('chevron-lg-left', size: 12)
|
||||
%button.fade-right{ type: 'button', title: _('Scroll right'), 'aria-label': _('Scroll right') }
|
||||
= sprite_icon('chevron-lg-right', size: 12)
|
||||
= gl_tabs_nav({ class: 'scrolling-tabs nav-links gl-display-flex gl-flex-grow-1 gl-w-full nav gl-tabs-nav nav gl-tabs-nav' }) do
|
||||
= gl_tabs_nav({ class: 'scrolling-tabs nav-links gl-display-flex gl-grow gl-w-full nav gl-tabs-nav nav gl-tabs-nav' }) do
|
||||
= gl_tab_link_to _('All'), admin_projects_path(visibility_level: nil), { item_active: params[:visibility_level].empty? }
|
||||
= gl_tab_link_to _('Private'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
|
||||
= gl_tab_link_to _('Internal'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
|
||||
= gl_tab_link_to _('Public'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||
|
||||
.nav-controls
|
||||
= render 'shared/projects/search_form', autofocus: true, admin_view: true
|
||||
.md:gl-flex.gl-min-w-0.gl-grow.gl-border-t-0.row-content-block
|
||||
= render 'shared/projects/search_form', admin_view: true
|
||||
|
||||
= render 'projects'
|
||||
|
|
|
|||
|
|
@ -36,11 +36,11 @@
|
|||
= stylesheet_link_tag_defer "application_utilities_dark"
|
||||
- elsif user_application_system_mode?
|
||||
%meta{ name: 'color-scheme', content: 'light dark' }
|
||||
= stylesheet_link_tag "application", media: "(prefers-color-scheme: light)"
|
||||
= stylesheet_link_tag "application_dark", media: "(prefers-color-scheme: dark)"
|
||||
= universal_stylesheet_link_tag "application", media: "(prefers-color-scheme: light)"
|
||||
= universal_stylesheet_link_tag "application_dark", media: "(prefers-color-scheme: dark)"
|
||||
= yield :page_specific_styles
|
||||
= stylesheet_link_tag "application_utilities", media: "(prefers-color-scheme: light)"
|
||||
= stylesheet_link_tag "application_utilities_dark", media: "(prefers-color-scheme: dark)"
|
||||
= universal_stylesheet_link_tag "application_utilities", media: "(prefers-color-scheme: light)"
|
||||
= universal_stylesheet_link_tag "application_utilities_dark", media: "(prefers-color-scheme: dark)"
|
||||
- else
|
||||
= stylesheet_link_tag_defer "application"
|
||||
= yield :page_specific_styles
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
- placeholder = local_assigns[:search_form_placeholder] ? search_form_placeholder : _('Filter by name')
|
||||
- admin_view ||= false
|
||||
- top_padding = admin_view ? 'gl-lg-pt-3' : ''
|
||||
|
||||
= form_tag filter_projects_path, method: :get, class: "project-filter-form gl-display-flex! gl-flex-wrap gl-w-full gl-gap-3 #{top_padding}", data: { testid: 'project-filter-form-container' }, id: 'project-filter-form' do |f|
|
||||
= form_tag filter_projects_path, method: :get, class: "project-filter-form !gl-flex gl-flex-wrap gl-w-full gl-gap-3", data: { testid: 'project-filter-form-container' }, id: 'project-filter-form' do |f|
|
||||
= search_field_tag :name, params[:name],
|
||||
placeholder: placeholder,
|
||||
class: "project-filter-form-field form-control input-short js-projects-list-filter gl-m-0!",
|
||||
class: "project-filter-form-field form-control input-short js-projects-list-filter !gl-m-0 gl-grow",
|
||||
spellcheck: false,
|
||||
id: 'project-filter-form-field',
|
||||
autofocus: local_assigns[:autofocus]
|
||||
|
|
@ -28,7 +27,7 @@
|
|||
- if params[:language].present?
|
||||
= hidden_field_tag :language, params[:language]
|
||||
|
||||
.dropdown{ class: 'gl-m-0!' }
|
||||
.dropdown{ class: '!gl-m-0' }
|
||||
= dropdown_toggle(search_language_placeholder, { toggle: 'dropdown', testid: 'project-language-dropdown' })
|
||||
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
|
||||
%li
|
||||
|
|
@ -48,8 +47,5 @@
|
|||
- if params[:namespace_id].present?
|
||||
- namespace = Namespace.find(params[:namespace_id])
|
||||
- selected_text = "#{namespace.kind}: #{namespace.full_path}" if namespace
|
||||
.gl-display-flex.gl-w-full.gl-md-w-auto{ class: 'gl-m-0!' }
|
||||
.gl-flex.gl-w-full.gl-md-w-auto{ class: '!gl-m-0' }
|
||||
.js-namespace-select{ data: { field_name: 'namespace_id', selected_id: namespace&.id, selected_text: selected_text, update_location: 'true' } }
|
||||
|
||||
= link_button_to new_project_path, class: 'gl-display-inline gl-mb-0!', variant: :confirm do
|
||||
= _('New Project')
|
||||
|
|
|
|||
|
|
@ -40,6 +40,13 @@ To move databases from one instance to another:
|
|||
/opt/gitlab/embedded/bin/pg_dump -h $SRC_PGHOST -U $SRC_PGUSER -c -C -f praefect_production.sql praefect_production
|
||||
```
|
||||
|
||||
NOTE:
|
||||
In rare occasions, you might notice database performance issues after you perform
|
||||
a `pg_dump` and restore. This can happen because `pg_dump` does not contain the statistics
|
||||
[used by the optimizer to make query planning decisions](https://www.postgresql.org/docs/14/app-pgdump.html).
|
||||
If performance degrades after a restore, fix the problem by finding the problematic query,
|
||||
then running ANALYZE on the tables used by the query.
|
||||
|
||||
1. Restore the databases to the destination (this overwrites any existing databases with the same names):
|
||||
|
||||
```shell
|
||||
|
|
|
|||
|
|
@ -1777,17 +1777,19 @@ Refer to the [external Sidekiq documentation](../sidekiq/index.md) for more info
|
|||
To configure the Sidekiq nodes, on each one:
|
||||
|
||||
1. SSH in to the Sidekiq server.
|
||||
1. Confirm that you can access the PostgreSQL, Gitaly, and Redis ports:
|
||||
|
||||
```shell
|
||||
telnet <GitLab host> 5432 # PostgreSQL
|
||||
telnet <GitLab host> 8075 # Gitaly
|
||||
telnet <GitLab host> 6379 # Redis
|
||||
```
|
||||
|
||||
1. [Download and install](https://about.gitlab.com/install/) the Linux
|
||||
package of your choice. Be sure to follow _only_ installation steps 1 and 2
|
||||
on the page.
|
||||
1. Create or edit `/etc/gitlab/gitlab.rb` and use the following configuration:
|
||||
|
||||
<!--
|
||||
Updates to example must be made at:
|
||||
- https://gitlab.com/gitlab-org/gitlab/blob/master/doc/administration/sidekiq.md
|
||||
- all reference architecture pages
|
||||
-->
|
||||
|
||||
```ruby
|
||||
# https://docs.gitlab.com/omnibus/roles/#sidekiq-roles
|
||||
roles(["sidekiq_role"])
|
||||
|
|
|
|||
|
|
@ -1783,17 +1783,19 @@ Refer to the [external Sidekiq documentation](../sidekiq/index.md) for more info
|
|||
To configure the Sidekiq nodes, on each one:
|
||||
|
||||
1. SSH in to the Sidekiq server.
|
||||
1. Confirm that you can access the PostgreSQL, Gitaly, and Redis ports:
|
||||
|
||||
```shell
|
||||
telnet <GitLab host> 5432 # PostgreSQL
|
||||
telnet <GitLab host> 8075 # Gitaly
|
||||
telnet <GitLab host> 6379 # Redis
|
||||
```
|
||||
|
||||
1. [Download and install](https://about.gitlab.com/install/) the Linux
|
||||
package of your choice. Be sure to follow _only_ installation steps 1 and 2
|
||||
on the page.
|
||||
1. Create or edit `/etc/gitlab/gitlab.rb` and use the following configuration:
|
||||
|
||||
<!--
|
||||
Updates to example must be made at:
|
||||
- https://gitlab.com/gitlab-org/gitlab/blob/master/doc/administration/sidekiq.md
|
||||
- all reference architecture pages
|
||||
-->
|
||||
|
||||
```ruby
|
||||
# https://docs.gitlab.com/omnibus/roles/#sidekiq-roles
|
||||
roles(["sidekiq_role"])
|
||||
|
|
|
|||
|
|
@ -625,17 +625,19 @@ Refer to the [external Sidekiq documentation](../sidekiq/index.md) for more info
|
|||
To configure the Sidekiq server, on the server node you want to use for Sidekiq:
|
||||
|
||||
1. SSH in to the Sidekiq server.
|
||||
1. Confirm that you can access the PostgreSQL, Gitaly, and Redis ports:
|
||||
|
||||
```shell
|
||||
telnet <GitLab host> 5432 # PostgreSQL
|
||||
telnet <GitLab host> 8075 # Gitaly
|
||||
telnet <GitLab host> 6379 # Redis
|
||||
```
|
||||
|
||||
1. [Download and install](https://about.gitlab.com/install/) the Linux
|
||||
package of your choice. Be sure to follow _only_ installation steps 1 and 2
|
||||
on the page.
|
||||
1. Create or edit `/etc/gitlab/gitlab.rb` and use the following configuration:
|
||||
|
||||
<!--
|
||||
Updates to example must be made at:
|
||||
- https://gitlab.com/gitlab-org/gitlab/blob/master/doc/administration/sidekiq.md
|
||||
- all reference architecture pages
|
||||
-->
|
||||
|
||||
```ruby
|
||||
# https://docs.gitlab.com/omnibus/roles/#sidekiq-roles
|
||||
roles(["sidekiq_role"])
|
||||
|
|
|
|||
|
|
@ -1617,17 +1617,19 @@ The following IPs will be used as an example:
|
|||
To configure the Sidekiq nodes, on each one:
|
||||
|
||||
1. SSH in to the Sidekiq server.
|
||||
1. Confirm that you can access the PostgreSQL, Gitaly, and Redis ports:
|
||||
|
||||
```shell
|
||||
telnet <GitLab host> 5432 # PostgreSQL
|
||||
telnet <GitLab host> 8075 # Gitaly
|
||||
telnet <GitLab host> 6379 # Redis
|
||||
```
|
||||
|
||||
1. [Download and install](https://about.gitlab.com/install/) the Linux
|
||||
package of your choice. Be sure to follow _only_ installation steps 1 and 2
|
||||
on the page.
|
||||
1. Create or edit `/etc/gitlab/gitlab.rb` and use the following configuration:
|
||||
|
||||
<!--
|
||||
Updates to example must be made at:
|
||||
- https://gitlab.com/gitlab-org/gitlab/blob/master/doc/administration/sidekiq.md
|
||||
- all reference architecture pages
|
||||
-->
|
||||
|
||||
```ruby
|
||||
# https://docs.gitlab.com/omnibus/roles/#sidekiq-roles
|
||||
roles(["sidekiq_role"])
|
||||
|
|
|
|||
|
|
@ -1793,17 +1793,19 @@ Refer to the [external Sidekiq documentation](../sidekiq/index.md) for more info
|
|||
To configure the Sidekiq nodes, on each one:
|
||||
|
||||
1. SSH in to the Sidekiq server.
|
||||
1. Confirm that you can access the PostgreSQL, Gitaly, and Redis ports:
|
||||
|
||||
```shell
|
||||
telnet <GitLab host> 5432 # PostgreSQL
|
||||
telnet <GitLab host> 8075 # Gitaly
|
||||
telnet <GitLab host> 6379 # Redis
|
||||
```
|
||||
|
||||
1. [Download and install](https://about.gitlab.com/install/) the Linux
|
||||
package of your choice. Be sure to follow _only_ installation steps 1 and 2
|
||||
on the page.
|
||||
1. Create or edit `/etc/gitlab/gitlab.rb` and use the following configuration:
|
||||
|
||||
<!--
|
||||
Updates to example must be made at:
|
||||
- https://gitlab.com/gitlab-org/gitlab/blob/master/doc/administration/sidekiq.md
|
||||
- all reference architecture pages
|
||||
-->
|
||||
|
||||
```ruby
|
||||
# https://docs.gitlab.com/omnibus/roles/#sidekiq-roles
|
||||
roles(["sidekiq_role"])
|
||||
|
|
|
|||
|
|
@ -1613,17 +1613,19 @@ Refer to the [external Sidekiq documentation](../sidekiq/index.md) for more info
|
|||
To configure the Sidekiq nodes, on each one:
|
||||
|
||||
1. SSH in to the Sidekiq server.
|
||||
1. Confirm that you can access the PostgreSQL, Gitaly, and Redis ports:
|
||||
|
||||
```shell
|
||||
telnet <GitLab host> 5432 # PostgreSQL
|
||||
telnet <GitLab host> 8075 # Gitaly
|
||||
telnet <GitLab host> 6379 # Redis
|
||||
```
|
||||
|
||||
1. [Download and install](https://about.gitlab.com/install/) the Linux package
|
||||
package of your choice. Be sure to follow _only_ installation steps 1 and 2
|
||||
on the page.
|
||||
1. Create or edit `/etc/gitlab/gitlab.rb` and use the following configuration:
|
||||
|
||||
<!--
|
||||
Updates to example must be made at:
|
||||
- https://gitlab.com/gitlab-org/gitlab/blob/master/doc/administration/sidekiq.md
|
||||
- all reference architecture pages
|
||||
-->
|
||||
|
||||
```ruby
|
||||
# https://docs.gitlab.com/omnibus/roles/#sidekiq-roles
|
||||
roles(["sidekiq_role"])
|
||||
|
|
|
|||
|
|
@ -17,25 +17,12 @@ PostgreSQL, and Gitaly instances.
|
|||
|
||||
By default, GitLab uses UNIX sockets and is not set up to communicate via TCP. To change this:
|
||||
|
||||
1. [Configure packaged PostgreSQL server to listen on TCP/IP](https://docs.gitlab.com/omnibus/settings/database.html#configure-packaged-postgresql-server-to-listen-on-tcpip) adding the Sidekiq server IP addresses to `postgresql['md5_auth_cidr_addresses']`
|
||||
1. [Make the bundled Redis reachable via TCP](https://docs.gitlab.com/omnibus/settings/redis.html#making-the-bundled-redis-reachable-via-tcp)
|
||||
1. Edit the `/etc/gitlab/gitlab.rb` file on your GitLab instance, and add the following:
|
||||
|
||||
```ruby
|
||||
|
||||
## PostgreSQL
|
||||
|
||||
# Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
|
||||
postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH'
|
||||
postgresql['listen_address'] = '0.0.0.0'
|
||||
postgresql['port'] = 5432
|
||||
|
||||
# Add the Sidekiq nodes to PostgreSQL's trusted addresses.
|
||||
# In the following example, 10.10.1.30/32 is the private IP
|
||||
# of the Sidekiq server.
|
||||
postgresql['md5_auth_cidr_addresses'] = %w(127.0.0.1/32 10.10.1.30/32)
|
||||
postgresql['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 10.10.1.30/32)
|
||||
|
||||
## Gitaly
|
||||
|
||||
gitaly['configuration'] = {
|
||||
# ...
|
||||
#
|
||||
|
|
@ -48,18 +35,10 @@ By default, GitLab uses UNIX sockets and is not set up to communicate via TCP. T
|
|||
},
|
||||
}
|
||||
|
||||
gitaly['auth_token'] = ''
|
||||
praefect['configuration'][:auth][:token] = 'abc123secret'
|
||||
gitlab_rails['gitaly_token'] = 'abc123secret'
|
||||
|
||||
## Redis configuration
|
||||
|
||||
redis['bind'] = '0.0.0.0'
|
||||
redis['port'] = 6379
|
||||
# Password to Authenticate Redis
|
||||
redis['password'] = 'redis-password-goes-here'
|
||||
gitlab_rails['redis_password'] = 'redis-password-goes-here'
|
||||
|
||||
```
|
||||
|
||||
1. Run `reconfigure`:
|
||||
|
|
@ -76,106 +55,7 @@ By default, GitLab uses UNIX sockets and is not set up to communicate via TCP. T
|
|||
|
||||
## Set up Sidekiq instance
|
||||
|
||||
1. SSH into the Sidekiq server.
|
||||
|
||||
1. Confirm that you can access the PostgreSQL, Gitaly, and Redis ports:
|
||||
|
||||
```shell
|
||||
telnet <GitLab host> 5432 # PostgreSQL
|
||||
telnet <GitLab host> 8075 # Gitaly
|
||||
telnet <GitLab host> 6379 # Redis
|
||||
```
|
||||
|
||||
1. [Download and install](https://about.gitlab.com/install/) the Linux package
|
||||
using steps 1 and 2. **Do not complete any other steps.**
|
||||
|
||||
1. Copy the `/etc/gitlab/gitlab.rb` file from the GitLab instance and add the following settings. Make sure
|
||||
to replace them with your values:
|
||||
|
||||
<!--
|
||||
Updates to example must be made at:
|
||||
- https://gitlab.com/gitlab-org/gitlab/blob/master/doc/administration/sidekiq.md
|
||||
- all reference architecture pages
|
||||
-->
|
||||
|
||||
```ruby
|
||||
# https://docs.gitlab.com/omnibus/roles/#sidekiq-roles
|
||||
roles(["sidekiq_role"])
|
||||
|
||||
##
|
||||
## To maintain uniformity of links across nodes, the
|
||||
## `external_url` on the Sidekiq server should point to the external URL that users
|
||||
## use to access GitLab. This can be either:
|
||||
##
|
||||
## - The `external_url` set on your application server.
|
||||
## - The URL of a external load balancer, which routes traffic to the GitLab application server.
|
||||
##
|
||||
external_url 'https://gitlab.example.com'
|
||||
|
||||
# Configure the gitlab-shell API callback URL. Without this, `git push` will
|
||||
# fail. This can be your 'front door' GitLab URL or an internal load
|
||||
# balancer.
|
||||
gitlab_rails['internal_api_url'] = 'GITLAB_URL'
|
||||
gitlab_shell['secret_token'] = 'SHELL_TOKEN'
|
||||
|
||||
########################################
|
||||
#### Redis ###
|
||||
########################################
|
||||
|
||||
## Must be the same in every sentinel node.
|
||||
redis['master_name'] = 'gitlab-redis' # Required if you have set up redis cluster
|
||||
## The same password for Redis authentication you set up for the master node.
|
||||
redis['master_password'] = '<redis_master_password>'
|
||||
|
||||
### If redis is running on the main Gitlab instance and you have opened the TCP port as above add the following
|
||||
gitlab_rails['redis_host'] = '<gitlab_host>'
|
||||
gitlab_rails['redis_port'] = 6379
|
||||
|
||||
#######################################
|
||||
### Gitaly ###
|
||||
#######################################
|
||||
|
||||
## Replace <gitaly_token> with the one you set up, see
|
||||
## https://docs.gitlab.com/ee/administration/gitaly/configure_gitaly.html#about-the-gitaly-token
|
||||
git_data_dirs({
|
||||
"default" => {
|
||||
"gitaly_address" => "tcp://<gitlab_host>:8075",
|
||||
"gitaly_token" => "<gitaly_token>"
|
||||
}
|
||||
})
|
||||
|
||||
#######################################
|
||||
### Postgres ###
|
||||
#######################################
|
||||
|
||||
# Replace <database_host> and <database_password>
|
||||
gitlab_rails['db_host'] = '<database_host>'
|
||||
gitlab_rails['db_port'] = 5432
|
||||
gitlab_rails['db_password'] = '<database_password>'
|
||||
## Prevent database migrations from running on upgrade automatically
|
||||
gitlab_rails['auto_migrate'] = false
|
||||
|
||||
#######################################
|
||||
### Sidekiq configuration ###
|
||||
#######################################
|
||||
sidekiq['listen_address'] = "0.0.0.0"
|
||||
|
||||
## Set number of Sidekiq queue processes to the same number as available CPUs
|
||||
sidekiq['queue_groups'] = ['*'] * 4
|
||||
|
||||
## Set number of Sidekiq threads per queue process to the recommend number of 20
|
||||
sidekiq['max_concurrency'] = 20
|
||||
```
|
||||
|
||||
1. Copy the `/etc/gitlab/gitlab-secrets.json` file from the GitLab instance and replace the file in the Sidekiq instance.
|
||||
|
||||
1. Reconfigure GitLab:
|
||||
|
||||
```shell
|
||||
sudo gitlab-ctl reconfigure
|
||||
```
|
||||
|
||||
1. Restart the Sidekiq instance after completing the process and finishing the database migrations.
|
||||
Find [your reference architecture](../reference_architectures/index.md#available-reference-architectures) and follow the Sidekiq instance setup details.
|
||||
|
||||
## Configure multiple Sidekiq nodes with shared storage
|
||||
|
||||
|
|
|
|||
|
|
@ -118,25 +118,6 @@ The rule for this job compares all files and paths in the current branch
|
|||
recursively (`**/*`) against the `main` branch. The rule matches and the
|
||||
job runs only when there are changes to the files in the branch.
|
||||
|
||||
### Use variables in `rules:changes`
|
||||
|
||||
You can use CI/CD variables in `rules:changes` expressions to determine when
|
||||
to add jobs to a pipeline:
|
||||
|
||||
```yaml
|
||||
docker build:
|
||||
variables:
|
||||
DOCKERFILES_DIR: 'path/to/files'
|
||||
script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
|
||||
rules:
|
||||
- changes:
|
||||
- $DOCKERFILES_DIR/**/*
|
||||
```
|
||||
|
||||
You can use the `$` character for both variables and paths. For example, if the
|
||||
`$DOCKERFILES_DIR` variable exists, its value is used. If it does not exist, the
|
||||
`$` is interpreted as being part of a path.
|
||||
|
||||
## Common `if` clauses with predefined variables
|
||||
|
||||
`rules:if` clauses are commonly used with [predefined CI/CD variables](../variables/predefined_variables.md),
|
||||
|
|
|
|||
|
|
@ -4104,8 +4104,7 @@ or [merge request pipelines](../pipelines/merge_request_pipelines.md), though
|
|||
|
||||
An array including any number of:
|
||||
|
||||
- Paths to files. The [file paths can include variables](../jobs/job_rules.md#use-variables-in-ruleschanges).
|
||||
A file path array can also be in [`rules:changes:paths`](#ruleschangespaths).
|
||||
- Paths to files. The file paths can include [CI/CD variables](../variables/where_variables_can_be_used.md#gitlab-ciyml-file).
|
||||
- Wildcard paths for:
|
||||
- Single directories, for example `path/to/directory/*`.
|
||||
- A directory and all its subdirectories, for example `path/to/directory/**/*`.
|
||||
|
|
@ -4125,23 +4124,37 @@ docker build:
|
|||
- Dockerfile
|
||||
when: manual
|
||||
allow_failure: true
|
||||
|
||||
docker build alternative:
|
||||
variables:
|
||||
DOCKERFILES_DIR: 'path/to/dockerfiles'
|
||||
script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
changes:
|
||||
- $DOCKERFILES_DIR/**/*
|
||||
```
|
||||
|
||||
- If the pipeline is a merge request pipeline, check `Dockerfile` for changes.
|
||||
In this example:
|
||||
|
||||
- If the pipeline is a merge request pipeline, check `Dockerfile` and the files in
|
||||
`$DOCKERFILES_DIR/**/*` for changes.
|
||||
- If `Dockerfile` has changed, add the job to the pipeline as a manual job, and the pipeline
|
||||
continues running even if the job is not triggered (`allow_failure: true`).
|
||||
- A maximum of 50 patterns or file paths can be defined per `rules:changes` section.
|
||||
- If `Dockerfile` has not changed, do not add job to any pipeline (same as `when: never`).
|
||||
- [`rules:changes:paths`](#ruleschangespaths) is the same as `rules:changes` without
|
||||
any subkeys.
|
||||
- If a file in `$DOCKERFILES_DIR/**/*` has changed, add the job to the pipeline.
|
||||
- If no listed files have changed, do not add either job to any pipeline (same as `when: never`).
|
||||
|
||||
**Additional details**:
|
||||
|
||||
- Glob patterns are interpreted with Ruby's [`File.fnmatch`](https://docs.ruby-lang.org/en/master/File.html#method-c-fnmatch)
|
||||
with the [flags](https://docs.ruby-lang.org/en/master/File/Constants.html#module-File::Constants-label-Filename+Globbing+Constants+-28File-3A-3AFNM_-2A-29)
|
||||
`File::FNM_PATHNAME | File::FNM_DOTMATCH | File::FNM_EXTGLOB`.
|
||||
- A maximum of 50 patterns or file paths can be defined per `rules:changes` section.
|
||||
- `changes` resolves to `true` if any of the matching files are changed (an `OR` operation).
|
||||
- For additional examples, see [Specify when jobs run with `rules`](../jobs/job_rules.md).
|
||||
- You can use the `$` character for both variables and paths. For example, if the
|
||||
`$VAR` variable exists, its value is used. If it does not exist, the `$` is interpreted
|
||||
as being part of a path.
|
||||
|
||||
**Related topics**:
|
||||
|
||||
|
|
@ -4161,7 +4174,7 @@ any subkeys. All additional details and related topics are the same.
|
|||
|
||||
**Possible inputs**:
|
||||
|
||||
- An array of file paths. [File paths can include variables](../jobs/job_rules.md#use-variables-in-ruleschanges).
|
||||
- An array of file paths. File paths can include [CI/CD variables](../variables/where_variables_can_be_used.md#gitlab-ciyml-file).
|
||||
|
||||
**Example of `rules:changes:paths`**:
|
||||
|
||||
|
|
@ -4234,7 +4247,9 @@ Use `exists` to run a job when certain files exist in the repository.
|
|||
|
||||
**Possible inputs**:
|
||||
|
||||
- An array of file paths. Paths are relative to the project directory (`$CI_PROJECT_DIR`) and can't directly link outside it. File paths can use glob patterns and [CI/CD variables](../variables/where_variables_can_be_used.md#gitlab-ciyml-file).
|
||||
- An array of file paths. Paths are relative to the project directory (`$CI_PROJECT_DIR`)
|
||||
and can't directly link outside it. File paths can use glob patterns and
|
||||
[CI/CD variables](../variables/where_variables_can_be_used.md#gitlab-ciyml-file).
|
||||
|
||||
**Example of `rules:exists`**:
|
||||
|
||||
|
|
@ -4246,10 +4261,12 @@ job:
|
|||
- Dockerfile
|
||||
|
||||
job2:
|
||||
variables:
|
||||
DOCKERPATH: "**/Dockerfile"
|
||||
script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
|
||||
rules:
|
||||
- exists:
|
||||
- "**/Dockerfile"
|
||||
- $DOCKERPATH
|
||||
```
|
||||
|
||||
In this example:
|
||||
|
|
|
|||
|
|
@ -1249,11 +1249,37 @@ include a visual representation to help readers understand it, you can:
|
|||
The Markdown code for including an image in a document is:
|
||||
``
|
||||
|
||||
The image description is the alt text for the rendered image on the
|
||||
documentation site. For accessibility and SEO, use [descriptions](https://webaim.org/techniques/alttext/)
|
||||
that are accurate, succinct, and unique.
|
||||
#### Alternative text
|
||||
|
||||
Don't use **image of** or **graphic of** to describe the image.
|
||||
Alt text provides an accessible experience.
|
||||
Screen readers use alt text to describe the image, and alt text displays
|
||||
if an image fails to download.
|
||||
|
||||
Alt text should describe the context of the image, not the content. Add context that
|
||||
relates to the topic of the page or section. Consider what you would say about the image
|
||||
if you were helping someone read and interact with the page and they couldn't see it.
|
||||
|
||||
Do:
|
||||
|
||||
``
|
||||
|
||||
Do not:
|
||||
|
||||
``
|
||||
|
||||
When writing alt text:
|
||||
|
||||
- Write short, descriptive alt text in 155 characters or fewer. Screen readers
|
||||
typically stop reading after this amount.
|
||||
- If the image has complex information, like a workflow diagram, use a short alt text to identify the image and
|
||||
include detailed information in the text.
|
||||
- Use complete sentences.
|
||||
- Use punctuation.
|
||||
- Use sentence case and avoid using all-caps. Some screenreaders read capitals as individual letters.
|
||||
- Don't use phrases like **Image of** or **Graphic of**.
|
||||
- Don't use a string of keywords. Include keywords in a complete sentence to enhance context.
|
||||
- Introduce the image in the section text, not the alt text.
|
||||
- Try to avoid repeating content that you've already used in the section text.
|
||||
|
||||
#### Automatic screenshot generator
|
||||
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ Service classes represent operations that coordinates changes between models
|
|||
1. When there is no operation, there is no need to execute a service. The class would
|
||||
probably be better designed as an entity, a value object, or a policy.
|
||||
|
||||
When implementing a service class, consider:
|
||||
When implementing a service class, consider using the following patterns:
|
||||
|
||||
1. A service class initializer should contain in its arguments:
|
||||
1. A [model](#models) instance that is being acted upon. Should be first positional
|
||||
|
|
@ -187,9 +187,10 @@ When implementing a service class, consider:
|
|||
end
|
||||
```
|
||||
|
||||
1. It implements a single public instance method `#execute`, which invokes service class behavior:
|
||||
1. The service class should implements a single public instance method `#execute`, which invokes service class behavior:
|
||||
- The `#execute` method takes no arguments. All required data is passed into initializer.
|
||||
- Optional. If needed, the `#execute` method returns its result via [`ServiceResponse`](#serviceresponse).
|
||||
|
||||
1. If a return value is needed, the `#execute` method should returns its result via [`ServiceResponse`](#serviceresponse) object.
|
||||
|
||||
Several base classes implement the service classes convention. You may consider inheriting from:
|
||||
|
||||
|
|
@ -197,6 +198,18 @@ Several base classes implement the service classes convention. You may consider
|
|||
- `BaseProjectService` for services scoped to projects.
|
||||
- `BaseGroupService` for services scoped to groups.
|
||||
|
||||
For some domains or [bounded contexts](software_design.md#bounded-contexts), it may make sense for
|
||||
service classes to use different patterns. For example, the Remote Development domain uses a
|
||||
[layered architecture](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/remote_development/README.md#layered-architecture)
|
||||
with domain logic isolated to a separate domain layer following a standard pattern, which allows for a very
|
||||
[minimal service layer](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/remote_development/README.md#minimal-service-layer)
|
||||
which consists of only a single reusable `CommonService` class. It also uses
|
||||
[functional patterns with stateless singleton class methods](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/remote_development/README.md#functional-patterns).
|
||||
See the Remote Development [service layer code example](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/remote_development/README.md#service-layer-code-example) for more details.
|
||||
However, even though the invocation signature of services via this pattern is different,
|
||||
it still respects the standard Service Class contract of always returning all results via a
|
||||
[`ServiceResponse`](#serviceresponse) object.
|
||||
|
||||
Classes that are not service objects should be
|
||||
[created elsewhere](software_design.md#use-namespaces-to-define-bounded-contexts),
|
||||
such as in `lib`.
|
||||
|
|
|
|||
|
|
@ -68,6 +68,31 @@ curl --verbose --user "$USER:$API_TOKEN" "https://$ATLASSIAN_SUBDOMAIN.atlassian
|
|||
|
||||
If the user can access the issue, Jira responds with a `200 OK` and the returned JSON includes the Jira issue details.
|
||||
|
||||
### Verify GitLab can post a comment to a Jira issue
|
||||
|
||||
WARNING:
|
||||
Commands that change data can cause damage if not run correctly or under the right conditions. Always run commands in a test environment first and have a backup instance ready to restore.
|
||||
|
||||
To help troubleshoot your Jira integration, you can check whether
|
||||
GitLab can post a comment to a Jira issue using the project's Jira
|
||||
integration settings.
|
||||
|
||||
To do so:
|
||||
|
||||
- From a [Rails console](../../administration/operations/rails_console.md#starting-a-rails-console-session),
|
||||
run the following:
|
||||
|
||||
```ruby
|
||||
jira_issue_id = "ALPHA-1" # Change to your Jira issue ID
|
||||
project = Project.find_by_full_path("group/project") # Change to your project's path
|
||||
|
||||
integration = project.integrations.find_by(type: "Integrations::Jira")
|
||||
jira_issue = integration.client.Issue.find(jira_issue_id)
|
||||
jira_issue.comments.build.save!(body: 'This is a test comment from GitLab via the Rails console')
|
||||
```
|
||||
|
||||
If the command is successful, a comment is added to the Jira issue.
|
||||
|
||||
## GitLab cannot create a Jira issue
|
||||
|
||||
When you try to create a Jira issue from a vulnerability, you might see a "field is required" error. For example, `Components is required` because a field called
|
||||
|
|
|
|||
|
|
@ -356,7 +356,7 @@ If this is not the case, there are two options:
|
|||
|
||||
For a complete list of upgrade notices and special considerations for older versions, see the [Mattermost documentation](https://docs.mattermost.com/administration/important-upgrade-notes.html).
|
||||
|
||||
### GitLab Mattermost versions shipped with the Linux package
|
||||
### GitLab Mattermost versions and edition shipped with the Linux package
|
||||
|
||||
Below is a list of Mattermost version changes for GitLab 15.0 and later:
|
||||
|
||||
|
|
@ -389,6 +389,9 @@ Below is a list of Mattermost version changes for GitLab 15.0 and later:
|
|||
NOTE:
|
||||
The Mattermost upgrade notes refer to different impacts when used with a PostgreSQL versus a MySQL database. The GitLab Mattermost included with the Linux package uses a PostgreSQL database.
|
||||
|
||||
The Linux package bundles the [Mattermost Team Edition](https://docs.mattermost.com/about/editions-and-offerings.html#mattermost-team-edition), which is a free and open source edition and does not include its commercial features.
|
||||
To upgrade to the [Mattermost Enterprise Edition](https://docs.mattermost.com/about/editions-and-offerings.html#mattermost-enterprise-edition) see the Mattermost [documentation on upgrading](https://docs.mattermost.com/install/enterprise-install-upgrade.html#upgrading-to-enterprise-edition-in-gitlab-omnibus).
|
||||
|
||||
## OAuth 2.0 sequence diagram
|
||||
|
||||
The following image is a sequence diagram for how GitLab works as an OAuth 2.0
|
||||
|
|
|
|||
|
|
@ -25,10 +25,11 @@ which users can make changes to your packages.
|
|||
|
||||
When a package is protected, the default behavior enforces these restrictions on the package:
|
||||
|
||||
| Action | Who can do it |
|
||||
|:-------------------------|:----------------------------------------------------------------------------------|
|
||||
| Protect a package | At least the Maintainer role. |
|
||||
| Push a new package | At least the role set in [**Minimum access level for push**](#protect-a-package). |
|
||||
| Action | Who can do it |
|
||||
|:-----------------------------------------|:----------------------------------------------------------------------------------|
|
||||
| Protect a package | At least the Maintainer role. |
|
||||
| Push a new package | At least the role set in [**Minimum access level for push**](#protect-a-package). |
|
||||
| Push a new package with a deploy token | Any user with a valid deploy token, even if a package is protected. |
|
||||
|
||||
## Protect a package
|
||||
|
||||
|
|
|
|||
|
|
@ -148,6 +148,7 @@ DETAILS:
|
|||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/430728) in GitLab 16.11 [with a flag](../../../../administration/feature_flags.md) named `mr_reviewer_requests_changes`. Disabled by default.
|
||||
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/451211) in GitLab 17.2.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
# Functional Programming in Ruby
|
||||
|
||||
## Overview
|
||||
|
||||
The `Gitlab::Fp` module contains support for functional programming patterns ("FP") in Ruby.
|
||||
|
||||
It is in the process of being extracted from the `RemoteDevelopment` domain code,
|
||||
and made available for wider use in the monolith.
|
||||
|
||||
See the following sections in the [Remote Development Rails domain developer documentation](../../../ee/lib/remote_development/README.md) for more context:
|
||||
|
||||
- ["Functional Programming"](../../../ee/lib/remote_development/README.md#functional-patterns)
|
||||
- ["Railway Oriented Programming and the Result Class"](../../../ee/lib/remote_development/README.md#railway-oriented-programming-and-the-result-class)
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
# noinspection RubyClassModuleNamingConvention -- JetBrains is changing this to allow shorter names
|
||||
module Fp
|
||||
module RopHelpers
|
||||
# @param [Class] base
|
||||
# @return void
|
||||
def self.extended(base)
|
||||
base.class_eval do
|
||||
private_class_method :retrieve_single_public_singleton_method, :public_singleton_methods_to_ignore
|
||||
end
|
||||
end
|
||||
|
||||
# @param [Class] fp_module_or_class
|
||||
# @raise [RuntimeError]
|
||||
# @return [Symbol]
|
||||
def retrieve_single_public_singleton_method(fp_module_or_class)
|
||||
fp_class_singleton_methods = fp_module_or_class.singleton_methods(false)
|
||||
public_singleton_methods = fp_class_singleton_methods - public_singleton_methods_to_ignore
|
||||
|
||||
return public_singleton_methods.first if public_singleton_methods.size == 1
|
||||
|
||||
fp_doc_link =
|
||||
"https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/remote_development/README.md#functional-patterns"
|
||||
|
||||
rop_doc_link =
|
||||
"https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/remote_development/README.md#railway-oriented-programming-and-the-result-class"
|
||||
|
||||
if public_singleton_methods.size > 1
|
||||
err_msg =
|
||||
"Railway Oriented Programming (ROP) pattern violation in class `#{fp_module_or_class}`. " \
|
||||
"Expected exactly one (1) public entry point singleton/class method to be present " \
|
||||
"in a class which is used with the ROP pattern, but " \
|
||||
"#{public_singleton_methods.size} " \
|
||||
"public singleton methods were found: #{public_singleton_methods.sort.join(', ')}. " \
|
||||
"You can make the non-entry-point method(s) private via `private_class_method :method_name`. " \
|
||||
"See #{fp_doc_link} and #{rop_doc_link} for more information."
|
||||
raise(ArgumentError, err_msg)
|
||||
end
|
||||
|
||||
err_msg =
|
||||
"Railway Oriented Programming (ROP) pattern violation in class `#{fp_module_or_class}`. " \
|
||||
"Expected exactly one public entry point singleton/class method to be present " \
|
||||
"in a class which is used with the ROP pattern, but " \
|
||||
"no public singleton methods were found. " \
|
||||
"See #{fp_doc_link} and #{rop_doc_link} for more information."
|
||||
raise(ArgumentError, err_msg)
|
||||
end
|
||||
|
||||
# @return [Array<Symbol>]
|
||||
def public_singleton_methods_to_ignore
|
||||
# Singleton methods added by other libraries that we need to ignore.
|
||||
Module.singleton_methods(false) + Class.singleton_methods(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,6 +4,14 @@ require 'active_model/errors'
|
|||
|
||||
module RemoteDevelopment
|
||||
module MessageSupport
|
||||
# @param [Class] base
|
||||
# @return void
|
||||
def self.extended(base)
|
||||
base.class_eval do
|
||||
private_class_method :generate_error_response_from_message
|
||||
end
|
||||
end
|
||||
|
||||
# @param [RemoteDevelopment::Message] message
|
||||
# @param [Symbol] reason
|
||||
# @return [Hash]
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ module RemoteDevelopment
|
|||
include Messages
|
||||
extend MessageSupport
|
||||
|
||||
private_class_method :generate_error_response_from_message
|
||||
|
||||
# @param [Hash] context
|
||||
# @return [Hash]
|
||||
# @raise [UnmatchedResultError]
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'gitlab-qa', '~> 14', '>= 14.10.0', require: 'gitlab/qa'
|
||||
gem 'gitlab-qa', '~> 14', '>= 14.11.0', require: 'gitlab/qa'
|
||||
gem 'gitlab_quality-test_tooling', '~> 1.29.0', require: false
|
||||
gem 'gitlab-utils', path: '../gems/gitlab-utils'
|
||||
gem 'activesupport', '~> 7.0.8.4' # This should stay in sync with the root's Gemfile
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ GEM
|
|||
gitlab (4.19.0)
|
||||
httparty (~> 0.20)
|
||||
terminal-table (>= 1.5.1)
|
||||
gitlab-qa (14.10.0)
|
||||
gitlab-qa (14.11.0)
|
||||
activesupport (>= 6.1, < 7.2)
|
||||
gitlab (~> 4.19)
|
||||
http (~> 5.0)
|
||||
|
|
@ -392,7 +392,7 @@ DEPENDENCIES
|
|||
fog-core (= 2.1.0)
|
||||
fog-google (~> 1.19)
|
||||
gitlab-cng!
|
||||
gitlab-qa (~> 14, >= 14.10.0)
|
||||
gitlab-qa (~> 14, >= 14.11.0)
|
||||
gitlab-utils!
|
||||
gitlab_quality-test_tooling (~> 1.29.0)
|
||||
influxdb-client (~> 3.1)
|
||||
|
|
@ -418,4 +418,4 @@ DEPENDENCIES
|
|||
zeitwerk (~> 2.6, >= 2.6.15)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.13
|
||||
2.5.14
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ module QA
|
|||
class Index < QA::Page::Base
|
||||
view 'app/assets/javascripts/ci/artifacts/components/job_artifacts_table.vue' do
|
||||
element 'select-all-artifacts-checkbox'
|
||||
element 'job-artifact-table-row'
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/ci/artifacts/components/artifacts_bulk_delete.vue' do
|
||||
|
|
@ -17,11 +18,6 @@ module QA
|
|||
element 'artifacts-bulk-delete-modal'
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/ci/artifacts/components/job_artifacts_table.vue' do
|
||||
element 'job-artifacts-count'
|
||||
element 'job-artifacts-size'
|
||||
end
|
||||
|
||||
def select_all
|
||||
check_element('select-all-artifacts-checkbox', true)
|
||||
end
|
||||
|
|
@ -34,12 +30,8 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
def job_artifacts_count_by_row(row: 1)
|
||||
all_elements('job-artifacts-count', minimum: row)[row - 1].text.gsub(/[^0-9]/, '').to_i
|
||||
end
|
||||
|
||||
def job_artifacts_size_by_row(row: 1)
|
||||
all_elements('job-artifacts-size', minimum: row)[row - 1].text.gsub(/[^0-9]/, '').to_f
|
||||
def has_no_artifacts?
|
||||
has_no_element?('job-artifact-table-row')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,14 +31,8 @@ module QA
|
|||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/425725' do
|
||||
Page::Project::Artifacts::Index.perform do |index|
|
||||
index.delete_selected_artifacts
|
||||
position = rand(1..20)
|
||||
artifacts_count = index.job_artifacts_count_by_row(row: position)
|
||||
artifacts_size = index.job_artifacts_size_by_row(row: position)
|
||||
|
||||
aggregate_failures 'job artifacts count and size' do
|
||||
expect(artifacts_count).to eq(0), 'Failed to delete artifact'
|
||||
expect(artifacts_size).to eq(0), 'Failed to delete artifact'
|
||||
end
|
||||
expect(index).to have_no_artifacts
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -189,7 +189,15 @@ module QA
|
|||
element.to_s.underline.bright
|
||||
end
|
||||
|
||||
# Log message for has_element? and has_no_element? methods
|
||||
#
|
||||
# @param [String] method the method name
|
||||
# @param [Symbol, String, QA::Page::Element] name the name of the element
|
||||
# @param [Boolean] found the result of the method
|
||||
# @param [Hash] kwargs
|
||||
def log_has_element_or_not(method, name, found, **kwargs)
|
||||
name = name.name if name.is_a? QA::Page::Element
|
||||
|
||||
msg = ["#{method} :#{name}"]
|
||||
msg << %(with text "#{kwargs[:text]}") if kwargs[:text]
|
||||
msg << "class: #{kwargs[:class]}" if kwargs[:class]
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ function run_rubocop {
|
|||
while IFS= read -r -d '' file; do
|
||||
files_for_rubocop+=("$file")
|
||||
done < <(find . -path './**/remote_development/*.rb' -print0)
|
||||
files_for_rails+=(
|
||||
files_for_rubocop+=(
|
||||
"lib/gitlab/fp/rop_helpers.rb"
|
||||
"lib/result.rb"
|
||||
"spec/lib/result_spec.rb"
|
||||
"spec/support/matchers/invoke_rop_steps.rb"
|
||||
|
|
@ -49,7 +50,7 @@ function run_rspec_fast {
|
|||
files_for_fast=()
|
||||
while IFS= read -r file; do
|
||||
files_for_fast+=("$file")
|
||||
done < <(find ee/spec -path '**/remote_development/*_spec.rb' -exec grep -lE 'require_relative.*rd_fast_spec_helper' {} +)
|
||||
done < <(find spec ee/spec -path '**/remote_development/*_spec.rb' -exec grep -lE 'require_relative.*rd_fast_spec_helper' {} +)
|
||||
|
||||
bin/rspec "${files_for_fast[@]}"
|
||||
}
|
||||
|
|
@ -64,13 +65,14 @@ function run_rspec_rails {
|
|||
files_for_rails=()
|
||||
while IFS= read -r file; do
|
||||
files_for_rails+=("$file")
|
||||
done < <(find ee/spec -path '**/remote_development/*_spec.rb' | grep -v 'qa/qa' | grep -v '/features/')
|
||||
done < <(find spec ee/spec -path '**/remote_development/*_spec.rb' | grep -v 'qa/qa' | grep -v '/features/')
|
||||
|
||||
files_for_rails+=(
|
||||
"ee/spec/graphql/types/query_type_spec.rb"
|
||||
"ee/spec/graphql/types/subscription_type_spec.rb"
|
||||
"ee/spec/requests/api/internal/kubernetes_spec.rb"
|
||||
"spec/graphql/types/subscription_type_spec.rb"
|
||||
"spec/lib/gitlab/fp/rop_helpers_spec.rb"
|
||||
"spec/lib/result_spec.rb"
|
||||
"spec/support_specs/matchers/result_matchers_spec.rb"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -118,6 +118,24 @@ describe('JobArtifactsTable component', () => {
|
|||
};
|
||||
|
||||
const job = getJobArtifactsResponse.data.project.jobs.nodes[0];
|
||||
const emptyJob = {
|
||||
...job,
|
||||
artifacts: { nodes: [] },
|
||||
};
|
||||
|
||||
const getJobArtifactsResponseWithEmptyJob = {
|
||||
data: {
|
||||
...getJobArtifactsResponse.data,
|
||||
project: {
|
||||
...getJobArtifactsResponse.data.project,
|
||||
jobs: {
|
||||
nodes: [emptyJob],
|
||||
pageInfo: { ...getJobArtifactsResponse.data.project.jobs.pageInfo },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const archiveArtifact = job.artifacts.nodes.find(
|
||||
(artifact) => artifact.fileType === ARCHIVE_FILE_TYPE,
|
||||
);
|
||||
|
|
@ -810,6 +828,47 @@ describe('JobArtifactsTable component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('refetch behavior', () => {
|
||||
describe('without no empty jobs', () => {
|
||||
const query = jest.fn().mockResolvedValue(getJobArtifactsResponse);
|
||||
|
||||
beforeEach(async () => {
|
||||
createComponent({
|
||||
handlers: {
|
||||
getJobArtifactsQuery: query,
|
||||
},
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('only fetches artifacts once', () => {
|
||||
expect(query).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an empty job', () => {
|
||||
const query = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(getJobArtifactsResponseWithEmptyJob)
|
||||
.mockResolvedValue(getJobArtifactsResponse);
|
||||
|
||||
beforeEach(async () => {
|
||||
createComponent({
|
||||
handlers: {
|
||||
getJobArtifactsQuery: query,
|
||||
},
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('refetches to clear empty jobs', () => {
|
||||
expect(query).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('pagination', () => {
|
||||
const { pageInfo } = getJobArtifactsResponseThatPaginates.data.project.jobs;
|
||||
const query = jest.fn().mockResolvedValue(getJobArtifactsResponseThatPaginates);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
import getJobArtifactsResponse from 'test_fixtures/graphql/ci/artifacts/graphql/queries/get_job_artifacts.query.graphql.json';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
import { totalArtifactsSizeForJob } from '~/ci/artifacts/utils';
|
||||
import {
|
||||
totalArtifactsSizeForJob,
|
||||
mapArchivesToJobNodes,
|
||||
mapBooleansToJobNodes,
|
||||
} from '~/ci/artifacts/utils';
|
||||
|
||||
const job = getJobArtifactsResponse.data.project.jobs.nodes[0];
|
||||
const emptyJob = {
|
||||
...job,
|
||||
artifacts: { nodes: [] },
|
||||
};
|
||||
const artifacts = job.artifacts.nodes;
|
||||
|
||||
describe('totalArtifactsSizeForJob', () => {
|
||||
|
|
@ -14,3 +22,21 @@ describe('totalArtifactsSizeForJob', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapArchivesToJobNodes', () => {
|
||||
it('sets archive to the archive artifact for each job node', () => {
|
||||
expect([job, emptyJob].map(mapArchivesToJobNodes)).toMatchObject([
|
||||
{ archive: { name: 'ci_build_artifacts.zip' } },
|
||||
{ archive: {} },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapBooleansToJobNodes', () => {
|
||||
it('sets hasArtifacts and hasMetadata for each job node', () => {
|
||||
expect([job, emptyJob].map(mapBooleansToJobNodes)).toMatchObject([
|
||||
{ hasArtifacts: true, hasMetadata: true },
|
||||
{ hasArtifacts: false, hasMetadata: false },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -365,6 +365,7 @@ describe('Pipeline schedules app', () => {
|
|||
last: null,
|
||||
nextPageCursor: '',
|
||||
prevPageCursor: '',
|
||||
sortValue: 'ID_DESC',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -426,6 +427,7 @@ describe('Pipeline schedules app', () => {
|
|||
last: null,
|
||||
nextPageCursor: '',
|
||||
prevPageCursor: '',
|
||||
sortValue: 'ID_DESC',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -439,6 +441,7 @@ describe('Pipeline schedules app', () => {
|
|||
last: null,
|
||||
prevPageCursor: '',
|
||||
nextPageCursor: pageInfo.endCursor,
|
||||
sortValue: 'ID_DESC',
|
||||
});
|
||||
expect(findPagination().props('value')).toEqual(2);
|
||||
});
|
||||
|
|
@ -456,6 +459,50 @@ describe('Pipeline schedules app', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when sorting changes', () => {
|
||||
const newSort = 'DESCRIPTION_ASC';
|
||||
|
||||
beforeEach(async () => {
|
||||
createComponent([[getPipelineSchedulesQuery, successHandler]]);
|
||||
|
||||
await waitForPromises();
|
||||
await findTable().vm.$emit('update-sorting', newSort, 'description', false);
|
||||
});
|
||||
|
||||
it('passes it to the graphql query', () => {
|
||||
expect(successHandler).toHaveBeenCalledTimes(2);
|
||||
expect(successHandler.mock.calls[1][0]).toEqual({
|
||||
projectPath: 'gitlab-org/gitlab',
|
||||
ids: null,
|
||||
first: SCHEDULES_PER_PAGE,
|
||||
last: null,
|
||||
nextPageCursor: '',
|
||||
prevPageCursor: '',
|
||||
sortValue: newSort,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when update-sorting event is emitted', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent([[getPipelineSchedulesQuery, successHandlerWithPagination]]);
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('resets the page count', async () => {
|
||||
expect(findPagination().props('value')).toEqual(1);
|
||||
|
||||
await setPage(2);
|
||||
|
||||
expect(findPagination().props('value')).toEqual(2);
|
||||
|
||||
await findTable().vm.$emit('update-sorting', 'DESCRIPTION_DESC', 'description', true);
|
||||
await waitForPromises();
|
||||
|
||||
expect(findPagination().props('value')).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
it.each`
|
||||
description | handler | buttonDisabled | alertExists
|
||||
${'limit reached'} | ${planLimitReachedHandler} | ${true} | ${true}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
import { GlTable } from '@gitlab/ui';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import PipelineSchedulesTable from '~/ci/pipeline_schedules/components/table/pipeline_schedules_table.vue';
|
||||
import {
|
||||
TH_DESCRIPTION_TEST_ID,
|
||||
TH_TARGET_TEST_ID,
|
||||
TH_NEXT_TEST_ID,
|
||||
} from '~/ci/pipeline_schedules/constants';
|
||||
import { mockPipelineScheduleNodes, mockPipelineScheduleCurrentUser } from '../mock_data';
|
||||
|
||||
describe('Pipeline schedules table', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = mountExtended(PipelineSchedulesTable, {
|
||||
propsData: {
|
||||
schedules: mockPipelineScheduleNodes,
|
||||
currentUser: mockPipelineScheduleCurrentUser,
|
||||
sortBy: 'ID',
|
||||
sortDesc: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findTable = () => wrapper.findComponent(GlTable);
|
||||
|
||||
describe('sorting', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it.each`
|
||||
sortValue | sortBy | sortDesc
|
||||
${'DESCRIPTION_ASC'} | ${'description'} | ${false}
|
||||
${'DESCRIPTION_DESC'} | ${'description'} | ${true}
|
||||
${'REF_ASC'} | ${'target'} | ${false}
|
||||
${'REF_DESC'} | ${'target'} | ${true}
|
||||
${'NEXT_RUN_AT_ASC'} | ${'next'} | ${false}
|
||||
${'NEXT_RUN_AT_DESC'} | ${'next'} | ${true}
|
||||
`(
|
||||
'emits sort data in expected format for sortValue $sortValue',
|
||||
({ sortValue, sortBy, sortDesc }) => {
|
||||
findTable().vm.$emit('sort-changed', { sortBy, sortDesc });
|
||||
|
||||
expect(wrapper.emitted('update-sorting')[0]).toEqual([sortValue, sortBy, sortDesc]);
|
||||
},
|
||||
);
|
||||
|
||||
it('emits no update-sorting event when called with unsortable column', () => {
|
||||
findTable().vm.$emit('sort-changed', { sortBy: 'actions', sortDesc: false });
|
||||
|
||||
expect(wrapper.emitted('update-sorting')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('emits no update-sorting event when called with unknown column', () => {
|
||||
findTable().vm.$emit('sort-changed', { sortBy: 'not-defined-never', sortDesc: false });
|
||||
|
||||
expect(wrapper.emitted('update-sorting')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('sorting the pipeline schedules table by column', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it.each`
|
||||
description | selector
|
||||
${'description'} | ${TH_DESCRIPTION_TEST_ID}
|
||||
${'target'} | ${TH_TARGET_TEST_ID}
|
||||
${'next'} | ${TH_NEXT_TEST_ID}
|
||||
`('updates sort with new direction when sorting by $description', async ({ selector }) => {
|
||||
const [[attr, value]] = Object.entries(selector);
|
||||
const columnHeader = () => wrapper.find(`[${attr}="${value}"]`);
|
||||
expect(columnHeader().attributes('aria-sort')).toBe('none');
|
||||
columnHeader().trigger('click');
|
||||
await waitForPromises();
|
||||
expect(columnHeader().attributes('aria-sort')).toBe('ascending');
|
||||
columnHeader().trigger('click');
|
||||
await waitForPromises();
|
||||
expect(columnHeader().attributes('aria-sort')).toBe('descending');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Fp::RopHelpers, feature_category: :shared do
|
||||
describe '.retrieve_single_public_singleton_method' do
|
||||
let(:extending_class) do
|
||||
Class.new do
|
||||
extend Gitlab::Fp::RopHelpers
|
||||
|
||||
def self.execute(class_object)
|
||||
retrieve_single_public_singleton_method(class_object)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:doc_link) do
|
||||
"https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/remote_development/README.md#functional-patterns " \
|
||||
"and https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/remote_development/README.md#railway-oriented-programming-and-the-result-class"
|
||||
end
|
||||
|
||||
before do
|
||||
stub_const(class_or_module_name, class_or_module)
|
||||
end
|
||||
|
||||
context "when there is exactly one public singleton method" do
|
||||
shared_examples "a class or module with a single public singleton method" do
|
||||
it "returns the single public singleton method", :unlimited_max_formatted_output_length do
|
||||
expect(extending_class.execute(class_or_module)).to eq(:public_method_one)
|
||||
end
|
||||
end
|
||||
|
||||
let(:class_or_module_name) { 'ClassWithOnePublicSingletonMethod' }
|
||||
|
||||
let(:expected_error_message_pattern) do
|
||||
/violation.*`#{class_or_module_name}`.*2.*found.*public_method_one, public_method_two.*private.*#{doc_link}/
|
||||
end
|
||||
|
||||
context "for a class" do
|
||||
let(:class_or_module) do
|
||||
Class.new do
|
||||
def self.public_method_one
|
||||
puts 'no-op'
|
||||
end
|
||||
|
||||
def self.private_method
|
||||
puts 'no-op'
|
||||
end
|
||||
|
||||
private_class_method :private_method
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like "a class or module with a single public singleton method"
|
||||
end
|
||||
|
||||
context "for a module" do
|
||||
let(:class_or_module) do
|
||||
Module.new do
|
||||
def self.public_method_one
|
||||
puts 'no-op'
|
||||
end
|
||||
|
||||
def self.private_method
|
||||
puts 'no-op'
|
||||
end
|
||||
|
||||
private_class_method :private_method
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like "a class or module with a single public singleton method"
|
||||
end
|
||||
end
|
||||
|
||||
context "for invalid arguments" do
|
||||
shared_examples "a class or module without a single public singleton method" do
|
||||
it "raises an error", :unlimited_max_formatted_output_length do
|
||||
expect { extending_class.execute(class_or_module) }
|
||||
.to raise_error(ArgumentError, expected_error_message_pattern)
|
||||
end
|
||||
end
|
||||
|
||||
context "when there is more than one public singleton method" do
|
||||
let(:class_or_module_name) { 'ClassWithMultiplePublicSingletonMethods' }
|
||||
|
||||
let(:expected_error_message_pattern) do
|
||||
/violation.*`#{class_or_module_name}`.*2.*found.*public_method_one, public_method_two.*private.*#{doc_link}/
|
||||
end
|
||||
|
||||
context "for a class" do
|
||||
let(:class_or_module) do
|
||||
Class.new do
|
||||
def self.public_method_one
|
||||
puts 'no-op'
|
||||
end
|
||||
|
||||
def self.public_method_two
|
||||
puts 'no-op'
|
||||
end
|
||||
|
||||
def self.private_method
|
||||
puts 'no-op'
|
||||
end
|
||||
|
||||
private_class_method :private_method
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like "a class or module without a single public singleton method"
|
||||
end
|
||||
|
||||
context "for a module" do
|
||||
let(:class_or_module) do
|
||||
Module.new do
|
||||
def self.public_method_one
|
||||
puts 'no-op'
|
||||
end
|
||||
|
||||
def self.public_method_two
|
||||
puts 'no-op'
|
||||
end
|
||||
|
||||
def self.private_method
|
||||
puts 'no-op'
|
||||
end
|
||||
|
||||
private_class_method :private_method
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like "a class or module without a single public singleton method"
|
||||
end
|
||||
end
|
||||
|
||||
context "when there are no public singleton methods" do
|
||||
let(:class_or_module_name) { 'ClassWithNoPublicSingletonMethods' }
|
||||
|
||||
let(:expected_error_message_pattern) do
|
||||
/violation.*`#{class_or_module_name}`.*no public singleton methods were found.*#{doc_link}/
|
||||
end
|
||||
|
||||
context "for a class" do
|
||||
let(:class_or_module) do
|
||||
Class.new do
|
||||
def self.private_method
|
||||
puts 'no-op'
|
||||
end
|
||||
|
||||
private_class_method :private_method
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like "a class or module without a single public singleton method"
|
||||
end
|
||||
|
||||
context "for a module" do
|
||||
let(:class_or_module) do
|
||||
Module.new do
|
||||
def self.private_method
|
||||
puts 'no-op'
|
||||
end
|
||||
|
||||
private_class_method :private_method
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like "a class or module without a single public singleton method"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -3,6 +3,18 @@
|
|||
require_relative 'rd_fast_spec_helper'
|
||||
|
||||
RSpec.describe RemoteDevelopment::MessageSupport, :rd_fast, feature_category: :remote_development do
|
||||
let(:extending_class) do
|
||||
Class.new do
|
||||
extend RemoteDevelopment::MessageSupport
|
||||
|
||||
# @param [RemoteDevelopment::Message] message
|
||||
# @return [Hash]
|
||||
def self.execute(message)
|
||||
generate_error_response_from_message(message: message, reason: :does_not_matter)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:object) { Object.new.extend(described_class) }
|
||||
|
||||
describe '.generate_error_response_from_message' do
|
||||
|
|
@ -10,7 +22,7 @@ RSpec.describe RemoteDevelopment::MessageSupport, :rd_fast, feature_category: :r
|
|||
let(:message) { RemoteDevelopment::Message.new(content: { unsupported: 'unmatched' }) }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { object.generate_error_response_from_message(message: message, reason: :does_not_matter) }
|
||||
expect { extending_class.execute(message) }
|
||||
.to raise_error(/Unexpected message content/)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ require 'fast_spec_helper'
|
|||
#
|
||||
# To support this, we have intentionally used some `rubocop:disable` comments to allow for more
|
||||
# explicit and readable examples.
|
||||
# rubocop:disable RSpec/DescribedClass, Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
|
||||
# rubocop:disable RSpec/DescribedClass, Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration -- intentionally disabled per comment above
|
||||
RSpec.describe Result, feature_category: :remote_development do
|
||||
describe 'usage of Result.ok and Result.err' do
|
||||
context 'when checked with .ok? and .err?' do
|
||||
|
|
|
|||
|
|
@ -429,6 +429,28 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
|
|||
|
||||
it_behaves_like params[:shared_examples_name]
|
||||
end
|
||||
|
||||
context 'with deploy token' do
|
||||
let_it_be(:deploy_token) { create(:deploy_token, projects: [project]) }
|
||||
let_it_be(:current_user) { deploy_token }
|
||||
let_it_be(:user) { nil }
|
||||
|
||||
where(:package_name_pattern, :minimum_access_level_for_push, :shared_examples_name) do
|
||||
ref(:package_name) | :maintainer | 'valid package'
|
||||
ref(:package_name) | :owner | 'valid package'
|
||||
ref(:package_name) | :admin | 'valid package'
|
||||
ref(:package_name_pattern_no_match) | :owner | 'valid package'
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
package_protection_rule.update!(package_name_pattern: package_name_pattern,
|
||||
minimum_access_level_for_push: minimum_access_level_for_push)
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#lease_key' do
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../../lib/gitlab/fp/rop_helpers'
|
||||
|
||||
module InvokeRopSteps
|
||||
private
|
||||
|
||||
def public_methods_to_ignore
|
||||
# Singleton methods to exist on class objects by default that we need to ignore.
|
||||
[:yaml_tag, :method]
|
||||
end
|
||||
include Gitlab::Fp::RopHelpers
|
||||
|
||||
def add_err_result_for_step(err_result_for_step, err_results_for_steps)
|
||||
result_type = :err
|
||||
|
|
@ -117,7 +116,7 @@ module InvokeRopSteps
|
|||
step_action = rop_step[1]
|
||||
expected_rop_step = {
|
||||
step_class: step_class,
|
||||
step_class_method: retrieve_rop_class_method(step_class),
|
||||
step_class_method: retrieve_single_public_singleton_method(step_class),
|
||||
step_action: step_action
|
||||
}
|
||||
|
||||
|
|
@ -144,16 +143,6 @@ module InvokeRopSteps
|
|||
expected_rop_steps
|
||||
end
|
||||
|
||||
def retrieve_rop_class_method(step_class)
|
||||
public_methods = step_class.singleton_methods(false).reject { |method| public_methods_to_ignore.include?(method) }
|
||||
|
||||
if public_methods.size != 1
|
||||
raise "Pattern violation in class #{step_class}: exactly one public method must be present in an ROP class"
|
||||
end
|
||||
|
||||
public_methods.first
|
||||
end
|
||||
|
||||
def setup_mock_expectations_for_steps(steps:, context_passed_along_steps:)
|
||||
steps.each do |step|
|
||||
step => {
|
||||
|
|
@ -218,9 +207,9 @@ RSpec::Matchers.define :invoke_rop_steps do |rop_steps|
|
|||
end
|
||||
|
||||
chain :from_main_class do |clazz|
|
||||
validate_main_class(clazz)
|
||||
main_class = clazz
|
||||
main_class_method = retrieve_rop_class_method(main_class)
|
||||
validate_main_class(main_class)
|
||||
main_class_method = retrieve_single_public_singleton_method(main_class)
|
||||
expect(main_class).to receive(main_class_method).and_call_original
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue