Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b9b58dba70
commit
8060e5c609
|
|
@ -236,6 +236,21 @@ db:check-migrations-single-db:
|
|||
- .single-db
|
||||
- .rails:rules:single-db
|
||||
|
||||
db:post_deployment_migrations_validator:
|
||||
extends:
|
||||
- .db-job-base
|
||||
- .rails:rules:ee-and-foss-mr-with-migration
|
||||
script:
|
||||
- git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME:$CI_MERGE_REQUEST_TARGET_BRANCH_NAME --depth 20
|
||||
- scripts/post_deployment_migrations_validator
|
||||
allow_failure: true
|
||||
|
||||
db:post_deployment_migrations_validator-single-db:
|
||||
extends:
|
||||
- db:post_deployment_migrations_validator
|
||||
- .single-db
|
||||
- .rails:rules:single-db
|
||||
|
||||
db:migrate-non-superuser:
|
||||
extends:
|
||||
- .db-job-base
|
||||
|
|
|
|||
4
Gemfile
4
Gemfile
|
|
@ -241,7 +241,7 @@ gem 'ruby-progressbar', '~> 1.10'
|
|||
gem 'settingslogic', '~> 2.0.9'
|
||||
|
||||
# Linear-time regex library for untrusted regular expressions
|
||||
gem 're2', '~> 1.4.0'
|
||||
gem 're2', '~> 1.5.0'
|
||||
|
||||
# Misc
|
||||
|
||||
|
|
@ -365,7 +365,7 @@ gem 'prometheus-client-mmap', '~> 0.16', require: 'prometheus/client'
|
|||
gem 'warning', '~> 1.3.0'
|
||||
|
||||
group :development do
|
||||
gem 'lefthook', '~> 1.1.2', require: false
|
||||
gem 'lefthook', '~> 1.1.3', require: false
|
||||
gem 'rubocop'
|
||||
gem 'solargraph', '~> 0.47.2', require: false
|
||||
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@
|
|||
{"name":"kramdown-parser-gfm","version":"1.1.0","platform":"ruby","checksum":"fb39745516427d2988543bf01fc4cf0ab1149476382393e0e9c48592f6581729"},
|
||||
{"name":"kubeclient","version":"4.9.3","platform":"ruby","checksum":"d5d38e719fbac44f396851aa57cd1b9f4f7dab4410ab680ccd21c9b741230046"},
|
||||
{"name":"launchy","version":"2.5.0","platform":"ruby","checksum":"954243c4255920982ce682f89a42e76372dba94770bf09c23a523e204bdebef5"},
|
||||
{"name":"lefthook","version":"1.1.2","platform":"ruby","checksum":"fdbe2a62faf6ae2a9f9be64e105ca8f06c527c5ba9a3edb2cdcf024025227897"},
|
||||
{"name":"lefthook","version":"1.1.3","platform":"ruby","checksum":"3f8337b2176f49e6d4ab8f0f4494c8d1be0548d79bca898fbf2184d717092b75"},
|
||||
{"name":"letter_opener","version":"1.7.0","platform":"ruby","checksum":"095bc0d58e006e5b43ea7d219e64ecf2de8d1f7d9dafc432040a845cf59b4725"},
|
||||
{"name":"letter_opener_web","version":"2.0.0","platform":"ruby","checksum":"33860ad41e1785d75456500e8ca8bba8ed71ee6eaf08a98d06bbab67c5577b6f"},
|
||||
{"name":"libyajl2","version":"1.2.0","platform":"ruby","checksum":"1117cd1e48db013b626e36269bbf1cef210538ca6d2e62d3fa3db9ded005b258"},
|
||||
|
|
@ -455,7 +455,7 @@
|
|||
{"name":"rbtree","version":"0.4.4","platform":"ruby","checksum":"c1277a502a96fe8fd8656cb619db1ac87145df809ea4db35f7242b50bb161d5c"},
|
||||
{"name":"rchardet","version":"1.8.0","platform":"ruby","checksum":"693acd5253d5ade81a51940697955f6dd4bb2f0d245bda76a8e23deec70a52c7"},
|
||||
{"name":"rdoc","version":"6.3.2","platform":"ruby","checksum":"def4a720235c27d56c176ae73555e647eb04ea58a8bbaa927f8f9f79de7805a6"},
|
||||
{"name":"re2","version":"1.4.0","platform":"ruby","checksum":"5c07d2351be1159530e2b815aae499b6524942e79bf21d560fcff5b2fa20ea8f"},
|
||||
{"name":"re2","version":"1.5.0","platform":"ruby","checksum":"35fe8b408de9f1ef609b1e54e01ea1e55413ca3e9daf1e4b20756d9a02f630cc"},
|
||||
{"name":"recaptcha","version":"4.13.1","platform":"ruby","checksum":"dc6c2cb78afa87034358b7ba1c6f7175972b5709fdf7500e2550623e119e3788"},
|
||||
{"name":"recursive-open-struct","version":"1.1.3","platform":"ruby","checksum":"a3538a72552fcebcd0ada657bdff313641a4a5fbc482c08cfb9a65acb1c9de5a"},
|
||||
{"name":"redcarpet","version":"3.5.1","platform":"ruby","checksum":"717f64cb6ec11c8d9ec9b521ed26ca2eeda68b4fe1fc3388a641176dbd47732f"},
|
||||
|
|
|
|||
10
Gemfile.lock
10
Gemfile.lock
|
|
@ -801,7 +801,7 @@ GEM
|
|||
rest-client (~> 2.0)
|
||||
launchy (2.5.0)
|
||||
addressable (~> 2.7)
|
||||
lefthook (1.1.2)
|
||||
lefthook (1.1.3)
|
||||
letter_opener (1.7.0)
|
||||
launchy (~> 2.2)
|
||||
letter_opener_web (2.0.0)
|
||||
|
|
@ -1136,7 +1136,7 @@ GEM
|
|||
rbtree (0.4.4)
|
||||
rchardet (1.8.0)
|
||||
rdoc (6.3.2)
|
||||
re2 (1.4.0)
|
||||
re2 (1.5.0)
|
||||
recaptcha (4.13.1)
|
||||
json
|
||||
recursive-open-struct (1.1.3)
|
||||
|
|
@ -1680,7 +1680,7 @@ DEPENDENCIES
|
|||
knapsack (~> 1.21.1)
|
||||
kramdown (~> 2.3.1)
|
||||
kubeclient (~> 4.9.3)
|
||||
lefthook (~> 1.1.2)
|
||||
lefthook (~> 1.1.3)
|
||||
letter_opener_web (~> 2.0.0)
|
||||
license_finder (~> 7.0)
|
||||
licensee (~> 9.15)
|
||||
|
|
@ -1751,7 +1751,7 @@ DEPENDENCIES
|
|||
rainbow (~> 3.0)
|
||||
rbtrace (~> 0.4)
|
||||
rdoc (~> 6.3.2)
|
||||
re2 (~> 1.4.0)
|
||||
re2 (~> 1.5.0)
|
||||
recaptcha (~> 4.11)
|
||||
redis (~> 4.7.0)
|
||||
redis-actionpack (~> 5.3.0)
|
||||
|
|
@ -1834,4 +1834,4 @@ DEPENDENCIES
|
|||
yajl-ruby (~> 1.4.3)
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.22
|
||||
2.3.23
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ export default {
|
|||
/** @type {HTMLFormElement} */
|
||||
this.form = document.querySelector(FORM_SELECTOR);
|
||||
|
||||
/** @type {HTMLInputElement} */
|
||||
this.submitButton = this.form.querySelector('input[type=submit]');
|
||||
/** @type {HTMLButtonElement} */
|
||||
this.submitButton = this.form.querySelector('[type=submit]');
|
||||
},
|
||||
methods: {
|
||||
beforeDisplayResults() {
|
||||
|
|
@ -75,6 +75,7 @@ export default {
|
|||
this.errors = errors;
|
||||
|
||||
this.submitButton.classList.remove('disabled');
|
||||
this.submitButton.removeAttribute('disabled');
|
||||
},
|
||||
onSuccess(event) {
|
||||
this.beforeDisplayResults();
|
||||
|
|
@ -84,7 +85,7 @@ export default {
|
|||
|
||||
this.infoAlert = createAlert({ message: this.alertInfoMessage, variant: VARIANT_INFO });
|
||||
|
||||
// Selectively reset all input fields except for the date picker and submit.
|
||||
// Selectively reset all input fields except for the date picker.
|
||||
// The form token creation is not controlled by Vue.
|
||||
this.form.querySelectorAll('input[type=text]:not([id$=expires_at])').forEach((el) => {
|
||||
el.value = '';
|
||||
|
|
|
|||
|
|
@ -149,11 +149,6 @@ export default {
|
|||
isSearchFiltered() {
|
||||
return isSearchFiltered(this.search);
|
||||
},
|
||||
shouldRenderAllAvailableToggle() {
|
||||
// Feature flag for `runners_finder_all_available`
|
||||
// See: https://gitlab.com/gitlab-org/gitlab/-/issues/374525
|
||||
return this.glFeatures?.runnersFinderAllAvailable;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
search: {
|
||||
|
|
@ -235,7 +230,6 @@ export default {
|
|||
class="gl-flex-grow-1 gl-align-self-stretch"
|
||||
/>
|
||||
<runner-membership-toggle
|
||||
v-if="shouldRenderAllAvailableToggle"
|
||||
v-model="search.membership"
|
||||
class="gl-align-self-end gl-md-align-self-center"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import 'deckar01-task_list';
|
||||
import { __ } from '~/locale';
|
||||
import createFlash from './flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
|
||||
export default class TaskList {
|
||||
|
|
@ -23,7 +23,7 @@ export default class TaskList {
|
|||
errorMessages = e.response.data.errors.join(' ');
|
||||
}
|
||||
|
||||
return createFlash({
|
||||
return createAlert({
|
||||
message: errorMessages || __('Update failed'),
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
GlSprintf,
|
||||
GlToggle,
|
||||
} from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { __, s__ } from '~/locale';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import addProjectCIJobTokenScopeMutation from '../graphql/mutations/add_project_ci_job_token_scope.mutation.graphql';
|
||||
|
|
@ -63,7 +63,7 @@ export default {
|
|||
return data.project.ciCdSettings.jobTokenScopeEnabled;
|
||||
},
|
||||
error() {
|
||||
createFlash({ message: this.$options.i18n.scopeFetchError });
|
||||
createAlert({ message: this.$options.i18n.scopeFetchError });
|
||||
},
|
||||
},
|
||||
projects: {
|
||||
|
|
@ -77,7 +77,7 @@ export default {
|
|||
return data.project?.ciJobTokenScope?.projects?.nodes ?? [];
|
||||
},
|
||||
error() {
|
||||
createFlash({ message: this.$options.i18n.projectsFetchError });
|
||||
createAlert({ message: this.$options.i18n.projectsFetchError });
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -117,7 +117,7 @@ export default {
|
|||
throw new Error(errors[0]);
|
||||
}
|
||||
} catch (error) {
|
||||
createFlash({ message: error });
|
||||
createAlert({ message: error });
|
||||
}
|
||||
},
|
||||
async addProject() {
|
||||
|
|
@ -140,7 +140,7 @@ export default {
|
|||
throw new Error(errors[0]);
|
||||
}
|
||||
} catch (error) {
|
||||
createFlash({ message: error });
|
||||
createAlert({ message: error });
|
||||
} finally {
|
||||
this.clearTargetProjectPath();
|
||||
this.getProjects();
|
||||
|
|
@ -166,7 +166,7 @@ export default {
|
|||
throw new Error(errors[0]);
|
||||
}
|
||||
} catch (error) {
|
||||
createFlash({ message: error });
|
||||
createAlert({ message: error });
|
||||
} finally {
|
||||
this.getProjects();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { GlButton, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { s__, __ } from '~/locale';
|
||||
|
|
@ -139,7 +139,7 @@ export default {
|
|||
this.fetchingApprovals = false;
|
||||
})
|
||||
.catch(() =>
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: FETCH_ERROR,
|
||||
}),
|
||||
);
|
||||
|
|
@ -154,7 +154,7 @@ export default {
|
|||
this.updateApproval(
|
||||
() => this.service.approveMergeRequest(),
|
||||
() =>
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: APPROVE_ERROR,
|
||||
}),
|
||||
);
|
||||
|
|
@ -167,7 +167,7 @@ export default {
|
|||
this.hasApprovalAuthError = true;
|
||||
return;
|
||||
}
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: APPROVE_ERROR,
|
||||
});
|
||||
},
|
||||
|
|
@ -177,7 +177,7 @@ export default {
|
|||
this.updateApproval(
|
||||
() => this.service.unapproveMergeRequest(),
|
||||
() =>
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: UNAPPROVE_ERROR,
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
import { __, s__ } from '~/locale';
|
||||
|
|
@ -130,7 +130,7 @@ export default {
|
|||
}
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: errorMessage,
|
||||
});
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { GlSkeletonLoader, GlSprintf } from '@gitlab/ui';
|
||||
import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge';
|
||||
import autoMergeEnabledQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { AUTO_MERGE_STRATEGIES } from '../../constants';
|
||||
|
|
@ -113,7 +113,7 @@ export default {
|
|||
})
|
||||
.catch(() => {
|
||||
this.isCancellingAutoMerge = false;
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Something went wrong. Please try again.'),
|
||||
});
|
||||
});
|
||||
|
|
@ -141,7 +141,7 @@ export default {
|
|||
})
|
||||
.catch(() => {
|
||||
this.isRemovingSourceBranch = false;
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Something went wrong. Please try again.'),
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import api from '~/api';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { s__, __ } from '~/locale';
|
||||
import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/constants';
|
||||
import modalEventHub from '~/projects/commit/event_hub';
|
||||
|
|
@ -131,7 +131,7 @@ export default {
|
|||
})
|
||||
.catch(() => {
|
||||
this.isMakingRequest = false;
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Something went wrong. Please try again.'),
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { GlButton, GlSkeletonLoader } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import toast from '~/vue_shared/plugins/global_toast';
|
||||
|
|
@ -111,7 +111,7 @@ export default {
|
|||
if (error.response && error.response.data && error.response.data.merge_error) {
|
||||
this.rebasingError = error.response.data.merge_error;
|
||||
} else {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Something went wrong. Please try again.'),
|
||||
});
|
||||
}
|
||||
|
|
@ -142,7 +142,7 @@ export default {
|
|||
})
|
||||
.catch(() => {
|
||||
this.isMakingRequest = false;
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Something went wrong. Please try again.'),
|
||||
});
|
||||
stopPolling();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
import { isEmpty } from 'lodash';
|
||||
import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge';
|
||||
import readyToMergeQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { secondsToMilliseconds } from '~/lib/utils/datetime_utility';
|
||||
import simplePoll from '~/lib/utils/simple_poll';
|
||||
import { __, s__, n__ } from '~/locale';
|
||||
|
|
@ -444,7 +444,7 @@ export default {
|
|||
.catch(() => {
|
||||
this.isMakingRequest = false;
|
||||
this.mr.transitionStateMachine({ transition: MERGE_FAILURE });
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Something went wrong. Please try again.'),
|
||||
});
|
||||
});
|
||||
|
|
@ -488,7 +488,7 @@ export default {
|
|||
}
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Something went wrong while deleting the source branch. Please try again.'),
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { GlButton } from '@gitlab/ui';
|
||||
import { produce } from 'immer';
|
||||
import $ from 'jquery';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import toast from '~/vue_shared/plugins/global_toast';
|
||||
import { __ } from '~/locale';
|
||||
import MergeRequest from '~/merge_request';
|
||||
|
|
@ -77,7 +77,7 @@ export default {
|
|||
},
|
||||
) {
|
||||
if (errors?.length) {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Something went wrong. Please try again.'),
|
||||
});
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ export default {
|
|||
},
|
||||
)
|
||||
.catch(() =>
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Something went wrong. Please try again.'),
|
||||
}),
|
||||
)
|
||||
|
|
@ -152,7 +152,7 @@ export default {
|
|||
})
|
||||
.catch(() => {
|
||||
this.isMakingRequest = false;
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Something went wrong. Please try again.'),
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import MrWidgetApprovals from 'ee_else_ce/vue_merge_request_widget/components/ap
|
|||
import MRWidgetService from 'ee_else_ce/vue_merge_request_widget/services/mr_widget_service';
|
||||
import MRWidgetStore from 'ee_else_ce/vue_merge_request_widget/stores/mr_widget_store';
|
||||
import { stateToComponentMap as classState } from 'ee_else_ce/vue_merge_request_widget/stores/state_maps';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { secondsToMilliseconds } from '~/lib/utils/datetime_utility';
|
||||
import notify from '~/lib/utils/notify';
|
||||
import { sprintf, s__, __ } from '~/locale';
|
||||
|
|
@ -267,7 +267,7 @@ export default {
|
|||
this.initWidget(data);
|
||||
})
|
||||
.catch(() =>
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Unable to load the merge request widget. Try reloading the page.'),
|
||||
}),
|
||||
);
|
||||
|
|
@ -359,7 +359,7 @@ export default {
|
|||
}
|
||||
})
|
||||
.catch(() =>
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Something went wrong. Please try again.'),
|
||||
}),
|
||||
);
|
||||
|
|
@ -418,7 +418,7 @@ export default {
|
|||
.catch(() => this.throwDeploymentsError());
|
||||
},
|
||||
throwDeploymentsError() {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __(
|
||||
'Something went wrong while fetching the environments for this merge request. Please try again.',
|
||||
),
|
||||
|
|
@ -438,7 +438,7 @@ export default {
|
|||
}
|
||||
})
|
||||
.catch(() =>
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Something went wrong. Please try again.'),
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,11 +22,10 @@ module AccessTokensActions
|
|||
|
||||
if token_response.success?
|
||||
@resource_access_token = token_response.payload[:access_token]
|
||||
PersonalAccessToken.redis_store!(key_identity, @resource_access_token.token)
|
||||
|
||||
redirect_to resource_access_tokens_path, notice: _("Your new access token has been created.")
|
||||
render json: { new_token: @resource_access_token.token,
|
||||
active_access_tokens: active_resource_access_tokens }, status: :ok
|
||||
else
|
||||
redirect_to resource_access_tokens_path, alert: _("Failed to create new access token: %{token_response_message}") % { token_response_message: token_response.message }
|
||||
render json: { errors: token_response.errors }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
|
|
@ -63,12 +62,15 @@ module AccessTokensActions
|
|||
resource.members.load
|
||||
|
||||
@scopes = Gitlab::Auth.resource_bot_scopes
|
||||
@active_resource_access_tokens = finder(state: 'active').execute.preload_users
|
||||
@inactive_resource_access_tokens = finder(state: 'inactive', sort: 'expires_at_asc').execute.preload_users
|
||||
@new_resource_access_token = PersonalAccessToken.redis_getdel(key_identity)
|
||||
@active_resource_access_tokens = active_resource_access_tokens
|
||||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
def active_resource_access_tokens
|
||||
tokens = finder(state: 'active', sort: 'expires_at_asc_id_desc').execute.preload_users
|
||||
represent(tokens)
|
||||
end
|
||||
|
||||
def finder(options = {})
|
||||
PersonalAccessTokensFinder.new({ user: bot_users, impersonation: false }.merge(options))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,10 +5,6 @@ class Groups::RunnersController < Groups::ApplicationController
|
|||
before_action :authorize_update_runner!, only: [:edit, :update, :destroy, :pause, :resume]
|
||||
before_action :runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
|
||||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:runners_finder_all_available, @group)
|
||||
end
|
||||
|
||||
before_action only: [:show] do
|
||||
push_frontend_feature_flag(:enforce_runner_token_expires_at)
|
||||
end
|
||||
|
|
@ -41,8 +37,7 @@ class Groups::RunnersController < Groups::ApplicationController
|
|||
private
|
||||
|
||||
def runner
|
||||
group_params = { group: @group }
|
||||
group_params[:membership] = :all_available if Feature.enabled?(:runners_finder_all_available, @group)
|
||||
group_params = { group: @group, membership: :all_available }
|
||||
|
||||
@runner ||= Ci::RunnersFinder.new(current_user: current_user, params: group_params).execute
|
||||
.except(:limit, :offset)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ module Groups
|
|||
def resource_access_tokens_path
|
||||
group_settings_access_tokens_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def represent(tokens)
|
||||
::GroupAccessTokenSerializer.new.represent(tokens, group: resource)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -81,9 +81,9 @@ class GroupsController < Groups::ApplicationController
|
|||
successful_creation_hooks
|
||||
|
||||
notice = if @group.chat_team.present?
|
||||
"Group '#{@group.name}' and its Mattermost team were successfully created."
|
||||
format(_("Group %{group_name} and its Mattermost team were successfully created."), group_name: @group.name)
|
||||
else
|
||||
"Group '#{@group.name}' was successfully created."
|
||||
format(_("Group %{group_name} was successfully created."), group_name: @group.name)
|
||||
end
|
||||
|
||||
redirect_to @group, notice: notice
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ module Projects
|
|||
def resource_access_tokens_path
|
||||
namespace_project_settings_access_tokens_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def represent(tokens)
|
||||
::ProjectAccessTokenSerializer.new.represent(tokens, project: resource)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class NamespaceCalloutsController < Users::CalloutsController
|
||||
private
|
||||
|
||||
def callout
|
||||
Users::DismissNamespaceCalloutService.new(
|
||||
container: nil, current_user: current_user, params: callout_params
|
||||
).execute
|
||||
end
|
||||
|
||||
def callout_params
|
||||
params.permit(:namespace_id).merge(feature_name: feature_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -14,5 +14,9 @@ module Projects
|
|||
'required' => %w[project_id namespace_id root_namespace_id features]
|
||||
}
|
||||
end
|
||||
|
||||
def pages_related?
|
||||
data[:features].include?("pages_access_level")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -35,6 +35,36 @@ module Mutations
|
|||
required: false,
|
||||
description: copy_field_description(Types::Namespace::PackageSettingsType, :generic_duplicate_exception_regex)
|
||||
|
||||
argument :maven_package_requests_forwarding,
|
||||
GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: copy_field_description(Types::Namespace::PackageSettingsType, :maven_package_requests_forwarding)
|
||||
|
||||
argument :npm_package_requests_forwarding,
|
||||
GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: copy_field_description(Types::Namespace::PackageSettingsType, :npm_package_requests_forwarding)
|
||||
|
||||
argument :pypi_package_requests_forwarding,
|
||||
GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: copy_field_description(Types::Namespace::PackageSettingsType, :pypi_package_requests_forwarding)
|
||||
|
||||
argument :lock_maven_package_requests_forwarding,
|
||||
GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: copy_field_description(Types::Namespace::PackageSettingsType, :lock_maven_package_requests_forwarding)
|
||||
|
||||
argument :lock_npm_package_requests_forwarding,
|
||||
GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: copy_field_description(Types::Namespace::PackageSettingsType, :lock_npm_package_requests_forwarding)
|
||||
|
||||
argument :lock_pypi_package_requests_forwarding,
|
||||
GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: copy_field_description(Types::Namespace::PackageSettingsType, :lock_pypi_package_requests_forwarding)
|
||||
|
||||
field :package_settings,
|
||||
Types::Namespace::PackageSettingsType,
|
||||
null: true,
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ module Types
|
|||
value 'ALL_AVAILABLE',
|
||||
description:
|
||||
"Include all runners. This list includes runners for all projects in the group " \
|
||||
"and subgroups, as well as for the parent groups and instance. " \
|
||||
"Will not return runners if `runners_finder_all_available` feature flag is disabled.",
|
||||
"and subgroups, as well as for the parent groups and instance.",
|
||||
value: :all_available,
|
||||
deprecated: { milestone: '15.5', reason: :alpha }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,9 +8,50 @@ module Types
|
|||
|
||||
authorize :admin_package
|
||||
|
||||
field :generic_duplicate_exception_regex, Types::UntrustedRegexp, null: true, description: 'When generic_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect.'
|
||||
field :generic_duplicates_allowed, GraphQL::Types::Boolean, null: false, description: 'Indicates whether duplicate generic packages are allowed for this namespace.'
|
||||
field :maven_duplicate_exception_regex, Types::UntrustedRegexp, null: true, description: 'When maven_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect.'
|
||||
field :maven_duplicates_allowed, GraphQL::Types::Boolean, null: false, description: 'Indicates whether duplicate Maven packages are allowed for this namespace.'
|
||||
field :generic_duplicate_exception_regex, Types::UntrustedRegexp,
|
||||
null: true,
|
||||
description: 'When generic_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect.'
|
||||
field :generic_duplicates_allowed, GraphQL::Types::Boolean,
|
||||
null: false,
|
||||
description: 'Indicates whether duplicate generic packages are allowed for this namespace.'
|
||||
field :maven_duplicate_exception_regex, Types::UntrustedRegexp,
|
||||
null: true,
|
||||
description: 'When maven_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect.'
|
||||
field :maven_duplicates_allowed, GraphQL::Types::Boolean,
|
||||
null: false,
|
||||
description: 'Indicates whether duplicate Maven packages are allowed for this namespace.'
|
||||
|
||||
field :maven_package_requests_forwarding, GraphQL::Types::Boolean,
|
||||
null: true,
|
||||
description: 'Indicates whether Maven package forwarding is allowed for this namespace.'
|
||||
field :npm_package_requests_forwarding, GraphQL::Types::Boolean,
|
||||
null: true,
|
||||
description: 'Indicates whether npm package forwarding is allowed for this namespace.'
|
||||
field :pypi_package_requests_forwarding, GraphQL::Types::Boolean,
|
||||
null: true,
|
||||
description: 'Indicates whether PyPI package forwarding is allowed for this namespace.'
|
||||
|
||||
field :lock_maven_package_requests_forwarding, GraphQL::Types::Boolean,
|
||||
null: false,
|
||||
description: 'Indicates whether Maven package forwarding is locked for all descendent namespaces.'
|
||||
field :lock_npm_package_requests_forwarding, GraphQL::Types::Boolean,
|
||||
null: false,
|
||||
description: 'Indicates whether npm package forwarding is locked for all descendent namespaces.'
|
||||
field :lock_pypi_package_requests_forwarding, GraphQL::Types::Boolean,
|
||||
null: false,
|
||||
description: 'Indicates whether PyPI package forwarding is locked for all descendent namespaces.'
|
||||
|
||||
field :maven_package_requests_forwarding_locked, GraphQL::Types::Boolean,
|
||||
null: false,
|
||||
method: :maven_package_requests_forwarding_locked?,
|
||||
description: 'Indicates whether Maven package forwarding settings are locked by a parent namespace.'
|
||||
field :npm_package_requests_forwarding_locked, GraphQL::Types::Boolean,
|
||||
null: false,
|
||||
method: :npm_package_requests_forwarding_locked?,
|
||||
description: 'Indicates whether npm package forwarding settings are locked by a parent namespace.'
|
||||
field :pypi_package_requests_forwarding_locked, GraphQL::Types::Boolean,
|
||||
null: false,
|
||||
method: :pypi_package_requests_forwarding_locked?,
|
||||
description: 'Indicates whether PyPI package forwarding settings are locked by a parent namespace.'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,6 +16,54 @@ module EventsHelper
|
|||
'joined' => 'users'
|
||||
}.freeze
|
||||
|
||||
def localized_action_name_map
|
||||
{
|
||||
accepted: s_('Event|accepted'),
|
||||
approved: s_('Event|approved'),
|
||||
closed: s_('Event|closed'),
|
||||
'commented on': s_('Event|commented on'),
|
||||
created: s_('Event|created'),
|
||||
destroyed: s_('Event|destroyed'),
|
||||
joined: s_('Event|joined'),
|
||||
left: s_('Event|left'),
|
||||
opened: s_('Event|opened'),
|
||||
updated: s_('Event|updated'),
|
||||
'removed due to membership expiration from': s_('Event|removed due to membership expiration from')
|
||||
}.merge(localized_push_action_name_map,
|
||||
localized_created_project_action_name_map,
|
||||
localized_design_action_names
|
||||
).freeze
|
||||
end
|
||||
|
||||
def localized_push_action_name_map
|
||||
{
|
||||
'pushed new': s_('Event|pushed new'),
|
||||
deleted: s_('Event|deleted'),
|
||||
'pushed to': s_('Event|pushed to')
|
||||
}.freeze
|
||||
end
|
||||
|
||||
def localized_created_project_action_name_map
|
||||
{
|
||||
created: s_('Event|created'),
|
||||
imported: s_('Event|imported')
|
||||
}.freeze
|
||||
end
|
||||
|
||||
def localized_design_action_names
|
||||
{
|
||||
added: s_('Event|added'),
|
||||
updated: s_('Event|updated'),
|
||||
removed: s_('Event|removed')
|
||||
}.freeze
|
||||
end
|
||||
|
||||
def localized_action_name(event)
|
||||
action_name = event.action_name
|
||||
# The action fallback is used to cover the types were not included in the maps.
|
||||
localized_action_name_map[action_name.to_sym] || action_name
|
||||
end
|
||||
|
||||
def link_to_author(event, self_added: false)
|
||||
author = event.author
|
||||
|
||||
|
|
|
|||
|
|
@ -280,6 +280,7 @@ class Event < ApplicationRecord
|
|||
"opened"
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop: enable Metrics/CyclomaticComplexity
|
||||
# rubocop: enable Metrics/PerceivedComplexity
|
||||
|
||||
|
|
@ -447,9 +448,9 @@ class Event < ApplicationRecord
|
|||
|
||||
def design_action_names
|
||||
{
|
||||
created: _('added'),
|
||||
updated: _('updated'),
|
||||
destroyed: _('removed')
|
||||
created: 'added',
|
||||
updated: 'updated',
|
||||
destroyed: 'removed'
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Namespace::Detail < ApplicationRecord
|
||||
include IgnorableColumns
|
||||
|
||||
ignore_column :free_user_cap_over_limt_notified_at, remove_with: '15.7', remove_after: '2022-11-22'
|
||||
|
||||
belongs_to :namespace, inverse_of: :namespace_details
|
||||
validates :namespace, presence: true
|
||||
validates :description, length: { maximum: 255 }
|
||||
|
|
|
|||
|
|
@ -232,7 +232,6 @@ class User < ApplicationRecord
|
|||
has_many :callouts, class_name: 'Users::Callout'
|
||||
has_many :group_callouts, class_name: 'Users::GroupCallout'
|
||||
has_many :project_callouts, class_name: 'Users::ProjectCallout'
|
||||
has_many :namespace_callouts, class_name: 'Users::NamespaceCallout'
|
||||
has_many :term_agreements
|
||||
belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
|
||||
|
||||
|
|
@ -2098,14 +2097,6 @@ class User < ApplicationRecord
|
|||
callout_dismissed?(callout, ignore_dismissal_earlier_than)
|
||||
end
|
||||
|
||||
# Deprecated: do not use. See: https://gitlab.com/gitlab-org/gitlab/-/issues/371017
|
||||
def dismissed_callout_for_namespace?(feature_name:, namespace:, ignore_dismissal_earlier_than: nil)
|
||||
source_feature_name = "#{feature_name}_#{namespace.id}"
|
||||
callout = namespace_callouts_by_feature_name[source_feature_name]
|
||||
|
||||
callout_dismissed?(callout, ignore_dismissal_earlier_than)
|
||||
end
|
||||
|
||||
def dismissed_callout_for_project?(feature_name:, project:, ignore_dismissal_earlier_than: nil)
|
||||
callout = project_callouts.find_by(feature_name: feature_name, project: project)
|
||||
|
||||
|
|
@ -2138,11 +2129,6 @@ class User < ApplicationRecord
|
|||
.find_or_initialize_by(feature_name: ::Users::GroupCallout.feature_names[feature_name], group_id: group_id)
|
||||
end
|
||||
|
||||
def find_or_initialize_namespace_callout(feature_name, namespace_id)
|
||||
namespace_callouts
|
||||
.find_or_initialize_by(feature_name: ::Users::NamespaceCallout.feature_names[feature_name], namespace_id: namespace_id)
|
||||
end
|
||||
|
||||
def find_or_initialize_project_callout(feature_name, project_id)
|
||||
project_callouts
|
||||
.find_or_initialize_by(feature_name: ::Users::ProjectCallout.feature_names[feature_name], project_id: project_id)
|
||||
|
|
@ -2275,10 +2261,6 @@ class User < ApplicationRecord
|
|||
@group_callouts_by_feature_name ||= group_callouts.index_by(&:source_feature_name)
|
||||
end
|
||||
|
||||
def namespace_callouts_by_feature_name
|
||||
@namespace_callouts_by_feature_name ||= namespace_callouts.index_by(&:source_feature_name)
|
||||
end
|
||||
|
||||
def authorized_groups_without_shared_membership
|
||||
Group.from_union(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class NamespaceCallout < ApplicationRecord
|
||||
include Users::Calloutable
|
||||
|
||||
self.table_name = 'user_namespace_callouts'
|
||||
|
||||
belongs_to :namespace
|
||||
|
||||
enum feature_name: {
|
||||
invite_members_banner: 1,
|
||||
approaching_seat_count_threshold: 2, # EE-only
|
||||
storage_enforcement_banner_first_enforcement_threshold: 3, # EE-only
|
||||
storage_enforcement_banner_second_enforcement_threshold: 4, # EE-only
|
||||
storage_enforcement_banner_third_enforcement_threshold: 5, # EE-only
|
||||
storage_enforcement_banner_fourth_enforcement_threshold: 6, # EE-only
|
||||
preview_user_over_limit_free_plan_alert: 7, # EE-only
|
||||
user_reached_limit_free_plan_alert: 8, # EE-only
|
||||
web_hook_disabled: 9
|
||||
}
|
||||
|
||||
validates :namespace, presence: true
|
||||
validates :feature_name,
|
||||
presence: true,
|
||||
uniqueness: { scope: [:user_id, :namespace_id] },
|
||||
inclusion: { in: NamespaceCallout.feature_names.keys }
|
||||
|
||||
def source_feature_name
|
||||
"#{feature_name}_#{namespace_id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -11,7 +11,11 @@ module Users
|
|||
enum feature_name: {
|
||||
awaiting_members_banner: 1, # EE-only
|
||||
web_hook_disabled: 2,
|
||||
ultimate_feature_removal_banner: 3
|
||||
ultimate_feature_removal_banner: 3,
|
||||
storage_enforcement_banner_first_enforcement_threshold: 4, # EE-only
|
||||
storage_enforcement_banner_second_enforcement_threshold: 5, # EE-only
|
||||
storage_enforcement_banner_third_enforcement_threshold: 6, # EE-only
|
||||
storage_enforcement_banner_fourth_enforcement_threshold: 7 # EE-only
|
||||
}
|
||||
|
||||
validates :project, presence: true
|
||||
|
|
|
|||
|
|
@ -87,10 +87,6 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
|
|||
Gitlab::CurrentSettings.valid_runner_registrars.include?('group')
|
||||
end
|
||||
|
||||
condition(:runners_finder_all_available, scope: :subject) do
|
||||
Feature.enabled?(:runners_finder_all_available, group)
|
||||
end
|
||||
|
||||
rule { can?(:read_group) & design_management_enabled }.policy do
|
||||
enable :read_design_activity
|
||||
end
|
||||
|
|
@ -158,8 +154,6 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
|
|||
enable :read_group_all_available_runners
|
||||
end
|
||||
|
||||
rule { ~runners_finder_all_available }.prevent :read_group_all_available_runners
|
||||
|
||||
rule { reporter }.policy do
|
||||
enable :reporter_access
|
||||
enable :read_container_image
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class GroupAccessTokenEntity < AccessTokenEntityBase
|
|||
|
||||
revoke_group_settings_access_token_path(
|
||||
id: token,
|
||||
group_id: group.path)
|
||||
group_id: group.full_path)
|
||||
end
|
||||
|
||||
expose :role do |token, options|
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class ProjectAccessTokenEntity < AccessTokenEntityBase
|
|||
|
||||
revoke_namespace_project_settings_access_token_path(
|
||||
id: token,
|
||||
namespace_id: project.namespace.path,
|
||||
namespace_id: project.namespace.full_path,
|
||||
project_id: project.path)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,13 @@ module Namespaces
|
|||
ALLOWED_ATTRIBUTES = %i[maven_duplicates_allowed
|
||||
maven_duplicate_exception_regex
|
||||
generic_duplicates_allowed
|
||||
generic_duplicate_exception_regex].freeze
|
||||
generic_duplicate_exception_regex
|
||||
maven_package_requests_forwarding
|
||||
npm_package_requests_forwarding
|
||||
pypi_package_requests_forwarding
|
||||
lock_maven_package_requests_forwarding
|
||||
lock_npm_package_requests_forwarding
|
||||
lock_pypi_package_requests_forwarding].freeze
|
||||
|
||||
def execute
|
||||
return ServiceResponse.error(message: 'Access Denied', http_status: 403) unless allowed?
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class DismissNamespaceCalloutService < DismissCalloutService
|
||||
private
|
||||
|
||||
def callout
|
||||
current_user.find_or_initialize_namespace_callout(params[:feature_name], params[:namespace_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
= inline_event_icon(event)
|
||||
- if event.target
|
||||
%span.event-type.d-inline-block.gl-mr-2{ class: event.action_name }
|
||||
= event.action_name
|
||||
= localized_action_name(event)
|
||||
%span.event-target-type.gl-mr-2= event.target_type_name
|
||||
= link_to event_target_path(event), class: 'has-tooltip event-target-link gl-mr-2', title: event.target_title do
|
||||
= event.target.reference_link_text
|
||||
|
|
|
|||
|
|
@ -24,13 +24,11 @@
|
|||
= _('You can enable group access token creation in %{link_start}group settings%{link_end}.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
|
||||
|
||||
.col-lg-8
|
||||
- if @new_resource_access_token
|
||||
= render 'shared/access_tokens/created_container',
|
||||
type: type,
|
||||
new_token_value: @new_resource_access_token
|
||||
#js-new-access-token-app{ data: { access_token_type: type } }
|
||||
|
||||
- if current_user.can?(:create_resource_access_tokens, @group)
|
||||
= render 'shared/access_tokens/form',
|
||||
ajax: true,
|
||||
type: type,
|
||||
path: group_settings_access_tokens_path(@group),
|
||||
resource: @group,
|
||||
|
|
@ -41,10 +39,6 @@
|
|||
prefix: :resource_access_token,
|
||||
help_path: help_page_path('user/group/settings/group_access_tokens', anchor: 'scopes-for-a-group-access-token')
|
||||
|
||||
= render 'shared/access_tokens/table',
|
||||
active_tokens: @active_resource_access_tokens,
|
||||
resource: @group,
|
||||
type: type,
|
||||
type_plural: type_plural,
|
||||
revoke_route_helper: ->(token) { revoke_group_settings_access_token_path(id: token) },
|
||||
no_active_tokens_message: _('This group has no active access tokens.')
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_resource_access_tokens.to_json, no_active_tokens_message: _('This group has no active access tokens.'), show_role: true
|
||||
} }
|
||||
|
||||
|
|
|
|||
|
|
@ -24,13 +24,11 @@
|
|||
= _('You can enable project access token creation in %{link_start}group settings%{link_end}.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
|
||||
|
||||
.col-lg-8
|
||||
- if @new_resource_access_token
|
||||
= render 'shared/access_tokens/created_container',
|
||||
type: type,
|
||||
new_token_value: @new_resource_access_token
|
||||
#js-new-access-token-app{ data: { access_token_type: type } }
|
||||
|
||||
- if current_user.can?(:create_resource_access_tokens, @project)
|
||||
= render 'shared/access_tokens/form',
|
||||
ajax: true,
|
||||
type: type,
|
||||
path: project_settings_access_tokens_path(@project),
|
||||
resource: @project,
|
||||
|
|
@ -42,10 +40,5 @@
|
|||
description_prefix: :project_access_token,
|
||||
help_path: help_page_path('user/project/settings/project_access_tokens', anchor: 'scopes-for-a-project-access-token')
|
||||
|
||||
= render 'shared/access_tokens/table',
|
||||
active_tokens: @active_resource_access_tokens,
|
||||
resource: @project,
|
||||
type: type,
|
||||
type_plural: type_plural,
|
||||
revoke_route_helper: ->(token) { revoke_namespace_project_settings_access_token_path(id: token) },
|
||||
no_active_tokens_message: _('This project has no active access tokens.')
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_resource_access_tokens.to_json, no_active_tokens_message: _('This project has no active access tokens.'), show_role: true
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -1020,6 +1020,24 @@
|
|||
:weight: 1
|
||||
:idempotent: false
|
||||
:tags: []
|
||||
- :name: github_importer:github_import_attachments_import_issue
|
||||
:worker_name: Gitlab::GithubImport::Attachments::ImportIssueWorker
|
||||
:feature_category: :importers
|
||||
:has_external_dependencies: true
|
||||
:urgency: :low
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
:idempotent: false
|
||||
:tags: []
|
||||
- :name: github_importer:github_import_attachments_import_merge_request
|
||||
:worker_name: Gitlab::GithubImport::Attachments::ImportMergeRequestWorker
|
||||
:feature_category: :importers
|
||||
:has_external_dependencies: true
|
||||
:urgency: :low
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
:idempotent: false
|
||||
:tags: []
|
||||
- :name: github_importer:github_import_attachments_import_note
|
||||
:worker_name: Gitlab::GithubImport::Attachments::ImportNoteWorker
|
||||
:feature_category: :importers
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module GithubImport
|
||||
module Attachments
|
||||
class ImportIssueWorker # rubocop:disable Scalability/IdempotentWorker
|
||||
include ObjectImporter
|
||||
|
||||
def representation_class
|
||||
Representation::NoteText
|
||||
end
|
||||
|
||||
def importer_class
|
||||
Importer::NoteAttachmentsImporter
|
||||
end
|
||||
|
||||
def object_type
|
||||
:issue_attachment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module GithubImport
|
||||
module Attachments
|
||||
class ImportMergeRequestWorker # rubocop:disable Scalability/IdempotentWorker
|
||||
include ObjectImporter
|
||||
|
||||
def representation_class
|
||||
Representation::NoteText
|
||||
end
|
||||
|
||||
def importer_class
|
||||
Importer::NoteAttachmentsImporter
|
||||
end
|
||||
|
||||
def object_type
|
||||
:merge_request_attachment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -30,7 +30,9 @@ module Gitlab
|
|||
def importers
|
||||
[
|
||||
::Gitlab::GithubImport::Importer::Attachments::ReleasesImporter,
|
||||
::Gitlab::GithubImport::Importer::Attachments::NotesImporter
|
||||
::Gitlab::GithubImport::Importer::Attachments::NotesImporter,
|
||||
::Gitlab::GithubImport::Importer::Attachments::IssuesImporter,
|
||||
::Gitlab::GithubImport::Importer::Attachments::MergeRequestsImporter
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: runners_finder_all_available
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96770
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/374525
|
||||
milestone: '15.5'
|
||||
type: development
|
||||
group: group::runner
|
||||
default_enabled: false
|
||||
|
|
@ -4,17 +4,33 @@ return unless Gitlab::Runtime.application?
|
|||
return unless Gitlab::Utils.to_boolean(ENV['GITLAB_MEMORY_WATCHDOG_ENABLED'])
|
||||
|
||||
Gitlab::Cluster::LifecycleEvents.on_worker_start do
|
||||
handler =
|
||||
if Gitlab::Runtime.puma?
|
||||
Gitlab::Memory::Watchdog::PumaHandler.new
|
||||
elsif Gitlab::Runtime.sidekiq?
|
||||
Gitlab::Memory::Watchdog::TermProcessHandler.new
|
||||
else
|
||||
Gitlab::Memory::Watchdog::NullHandler.instance
|
||||
end
|
||||
watchdog = Gitlab::Memory::Watchdog.new
|
||||
max_strikes = ENV.fetch('GITLAB_MEMWD_MAX_STRIKES', 5).to_i
|
||||
sleep_time_seconds = ENV.fetch('GITLAB_MEMWD_SLEEP_TIME_SEC', 60).to_i
|
||||
max_mem_growth = ENV.fetch('GITLAB_MEMWD_MAX_MEM_GROWTH', 3.0).to_f
|
||||
max_heap_frag = ENV.fetch('GITLAB_MEMWD_MAX_HEAP_FRAG', 0.5).to_f
|
||||
|
||||
watchdog.configure do |config|
|
||||
config.handler =
|
||||
if Gitlab::Runtime.puma?
|
||||
Gitlab::Memory::Watchdog::PumaHandler.new
|
||||
elsif Gitlab::Runtime.sidekiq?
|
||||
Gitlab::Memory::Watchdog::TermProcessHandler.new
|
||||
else
|
||||
Gitlab::Memory::Watchdog::NullHandler.instance
|
||||
end
|
||||
|
||||
config.logger = Gitlab::AppLogger
|
||||
config.sleep_time_seconds = sleep_time_seconds
|
||||
# config.monitor.use MonitorClass, args*, max_strikes:, kwargs**, &block
|
||||
config.monitors.use Gitlab::Memory::Watchdog::Monitor::HeapFragmentation,
|
||||
max_heap_fragmentation: max_heap_frag,
|
||||
max_strikes: max_strikes
|
||||
|
||||
config.monitors.use Gitlab::Memory::Watchdog::Monitor::UniqueMemoryGrowth,
|
||||
max_mem_growth: max_mem_growth,
|
||||
max_strikes: max_strikes
|
||||
end
|
||||
|
||||
watchdog = Gitlab::Memory::Watchdog.new(
|
||||
handler: handler, logger: Gitlab::AppLogger
|
||||
)
|
||||
Gitlab::BackgroundTask.new(watchdog).start
|
||||
end
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ scope '-/users', module: :users do
|
|||
end
|
||||
|
||||
resources :callouts, only: [:create]
|
||||
resources :namespace_callouts, only: [:create]
|
||||
resources :group_callouts, only: [:create]
|
||||
resources :project_callouts, only: [:create]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DropNotesNoteTrigramIndex < Gitlab::Database::Migration[2.0]
|
||||
INDEX_NAME = 'index_notes_on_note_gin_trigram'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
remove_concurrent_index_by_name :notes, INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
# we never want to add this index back since it doesn't exist in production
|
||||
# we are only using this migration to cleanup other environments where this index does exist
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddAsyncIndexAuthorIdTargetProjectIdOnMergeRequests < Gitlab::Database::Migration[2.0]
|
||||
INDEX_NAME = 'index_merge_requests_on_author_id_and_id'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
prepare_async_index :merge_requests, %i[author_id id], name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
unprepare_async_index :merge_requests, %i[author_id id], name: INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
1275aff394d75cc254e664a81f52880bc248343dad7a07162973cafe268d40e6
|
||||
|
|
@ -0,0 +1 @@
|
|||
2ac315a49a5026938abc21a98974fd42b39b7535d86530085a01fc7f5687bb0e
|
||||
|
|
@ -59,6 +59,8 @@ verification methods:
|
|||
| Blobs | Pages _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
|
||||
| Blobs | CI Secure Files _(file system)_ | Geo with API | SHA256 checksum |
|
||||
| Blobs | CI Secure Files _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
|
||||
| Blobs | Incident Metric Images _(file system)_ | Geo with API/Managed | SHA256 checksum |
|
||||
| Blobs | Incident Metric Images _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
|
||||
|
||||
- (*1*): Redis replication can be used as part of HA with Redis sentinel. It's not used between Geo sites.
|
||||
- (*2*): Object storage replication can be performed by Geo or by your object storage provider/appliance
|
||||
|
|
@ -204,9 +206,9 @@ successfully, you must replicate their data using some other means.
|
|||
|[Versioned Terraform State](../../terraform_state.md) | **Yes** (13.5) | **Yes** (13.12) | [**Yes** (15.1)](https://gitlab.com/groups/gitlab-org/-/epics/5551) | [No](object_storage.md#verification-of-files-in-object-storage) | Replication is behind the feature flag `geo_terraform_state_version_replication`, enabled by default. Verification was behind the feature flag `geo_terraform_state_version_verification`, which was removed in 14.0. |
|
||||
|[External merge request diffs](../../merge_request_diffs.md) | **Yes** (13.5) | **Yes** (14.6) | [**Yes** (15.1)](https://gitlab.com/groups/gitlab-org/-/epics/5551) | [No](object_storage.md#verification-of-files-in-object-storage) | Replication is behind the feature flag `geo_merge_request_diff_replication`, enabled by default. Verification was behind the feature flag `geo_merge_request_diff_verification`, removed in 14.7.|
|
||||
|[Versioned snippets](../../../user/snippets.md#versioned-snippets) | [**Yes** (13.7)](https://gitlab.com/groups/gitlab-org/-/epics/2809) | [**Yes** (14.2)](https://gitlab.com/groups/gitlab-org/-/epics/2810) | N/A | N/A | Verification was implemented behind the feature flag `geo_snippet_repository_verification` in 13.11, and the feature flag was removed in 14.2. |
|
||||
|[GitLab Pages](../../pages/index.md) | [**Yes** (14.3)](https://gitlab.com/groups/gitlab-org/-/epics/589) | **Yes** (14.6) | [**Yes** (15.1)](https://gitlab.com/groups/gitlab-org/-/epics/5551) | [No](object_storage.md#verification-of-files-in-object-storage) | Behind feature flag `geo_pages_deployment_replication`, enabled by default. Verification was behind the feature flag `geo_pages_deployment_verification`, removed in 14.7. |
|
||||
|[Project-level Secure files](../../../ci/secure_files/index.md) | **Yes** (15.3) | **Yes** (15.3) | **Yes** (15.3) | [No](object_storage.md#verification-of-files-in-object-storage) | |
|
||||
|[Incident Metric Images](../../../operations/incident_management/incidents.md#metrics) | [Planned](https://gitlab.com/gitlab-org/gitlab/-/issues/362561) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/362561) | No | No | |
|
||||
| [GitLab Pages](../../pages/index.md) | [**Yes** (14.3)](https://gitlab.com/groups/gitlab-org/-/epics/589) | **Yes** (14.6) | [**Yes** (15.1)](https://gitlab.com/groups/gitlab-org/-/epics/5551) | [No](object_storage.md#verification-of-files-in-object-storage) | Behind feature flag `geo_pages_deployment_replication`, enabled by default. Verification was behind the feature flag `geo_pages_deployment_verification`, removed in 14.7. |
|
||||
| [Project-level Secure files](../../../ci/secure_files/index.md) | **Yes** (15.3) | **Yes** (15.3) | **Yes** (15.3) | [No](object_storage.md#verification-of-files-in-object-storage) | |
|
||||
| [Incident Metric Images](../../../operations/incident_management/incidents.md#metrics) | **Yes** (15.5) | **Yes**(15.5) | Yes | Yes | Replication/Verification is handled via the Uploads data type |
|
||||
|[Alert Metric Images](../../../operations/incident_management/alerts.md#metrics-tab) | [Planned](https://gitlab.com/gitlab-org/gitlab/-/issues/362564) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/362564) | No | No | |
|
||||
|[Server-side Git hooks](../../server_hooks.md) | [Not planned](https://gitlab.com/groups/gitlab-org/-/epics/1867) | No | N/A | N/A | Not planned because of current implementation complexity, low customer interest, and availability of alternatives to hooks. |
|
||||
|[Elasticsearch integration](../../../integration/advanced_search/elasticsearch.md) | [Not planned](https://gitlab.com/gitlab-org/gitlab/-/issues/1186) | No | No | No | Not planned because further product discovery is required and Elasticsearch (ES) clusters can be rebuilt. Secondaries use the same ES cluster as the primary. |
|
||||
|
|
|
|||
|
|
@ -683,6 +683,9 @@ ApplicationSetting.current
|
|||
|
||||
### Open object in `irb`
|
||||
|
||||
WARNING:
|
||||
Any command that changes data directly could be damaging if not run correctly, or under the right conditions. We highly recommend running them in a test environment with a backup of the instance ready to be restored, just in case.
|
||||
|
||||
Sometimes it is easier to go through a method if you are in the context of the object. You can shim into the namespace of `Object` to let you open `irb` in the context of any object:
|
||||
|
||||
```ruby
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ The following lists the currently supported OSs and their possible EOL dates.
|
|||
| Ubuntu 20.04 | GitLab CE / GitLab EE 13.2.0 | amd64, arm64 | [Ubuntu Install Documentation](https://about.gitlab.com/install/#ubuntu) | April 2025 | <https://wiki.ubuntu.com/Releases> |
|
||||
| Amazon Linux 2 | GitLab CE / GitLab EE 14.9.0 | amd64, arm64 | [Amazon Linux 2 Install Documentation](https://about.gitlab.com/install/#amazonlinux-2) | June 2023 | <https://aws.amazon.com/amazon-linux-2/faqs/> |
|
||||
| Raspberry Pi OS (Buster) (formerly known as Raspbian Buster) | GitLab CE 12.2.0 | armhf | [Raspberry Pi Install Documentation](https://about.gitlab.com/install/#raspberry-pi-os) | 2024 | [Raspberry Pi Details](https://www.raspberrypi.com/news/new-old-functionality-with-raspberry-pi-os-legacy/) |
|
||||
| Raspberry Pi OS (Bullseye) | GitLab CE 15.5.0 | armhf | [Raspberry Pi Install Documentation](https://about.gitlab.com/install/#raspberry-pi-os) | 2026 | [Raspberry Pi Details](https://www.raspberrypi.com/news/raspberry-pi-os-debian-bullseye/) |
|
||||
|
||||
NOTE:
|
||||
CentOS 8 was EOL on December 31, 2021. In GitLab 14.5 and later,
|
||||
|
|
|
|||
|
|
@ -185,8 +185,7 @@ as most of the fixes are relatively high risk, involving running code on the Rai
|
|||
|
||||
### Read only projects
|
||||
|
||||
If you have [set projects read only](../troubleshooting/gitlab_rails_cheat_sheet.md#make-a-project-read-only-can-only-be-done-in-the-console)
|
||||
they might fail to migrate.
|
||||
If you have set projects as read only they might fail to migrate.
|
||||
|
||||
1. [Start a Rails console](../operations/rails_console.md#starting-a-rails-console-session).
|
||||
|
||||
|
|
@ -233,7 +232,7 @@ Delete the project using the Rails console:
|
|||
- Replace `admin_handle` with the handle of an instance administrator or with `root`.
|
||||
- Verify the output before proceeding. **There are no other checks performed**.
|
||||
|
||||
1. [Destroy the project](../troubleshooting/gitlab_rails_cheat_sheet.md#destroy-a-project) **immediately**:
|
||||
1. [Destroy the project](../../user/project/working_with_projects.md#delete-a-project-using-console) **immediately**:
|
||||
|
||||
```ruby
|
||||
Projects::DestroyService.new(project, user).execute
|
||||
|
|
|
|||
|
|
@ -75,114 +75,6 @@ Benchmark.bm do |x|
|
|||
end
|
||||
```
|
||||
|
||||
## Projects
|
||||
|
||||
### Clear a project's cache
|
||||
|
||||
```ruby
|
||||
ProjectCacheWorker.perform_async(project.id)
|
||||
```
|
||||
|
||||
### Expire the .exists? cache
|
||||
|
||||
```ruby
|
||||
project.repository.expire_exists_cache
|
||||
```
|
||||
|
||||
### Make all projects private
|
||||
|
||||
```ruby
|
||||
Project.update_all(visibility_level: 0)
|
||||
```
|
||||
|
||||
### Find projects that are pending deletion
|
||||
|
||||
```ruby
|
||||
#
|
||||
# This section lists all the projects which are pending deletion
|
||||
#
|
||||
projects = Project.where(pending_delete: true)
|
||||
projects.each do |p|
|
||||
puts "Project ID: #{p.id}"
|
||||
puts "Project name: #{p.name}"
|
||||
puts "Repository path: #{p.repository.full_path}"
|
||||
end
|
||||
|
||||
#
|
||||
# Assign a user (the root user does)
|
||||
#
|
||||
user = User.find_by_username('root')
|
||||
|
||||
#
|
||||
# For each project listed repeat these two commands
|
||||
#
|
||||
|
||||
# Find the project, update the xxx-changeme values from above
|
||||
project = Project.find_by_full_path('group-changeme/project-changeme')
|
||||
|
||||
# Immediately delete the project
|
||||
::Projects::DestroyService.new(project, user, {}).execute
|
||||
```
|
||||
|
||||
### Destroy a project
|
||||
|
||||
```ruby
|
||||
project = Project.find_by_full_path('<project_path>')
|
||||
user = User.find_by_username('<username>')
|
||||
ProjectDestroyWorker.perform_async(project.id, user.id, {})
|
||||
# or ProjectDestroyWorker.new.perform(project.id, user.id, {})
|
||||
# or Projects::DestroyService.new(project, user).execute
|
||||
```
|
||||
|
||||
If this fails, display why it doesn't work with:
|
||||
|
||||
```ruby
|
||||
project = Project.find_by_full_path('<project_path>')
|
||||
project.delete_error
|
||||
```
|
||||
|
||||
### Remove fork relationship manually
|
||||
|
||||
```ruby
|
||||
p = Project.find_by_full_path('<project_path>')
|
||||
u = User.find_by_username('<username>')
|
||||
::Projects::UnlinkForkService.new(p, u).execute
|
||||
```
|
||||
|
||||
### Make a project read-only (can only be done in the console)
|
||||
|
||||
```ruby
|
||||
# Make a project read-only
|
||||
project.repository_read_only = true; project.save
|
||||
|
||||
# OR
|
||||
project.update!(repository_read_only: true)
|
||||
```
|
||||
|
||||
### Transfer project from one namespace to another
|
||||
|
||||
```ruby
|
||||
p = Project.find_by_full_path('<project_path>')
|
||||
|
||||
# To set the owner of the project
|
||||
current_user= p.creator
|
||||
|
||||
# Namespace where you want this to be moved.
|
||||
namespace = Namespace.find_by_full_path("<new_namespace>")
|
||||
|
||||
::Projects::TransferService.new(p, current_user).execute(namespace)
|
||||
```
|
||||
|
||||
### Find projects using an SQL query
|
||||
|
||||
Find and store an array of projects based on an SQL query:
|
||||
|
||||
```ruby
|
||||
# Finds projects that end with '%ject'
|
||||
projects = Project.find_by_sql("SELECT * FROM projects WHERE name LIKE '%ject'")
|
||||
=> [#<Project id:12 root/my-first-project>>, #<Project id:13 root/my-second-project>>]
|
||||
```
|
||||
|
||||
## Imports and exports
|
||||
|
||||
### Import a project
|
||||
|
|
|
|||
|
|
@ -5408,9 +5408,15 @@ Input type: `UpdateNamespacePackageSettingsInput`
|
|||
| <a id="mutationupdatenamespacepackagesettingsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationupdatenamespacepackagesettingsgenericduplicateexceptionregex"></a>`genericDuplicateExceptionRegex` | [`UntrustedRegexp`](#untrustedregexp) | When generic_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect. |
|
||||
| <a id="mutationupdatenamespacepackagesettingsgenericduplicatesallowed"></a>`genericDuplicatesAllowed` | [`Boolean`](#boolean) | Indicates whether duplicate generic packages are allowed for this namespace. |
|
||||
| <a id="mutationupdatenamespacepackagesettingslockmavenpackagerequestsforwarding"></a>`lockMavenPackageRequestsForwarding` | [`Boolean`](#boolean) | Indicates whether Maven package forwarding is locked for all descendent namespaces. |
|
||||
| <a id="mutationupdatenamespacepackagesettingslocknpmpackagerequestsforwarding"></a>`lockNpmPackageRequestsForwarding` | [`Boolean`](#boolean) | Indicates whether npm package forwarding is locked for all descendent namespaces. |
|
||||
| <a id="mutationupdatenamespacepackagesettingslockpypipackagerequestsforwarding"></a>`lockPypiPackageRequestsForwarding` | [`Boolean`](#boolean) | Indicates whether PyPI package forwarding is locked for all descendent namespaces. |
|
||||
| <a id="mutationupdatenamespacepackagesettingsmavenduplicateexceptionregex"></a>`mavenDuplicateExceptionRegex` | [`UntrustedRegexp`](#untrustedregexp) | When maven_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect. |
|
||||
| <a id="mutationupdatenamespacepackagesettingsmavenduplicatesallowed"></a>`mavenDuplicatesAllowed` | [`Boolean`](#boolean) | Indicates whether duplicate Maven packages are allowed for this namespace. |
|
||||
| <a id="mutationupdatenamespacepackagesettingsmavenpackagerequestsforwarding"></a>`mavenPackageRequestsForwarding` | [`Boolean`](#boolean) | Indicates whether Maven package forwarding is allowed for this namespace. |
|
||||
| <a id="mutationupdatenamespacepackagesettingsnamespacepath"></a>`namespacePath` | [`ID!`](#id) | Namespace path where the namespace package setting is located. |
|
||||
| <a id="mutationupdatenamespacepackagesettingsnpmpackagerequestsforwarding"></a>`npmPackageRequestsForwarding` | [`Boolean`](#boolean) | Indicates whether npm package forwarding is allowed for this namespace. |
|
||||
| <a id="mutationupdatenamespacepackagesettingspypipackagerequestsforwarding"></a>`pypiPackageRequestsForwarding` | [`Boolean`](#boolean) | Indicates whether PyPI package forwarding is allowed for this namespace. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
|
@ -15759,8 +15765,17 @@ Namespace-level Package Registry settings.
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="packagesettingsgenericduplicateexceptionregex"></a>`genericDuplicateExceptionRegex` | [`UntrustedRegexp`](#untrustedregexp) | When generic_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect. |
|
||||
| <a id="packagesettingsgenericduplicatesallowed"></a>`genericDuplicatesAllowed` | [`Boolean!`](#boolean) | Indicates whether duplicate generic packages are allowed for this namespace. |
|
||||
| <a id="packagesettingslockmavenpackagerequestsforwarding"></a>`lockMavenPackageRequestsForwarding` | [`Boolean!`](#boolean) | Indicates whether Maven package forwarding is locked for all descendent namespaces. |
|
||||
| <a id="packagesettingslocknpmpackagerequestsforwarding"></a>`lockNpmPackageRequestsForwarding` | [`Boolean!`](#boolean) | Indicates whether npm package forwarding is locked for all descendent namespaces. |
|
||||
| <a id="packagesettingslockpypipackagerequestsforwarding"></a>`lockPypiPackageRequestsForwarding` | [`Boolean!`](#boolean) | Indicates whether PyPI package forwarding is locked for all descendent namespaces. |
|
||||
| <a id="packagesettingsmavenduplicateexceptionregex"></a>`mavenDuplicateExceptionRegex` | [`UntrustedRegexp`](#untrustedregexp) | When maven_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect. |
|
||||
| <a id="packagesettingsmavenduplicatesallowed"></a>`mavenDuplicatesAllowed` | [`Boolean!`](#boolean) | Indicates whether duplicate Maven packages are allowed for this namespace. |
|
||||
| <a id="packagesettingsmavenpackagerequestsforwarding"></a>`mavenPackageRequestsForwarding` | [`Boolean`](#boolean) | Indicates whether Maven package forwarding is allowed for this namespace. |
|
||||
| <a id="packagesettingsmavenpackagerequestsforwardinglocked"></a>`mavenPackageRequestsForwardingLocked` | [`Boolean!`](#boolean) | Indicates whether Maven package forwarding settings are locked by a parent namespace. |
|
||||
| <a id="packagesettingsnpmpackagerequestsforwarding"></a>`npmPackageRequestsForwarding` | [`Boolean`](#boolean) | Indicates whether npm package forwarding is allowed for this namespace. |
|
||||
| <a id="packagesettingsnpmpackagerequestsforwardinglocked"></a>`npmPackageRequestsForwardingLocked` | [`Boolean!`](#boolean) | Indicates whether npm package forwarding settings are locked by a parent namespace. |
|
||||
| <a id="packagesettingspypipackagerequestsforwarding"></a>`pypiPackageRequestsForwarding` | [`Boolean`](#boolean) | Indicates whether PyPI package forwarding is allowed for this namespace. |
|
||||
| <a id="packagesettingspypipackagerequestsforwardinglocked"></a>`pypiPackageRequestsForwardingLocked` | [`Boolean!`](#boolean) | Indicates whether PyPI package forwarding settings are locked by a parent namespace. |
|
||||
|
||||
### `PackageTag`
|
||||
|
||||
|
|
@ -19987,7 +20002,7 @@ Values for filtering runners in namespaces. The previous type name `RunnerMember
|
|||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="cirunnermembershipfilterall_available"></a>`ALL_AVAILABLE` **{warning-solid}** | **Introduced** in 15.5. This feature is in Alpha. It can be changed or removed at any time. Include all runners. This list includes runners for all projects in the group and subgroups, as well as for the parent groups and instance. Will not return runners if `runners_finder_all_available` feature flag is disabled. |
|
||||
| <a id="cirunnermembershipfilterall_available"></a>`ALL_AVAILABLE` **{warning-solid}** | **Introduced** in 15.5. This feature is in Alpha. It can be changed or removed at any time. Include all runners. This list includes runners for all projects in the group and subgroups, as well as for the parent groups and instance. |
|
||||
| <a id="cirunnermembershipfilterdescendants"></a>`DESCENDANTS` | Include runners that have either a direct or inherited relationship. These runners can be specific to a project or a group. |
|
||||
| <a id="cirunnermembershipfilterdirect"></a>`DIRECT` | Include runners that have a direct relationship. |
|
||||
|
||||
|
|
|
|||
|
|
@ -194,10 +194,7 @@ From this page, you can edit, pause, and remove runners from the group, its subg
|
|||
|
||||
#### Filter group runners to show only inherited
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/337838/) in GitLab 15.5, [with a flag](../../administration/feature_flags.md) named `runners_finder_all_available`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `runners_finder_all_available`. On GitLab.com, this feature is not available.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/337838/) in GitLab 15.5.
|
||||
|
||||
You can choose to show all runners in the list, or show only
|
||||
those that are inherited from the instance or other groups.
|
||||
|
|
|
|||
|
|
@ -135,6 +135,8 @@ For each entity with Markdown text in the project, we schedule a job of:
|
|||
|
||||
- `Gitlab::GithubImport::ImportReleaseAttachmentsWorker` for every release.
|
||||
- `Gitlab::GithubImport::ImportNoteAttachmentsWorker` for every note.
|
||||
- `Gitlab::GithubImport::ImportIssueAttachmentsWorker` for every issue.
|
||||
- `Gitlab::GithubImport::ImportMergeRequestAttachmentsWorker` for every merge request.
|
||||
|
||||
Each job:
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ and issue creation. Your instance becomes read-only and
|
|||
an expiration message displays to all administrators. You have a 14-day grace period
|
||||
before this occurs.
|
||||
|
||||
To resume functionality, [renew your subscription](../../subscriptions/self_managed/index.md#renew-a-subscription).
|
||||
To resume functionality, [renew your subscription](../../subscriptions/self_managed/index.md#renew-a-subscription).
|
||||
|
||||
If the license has been expired for more than 30 days, you must purchase a [new subscription](../../subscriptions/self_managed/index.md) to resume functionality.
|
||||
|
||||
|
|
|
|||
|
|
@ -196,6 +196,8 @@ The following items of a project are imported:
|
|||
- Attachments for:
|
||||
- Release notes. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15620) in GitLab 15.4.
|
||||
- Comments and notes. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18052) in GitLab 15.5.
|
||||
- Issue description. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18052) in GitLab 15.5.
|
||||
- Merge Request description. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18052) in GitLab 15.5.
|
||||
|
||||
All attachment imports are disabled by default behind
|
||||
`github_importer_attachments_import` [feature flag](../../../administration/feature_flags.md). From GitLab 15.5, can be imported
|
||||
|
|
|
|||
|
|
@ -376,3 +376,33 @@ Configure Error Tracking to discover and view [Sentry errors within GitLab](../.
|
|||
|
||||
[Add Storage credentials](../../../operations/incident_management/status_page.md#sync-incidents-to-the-status-page)
|
||||
to enable the syncing of public Issues to a [deployed status page](../../../operations/incident_management/status_page.md#create-a-status-page-project).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
When working with project settings, you might encounter the following issues, or require alternate methods to complete specific tasks.
|
||||
|
||||
### Remove a fork relationship through console
|
||||
|
||||
If removing the fork through the UI or API is not working, you can attempt the fork relationship removal in a [Rails console session](../../../administration/operations/rails_console.md#starting-a-rails-console-session).
|
||||
|
||||
```ruby
|
||||
p = Project.find_by_full_path('<project_path>')
|
||||
u = User.find_by_username('<username>')
|
||||
Projects::UnlinkForkService.new(p, u).execute
|
||||
```
|
||||
|
||||
### Transfer a project through console
|
||||
|
||||
If transferring a project through the UI or API is not working, you can attempt the transfer in a [Rails console session](../../../administration/operations/rails_console.md#starting-a-rails-console-session).
|
||||
|
||||
```ruby
|
||||
p = Project.find_by_full_path('<project_path>')
|
||||
|
||||
# To set the owner of the project
|
||||
current_user = p.creator
|
||||
|
||||
# Namespace where you want this to be moved
|
||||
namespace = Namespace.find_by_full_path("<new_namespace>")
|
||||
|
||||
Projects::TransferService.new(p, current_user).execute(namespace)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -90,8 +90,7 @@ To create a blank project:
|
|||
- In the **Project slug** field, enter the path to your project. The GitLab instance uses the
|
||||
slug as the URL path to the project. To change the slug, first enter the project name,
|
||||
then change the slug.
|
||||
- In the **Project description (optional)** field, enter the description of your project's dashboard.
|
||||
- In the **Project target (optional)** field, select your project's deployment target.
|
||||
- In the **Project deployment target (optional)** field, select your project's deployment target.
|
||||
This information helps GitLab better understand its users and their deployment requirements.
|
||||
- To modify the project's [viewing and access rights](../public_access.md) for
|
||||
users, change the **Visibility Level**.
|
||||
|
|
@ -496,3 +495,67 @@ download starts, the `insteadOf` configuration sends the traffic to the secondar
|
|||
- [Fork a project](repository/forking_workflow.md#creating-a-fork).
|
||||
- [Adjust project visibility and access levels](settings/index.md#configure-project-visibility-features-and-permissions).
|
||||
- [Limitations on project and group names](../../user/reserved_names.md#limitations-on-project-and-group-names)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
When working with projects, you might encounter the following issues, or require alternate methods to complete specific tasks.
|
||||
|
||||
### Find projects using an SQL query
|
||||
|
||||
While in [a Rails console session](../../administration/operations/rails_console.md#starting-a-rails-console-session), you can find and store an array of projects based on a SQL query:
|
||||
|
||||
```ruby
|
||||
# Finds projects that end with '%ject'
|
||||
projects = Project.find_by_sql("SELECT * FROM projects WHERE name LIKE '%ject'")
|
||||
=> [#<Project id:12 root/my-first-project>>, #<Project id:13 root/my-second-project>>]
|
||||
```
|
||||
|
||||
### Clear a project's or repository's cache
|
||||
|
||||
If a project or repository has been updated but the state is not reflected in the UI, you may need to clear the project's or repository's cache.
|
||||
You can do so through [a Rails console session](../../administration/operations/rails_console.md#starting-a-rails-console-session) and one of the following:
|
||||
|
||||
WARNING:
|
||||
Any command that changes data directly could be damaging if not run correctly, or under the right conditions. We highly recommend running them in a test environment with a backup of the instance ready to be restored, just in case.
|
||||
|
||||
```ruby
|
||||
## Clear project cache
|
||||
ProjectCacheWorker.perform_async(project.id)
|
||||
|
||||
## Clear repository .exists? cache
|
||||
project.repository.expire_exists_cache
|
||||
```
|
||||
|
||||
### Find projects that are pending deletion
|
||||
|
||||
If you need to find all projects marked for deletion but that have not yet been deleted,
|
||||
[start a Rails console session](../../administration/operations/rails_console.md#starting-a-rails-console-session) and run the following:
|
||||
|
||||
```ruby
|
||||
projects = Project.where(pending_delete: true)
|
||||
projects.each do |p|
|
||||
puts "Project ID: #{p.id}"
|
||||
puts "Project name: #{p.name}"
|
||||
puts "Repository path: #{p.repository.full_path}"
|
||||
end
|
||||
```
|
||||
|
||||
### Delete a project using console
|
||||
|
||||
If a project cannot be deleted, you can attempt to delete it through [Rails console](../../administration/operations/rails_console.md#starting-a-rails-console-session).
|
||||
|
||||
WARNING:
|
||||
Any command that changes data directly could be damaging if not run correctly, or under the right conditions. We highly recommend running them in a test environment with a backup of the instance ready to be restored, just in case.
|
||||
|
||||
```ruby
|
||||
project = Project.find_by_full_path('<project_path>')
|
||||
user = User.find_by_username('<username>')
|
||||
ProjectDestroyWorker.new.perform(project.id, user.id, {})
|
||||
```
|
||||
|
||||
If this fails, display why it doesn't work with:
|
||||
|
||||
```ruby
|
||||
project = Project.find_by_full_path('<project_path>')
|
||||
project.delete_error
|
||||
```
|
||||
|
|
|
|||
|
|
@ -44,9 +44,10 @@ module API
|
|||
end
|
||||
|
||||
get ":channel/index.yaml" do
|
||||
authorize_read_package!(authorized_user_project)
|
||||
project = authorized_user_project(action: :read_package)
|
||||
authorize_read_package!(project)
|
||||
|
||||
packages = Packages::Helm::PackagesFinder.new(authorized_user_project, params[:channel]).execute
|
||||
packages = Packages::Helm::PackagesFinder.new(project, params[:channel]).execute
|
||||
|
||||
env['api.format'] = :yaml
|
||||
present ::Packages::Helm::IndexPresenter.new(params[:id], params[:channel], packages),
|
||||
|
|
@ -61,11 +62,12 @@ module API
|
|||
requires :file_name, type: String, desc: 'Helm package file name'
|
||||
end
|
||||
get ":channel/charts/:file_name.tgz" do
|
||||
authorize_read_package!(authorized_user_project)
|
||||
project = authorized_user_project(action: :read_package)
|
||||
authorize_read_package!(project)
|
||||
|
||||
package_file = Packages::Helm::PackageFilesFinder.new(authorized_user_project, params[:channel], file_name: "#{params[:file_name]}.tgz").most_recent!
|
||||
package_file = Packages::Helm::PackageFilesFinder.new(project, params[:channel], file_name: "#{params[:file_name]}.tgz").most_recent!
|
||||
|
||||
track_package_event('pull_package', :helm, project: authorized_user_project, namespace: authorized_user_project.namespace)
|
||||
track_package_event('pull_package', :helm, project: project, namespace: project.namespace)
|
||||
|
||||
present_package_file!(package_file)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ module Bitbucket
|
|||
@connection = Connection.new(options)
|
||||
end
|
||||
|
||||
def last_issue(repo)
|
||||
parsed_response = connection.get("/repositories/#{repo}/issues?pagelen=1&sort=-created_on&state=ALL")
|
||||
Bitbucket::Representation::Issue.new(parsed_response['values'].first)
|
||||
end
|
||||
|
||||
def issues(repo)
|
||||
path = "/repositories/#{repo}/issues"
|
||||
get_collection(path, :issue)
|
||||
|
|
|
|||
|
|
@ -67,6 +67,14 @@ module Gitlab
|
|||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def allocate_issues_internal_id!(project, client)
|
||||
last_bitbucket_issue = client.last_issue(repo)
|
||||
|
||||
return unless last_bitbucket_issue
|
||||
|
||||
Issue.track_project_iid!(project, last_bitbucket_issue.iid)
|
||||
end
|
||||
|
||||
def repo
|
||||
@repo ||= client.repo(project.import_source)
|
||||
end
|
||||
|
|
@ -84,6 +92,10 @@ module Gitlab
|
|||
def import_issues
|
||||
return unless repo.issues_enabled?
|
||||
|
||||
# If a user creates an issue while the import is in progress, this can lead to an import failure.
|
||||
# The workaround is to allocate IIDs before starting the importer.
|
||||
allocate_issues_internal_id!(project, client)
|
||||
|
||||
create_labels
|
||||
|
||||
issue_type_id = WorkItems::Type.default_issue_type.id
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ module Gitlab
|
|||
store.subscribe ::Pages::InvalidateDomainCacheWorker,
|
||||
to: ::Projects::ProjectAttributesChangedEvent,
|
||||
if: -> (event) { event.pages_related? }
|
||||
store.subscribe ::Pages::InvalidateDomainCacheWorker,
|
||||
to: ::Projects::ProjectFeaturesChangedEvent,
|
||||
if: -> (event) { event.pages_related? }
|
||||
store.subscribe ::Pages::InvalidateDomainCacheWorker, to: ::Groups::GroupTransferedEvent
|
||||
store.subscribe ::Pages::InvalidateDomainCacheWorker, to: ::Groups::GroupPathChangedEvent
|
||||
store.subscribe ::Pages::InvalidateDomainCacheWorker, to: ::Groups::GroupDeletedEvent
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ module Gitlab
|
|||
# The method that will be called for traversing through all the objects to
|
||||
# import, yielding them to the supplied block.
|
||||
def each_object_to_import
|
||||
collection.each_batch(of: BATCH_SIZE) do |batch|
|
||||
collection.each_batch(of: BATCH_SIZE, column: ordering_column) do |batch|
|
||||
batch.each do |record|
|
||||
next if already_imported?(record)
|
||||
|
||||
|
|
@ -41,6 +41,10 @@ module Gitlab
|
|||
raise Gitlab::GithubImport::Exceptions::NotImplementedError, '#collection'
|
||||
end
|
||||
|
||||
def ordering_column
|
||||
:id
|
||||
end
|
||||
|
||||
def object_representation(object)
|
||||
representation_class.from_db_record(object)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module GithubImport
|
||||
module Importer
|
||||
module Attachments
|
||||
class IssuesImporter < ::Gitlab::GithubImport::Importer::Attachments::BaseImporter
|
||||
def sidekiq_worker_class
|
||||
::Gitlab::GithubImport::Attachments::ImportIssueWorker
|
||||
end
|
||||
|
||||
def collection_method
|
||||
:issue_attachments
|
||||
end
|
||||
|
||||
def object_type
|
||||
:issue_attachment
|
||||
end
|
||||
|
||||
def id_for_already_imported_cache(issue)
|
||||
issue.id
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def collection
|
||||
project.issues.select(:id, :description)
|
||||
end
|
||||
|
||||
def ordering_column
|
||||
:iid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module GithubImport
|
||||
module Importer
|
||||
module Attachments
|
||||
class MergeRequestsImporter < ::Gitlab::GithubImport::Importer::Attachments::BaseImporter
|
||||
def sidekiq_worker_class
|
||||
::Gitlab::GithubImport::Attachments::ImportMergeRequestWorker
|
||||
end
|
||||
|
||||
def collection_method
|
||||
:merge_request_attachments
|
||||
end
|
||||
|
||||
def object_type
|
||||
:merge_request_attachment
|
||||
end
|
||||
|
||||
def id_for_already_imported_cache(merge_request)
|
||||
merge_request.id
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def collection
|
||||
project.merge_requests.select(:id, :description)
|
||||
end
|
||||
|
||||
def ordering_column
|
||||
:iid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -43,6 +43,10 @@ module Gitlab
|
|||
case note_text.record_type
|
||||
when ::Release.name
|
||||
::Release.find(note_text.record_db_id).update_column(:description, text)
|
||||
when ::Issue.name
|
||||
::Issue.find(note_text.record_db_id).update_column(:description, text)
|
||||
when ::MergeRequest.name
|
||||
::MergeRequest.find(note_text.record_db_id).update_column(:description, text)
|
||||
when ::Note.name
|
||||
::Note.find(note_text.record_db_id).update_column(:note, text)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# This class only partly represents Release record from DB and
|
||||
# This class only partly represents MODELS_ALLOWLIST records from DB and
|
||||
# is used to connect ReleasesAttachmentsImporter, NotesAttachmentsImporter etc.
|
||||
# with NoteAttachmentsImporter without modifying ObjectImporter a lot.
|
||||
# Attachments are inside release's `description`.
|
||||
|
|
@ -11,7 +11,7 @@ module Gitlab
|
|||
include ToHash
|
||||
include ExposeAttribute
|
||||
|
||||
MODELS_WHITELIST = [::Release, ::Note].freeze
|
||||
MODELS_ALLOWLIST = [::Release, ::Note, ::Issue, ::MergeRequest].freeze
|
||||
ModelNotSupported = Class.new(StandardError)
|
||||
|
||||
attr_reader :attributes
|
||||
|
|
@ -21,12 +21,13 @@ module Gitlab
|
|||
class << self
|
||||
# Builds a note text representation from DB record of Note or Release.
|
||||
#
|
||||
# record - An instance of `Release` or `Note` model.
|
||||
# record - An instance of `Note`, `Release`, `Issue`, `MergeRequest` model
|
||||
def from_db_record(record)
|
||||
check_record_class!(record)
|
||||
|
||||
record_type = record.class.name
|
||||
text = record.is_a?(Release) ? record.description : record.note
|
||||
# only column for note is different along MODELS_ALLOWLIST
|
||||
text = record.is_a?(::Note) ? record.note : record.description
|
||||
new(
|
||||
record_db_id: record.id,
|
||||
record_type: record_type,
|
||||
|
|
@ -41,7 +42,7 @@ module Gitlab
|
|||
private
|
||||
|
||||
def check_record_class!(record)
|
||||
raise ModelNotSupported, record.class.name if MODELS_WHITELIST.exclude?(record.class)
|
||||
raise ModelNotSupported, record.class.name if MODELS_ALLOWLIST.exclude?(record.class)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,25 +2,10 @@
|
|||
|
||||
module Gitlab
|
||||
module Memory
|
||||
# A background thread that observes Ruby heap fragmentation and calls
|
||||
# into a handler when the Ruby heap has been fragmented for an extended
|
||||
# period of time.
|
||||
#
|
||||
# See Gitlab::Metrics::Memory for how heap fragmentation is defined.
|
||||
#
|
||||
# To decide whether a given fragmentation level is being exceeded,
|
||||
# the watchdog regularly polls the GC. Whenever a violation occurs
|
||||
# a strike is issued. If the maximum number of strikes are reached,
|
||||
# a handler is invoked to deal with the situation.
|
||||
#
|
||||
# The duration for which a process may be above a given fragmentation
|
||||
# threshold is computed as `max_strikes * sleep_time_seconds`.
|
||||
# A background thread that monitors Ruby memory and calls
|
||||
# into a handler when the Ruby process violates defined limits
|
||||
# for an extended period of time.
|
||||
class Watchdog
|
||||
DEFAULT_SLEEP_TIME_SECONDS = 60 * 5
|
||||
DEFAULT_MAX_HEAP_FRAG = 0.5
|
||||
DEFAULT_MAX_MEM_GROWTH = 3.0
|
||||
DEFAULT_MAX_STRIKES = 5
|
||||
|
||||
# This handler does nothing. It returns `false` to indicate to the
|
||||
# caller that the situation has not been dealt with so it will
|
||||
# receive calls repeatedly if fragmentation remains high.
|
||||
|
|
@ -62,73 +47,27 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
# max_heap_fragmentation:
|
||||
# The degree to which the Ruby heap is allowed to be fragmented. Range [0,1].
|
||||
# max_mem_growth:
|
||||
# A multiplier for how much excess private memory a worker can map compared to a reference process
|
||||
# (itself or the primary in a pre-fork server.)
|
||||
# max_strikes:
|
||||
# How many times the process is allowed to be above max_heap_fragmentation before
|
||||
# a handler is invoked.
|
||||
# sleep_time_seconds:
|
||||
# Used to control the frequency with which the watchdog will wake up and poll the GC.
|
||||
def initialize(
|
||||
handler: NullHandler.instance,
|
||||
logger: Logger.new($stdout),
|
||||
max_heap_fragmentation: ENV['GITLAB_MEMWD_MAX_HEAP_FRAG']&.to_f || DEFAULT_MAX_HEAP_FRAG,
|
||||
max_mem_growth: ENV['GITLAB_MEMWD_MAX_MEM_GROWTH']&.to_f || DEFAULT_MAX_MEM_GROWTH,
|
||||
max_strikes: ENV['GITLAB_MEMWD_MAX_STRIKES']&.to_i || DEFAULT_MAX_STRIKES,
|
||||
sleep_time_seconds: ENV['GITLAB_MEMWD_SLEEP_TIME_SEC']&.to_i || DEFAULT_SLEEP_TIME_SECONDS,
|
||||
**options)
|
||||
super(**options)
|
||||
|
||||
@handler = handler
|
||||
@logger = logger
|
||||
@sleep_time_seconds = sleep_time_seconds
|
||||
@max_strikes = max_strikes
|
||||
@stats = {
|
||||
heap_frag: {
|
||||
max: max_heap_fragmentation,
|
||||
strikes: 0
|
||||
},
|
||||
mem_growth: {
|
||||
max: max_mem_growth,
|
||||
strikes: 0
|
||||
}
|
||||
}
|
||||
|
||||
def initialize
|
||||
@configuration = Configuration.new
|
||||
@alive = true
|
||||
|
||||
init_prometheus_metrics(max_heap_fragmentation)
|
||||
init_prometheus_metrics
|
||||
end
|
||||
|
||||
attr_reader :max_strikes, :sleep_time_seconds
|
||||
|
||||
def max_heap_fragmentation
|
||||
@stats[:heap_frag][:max]
|
||||
end
|
||||
|
||||
def max_mem_growth
|
||||
@stats[:mem_growth][:max]
|
||||
end
|
||||
|
||||
def strikes(stat)
|
||||
@stats[stat][:strikes]
|
||||
def configure
|
||||
yield @configuration
|
||||
end
|
||||
|
||||
def call
|
||||
@logger.info(log_labels.merge(message: 'started'))
|
||||
logger.info(log_labels.merge(message: 'started'))
|
||||
|
||||
while @alive
|
||||
sleep(@sleep_time_seconds)
|
||||
sleep(sleep_time_seconds)
|
||||
|
||||
next unless Feature.enabled?(:gitlab_memory_watchdog, type: :ops)
|
||||
|
||||
monitor_heap_fragmentation
|
||||
monitor_memory_growth
|
||||
monitor if Feature.enabled?(:gitlab_memory_watchdog, type: :ops)
|
||||
end
|
||||
|
||||
@logger.info(log_labels.merge(message: 'stopped'))
|
||||
logger.info(log_labels.merge(message: 'stopped'))
|
||||
end
|
||||
|
||||
def stop
|
||||
|
|
@ -137,71 +76,24 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def monitor_memory_condition(stat_key)
|
||||
return unless @alive
|
||||
def monitor
|
||||
@configuration.monitors.call_each do |result|
|
||||
break unless @alive
|
||||
|
||||
stat = @stats[stat_key]
|
||||
next unless result.threshold_violated?
|
||||
|
||||
ok, labels = yield(stat)
|
||||
@counter_violations.increment(reason: result.monitor_name)
|
||||
|
||||
if ok
|
||||
stat[:strikes] = 0
|
||||
else
|
||||
stat[:strikes] += 1
|
||||
@counter_violations.increment(reason: stat_key.to_s)
|
||||
end
|
||||
next unless result.strikes_exceeded?
|
||||
|
||||
if stat[:strikes] > @max_strikes
|
||||
@alive = !memory_limit_exceeded_callback(stat_key, labels)
|
||||
stat[:strikes] = 0
|
||||
@alive = !memory_limit_exceeded_callback(result.monitor_name, result.payload)
|
||||
end
|
||||
end
|
||||
|
||||
def monitor_heap_fragmentation
|
||||
monitor_memory_condition(:heap_frag) do |stat|
|
||||
heap_fragmentation = Gitlab::Metrics::Memory.gc_heap_fragmentation
|
||||
[
|
||||
heap_fragmentation <= stat[:max],
|
||||
{
|
||||
message: 'heap fragmentation limit exceeded',
|
||||
memwd_cur_heap_frag: heap_fragmentation,
|
||||
memwd_max_heap_frag: stat[:max]
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def monitor_memory_growth
|
||||
monitor_memory_condition(:mem_growth) do |stat|
|
||||
worker_uss = Gitlab::Metrics::System.memory_usage_uss_pss[:uss]
|
||||
reference_uss = reference_mem[:uss]
|
||||
memory_limit = stat[:max] * reference_uss
|
||||
[
|
||||
worker_uss <= memory_limit,
|
||||
{
|
||||
message: 'memory limit exceeded',
|
||||
memwd_uss_bytes: worker_uss,
|
||||
memwd_ref_uss_bytes: reference_uss,
|
||||
memwd_max_uss_bytes: memory_limit
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
# On pre-fork systems this would be the primary process memory from which workers fork.
|
||||
# Otherwise it is the current process' memory.
|
||||
#
|
||||
# We initialize this lazily because in the initializer the application may not have
|
||||
# finished booting yet, which would yield an incorrect baseline.
|
||||
def reference_mem
|
||||
@reference_mem ||= Gitlab::Metrics::System.memory_usage_uss_pss(pid: Gitlab::Cluster::PRIMARY_PID)
|
||||
end
|
||||
|
||||
def memory_limit_exceeded_callback(stat_key, handler_labels)
|
||||
all_labels = log_labels.merge(handler_labels)
|
||||
.merge(memwd_cur_strikes: strikes(stat_key))
|
||||
@logger.warn(all_labels)
|
||||
@counter_violations_handled.increment(reason: stat_key.to_s)
|
||||
def memory_limit_exceeded_callback(monitor_name, monitor_payload)
|
||||
all_labels = log_labels.merge(monitor_payload)
|
||||
logger.warn(all_labels)
|
||||
@counter_violations_handled.increment(reason: monitor_name)
|
||||
|
||||
handler.call
|
||||
end
|
||||
|
|
@ -211,7 +103,15 @@ module Gitlab
|
|||
# all that happens is we collect logs and Prometheus events for fragmentation violations.
|
||||
return NullHandler.instance unless Feature.enabled?(:enforce_memory_watchdog, type: :ops)
|
||||
|
||||
@handler
|
||||
@configuration.handler
|
||||
end
|
||||
|
||||
def logger
|
||||
@configuration.logger
|
||||
end
|
||||
|
||||
def sleep_time_seconds
|
||||
@configuration.sleep_time_seconds
|
||||
end
|
||||
|
||||
def log_labels
|
||||
|
|
@ -219,27 +119,20 @@ module Gitlab
|
|||
pid: $$,
|
||||
worker_id: worker_id,
|
||||
memwd_handler_class: handler.class.name,
|
||||
memwd_sleep_time_s: @sleep_time_seconds,
|
||||
memwd_max_strikes: @max_strikes,
|
||||
memwd_sleep_time_s: sleep_time_seconds,
|
||||
memwd_rss_bytes: process_rss_bytes
|
||||
}
|
||||
end
|
||||
|
||||
def worker_id
|
||||
::Prometheus::PidProvider.worker_id
|
||||
end
|
||||
|
||||
def process_rss_bytes
|
||||
Gitlab::Metrics::System.memory_usage_rss
|
||||
end
|
||||
|
||||
def init_prometheus_metrics(max_heap_fragmentation)
|
||||
@heap_frag_limit = Gitlab::Metrics.gauge(
|
||||
:gitlab_memwd_heap_frag_limit,
|
||||
'The configured limit for how fragmented the Ruby heap is allowed to be'
|
||||
)
|
||||
@heap_frag_limit.set({}, max_heap_fragmentation)
|
||||
def worker_id
|
||||
::Prometheus::PidProvider.worker_id
|
||||
end
|
||||
|
||||
def init_prometheus_metrics
|
||||
default_labels = { pid: worker_id }
|
||||
@counter_violations = Gitlab::Metrics.counter(
|
||||
:gitlab_memwd_violations_total,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Memory
|
||||
class Watchdog
|
||||
class Configuration
|
||||
class MonitorStack
|
||||
def initialize
|
||||
@monitors = []
|
||||
end
|
||||
|
||||
def use(monitor_class, *args, **kwargs, &block)
|
||||
remove(monitor_class)
|
||||
@monitors.push(build_monitor_state(monitor_class, *args, **kwargs, &block))
|
||||
end
|
||||
|
||||
def call_each
|
||||
@monitors.each do |monitor|
|
||||
yield monitor.call
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remove(monitor_class)
|
||||
@monitors.delete_if { |monitor| monitor.monitor_class == monitor_class }
|
||||
end
|
||||
|
||||
def build_monitor_state(monitor_class, *args, max_strikes:, **kwargs, &block)
|
||||
monitor = build_monitor(monitor_class, *args, **kwargs, &block)
|
||||
|
||||
Gitlab::Memory::Watchdog::MonitorState.new(monitor, max_strikes: max_strikes)
|
||||
end
|
||||
|
||||
def build_monitor(monitor_class, *args, **kwargs, &block)
|
||||
monitor_class.new(*args, **kwargs, &block)
|
||||
end
|
||||
end
|
||||
|
||||
DEFAULT_SLEEP_TIME_SECONDS = 60
|
||||
|
||||
attr_reader :monitors
|
||||
attr_writer :logger, :handler, :sleep_time_seconds
|
||||
|
||||
def initialize
|
||||
@monitors = MonitorStack.new
|
||||
end
|
||||
|
||||
def handler
|
||||
@handler ||= NullHandler.instance
|
||||
end
|
||||
|
||||
def logger
|
||||
@logger ||= Gitlab::Logger.new($stdout)
|
||||
end
|
||||
|
||||
# Used to control the frequency with which the watchdog will wake up and poll the GC.
|
||||
def sleep_time_seconds
|
||||
@sleep_time_seconds ||= DEFAULT_SLEEP_TIME_SECONDS
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Memory
|
||||
class Watchdog
|
||||
module Monitor
|
||||
# A monitor that observes Ruby heap fragmentation and calls
|
||||
# memory_violation_callback when the Ruby heap has been fragmented for an extended
|
||||
# period of time.
|
||||
#
|
||||
# See Gitlab::Metrics::Memory for how heap fragmentation is defined.
|
||||
class HeapFragmentation
|
||||
attr_reader :max_heap_fragmentation
|
||||
|
||||
# max_heap_fragmentation:
|
||||
# The degree to which the Ruby heap is allowed to be fragmented. Range [0,1].
|
||||
def initialize(max_heap_fragmentation:)
|
||||
@max_heap_fragmentation = max_heap_fragmentation
|
||||
init_frag_limit_metrics
|
||||
end
|
||||
|
||||
def call
|
||||
heap_fragmentation = Gitlab::Metrics::Memory.gc_heap_fragmentation
|
||||
|
||||
return { threshold_violated: false, payload: {} } unless heap_fragmentation > max_heap_fragmentation
|
||||
|
||||
{ threshold_violated: true, payload: payload(heap_fragmentation) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def payload(heap_fragmentation)
|
||||
{
|
||||
message: 'heap fragmentation limit exceeded',
|
||||
memwd_cur_heap_frag: heap_fragmentation,
|
||||
memwd_max_heap_frag: max_heap_fragmentation
|
||||
}
|
||||
end
|
||||
|
||||
def init_frag_limit_metrics
|
||||
heap_frag_limit = Gitlab::Metrics.gauge(
|
||||
:gitlab_memwd_heap_frag_limit,
|
||||
'The configured limit for how fragmented the Ruby heap is allowed to be'
|
||||
)
|
||||
heap_frag_limit.set({}, max_heap_fragmentation)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Memory
|
||||
class Watchdog
|
||||
module Monitor
|
||||
class UniqueMemoryGrowth
|
||||
attr_reader :max_mem_growth
|
||||
|
||||
def initialize(max_mem_growth:)
|
||||
@max_mem_growth = max_mem_growth
|
||||
end
|
||||
|
||||
def call
|
||||
worker_uss = Gitlab::Metrics::System.memory_usage_uss_pss[:uss]
|
||||
reference_uss = reference_mem[:uss]
|
||||
memory_limit = max_mem_growth * reference_uss
|
||||
|
||||
return { threshold_violated: false, payload: {} } unless worker_uss > memory_limit
|
||||
|
||||
{ threshold_violated: true, payload: payload(worker_uss, reference_uss, memory_limit) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def payload(worker_uss, reference_uss, memory_limit)
|
||||
{
|
||||
message: 'memory limit exceeded',
|
||||
memwd_uss_bytes: worker_uss,
|
||||
memwd_ref_uss_bytes: reference_uss,
|
||||
memwd_max_uss_bytes: memory_limit
|
||||
}
|
||||
end
|
||||
|
||||
# On pre-fork systems this would be the primary process memory from which workers fork.
|
||||
# Otherwise it is the current process' memory.
|
||||
#
|
||||
# We initialize this lazily because in the initializer the application may not have
|
||||
# finished booting yet, which would yield an incorrect baseline.
|
||||
def reference_mem
|
||||
@reference_mem ||= Gitlab::Metrics::System.memory_usage_uss_pss(pid: Gitlab::Cluster::PRIMARY_PID)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Memory
|
||||
class Watchdog
|
||||
class MonitorState
|
||||
class Result
|
||||
attr_reader :payload
|
||||
|
||||
def initialize(strikes_exceeded:, threshold_violated:, monitor_class:, payload: )
|
||||
@strikes_exceeded = strikes_exceeded
|
||||
@threshold_violated = threshold_violated
|
||||
@monitor_class = monitor_class
|
||||
@payload = payload
|
||||
end
|
||||
|
||||
def strikes_exceeded?
|
||||
@strikes_exceeded
|
||||
end
|
||||
|
||||
def threshold_violated?
|
||||
@threshold_violated
|
||||
end
|
||||
|
||||
def monitor_name
|
||||
@monitor_class.name.demodulize.underscore.to_sym
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(monitor, max_strikes:)
|
||||
@monitor = monitor
|
||||
@max_strikes = max_strikes
|
||||
@strikes = 0
|
||||
end
|
||||
|
||||
def call
|
||||
reset_strikes if strikes_exceeded?
|
||||
|
||||
monitor_result = @monitor.call
|
||||
|
||||
if monitor_result[:threshold_violated]
|
||||
issue_strike
|
||||
else
|
||||
reset_strikes
|
||||
end
|
||||
|
||||
build_result(monitor_result)
|
||||
end
|
||||
|
||||
def monitor_class
|
||||
@monitor.class
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_result(monitor_result)
|
||||
Result.new(
|
||||
strikes_exceeded: strikes_exceeded?,
|
||||
monitor_class: monitor_class,
|
||||
threshold_violated: monitor_result[:threshold_violated],
|
||||
payload: payload.merge(monitor_result[:payload]))
|
||||
end
|
||||
|
||||
def payload
|
||||
{
|
||||
memwd_max_strikes: @max_strikes,
|
||||
memwd_cur_strikes: @strikes
|
||||
}
|
||||
end
|
||||
|
||||
def strikes_exceeded?
|
||||
@strikes > @max_strikes
|
||||
end
|
||||
|
||||
def issue_strike
|
||||
@strikes += 1
|
||||
end
|
||||
|
||||
def reset_strikes
|
||||
@strikes = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -15820,6 +15820,57 @@ msgstr ""
|
|||
msgid "Events API"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|accepted"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|added"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|approved"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|closed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|commented on"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|created"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|deleted"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|destroyed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|imported"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|joined"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|left"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|opened"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|pushed new"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|pushed to"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|removed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|removed due to membership expiration from"
|
||||
msgstr ""
|
||||
|
||||
msgid "Event|updated"
|
||||
msgstr ""
|
||||
|
||||
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -16273,9 +16324,6 @@ msgstr ""
|
|||
msgid "Failed to create import label for jira import."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to create new access token: %{token_response_message}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to create repository"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -18677,6 +18725,9 @@ msgstr ""
|
|||
msgid "Group"
|
||||
msgstr ""
|
||||
|
||||
msgid "Group %{group_name} and its Mattermost team were successfully created."
|
||||
msgstr ""
|
||||
|
||||
msgid "Group %{group_name} couldn't be exported."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -35312,10 +35363,10 @@ msgstr ""
|
|||
msgid "Saving project."
|
||||
msgstr ""
|
||||
|
||||
msgid "ScanExecutionPolicy|%{ifLabelStart}if%{ifLabelEnd} %{rules} actions for the %{scopes} %{branches}"
|
||||
msgid "ScanExecutionPolicy|%{period} %{days} at %{time}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ScanExecutionPolicy|%{period} %{days} at %{time}"
|
||||
msgid "ScanExecutionPolicy|%{rules} actions for the %{scopes} %{branches} %{agents} %{namespaces}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ScanExecutionPolicy|%{thenLabelStart}Then%{thenLabelEnd} Require a %{scan} scan to run"
|
||||
|
|
@ -35336,9 +35387,15 @@ msgstr ""
|
|||
msgid "ScanExecutionPolicy|Schedule rule component"
|
||||
msgstr ""
|
||||
|
||||
msgid "ScanExecutionPolicy|Select agent"
|
||||
msgstr ""
|
||||
|
||||
msgid "ScanExecutionPolicy|Select branches"
|
||||
msgstr ""
|
||||
|
||||
msgid "ScanExecutionPolicy|Select namespaces"
|
||||
msgstr ""
|
||||
|
||||
msgid "ScanExecutionPolicy|Select scanner profile"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -35348,9 +35405,15 @@ msgstr ""
|
|||
msgid "ScanExecutionPolicy|Site profile"
|
||||
msgstr ""
|
||||
|
||||
msgid "ScanExecutionPolicy|agent"
|
||||
msgstr ""
|
||||
|
||||
msgid "ScanExecutionPolicy|branch"
|
||||
msgstr ""
|
||||
|
||||
msgid "ScanExecutionPolicy|in namespaces"
|
||||
msgstr ""
|
||||
|
||||
msgid "ScanResultPolicy|%{ifLabelStart}if%{ifLabelEnd} %{scanners} find(s) more than %{vulnerabilitiesAllowed} %{severities} %{vulnerabilityStates} vulnerabilities in an open merge request targeting %{branches}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -46784,9 +46847,6 @@ msgstr ""
|
|||
msgid "Your new %{type}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your new access token has been created."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your new comment"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -46956,9 +47016,6 @@ msgstr[1] ""
|
|||
msgid "access:"
|
||||
msgstr ""
|
||||
|
||||
msgid "added"
|
||||
msgstr ""
|
||||
|
||||
msgid "added %{emails}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -48625,9 +48682,6 @@ msgstr ""
|
|||
msgid "remove weight"
|
||||
msgstr ""
|
||||
|
||||
msgid "removed"
|
||||
msgstr ""
|
||||
|
||||
msgid "removed a %{link_type} link"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ gem 'gitlab-qa', '~> 8', require: 'gitlab/qa'
|
|||
gem 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile
|
||||
gem 'allure-rspec', '~> 2.16.0'
|
||||
gem 'capybara', '~> 3.35.0'
|
||||
gem 'capybara-screenshot', '~> 1.0.23'
|
||||
gem 'capybara-screenshot', '~> 1.0.26'
|
||||
gem 'rake', '~> 13'
|
||||
gem 'rspec', '~> 3.10'
|
||||
gem 'selenium-webdriver', '~> 4.0'
|
||||
|
|
@ -21,7 +21,7 @@ gem 'rotp', '~> 3.1.0'
|
|||
gem 'timecop', '~> 0.9.5'
|
||||
gem 'parallel', '~> 1.19'
|
||||
gem 'rainbow', '~> 3.0.0'
|
||||
gem 'rspec-parameterized', '~> 0.4.2'
|
||||
gem 'rspec-parameterized', '~> 0.5.2'
|
||||
gem 'octokit', '~> 5.6.1'
|
||||
gem "faraday-retry", "~> 2.0"
|
||||
gem 'webdrivers', '~> 5.2'
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
abstract_type (0.0.7)
|
||||
activesupport (6.1.4.7)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
zeitwerk (~> 2.3)
|
||||
adamantium (0.2.0)
|
||||
ice_nine (~> 0.11.0)
|
||||
memoizable (~> 0.4.0)
|
||||
addressable (2.8.1)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
airborne (0.3.4)
|
||||
|
|
@ -39,7 +35,7 @@ GEM
|
|||
rack-test (>= 0.6.3)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
capybara-screenshot (1.0.23)
|
||||
capybara-screenshot (1.0.26)
|
||||
capybara (>= 1.0, < 4)
|
||||
launchy
|
||||
chemlab (0.9.2)
|
||||
|
|
@ -53,9 +49,6 @@ GEM
|
|||
childprocess (4.1.0)
|
||||
coderay (1.1.2)
|
||||
colorize (0.8.1)
|
||||
concord (0.1.5)
|
||||
adamantium (~> 0.2.0)
|
||||
equalizer (~> 0.0.9)
|
||||
concurrent-ruby (1.1.10)
|
||||
confiner (0.3.0)
|
||||
gitlab (>= 4.17)
|
||||
|
|
@ -66,7 +59,6 @@ GEM
|
|||
diff-lcs (1.3)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
equalizer (0.0.11)
|
||||
excon (0.92.4)
|
||||
faker (2.19.0)
|
||||
i18n (>= 1.6, < 2)
|
||||
|
|
@ -162,21 +154,18 @@ GEM
|
|||
httpclient (2.8.3)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
ice_nine (0.11.2)
|
||||
influxdb-client (1.17.0)
|
||||
jwt (2.5.0)
|
||||
knapsack (4.0.0)
|
||||
rake
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
launchy (2.5.0)
|
||||
addressable (~> 2.7)
|
||||
llhttp-ffi (0.4.0)
|
||||
ffi-compiler (~> 1.0)
|
||||
rake (~> 13.0)
|
||||
macaddr (1.7.2)
|
||||
systemu (~> 2.6.5)
|
||||
memoist (0.16.2)
|
||||
memoizable (0.4.2)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
method_source (0.9.0)
|
||||
mime-types (3.4.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
|
|
@ -204,7 +193,6 @@ GEM
|
|||
coderay
|
||||
parser
|
||||
unparser
|
||||
procto (0.0.3)
|
||||
pry (0.11.3)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
|
|
@ -244,7 +232,7 @@ GEM
|
|||
rspec-mocks (3.10.2)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-parameterized (0.4.2)
|
||||
rspec-parameterized (0.5.2)
|
||||
binding_ninja (>= 0.2.3)
|
||||
parser
|
||||
proc_to_ast
|
||||
|
|
@ -276,7 +264,6 @@ GEM
|
|||
table_print (1.5.7)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
thread_safe (0.3.6)
|
||||
timecop (0.9.5)
|
||||
trailblazer-option (0.1.2)
|
||||
tzinfo (2.0.5)
|
||||
|
|
@ -286,14 +273,9 @@ GEM
|
|||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (2.3.0)
|
||||
unparser (0.4.7)
|
||||
abstract_type (~> 0.0.7)
|
||||
adamantium (~> 0.2.0)
|
||||
concord (~> 0.1.5)
|
||||
unparser (0.6.5)
|
||||
diff-lcs (~> 1.3)
|
||||
equalizer (~> 0.0.9)
|
||||
parser (>= 2.6.5)
|
||||
procto (~> 0.0.2)
|
||||
parser (>= 3.1.0)
|
||||
uuid (2.3.9)
|
||||
macaddr (~> 1.0)
|
||||
warning (1.3.0)
|
||||
|
|
@ -317,7 +299,7 @@ DEPENDENCIES
|
|||
airborne (~> 0.3.4)
|
||||
allure-rspec (~> 2.16.0)
|
||||
capybara (~> 3.35.0)
|
||||
capybara-screenshot (~> 1.0.23)
|
||||
capybara-screenshot (~> 1.0.26)
|
||||
chemlab (~> 0.9)
|
||||
chemlab-library-www-gitlab-com (~> 0.1)
|
||||
confiner (~> 0.3)
|
||||
|
|
@ -339,7 +321,7 @@ DEPENDENCIES
|
|||
rest-client (~> 2.1.0)
|
||||
rotp (~> 3.1.0)
|
||||
rspec (~> 3.10)
|
||||
rspec-parameterized (~> 0.4.2)
|
||||
rspec-parameterized (~> 0.5.2)
|
||||
rspec-retry (~> 0.6.1)
|
||||
rspec_junit_formatter (~> 0.4.1)
|
||||
ruby-debug-ide (~> 0.7.3)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'open3'
|
||||
|
||||
class MigrationSchemaValidator
|
||||
FILENAME = 'db/structure.sql'
|
||||
|
||||
MIGRATION_DIRS = %w[db/migrate db/post_migrate].freeze
|
||||
|
||||
SCHEMA_VERSION_DIR = 'db/schema_migrations'
|
||||
|
||||
VERSION_DIGITS = 14
|
||||
|
||||
def validate!
|
||||
if committed_migrations.empty?
|
||||
puts "\e[32m No migrations found, skipping schema validation\e[0m"
|
||||
return
|
||||
end
|
||||
|
||||
validate_schema_on_rollback!
|
||||
validate_schema_on_migrate!
|
||||
validate_schema_version_files!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_schema_on_rollback!
|
||||
committed_migrations.reverse_each do |filename|
|
||||
version = find_migration_version(filename)
|
||||
|
||||
run("scripts/db_tasks db:migrate:down VERSION=#{version}")
|
||||
run("scripts/db_tasks db:schema:dump")
|
||||
end
|
||||
|
||||
git_command = "git diff #{diff_target} -- #{FILENAME}"
|
||||
base_message = "rollback of added migrations does not revert #{FILENAME} to previous state"
|
||||
|
||||
validate_clean_output!(git_command, base_message)
|
||||
end
|
||||
|
||||
def validate_schema_on_migrate!
|
||||
run("scripts/db_tasks db:migrate")
|
||||
run("scripts/db_tasks db:schema:dump")
|
||||
|
||||
git_command = "git diff -- #{FILENAME}"
|
||||
base_message = "the committed #{FILENAME} does not match the one generated by running added migrations"
|
||||
|
||||
validate_clean_output!(git_command, base_message)
|
||||
end
|
||||
|
||||
def validate_schema_version_files!
|
||||
git_command = "git add -A -n #{SCHEMA_VERSION_DIR}"
|
||||
base_message = "the committed files in #{SCHEMA_VERSION_DIR} do not match those expected by the added migrations"
|
||||
|
||||
validate_clean_output!(git_command, base_message)
|
||||
end
|
||||
|
||||
def committed_migrations
|
||||
@committed_migrations ||= begin
|
||||
git_command = "git diff --name-only --diff-filter=A #{diff_target} -- #{MIGRATION_DIRS.join(' ')}"
|
||||
|
||||
run(git_command).split("\n")
|
||||
end
|
||||
end
|
||||
|
||||
def diff_target
|
||||
@diff_target ||= pipeline_for_merged_results? ? target_branch : merge_base
|
||||
end
|
||||
|
||||
def merge_base
|
||||
run("git merge-base #{target_branch} #{source_ref}")
|
||||
end
|
||||
|
||||
def target_branch
|
||||
ENV['CI_MERGE_REQUEST_TARGET_BRANCH_NAME'] || ENV['TARGET'] || ENV['CI_DEFAULT_BRANCH'] || 'master'
|
||||
end
|
||||
|
||||
def source_ref
|
||||
ENV['CI_COMMIT_SHA'] || 'HEAD'
|
||||
end
|
||||
|
||||
def pipeline_for_merged_results?
|
||||
ENV.key?('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA')
|
||||
end
|
||||
|
||||
def find_migration_version(filename)
|
||||
file_basename = File.basename(filename)
|
||||
version_match = /\A(?<version>\d{#{VERSION_DIGITS}})_/o.match(file_basename)
|
||||
|
||||
die "#{filename} has an invalid migration version" if version_match.nil?
|
||||
|
||||
version_match[:version]
|
||||
end
|
||||
|
||||
def validate_clean_output!(command, base_message)
|
||||
command_output = run(command)
|
||||
|
||||
return if command_output.empty?
|
||||
|
||||
die "#{base_message}:\n#{command_output}"
|
||||
end
|
||||
|
||||
def die(message, error_code: 1)
|
||||
puts "\e[31mError: #{message}\e[0m"
|
||||
exit error_code
|
||||
end
|
||||
|
||||
def run(cmd)
|
||||
puts "\e[32m$ #{cmd}\e[37m"
|
||||
stdout_str, stderr_str, status = Open3.capture3(cmd)
|
||||
puts "#{stdout_str}#{stderr_str}\e[0m"
|
||||
|
||||
die "command failed: #{stderr_str}" unless status.success?
|
||||
|
||||
stdout_str.chomp
|
||||
end
|
||||
end
|
||||
|
|
@ -96,7 +96,7 @@ def timed(task)
|
|||
puts "#{task} finished in #{Time.now - start} seconds.\n"
|
||||
end
|
||||
|
||||
if $0 == __FILE__
|
||||
if $PROGRAM_NAME == __FILE__
|
||||
options = {
|
||||
dry_run: false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'migration_schema_validator'
|
||||
|
||||
class PostDeploymentMigrationsValidator < MigrationSchemaValidator
|
||||
def validate!
|
||||
if committed_migrations.empty?
|
||||
puts "\e[32m No migrations found, skipping post-deployment migrations validation\e[0m"
|
||||
return
|
||||
end
|
||||
|
||||
rollback_commited_migrations
|
||||
|
||||
run("SKIP_POST_DEPLOYMENT_MIGRATIONS=true scripts/db_tasks db:migrate")
|
||||
run("scripts/db_tasks db:migrate")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rollback_commited_migrations
|
||||
committed_migrations.reverse_each do |filename|
|
||||
version = find_migration_version(filename)
|
||||
|
||||
run("scripts/db_tasks db:migrate:down VERSION=#{version}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
PostDeploymentMigrationsValidator.new.validate!
|
||||
|
|
@ -265,7 +265,7 @@ def timed(task)
|
|||
puts "#{task} finished in #{Time.now - start} seconds.\n"
|
||||
end
|
||||
|
||||
if $0 == __FILE__
|
||||
if $PROGRAM_NAME == __FILE__
|
||||
options = {
|
||||
dry_run: false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,120 +2,6 @@
|
|||
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'open3'
|
||||
|
||||
class MigrationSchemaValidator
|
||||
FILENAME = 'db/structure.sql'
|
||||
|
||||
MIGRATION_DIRS = %w[db/migrate db/post_migrate].freeze
|
||||
|
||||
SCHEMA_VERSION_DIR = 'db/schema_migrations'
|
||||
|
||||
VERSION_DIGITS = 14
|
||||
|
||||
def validate!
|
||||
if committed_migrations.empty?
|
||||
puts "\e[32m No migrations found, skipping schema validation\e[0m"
|
||||
return
|
||||
end
|
||||
|
||||
validate_schema_on_rollback!
|
||||
validate_schema_on_migrate!
|
||||
validate_schema_version_files!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_schema_on_rollback!
|
||||
committed_migrations.reverse_each do |filename|
|
||||
version = find_migration_version(filename)
|
||||
|
||||
run("scripts/db_tasks db:migrate:down VERSION=#{version}")
|
||||
run("scripts/db_tasks db:schema:dump")
|
||||
end
|
||||
|
||||
git_command = "git diff #{diff_target} -- #{FILENAME}"
|
||||
base_message = "rollback of added migrations does not revert #{FILENAME} to previous state"
|
||||
|
||||
validate_clean_output!(git_command, base_message)
|
||||
end
|
||||
|
||||
def validate_schema_on_migrate!
|
||||
run("scripts/db_tasks db:migrate")
|
||||
run("scripts/db_tasks db:schema:dump")
|
||||
|
||||
git_command = "git diff -- #{FILENAME}"
|
||||
base_message = "the committed #{FILENAME} does not match the one generated by running added migrations"
|
||||
|
||||
validate_clean_output!(git_command, base_message)
|
||||
end
|
||||
|
||||
def validate_schema_version_files!
|
||||
git_command = "git add -A -n #{SCHEMA_VERSION_DIR}"
|
||||
base_message = "the committed files in #{SCHEMA_VERSION_DIR} do not match those expected by the added migrations"
|
||||
|
||||
validate_clean_output!(git_command, base_message)
|
||||
end
|
||||
|
||||
def committed_migrations
|
||||
@committed_migrations ||= begin
|
||||
git_command = "git diff --name-only --diff-filter=A #{diff_target} -- #{MIGRATION_DIRS.join(' ')}"
|
||||
|
||||
run(git_command).split("\n")
|
||||
end
|
||||
end
|
||||
|
||||
def diff_target
|
||||
@diff_target ||= pipeline_for_merged_results? ? target_branch : merge_base
|
||||
end
|
||||
|
||||
def merge_base
|
||||
run("git merge-base #{target_branch} #{source_ref}")
|
||||
end
|
||||
|
||||
def target_branch
|
||||
ENV['CI_MERGE_REQUEST_TARGET_BRANCH_NAME'] || ENV['TARGET'] || ENV['CI_DEFAULT_BRANCH'] || 'master'
|
||||
end
|
||||
|
||||
def source_ref
|
||||
ENV['CI_COMMIT_SHA'] || 'HEAD'
|
||||
end
|
||||
|
||||
def pipeline_for_merged_results?
|
||||
ENV.key?('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA')
|
||||
end
|
||||
|
||||
def find_migration_version(filename)
|
||||
file_basename = File.basename(filename)
|
||||
version_match = /\A(?<version>\d{#{VERSION_DIGITS}})_/o.match(file_basename)
|
||||
|
||||
die "#{filename} has an invalid migration version" if version_match.nil?
|
||||
|
||||
version_match[:version]
|
||||
end
|
||||
|
||||
def validate_clean_output!(command, base_message)
|
||||
command_output = run(command)
|
||||
|
||||
return if command_output.empty?
|
||||
|
||||
die "#{base_message}:\n#{command_output}"
|
||||
end
|
||||
|
||||
def die(message, error_code: 1)
|
||||
puts "\e[31mError: #{message}\e[0m"
|
||||
exit error_code
|
||||
end
|
||||
|
||||
def run(cmd)
|
||||
puts "\e[32m$ #{cmd}\e[37m"
|
||||
stdout_str, stderr_str, status = Open3.capture3(cmd)
|
||||
puts "#{stdout_str}#{stderr_str}\e[0m"
|
||||
|
||||
die "command failed: #{stderr_str}" unless status.success?
|
||||
|
||||
stdout_str.chomp
|
||||
end
|
||||
end
|
||||
require_relative 'migration_schema_validator'
|
||||
|
||||
MigrationSchemaValidator.new.validate!
|
||||
|
|
|
|||
|
|
@ -72,29 +72,11 @@ RSpec.describe Groups::RunnersController do
|
|||
expect(response).to render_template(:show)
|
||||
end
|
||||
|
||||
context 'when runners_finder_all_available is enabled' do
|
||||
before do
|
||||
stub_feature_flags(runners_finder_all_available: true)
|
||||
end
|
||||
it 'renders show with 200 status code instance runner' do
|
||||
get :show, params: { group_id: group, id: instance_runner }
|
||||
|
||||
it 'renders show with 200 status code instance runner' do
|
||||
get :show, params: { group_id: group, id: instance_runner }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template(:show)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when runners_finder_all_available is disabled' do
|
||||
before do
|
||||
stub_feature_flags(runners_finder_all_available: false)
|
||||
end
|
||||
|
||||
it 'renders show with a 404 instance runner' do
|
||||
get :show, params: { group_id: group, id: instance_runner }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template(:show)
|
||||
end
|
||||
|
||||
it 'renders show with 200 status code project runner' do
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ RSpec.describe GroupsController, factory_default: :keep do
|
|||
sign_in(user)
|
||||
|
||||
expect do
|
||||
post :create, params: { group: { name: 'new_group', path: "new_group" } }
|
||||
post :create, params: { group: { name: 'new_group', path: 'new_group' } }
|
||||
end.to change { Group.count }.by(1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
|
|
@ -240,13 +240,31 @@ RSpec.describe GroupsController, factory_default: :keep do
|
|||
sign_in(create(:admin))
|
||||
|
||||
expect do
|
||||
post :create, params: { group: { name: 'new_group', path: "new_group" } }
|
||||
post :create, params: { group: { name: 'new_group', path: 'new_group' } }
|
||||
end.to change { Group.count }.by(1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when creating chat team' do
|
||||
before do
|
||||
stub_mattermost_setting(enabled: true)
|
||||
end
|
||||
|
||||
it 'triggers Mattermost::CreateTeamService' do
|
||||
sign_in(user)
|
||||
|
||||
expect_next_instance_of(::Mattermost::CreateTeamService) do |service|
|
||||
expect(service).to receive(:execute).and_return({ name: 'test-chat-team', id: 1 })
|
||||
end
|
||||
|
||||
post :create, params: { group: { name: 'new_group', path: 'new_group', create_chat_team: 1 } }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when creating subgroups' do
|
||||
[true, false].each do |can_create_group_status|
|
||||
context "and can_create_group is #{can_create_group_status}" do
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :namespace_callout, class: 'Users::NamespaceCallout' do
|
||||
feature_name { :invite_members_banner }
|
||||
|
||||
user
|
||||
namespace
|
||||
end
|
||||
end
|
||||
|
|
@ -119,45 +119,27 @@ RSpec.describe "Group Runners" do
|
|||
create(:ci_runner, :instance, description: 'runner-baz', contacted_at: Time.zone.now)
|
||||
end
|
||||
|
||||
context "when runners_finder_all_available is enabled" do
|
||||
before do
|
||||
stub_feature_flags(runners_finder_all_available: true)
|
||||
|
||||
visit group_runners_path(group)
|
||||
end
|
||||
|
||||
context "when selecting 'Show only inherited'" do
|
||||
before do
|
||||
find("[data-testid='runner-membership-toggle'] button").click
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
it_behaves_like 'shows runner in list' do
|
||||
let(:runner) { instance_runner }
|
||||
end
|
||||
|
||||
it 'shows runner details page' do
|
||||
click_link("##{instance_runner.id} (#{instance_runner.short_sha})")
|
||||
|
||||
expect(current_url).to include(group_runner_path(group, instance_runner))
|
||||
expect(page).to have_content "#{s_('Runners|Description')} runner-baz"
|
||||
end
|
||||
end
|
||||
before do
|
||||
visit group_runners_path(group)
|
||||
end
|
||||
|
||||
context "when runners_finder_all_available is disabled" do
|
||||
context "when selecting 'Show only inherited'" do
|
||||
before do
|
||||
stub_feature_flags(runners_finder_all_available: false)
|
||||
find("[data-testid='runner-membership-toggle'] button").click
|
||||
|
||||
visit group_runners_path(group)
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
it "does not display 'Show only inherited' toggle" do
|
||||
expect(page).not_to have_content(s_('Runners|Show only inherited'))
|
||||
it_behaves_like 'shows runner in list' do
|
||||
let(:runner) { instance_runner }
|
||||
end
|
||||
|
||||
it_behaves_like 'shows no runners registered'
|
||||
it 'shows runner details page' do
|
||||
click_link("##{instance_runner.id} (#{instance_runner.short_sha})")
|
||||
|
||||
expect(current_url).to include(group_runner_path(group, instance_runner))
|
||||
expect(page).to have_content "#{s_('Runners|Description')} runner-baz"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -322,27 +322,11 @@ RSpec.describe Ci::RunnersFinder do
|
|||
context 'with :all_available membership' do
|
||||
let(:membership) { :all_available }
|
||||
|
||||
context 'with runners_finder_all_available FF disabled' do
|
||||
before do
|
||||
stub_feature_flags(runners_finder_all_available: false)
|
||||
end
|
||||
|
||||
it 'returns no runners' do
|
||||
expect(subject).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with runners_finder_all_available FF enabled' do
|
||||
before do
|
||||
stub_feature_flags(runners_finder_all_available: [target_group])
|
||||
end
|
||||
|
||||
it 'returns runners available to group' do
|
||||
expect(subject).to match_array([runner_project_7, runner_project_6, runner_project_5,
|
||||
runner_project_4, runner_project_3, runner_project_2,
|
||||
runner_project_1, runner_sub_group_4, runner_sub_group_3,
|
||||
runner_sub_group_2, runner_sub_group_1, runner_group, runner_instance])
|
||||
end
|
||||
it 'returns runners available to group' do
|
||||
expect(subject).to match_array([runner_project_7, runner_project_6, runner_project_5,
|
||||
runner_project_4, runner_project_3, runner_project_2,
|
||||
runner_project_1, runner_sub_group_4, runner_sub_group_3,
|
||||
runner_sub_group_2, runner_sub_group_1, runner_group, runner_instance])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -448,14 +432,8 @@ RSpec.describe Ci::RunnersFinder do
|
|||
context 'with :all_available membership' do
|
||||
let(:membership) { :all_available }
|
||||
|
||||
context 'with runners_finder_all_available FF enabled' do
|
||||
before do
|
||||
stub_feature_flags(runners_finder_all_available: [target_group])
|
||||
end
|
||||
|
||||
it 'returns no runners' do
|
||||
expect(subject).to be_empty
|
||||
end
|
||||
it 'returns no runners' do
|
||||
expect(subject).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ describe('~/access_tokens/components/new_access_token_app', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const findButtonEl = () => document.querySelector('[type=submit]');
|
||||
|
||||
const triggerSuccess = async (newToken = 'new token') => {
|
||||
wrapper
|
||||
.findComponent(DomElementListener)
|
||||
|
|
@ -41,7 +43,7 @@ describe('~/access_tokens/components/new_access_token_app', () => {
|
|||
<input type="text" id="expires_at" value="2022-01-01"/>
|
||||
<input type="text" value='1'/>
|
||||
<input type="checkbox" checked/>
|
||||
<input type="submit" value="Create"/>
|
||||
<button type="submit" value="Create" class="disabled" disabled="disabled"/>
|
||||
</form>`,
|
||||
);
|
||||
|
||||
|
|
@ -120,10 +122,10 @@ describe('~/access_tokens/components/new_access_token_app', () => {
|
|||
});
|
||||
|
||||
it('should not reset the submit button value', async () => {
|
||||
expect(document.querySelector('input[type=submit]').value).toBe('Create');
|
||||
expect(findButtonEl().value).toBe('Create');
|
||||
await triggerSuccess();
|
||||
|
||||
expect(document.querySelector('input[type=submit]').value).toBe('Create');
|
||||
expect(findButtonEl().value).toBe('Create');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -162,6 +164,17 @@ describe('~/access_tokens/components/new_access_token_app', () => {
|
|||
|
||||
expect(wrapper.findComponent(GlAlert).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should enable the submit button', async () => {
|
||||
const button = findButtonEl();
|
||||
expect(button).toBeDisabled();
|
||||
expect(button.className).toBe('disabled');
|
||||
|
||||
await triggerError();
|
||||
|
||||
expect(button).not.toBeDisabled();
|
||||
expect(button.className).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('before error or success', () => {
|
||||
|
|
|
|||
|
|
@ -159,55 +159,30 @@ describe('GroupRunnersApp', () => {
|
|||
});
|
||||
|
||||
describe('show all available runners toggle', () => {
|
||||
describe('when runners_finder_all_available is enabled', () => {
|
||||
it('shows the membership toggle', () => {
|
||||
createComponent({
|
||||
provide: {
|
||||
glFeatures: { runnersFinderAllAvailable: true },
|
||||
},
|
||||
});
|
||||
|
||||
expect(findRunnerMembershipToggle().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('sets the membership toggle', () => {
|
||||
setWindowLocation(`?membership[]=${MEMBERSHIP_ALL_AVAILABLE}`);
|
||||
createComponent({
|
||||
provide: {
|
||||
glFeatures: { runnersFinderAllAvailable: true },
|
||||
},
|
||||
});
|
||||
|
||||
expect(findRunnerMembershipToggle().props('value')).toBe(MEMBERSHIP_ALL_AVAILABLE);
|
||||
});
|
||||
|
||||
it('requests filter', async () => {
|
||||
createComponent({
|
||||
provide: {
|
||||
glFeatures: { runnersFinderAllAvailable: true },
|
||||
},
|
||||
});
|
||||
|
||||
findRunnerMembershipToggle().vm.$emit('input', MEMBERSHIP_ALL_AVAILABLE);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(mockGroupRunnersHandler).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
membership: MEMBERSHIP_ALL_AVAILABLE,
|
||||
}),
|
||||
);
|
||||
});
|
||||
it('shows the membership toggle', () => {
|
||||
createComponent();
|
||||
expect(findRunnerMembershipToggle().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('when runners_finder_all_available is disabled', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
it('sets the membership toggle', () => {
|
||||
setWindowLocation(`?membership[]=${MEMBERSHIP_ALL_AVAILABLE}`);
|
||||
|
||||
it('does not show the membership toggle', () => {
|
||||
expect(findRunnerMembershipToggle().exists()).toBe(false);
|
||||
});
|
||||
createComponent();
|
||||
|
||||
expect(findRunnerMembershipToggle().props('value')).toBe(MEMBERSHIP_ALL_AVAILABLE);
|
||||
});
|
||||
|
||||
it('requests filter', async () => {
|
||||
createComponent();
|
||||
findRunnerMembershipToggle().vm.$emit('input', MEMBERSHIP_ALL_AVAILABLE);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(mockGroupRunnersHandler).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
membership: MEMBERSHIP_ALL_AVAILABLE,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
|
|||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import TokenAccess from '~/token_access/components/token_access.vue';
|
||||
import addProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/add_project_ci_job_token_scope.mutation.graphql';
|
||||
import removeProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/remove_project_ci_job_token_scope.mutation.graphql';
|
||||
|
|
@ -144,7 +144,7 @@ describe('TokenAccess component', () => {
|
|||
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
expect(createAlert).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ describe('TokenAccess component', () => {
|
|||
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
expect(createAlert).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { nextTick } from 'vue';
|
||||
import { GlButton, GlSprintf } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import Approvals from '~/vue_merge_request_widget/components/approvals/approvals.vue';
|
||||
import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue';
|
||||
import ApprovalsSummaryOptional from '~/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue';
|
||||
|
|
@ -129,7 +129,7 @@ describe('MRWidget approvals', () => {
|
|||
});
|
||||
|
||||
it('flashes error', () => {
|
||||
expect(createFlash).toHaveBeenCalledWith({ message: FETCH_ERROR });
|
||||
expect(createAlert).toHaveBeenCalledWith({ message: FETCH_ERROR });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -268,7 +268,7 @@ describe('MRWidget approvals', () => {
|
|||
});
|
||||
|
||||
it('flashes error message', () => {
|
||||
expect(createFlash).toHaveBeenCalledWith({ message: APPROVE_ERROR });
|
||||
expect(createAlert).toHaveBeenCalledWith({ message: APPROVE_ERROR });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -319,7 +319,7 @@ describe('MRWidget approvals', () => {
|
|||
});
|
||||
|
||||
it('flashes error message', () => {
|
||||
expect(createFlash).toHaveBeenCalledWith({ message: UNAPPROVE_ERROR });
|
||||
expect(createAlert).toHaveBeenCalledWith({ message: UNAPPROVE_ERROR });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
import {
|
||||
|
|
@ -168,7 +168,7 @@ describe('DeploymentAction component', () => {
|
|||
});
|
||||
|
||||
it('should not throw an error', () => {
|
||||
expect(createFlash).not.toHaveBeenCalled();
|
||||
expect(createAlert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('response includes redirect_url', () => {
|
||||
|
|
@ -225,9 +225,8 @@ describe('DeploymentAction component', () => {
|
|||
finderFn().trigger('click');
|
||||
});
|
||||
|
||||
it('should call createFlash with error message', () => {
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
it('should call createAlert with error message', () => {
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: actionButtonMocks[configConst].errorMessage,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,8 +26,29 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update do
|
|||
|
||||
RSpec.shared_examples 'updating the namespace package setting' do
|
||||
it_behaves_like 'updating the namespace package setting attributes',
|
||||
from: { maven_duplicates_allowed: true, maven_duplicate_exception_regex: 'SNAPSHOT', generic_duplicates_allowed: true, generic_duplicate_exception_regex: 'foo' },
|
||||
to: { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE', generic_duplicates_allowed: false, generic_duplicate_exception_regex: 'bar' }
|
||||
from: {
|
||||
maven_duplicates_allowed: true,
|
||||
maven_duplicate_exception_regex: 'SNAPSHOT',
|
||||
generic_duplicates_allowed: true,
|
||||
generic_duplicate_exception_regex: 'foo',
|
||||
maven_package_requests_forwarding: nil,
|
||||
lock_maven_package_requests_forwarding: false,
|
||||
npm_package_requests_forwarding: nil,
|
||||
lock_npm_package_requests_forwarding: false,
|
||||
pypi_package_requests_forwarding: nil,
|
||||
lock_pypi_package_requests_forwarding: false
|
||||
}, to: {
|
||||
maven_duplicates_allowed: false,
|
||||
maven_duplicate_exception_regex: 'RELEASE',
|
||||
generic_duplicates_allowed: false,
|
||||
generic_duplicate_exception_regex: 'bar',
|
||||
maven_package_requests_forwarding: true,
|
||||
lock_maven_package_requests_forwarding: true,
|
||||
npm_package_requests_forwarding: true,
|
||||
lock_npm_package_requests_forwarding: true,
|
||||
pypi_package_requests_forwarding: true,
|
||||
lock_pypi_package_requests_forwarding: true
|
||||
}
|
||||
|
||||
it_behaves_like 'returning a success'
|
||||
|
||||
|
|
@ -59,11 +80,19 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update do
|
|||
context 'with existing namespace package setting' do
|
||||
let_it_be(:package_settings) { create(:namespace_package_setting, namespace: namespace) }
|
||||
let_it_be(:params) do
|
||||
{ namespace_path: namespace.full_path,
|
||||
{
|
||||
namespace_path: namespace.full_path,
|
||||
maven_duplicates_allowed: false,
|
||||
maven_duplicate_exception_regex: 'RELEASE',
|
||||
generic_duplicates_allowed: false,
|
||||
generic_duplicate_exception_regex: 'bar' }
|
||||
generic_duplicate_exception_regex: 'bar',
|
||||
maven_package_requests_forwarding: true,
|
||||
lock_maven_package_requests_forwarding: true,
|
||||
npm_package_requests_forwarding: true,
|
||||
lock_npm_package_requests_forwarding: true,
|
||||
pypi_package_requests_forwarding: true,
|
||||
lock_pypi_package_requests_forwarding: true
|
||||
}
|
||||
end
|
||||
|
||||
where(:user_role, :shared_examples_name) do
|
||||
|
|
|
|||
|
|
@ -14,4 +14,24 @@ RSpec.describe GitlabSchema.types['PackageSettings'] do
|
|||
|
||||
it { is_expected.to have_graphql_type(Types::UntrustedRegexp) }
|
||||
end
|
||||
|
||||
it 'includes package setting fields' do
|
||||
expected_fields = %w[
|
||||
maven_duplicates_allowed
|
||||
maven_duplicate_exception_regex
|
||||
generic_duplicates_allowed
|
||||
generic_duplicate_exception_regex
|
||||
maven_package_requests_forwarding
|
||||
lock_maven_package_requests_forwarding
|
||||
npm_package_requests_forwarding
|
||||
lock_npm_package_requests_forwarding
|
||||
pypi_package_requests_forwarding
|
||||
lock_pypi_package_requests_forwarding
|
||||
maven_package_requests_forwarding_locked
|
||||
npm_package_requests_forwarding_locked
|
||||
pypi_package_requests_forwarding_locked
|
||||
]
|
||||
|
||||
expect(described_class).to include_graphql_fields(*expected_fields)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,6 +24,45 @@ RSpec.describe EventsHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#localized_action_name' do
|
||||
it 'handles all valid design events' do
|
||||
created, updated, destroyed = %i[created updated destroyed].map do |trait|
|
||||
event = build(:design_event, trait)
|
||||
helper.localized_action_name(event)
|
||||
end
|
||||
|
||||
expect(created).to eq(_('added'))
|
||||
expect(updated).to eq(_('updated'))
|
||||
expect(destroyed).to eq(_('removed'))
|
||||
end
|
||||
|
||||
context 'handles correct base actions' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:trait, :localized_action_name) do
|
||||
:created | s_('Event|created')
|
||||
:updated | s_('Event|opened')
|
||||
:closed | s_('Event|closed')
|
||||
:reopened | s_('Event|opened')
|
||||
:commented | s_('Event|commented on')
|
||||
:merged | s_('Event|accepted')
|
||||
:joined | s_('Event|joined')
|
||||
:left | s_('Event|left')
|
||||
:destroyed | s_('Event|destroyed')
|
||||
:expired | s_('Event|removed due to membership expiration from')
|
||||
:approved | s_('Event|approved')
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'with correct name and method' do
|
||||
event = build(:event, trait)
|
||||
|
||||
expect(helper.localized_action_name(event)).to eq(localized_action_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#event_commit_title' do
|
||||
let(:message) { 'foo & bar ' + 'A' * 70 + '\n' + 'B' * 80 }
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ require 'fast_spec_helper'
|
|||
|
||||
RSpec.describe 'memory watchdog' do
|
||||
subject(:run_initializer) do
|
||||
load Rails.root.join('config/initializers/memory_watchdog.rb')
|
||||
load rails_root_join('config/initializers/memory_watchdog.rb')
|
||||
end
|
||||
|
||||
context 'when GITLAB_MEMORY_WATCHDOG_ENABLED is truthy' do
|
||||
|
|
@ -17,6 +17,7 @@ RSpec.describe 'memory watchdog' do
|
|||
context 'when runtime is an application' do
|
||||
let(:watchdog) { instance_double(Gitlab::Memory::Watchdog) }
|
||||
let(:background_task) { instance_double(Gitlab::BackgroundTask) }
|
||||
let(:logger) { Gitlab::AppLogger }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Runtime).to receive(:application?).and_return(true)
|
||||
|
|
@ -28,16 +29,65 @@ RSpec.describe 'memory watchdog' do
|
|||
run_initializer
|
||||
end
|
||||
|
||||
shared_examples 'starts watchdog with handler' do |handler_class|
|
||||
it "uses the #{handler_class} and starts the watchdog" do
|
||||
expect(Gitlab::Memory::Watchdog).to receive(:new).with(
|
||||
handler: an_instance_of(handler_class),
|
||||
logger: Gitlab::AppLogger).and_return(watchdog)
|
||||
expect(Gitlab::BackgroundTask).to receive(:new).with(watchdog).and_return(background_task)
|
||||
expect(background_task).to receive(:start)
|
||||
expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_start).and_yield
|
||||
shared_examples 'starts configured watchdog' do |handler_class|
|
||||
let(:configuration) { Gitlab::Memory::Watchdog::Configuration.new }
|
||||
let(:watchdog_monitors_params) do
|
||||
{
|
||||
Gitlab::Memory::Watchdog::Monitor::HeapFragmentation => {
|
||||
max_heap_fragmentation: max_heap_fragmentation,
|
||||
max_strikes: max_strikes
|
||||
},
|
||||
Gitlab::Memory::Watchdog::Monitor::UniqueMemoryGrowth => {
|
||||
max_mem_growth: max_mem_growth,
|
||||
max_strikes: max_strikes
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
run_initializer
|
||||
shared_examples 'configures and starts watchdog' do
|
||||
it "correctly configures and starts watchdog", :aggregate_failures do
|
||||
expect(watchdog).to receive(:configure).and_yield(configuration)
|
||||
|
||||
watchdog_monitors_params.each do |monitor_class, params|
|
||||
expect(configuration.monitors).to receive(:use).with(monitor_class, **params)
|
||||
end
|
||||
|
||||
expect(Gitlab::Memory::Watchdog).to receive(:new).and_return(watchdog)
|
||||
expect(Gitlab::BackgroundTask).to receive(:new).with(watchdog).and_return(background_task)
|
||||
expect(background_task).to receive(:start)
|
||||
expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_start).and_yield
|
||||
|
||||
run_initializer
|
||||
|
||||
expect(configuration.handler).to be_an_instance_of(handler_class)
|
||||
expect(configuration.logger).to eq(logger)
|
||||
expect(configuration.sleep_time_seconds).to eq(sleep_time_seconds)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when settings are not passed through the environment' do
|
||||
let(:max_strikes) { 5 }
|
||||
let(:max_heap_fragmentation) { 0.5 }
|
||||
let(:max_mem_growth) { 3.0 }
|
||||
let(:sleep_time_seconds) { 60 }
|
||||
|
||||
include_examples 'configures and starts watchdog'
|
||||
end
|
||||
|
||||
context 'when settings are passed through the environment' do
|
||||
let(:max_strikes) { 6 }
|
||||
let(:max_heap_fragmentation) { 0.4 }
|
||||
let(:max_mem_growth) { 2.0 }
|
||||
let(:sleep_time_seconds) { 50 }
|
||||
|
||||
before do
|
||||
stub_env('GITLAB_MEMWD_MAX_STRIKES', 6)
|
||||
stub_env('GITLAB_MEMWD_SLEEP_TIME_SEC', 50)
|
||||
stub_env('GITLAB_MEMWD_MAX_MEM_GROWTH', 2.0)
|
||||
stub_env('GITLAB_MEMWD_MAX_HEAP_FRAG', 0.4)
|
||||
end
|
||||
|
||||
include_examples 'configures and starts watchdog'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -59,7 +109,7 @@ RSpec.describe 'memory watchdog' do
|
|||
allow(Gitlab::Runtime).to receive(:puma?).and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'starts watchdog with handler', Gitlab::Memory::Watchdog::PumaHandler
|
||||
it_behaves_like 'starts configured watchdog', Gitlab::Memory::Watchdog::PumaHandler
|
||||
end
|
||||
# rubocop: enable RSpec/VerifiedDoubles
|
||||
|
||||
|
|
@ -68,11 +118,11 @@ RSpec.describe 'memory watchdog' do
|
|||
allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'starts watchdog with handler', Gitlab::Memory::Watchdog::TermProcessHandler
|
||||
it_behaves_like 'starts configured watchdog', Gitlab::Memory::Watchdog::TermProcessHandler
|
||||
end
|
||||
|
||||
context 'when other runtime' do
|
||||
it_behaves_like 'starts watchdog with handler', Gitlab::Memory::Watchdog::NullHandler
|
||||
it_behaves_like 'starts configured watchdog', Gitlab::Memory::Watchdog::NullHandler
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -58,16 +58,15 @@ RSpec.describe Gitlab::BitbucketImport::Importer do
|
|||
issues
|
||||
end
|
||||
|
||||
let(:project_identifier) { 'namespace/repo' }
|
||||
let(:data) { { 'token' => 'token' } }
|
||||
let_it_be(:project_identifier) { 'namespace/repo' }
|
||||
|
||||
let(:project) do
|
||||
let_it_be_with_reload(:project) do
|
||||
create(
|
||||
:project,
|
||||
:repository,
|
||||
import_source: project_identifier,
|
||||
import_url: "https://bitbucket.org/#{project_identifier}.git",
|
||||
import_data_attributes: { credentials: data }
|
||||
import_data_attributes: { credentials: { 'token' => 'token' } }
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -80,6 +79,14 @@ RSpec.describe Gitlab::BitbucketImport::Importer do
|
|||
}
|
||||
end
|
||||
|
||||
let(:last_issue_data) do
|
||||
{
|
||||
page: 1,
|
||||
pagelen: 1,
|
||||
values: [sample_issues_statuses.last]
|
||||
}
|
||||
end
|
||||
|
||||
let(:counter) { double('counter', increment: true) }
|
||||
|
||||
subject { described_class.new(project) }
|
||||
|
|
@ -243,6 +250,13 @@ RSpec.describe Gitlab::BitbucketImport::Importer do
|
|||
headers: { "Content-Type" => "application/json" },
|
||||
body: { has_issues: true, full_name: project_identifier }.to_json)
|
||||
|
||||
stub_request(
|
||||
:get,
|
||||
"https://api.bitbucket.org/2.0/repositories/#{project_identifier}/issues?pagelen=1&sort=-created_on&state=ALL"
|
||||
).to_return(status: 200,
|
||||
headers: { "Content-Type" => "application/json" },
|
||||
body: last_issue_data.to_json)
|
||||
|
||||
stub_request(
|
||||
:get,
|
||||
"https://api.bitbucket.org/2.0/repositories/#{project_identifier}/issues?pagelen=50&sort=created_on"
|
||||
|
|
@ -344,6 +358,12 @@ RSpec.describe Gitlab::BitbucketImport::Importer do
|
|||
end
|
||||
|
||||
describe 'issue import' do
|
||||
it 'allocates internal ids' do
|
||||
expect(Issue).to receive(:track_project_iid!).with(project, 6)
|
||||
|
||||
importer.execute
|
||||
end
|
||||
|
||||
it 'maps reporters to anonymous if bitbucket reporter is nil' do
|
||||
allow(importer).to receive(:import_wiki)
|
||||
importer.execute
|
||||
|
|
@ -363,6 +383,29 @@ RSpec.describe Gitlab::BitbucketImport::Importer do
|
|||
|
||||
expect(project.issues.map(&:work_item_type_id).uniq).to contain_exactly(WorkItems::Type.default_issue_type.id)
|
||||
end
|
||||
|
||||
context 'with issue comments' do
|
||||
let(:inline_note) do
|
||||
instance_double(Bitbucket::Representation::Comment, note: 'Hello world', author: 'someuser', created_at: Time.now, updated_at: Time.now)
|
||||
end
|
||||
|
||||
before do
|
||||
allow_next_instance_of(Bitbucket::Client) do |instance|
|
||||
allow(instance).to receive(:issue_comments).and_return([inline_note])
|
||||
end
|
||||
end
|
||||
|
||||
it 'imports issue comments' do
|
||||
allow(importer).to receive(:import_wiki)
|
||||
importer.execute
|
||||
|
||||
comment = project.notes.first
|
||||
expect(project.notes.size).to eq(7)
|
||||
expect(comment.note).to include(inline_note.note)
|
||||
expect(comment.note).to include(inline_note.author)
|
||||
expect(importer.errors).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'metrics' do
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue