Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5ed22fd289
commit
826a7475c3
|
|
@ -32,6 +32,11 @@ export default {
|
|||
required: false,
|
||||
default: undefined,
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: undefined,
|
||||
},
|
||||
triggerSource: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -82,6 +87,7 @@ export default {
|
|||
v-if="isButtonTrigger"
|
||||
v-bind="componentAttributes"
|
||||
:variant="variant"
|
||||
:category="category"
|
||||
:icon="icon"
|
||||
@click="openModal"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
|||
import { TYPENAME_MERGE_REQUEST } from '~/graphql_shared/constants';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import userAutocompleteWithMRPermissionsQuery from '~/graphql_shared/queries/project_autocomplete_users_with_mr_permissions.query.graphql';
|
||||
import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
|
||||
import UpdateReviewers from './update_reviewers.vue';
|
||||
import userPermissionsQuery from './queries/user_permissions.query.graphql';
|
||||
|
||||
|
|
@ -27,8 +28,9 @@ export default {
|
|||
GlAvatar,
|
||||
GlIcon,
|
||||
UpdateReviewers,
|
||||
InviteMembersTrigger,
|
||||
},
|
||||
inject: ['projectPath', 'issuableId', 'issuableIid'],
|
||||
inject: ['projectPath', 'issuableId', 'issuableIid', 'directlyInviteMembers'],
|
||||
props: {
|
||||
users: {
|
||||
type: Array,
|
||||
|
|
@ -125,6 +127,13 @@ export default {
|
|||
this.fetchedUsers = users;
|
||||
this.searching = false;
|
||||
},
|
||||
removeAllReviewers() {
|
||||
this.currentSelectedReviewers = [];
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
selectReviewer: __('Select reviewer'),
|
||||
unassign: __('Unassign'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -138,8 +147,10 @@ export default {
|
|||
<gl-collapsible-listbox
|
||||
v-model="currentSelectedReviewers"
|
||||
icon="plus"
|
||||
:toggle-text="__('Select reviewer')"
|
||||
:header-text="__('Select reviewer')"
|
||||
:toggle-text="$options.i18n.selectReviewer"
|
||||
toggle-class="!gl-text-primary"
|
||||
:header-text="$options.i18n.selectReviewer"
|
||||
:reset-button-label="$options.i18n.unassign"
|
||||
text-sr-only
|
||||
category="tertiary"
|
||||
no-caret
|
||||
|
|
@ -148,12 +159,14 @@ export default {
|
|||
multiple
|
||||
placement="bottom-end"
|
||||
is-check-centered
|
||||
class="reviewers-dropdown"
|
||||
:items="mappedUsers"
|
||||
:loading="loading"
|
||||
:searching="searching"
|
||||
@search="debouncedFetchAutocompleteUsers"
|
||||
@shown="shownDropdown"
|
||||
@hidden="updateReviewers"
|
||||
@reset="removeAllReviewers"
|
||||
>
|
||||
<template #list-item="{ item }">
|
||||
<span class="gl-flex gl-items-center">
|
||||
|
|
@ -168,10 +181,25 @@ export default {
|
|||
</div>
|
||||
<span class="gl-flex gl-flex-col">
|
||||
<span class="gl-whitespace-nowrap gl-font-bold">{{ item.text }}</span>
|
||||
<span class="gl-text-gray-400"> {{ item.secondaryText }}</span>
|
||||
<span class="gl-text-subtle"> {{ item.secondaryText }}</span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template v-if="directlyInviteMembers" #footer>
|
||||
<div
|
||||
class="gl-flex gl-flex-col gl-border-t-1 gl-border-t-gray-200 !gl-p-2 !gl-pt-0 gl-border-t-solid"
|
||||
>
|
||||
<invite-members-trigger
|
||||
trigger-element="button"
|
||||
:display-text="__('Invite members')"
|
||||
trigger-source="merge_request_reviewers_dropdown"
|
||||
category="tertiary"
|
||||
block
|
||||
class="!gl-mt-2 !gl-justify-start"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</gl-collapsible-listbox>
|
||||
</template>
|
||||
</update-reviewers>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import { joinPaths } from '~/lib/utils/url_utility';
|
|||
import { s__ } from '~/locale';
|
||||
import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
|
||||
import { uploadModel } from '../services/upload_model';
|
||||
import { emptyArtifactFile } from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'ImportArtifactZone',
|
||||
|
|
@ -42,16 +41,11 @@ export default {
|
|||
required: false,
|
||||
default: true,
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => emptyArtifactFile,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
file: this.value.file,
|
||||
subfolder: this.value.subfolder,
|
||||
file: null,
|
||||
subfolder: '',
|
||||
alert: null,
|
||||
progressLoaded: null,
|
||||
progressTotal: null,
|
||||
|
|
@ -87,7 +81,7 @@ export default {
|
|||
this.progressTotal = progressEvent.total;
|
||||
this.progressLoaded = progressEvent.loaded;
|
||||
},
|
||||
submitRequest(importPath) {
|
||||
uploadArtifact(importPath) {
|
||||
this.progressLoaded = 0;
|
||||
this.progressTotal = this.file.size;
|
||||
uploadModel({
|
||||
|
|
@ -107,19 +101,14 @@ export default {
|
|||
this.alert = { message: error, variant: 'danger' };
|
||||
});
|
||||
},
|
||||
emitInput(value) {
|
||||
this.$emit('input', { ...value });
|
||||
},
|
||||
changeSubfolder(subfolder) {
|
||||
this.subfolder = subfolder;
|
||||
this.emitInput({ file: this.file, subfolder });
|
||||
},
|
||||
uploadFile(file) {
|
||||
changeFile(file) {
|
||||
this.file = file;
|
||||
this.emitInput({ file, subfolder: this.subfolder });
|
||||
|
||||
if (this.submitOnSelect && this.path) {
|
||||
this.submitRequest(this.path);
|
||||
this.uploadArtifact(this.path);
|
||||
}
|
||||
},
|
||||
hideAlert() {
|
||||
|
|
@ -128,7 +117,6 @@ export default {
|
|||
discardFile() {
|
||||
this.file = null;
|
||||
this.subfolder = '';
|
||||
this.emitInput(emptyArtifactFile);
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
|
|
@ -188,7 +176,7 @@ export default {
|
|||
:upload-single-message="$options.i18n.uploadSingleMessage"
|
||||
:drop-to-start-message="$options.i18n.dropToStartMessage"
|
||||
:is-file-valid="() => true"
|
||||
@change="uploadFile"
|
||||
@change="changeFile"
|
||||
>
|
||||
<gl-alert v-if="file" variant="success" :dismissible="!loading" @dismiss="discardFile">
|
||||
<gl-progress-bar v-if="progressLoaded" :value="progressPercentage" />
|
||||
|
|
|
|||
|
|
@ -14,10 +14,9 @@ import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
|||
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { semverRegex, noSpacesRegex } from '~/lib/utils/regexp';
|
||||
import { uploadModel } from '../services/upload_model';
|
||||
import createModelVersionMutation from '../graphql/mutations/create_model_version.mutation.graphql';
|
||||
import createModelMutation from '../graphql/mutations/create_model.mutation.graphql';
|
||||
import { emptyArtifactFile, MODEL_CREATION_MODAL_ID } from '../constants';
|
||||
import { MODEL_CREATION_MODAL_ID } from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'ModelCreate',
|
||||
|
|
@ -49,7 +48,6 @@ export default {
|
|||
description: '',
|
||||
versionDescription: '',
|
||||
errorMessage: null,
|
||||
selectedFile: emptyArtifactFile,
|
||||
modelData: null,
|
||||
versionData: null,
|
||||
markdownDocPath: helpPagePath('user/markdown'),
|
||||
|
|
@ -137,22 +135,14 @@ export default {
|
|||
this.versionData = await this.createModelVersion(this.modelData.mlModelCreate.model.id);
|
||||
}
|
||||
const versionErrors = this.versionData?.mlModelVersionCreate?.errors || [];
|
||||
|
||||
if (versionErrors.length) {
|
||||
this.errorMessage = versionErrors.join(', ');
|
||||
this.versionData = null;
|
||||
} else {
|
||||
// Attempt importing model artifacts
|
||||
const { importPath } = this.versionData.mlModelVersionCreate.modelVersion._links;
|
||||
await uploadModel({
|
||||
importPath,
|
||||
file: this.selectedFile.file,
|
||||
subfolder: this.selectedFile.subfolder,
|
||||
maxAllowedFileSize: this.maxAllowedFileSize,
|
||||
onUploadProgress: this.$refs.importArtifactZoneRef.onUploadProgress,
|
||||
});
|
||||
|
||||
const { showPath } = this.versionData.mlModelVersionCreate.modelVersion._links;
|
||||
const { showPath, importPath } =
|
||||
this.versionData.mlModelVersionCreate.modelVersion._links;
|
||||
await this.$refs.importArtifactZoneRef.uploadArtifact(importPath);
|
||||
visitUrl(showPath);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -162,7 +152,6 @@ export default {
|
|||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
this.errorMessage = error;
|
||||
this.selectedFile = emptyArtifactFile;
|
||||
}
|
||||
},
|
||||
resetModal() {
|
||||
|
|
@ -171,7 +160,6 @@ export default {
|
|||
this.description = '';
|
||||
this.versionDescription = '';
|
||||
this.errorMessage = null;
|
||||
this.selectedFile = emptyArtifactFile;
|
||||
this.modelData = null;
|
||||
this.versionData = null;
|
||||
},
|
||||
|
|
@ -341,7 +329,6 @@ export default {
|
|||
<import-artifact-zone
|
||||
id="versionImportArtifactZone"
|
||||
ref="importArtifactZoneRef"
|
||||
v-model="selectedFile"
|
||||
class="gl-px-3 gl-py-0"
|
||||
:submit-on-select="false"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { helpPagePath } from '~/helpers/help_page_helper';
|
|||
import { noSpacesRegex } from '~/lib/utils/regexp';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import editModelMutation from '../graphql/mutations/edit_model.mutation.graphql';
|
||||
import { emptyArtifactFile, MODEL_EDIT_MODAL_ID } from '../constants';
|
||||
import { MODEL_EDIT_MODAL_ID } from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'ModelEdit',
|
||||
|
|
@ -98,7 +98,6 @@ export default {
|
|||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
this.errorMessage = error;
|
||||
this.selectedFile = emptyArtifactFile;
|
||||
}
|
||||
},
|
||||
resetModal() {
|
||||
|
|
|
|||
|
|
@ -14,9 +14,8 @@ import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
|||
import { semverRegex } from '~/lib/utils/regexp';
|
||||
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { uploadModel } from '../services/upload_model';
|
||||
import createModelVersionMutation from '../graphql/mutations/create_model_version.mutation.graphql';
|
||||
import { emptyArtifactFile, MODEL_VERSION_CREATION_MODAL_ID } from '../constants';
|
||||
import { MODEL_VERSION_CREATION_MODAL_ID } from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'ModelVersionCreate',
|
||||
|
|
@ -50,7 +49,6 @@ export default {
|
|||
version: null,
|
||||
description: '',
|
||||
errorMessage: null,
|
||||
selectedFile: emptyArtifactFile,
|
||||
versionData: null,
|
||||
submitButtonDisabled: true,
|
||||
markdownDocPath: helpPagePath('user/markdown'),
|
||||
|
|
@ -128,29 +126,20 @@ export default {
|
|||
this.errorMessage = errors.join(', ');
|
||||
this.versionData = null;
|
||||
} else {
|
||||
const { importPath } = this.versionData.mlModelVersionCreate.modelVersion._links;
|
||||
|
||||
await uploadModel({
|
||||
importPath,
|
||||
file: this.selectedFile.file,
|
||||
subfolder: this.selectedFile.subfolder,
|
||||
maxAllowedFileSize: this.maxAllowedFileSize,
|
||||
onUploadProgress: this.$refs.importArtifactZoneRef.onUploadProgress,
|
||||
});
|
||||
const { showPath } = this.versionData.mlModelVersionCreate.modelVersion._links;
|
||||
const { showPath, importPath } =
|
||||
this.versionData.mlModelVersionCreate.modelVersion._links;
|
||||
await this.$refs.importArtifactZoneRef.uploadArtifact(importPath);
|
||||
visitUrl(showPath);
|
||||
}
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
this.errorMessage = error;
|
||||
this.selectedFile = emptyArtifactFile;
|
||||
}
|
||||
},
|
||||
resetModal() {
|
||||
this.version = null;
|
||||
this.description = '';
|
||||
this.errorMessage = null;
|
||||
this.selectedFile = emptyArtifactFile;
|
||||
this.versionData = null;
|
||||
},
|
||||
hideAlert() {
|
||||
|
|
@ -251,7 +240,6 @@ export default {
|
|||
<import-artifact-zone
|
||||
id="versionImportArtifactZone"
|
||||
ref="importArtifactZoneRef"
|
||||
v-model="selectedFile"
|
||||
class="gl-px-3 gl-py-0"
|
||||
:submit-on-select="false"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -27,8 +27,3 @@ export const MLFLOW_USAGE_MODAL_ID = 'model-registry-mlflow-usage-modal';
|
|||
export const MODEL_VERSION_CREATION_MODAL_ID = 'create-model-version-modal';
|
||||
export const MODEL_CREATION_MODAL_ID = 'create-model-modal';
|
||||
export const MODEL_EDIT_MODAL_ID = 'edit-model-modal';
|
||||
|
||||
export const emptyArtifactFile = {
|
||||
file: null,
|
||||
subfolder: '',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { MountingPortal } from 'portal-vue';
|
||||
import { GlLoadingIcon, GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { n__ } from '~/locale';
|
||||
import { n__, s__ } from '~/locale';
|
||||
import ReviewerDrawer from '~/merge_requests/components/reviewers/reviewer_drawer.vue';
|
||||
|
||||
export default {
|
||||
|
|
@ -52,6 +52,11 @@ export default {
|
|||
this.drawerOpen = drawerOpen;
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
addEditReviewers: s__('MergeRequest|Add or edit reviewers'),
|
||||
changeReviewer: s__('MergeRequest|Change reviewer'),
|
||||
quickAdd: s__('MergeRequest|Quick add reviewers'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
|
@ -64,7 +69,9 @@ export default {
|
|||
<gl-button
|
||||
v-tooltip.hover
|
||||
:title="
|
||||
glFeatures.reviewerAssignDrawer ? __('Add or edit reviewers') : __('Change reviewer')
|
||||
glFeatures.reviewerAssignDrawer
|
||||
? $options.i18n.addEditReviewers
|
||||
: $options.i18n.changeReviewer
|
||||
"
|
||||
class="hide-collapsed gl-float-right gl-ml-auto"
|
||||
:class="{ 'js-sidebar-dropdown-toggle edit-link': !glFeatures.reviewerAssignDrawer }"
|
||||
|
|
|
|||
|
|
@ -134,7 +134,13 @@ function mountSidebarReviewers(mediator) {
|
|||
return;
|
||||
}
|
||||
|
||||
const { id, iid, fullPath, multipleApprovalRulesAvailable = false } = getSidebarOptions();
|
||||
const {
|
||||
id,
|
||||
iid,
|
||||
fullPath,
|
||||
multipleApprovalRulesAvailable = false,
|
||||
directlyInviteMembers = false,
|
||||
} = getSidebarOptions();
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
|
|
@ -145,6 +151,7 @@ function mountSidebarReviewers(mediator) {
|
|||
issuableId: String(id),
|
||||
projectPath: fullPath,
|
||||
multipleApprovalRulesAvailable: parseBoolean(multipleApprovalRulesAvailable),
|
||||
directlyInviteMembers: parseBoolean(directlyInviteMembers),
|
||||
},
|
||||
render: (createElement) =>
|
||||
createElement(SidebarReviewers, {
|
||||
|
|
|
|||
|
|
@ -284,6 +284,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.reviewers-dropdown .gl-new-dropdown-panel {
|
||||
min-width: $right-sidebar-width - $gl-padding;
|
||||
}
|
||||
|
||||
.merge-request-approved-icon {
|
||||
animation: approval-animate 350ms ease-in;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ module IssuablesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def issuable_sidebar_options(issuable)
|
||||
def issuable_sidebar_options(issuable, project)
|
||||
{
|
||||
endpoint: "#{issuable[:issuable_json_path]}?serializer=sidebar_extras",
|
||||
toggleSubscriptionEndpoint: issuable[:toggle_subscription_path],
|
||||
|
|
@ -293,7 +293,8 @@ module IssuablesHelper
|
|||
timeTrackingLimitToHours: Gitlab::CurrentSettings.time_tracking_limit_to_hours,
|
||||
canCreateTimelogs: issuable.dig(:current_user, :can_create_timelogs),
|
||||
createNoteEmail: issuable[:create_note_email],
|
||||
issuableType: issuable[:type]
|
||||
issuableType: issuable[:type],
|
||||
directlyInviteMembers: can_admin_project_member?(project).to_s
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -84,4 +84,4 @@
|
|||
.js-sidebar-move-issue-block{ data: { project_full_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } }
|
||||
|
||||
-# haml-lint:disable InlineJavaScript
|
||||
%script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable_sidebar).to_json.html_safe
|
||||
%script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable_sidebar, @project).to_json.html_safe
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ The user's state is set to active and they consume a
|
|||
[seat](../subscriptions/self_managed/index.md#billable-users).
|
||||
|
||||
NOTE:
|
||||
Users can also be unblocked using the [GitLab API](../api/users.md#unblock-user).
|
||||
Users can also be unblocked using the [GitLab API](../api/user_moderation.md#unblock-a-user).
|
||||
|
||||
The unblock option may be unavailable for LDAP users. To enable the unblock option,
|
||||
the LDAP identity first needs to be deleted:
|
||||
|
|
@ -162,7 +162,7 @@ To deactivate a user:
|
|||
The user receives an email notification that their account has been deactivated. After this email, they no longer receive notifications.
|
||||
For more information, see [user deactivation emails](../administration/settings/email.md#user-deactivation-emails).
|
||||
|
||||
To deactivate users with the GitLab API, see [deactivate user](../api/users.md#deactivate-user). For information about permanent user restrictions, see [block and unblock users](#block-and-unblock-users).
|
||||
To deactivate users with the GitLab API, see [deactivate user](../api/user_moderation.md#deactivate-a-user). For information about permanent user restrictions, see [block and unblock users](#block-and-unblock-users).
|
||||
|
||||
### Automatically deactivate dormant users
|
||||
|
||||
|
|
@ -240,7 +240,7 @@ The user's state is set to active and they consume a
|
|||
|
||||
NOTE:
|
||||
A deactivated user can also activate their account themselves by logging back in via the UI.
|
||||
Users can also be activated using the [GitLab API](../api/users.md#activate-user).
|
||||
Users can also be activated using the [GitLab API](../api/user_moderation.md#activate-a-user).
|
||||
|
||||
## Ban and unban users
|
||||
|
||||
|
|
|
|||
|
|
@ -85,18 +85,7 @@ page:
|
|||
|
||||

|
||||
|
||||
NOTE:
|
||||
Users can be [blocked](../api/users.md#block-user) and
|
||||
[unblocked](../api/users.md#unblock-user) using the GitLab API.
|
||||
## Related topics
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
one might have when setting this up, or when something is changed, or on upgrading, it's
|
||||
important to describe those, too. Think of things that may go wrong and include them here.
|
||||
This is important to minimize requests for support, and to avoid doc comments with
|
||||
questions that you know someone might ask.
|
||||
|
||||
Each scenario can be a third-level heading, for example `### Getting error message X`.
|
||||
If you have none to add when creating a doc, leave this section in place
|
||||
but commented out to help encourage others to add to it in the future. -->
|
||||
- [Moderate users (administration)](moderate_users.md)
|
||||
- [Review spam logs](review_spam_logs.md)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ You can resolve a spam log with one of the following effects:
|
|||
| **Remove log** | The spam log is removed from the list. |
|
||||
| **Trust user** | The user is trusted, and can create issues, notes, snippets, and merge requests without being blocked for spam. Spam logs are not created for trusted users. |
|
||||
|
||||
NOTE:
|
||||
Users can be [blocked](../api/users.md#block-user) and
|
||||
[unblocked](../api/users.md#unblock-user) using the GitLab API.
|
||||
## Related topics
|
||||
|
||||
- [Moderate users (administration)](moderate_users.md)
|
||||
- [Review abuse reports](review_abuse_reports.md)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
---
|
||||
stage: Govern
|
||||
group: Authentication
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# User moderation API
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** Self-managed, GitLab Dedicated
|
||||
|
||||
You can [activate, ban, and block users](../administration/moderate_users.md) by using the REST API.
|
||||
|
||||
## Activate a user
|
||||
|
||||
Activate the specified user.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must be an administrator.
|
||||
|
||||
```plaintext
|
||||
POST /users/:id/activate
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|------------|---------|----------|----------------------|
|
||||
| `id` | integer | yes | ID of specified user |
|
||||
|
||||
Returns:
|
||||
|
||||
- `201 OK` on success.
|
||||
- `404 User Not Found` if the user cannot be found.
|
||||
- `403 Forbidden` if the user cannot be activated because they are blocked by an administrator or by LDAP synchronization.
|
||||
|
||||
## Deactivate a user
|
||||
|
||||
Deactivate the specified user.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must be an administrator.
|
||||
|
||||
```plaintext
|
||||
POST /users/:id/deactivate
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|------------|---------|----------|----------------------|
|
||||
| `id` | integer | yes | ID of specified user |
|
||||
|
||||
Returns:
|
||||
|
||||
- `201 OK` on success.
|
||||
- `404 User Not Found` if user cannot be found.
|
||||
- `403 Forbidden` when trying to deactivate a user that is:
|
||||
- Blocked by administrator or by LDAP synchronization.
|
||||
- Not [dormant](../administration/moderate_users.md#automatically-deactivate-dormant-users).
|
||||
- Internal.
|
||||
|
||||
## Block a user
|
||||
|
||||
Block the specified user.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must be an administrator.
|
||||
|
||||
```plaintext
|
||||
POST /users/:id/block
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|------------|---------|----------|----------------------|
|
||||
| `id` | integer | yes | ID of specified user |
|
||||
|
||||
Returns:
|
||||
|
||||
- `201 OK` on success.
|
||||
- `404 User Not Found` if user cannot be found.
|
||||
- `403 Forbidden` when trying to block:
|
||||
- A user that is blocked through LDAP.
|
||||
- An internal user.
|
||||
|
||||
## Unblock a user
|
||||
|
||||
Unblock the specified user.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must be an administrator.
|
||||
|
||||
```plaintext
|
||||
POST /users/:id/unblock
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|------------|---------|----------|----------------------|
|
||||
| `id` | integer | yes | ID of specified user |
|
||||
|
||||
Returns `201 OK` on success, `404 User Not Found` is user cannot be found or
|
||||
`403 Forbidden` when trying to unblock a user blocked by LDAP synchronization.
|
||||
|
||||
## Ban a user
|
||||
|
||||
Ban the specified user.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must be an administrator.
|
||||
|
||||
```plaintext
|
||||
POST /users/:id/ban
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - ID of specified user
|
||||
|
||||
Returns:
|
||||
|
||||
- `201 OK` on success.
|
||||
- `404 User Not Found` if user cannot be found.
|
||||
- `403 Forbidden` when trying to ban a user that is not active.
|
||||
|
||||
## Unban a user
|
||||
|
||||
Unban the specified user. Available only for administrator.
|
||||
|
||||
```plaintext
|
||||
POST /users/:id/unban
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - ID of specified user
|
||||
|
||||
Returns:
|
||||
|
||||
- `201 OK` on success.
|
||||
- `404 User Not Found` if the user cannot be found.
|
||||
- `403 Forbidden` when trying to unban a user that is not banned.
|
||||
|
||||
## Related topics
|
||||
|
||||
- [Review abuse reports](../administration/review_abuse_reports.md)
|
||||
- [Review spam logs](../administration/review_spam_logs.md)
|
||||
150
doc/api/users.md
150
doc/api/users.md
|
|
@ -1873,156 +1873,6 @@ Parameters:
|
|||
| `id` | integer | yes | ID of specified user |
|
||||
| `email_id` | integer | yes | Email ID |
|
||||
|
||||
## Block user
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** Self-managed, GitLab Dedicated
|
||||
|
||||
Blocks the specified user. Available only for administrator.
|
||||
|
||||
```plaintext
|
||||
POST /users/:id/block
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|------------|---------|----------|----------------------|
|
||||
| `id` | integer | yes | ID of specified user |
|
||||
|
||||
Returns:
|
||||
|
||||
- `201 OK` on success.
|
||||
- `404 User Not Found` if user cannot be found.
|
||||
- `403 Forbidden` when trying to block:
|
||||
- A user that is blocked through LDAP.
|
||||
- An internal user.
|
||||
|
||||
## Unblock user
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** Self-managed, GitLab Dedicated
|
||||
|
||||
Unblocks the specified user. Available only for administrator.
|
||||
|
||||
```plaintext
|
||||
POST /users/:id/unblock
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|------------|---------|----------|----------------------|
|
||||
| `id` | integer | yes | ID of specified user |
|
||||
|
||||
Returns `201 OK` on success, `404 User Not Found` is user cannot be found or
|
||||
`403 Forbidden` when trying to unblock a user blocked by LDAP synchronization.
|
||||
|
||||
## Deactivate user
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** Self-managed, GitLab Dedicated
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22257) in GitLab 12.4.
|
||||
|
||||
Deactivates the specified user. Available only for administrator.
|
||||
|
||||
```plaintext
|
||||
POST /users/:id/deactivate
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|------------|---------|----------|----------------------|
|
||||
| `id` | integer | yes | ID of specified user |
|
||||
|
||||
Returns:
|
||||
|
||||
- `201 OK` on success.
|
||||
- `404 User Not Found` if user cannot be found.
|
||||
- `403 Forbidden` when trying to deactivate a user that is:
|
||||
- Blocked by administrator or by LDAP synchronization.
|
||||
- Not [dormant](../administration/moderate_users.md#automatically-deactivate-dormant-users).
|
||||
- Internal.
|
||||
|
||||
## Activate user
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** Self-managed, GitLab Dedicated
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22257) in GitLab 12.4.
|
||||
|
||||
Activates the specified user. Available only for administrator.
|
||||
|
||||
```plaintext
|
||||
POST /users/:id/activate
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|------------|---------|----------|----------------------|
|
||||
| `id` | integer | yes | ID of specified user |
|
||||
|
||||
Returns:
|
||||
|
||||
- `201 OK` on success.
|
||||
- `404 User Not Found` if the user cannot be found.
|
||||
- `403 Forbidden` if the user cannot be activated because they are blocked by an administrator or by LDAP synchronization.
|
||||
|
||||
## Ban user
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** Self-managed, GitLab Dedicated
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327354) in GitLab 14.3.
|
||||
|
||||
Bans the specified user. Available only for administrator.
|
||||
|
||||
```plaintext
|
||||
POST /users/:id/ban
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - ID of specified user
|
||||
|
||||
Returns:
|
||||
|
||||
- `201 OK` on success.
|
||||
- `404 User Not Found` if user cannot be found.
|
||||
- `403 Forbidden` when trying to ban a user that is not active.
|
||||
|
||||
## Unban user
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** Self-managed, GitLab Dedicated
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327354) in GitLab 14.3.
|
||||
|
||||
Unbans the specified user. Available only for administrator.
|
||||
|
||||
```plaintext
|
||||
POST /users/:id/unban
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - ID of specified user
|
||||
|
||||
Returns:
|
||||
|
||||
- `201 OK` on success.
|
||||
- `404 User Not Found` if the user cannot be found.
|
||||
- `403 Forbidden` when trying to unban a user that is not banned.
|
||||
|
||||
## Get user contribution events
|
||||
|
||||
See the [Events API documentation](events.md#get-user-contribution-events)
|
||||
|
|
|
|||
|
|
@ -950,14 +950,6 @@ Feature.enabled?(:ci_live_trace) # => false
|
|||
Feature.enabled?(:ci_live_trace, gate) # => true
|
||||
```
|
||||
|
||||
You can also disable a feature flag for a specific actor:
|
||||
|
||||
```ruby
|
||||
gate = stub_feature_flag_gate('CustomActor')
|
||||
|
||||
stub_feature_flags(ci_live_trace: false, thing: gate)
|
||||
```
|
||||
|
||||
### Controlling feature flags engine in tests
|
||||
|
||||
Our Flipper engine in the test environment works in a memory mode `Flipper::Adapters::Memory`.
|
||||
|
|
|
|||
|
|
@ -15,19 +15,19 @@ module API
|
|||
def public_key
|
||||
return unless object.public_key
|
||||
|
||||
Base64.encode64(object.public_key)
|
||||
Base64.strict_encode64(object.public_key)
|
||||
end
|
||||
|
||||
def client_cert
|
||||
return unless object.client_cert
|
||||
|
||||
Base64.encode64(object.client_cert)
|
||||
Base64.strict_encode64(object.client_cert)
|
||||
end
|
||||
|
||||
def ca_cert
|
||||
return unless object.ca_cert
|
||||
|
||||
Base64.encode64(object.ca_cert)
|
||||
Base64.strict_encode64(object.ca_cert)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ module API
|
|||
def jwt
|
||||
return unless object.private_key
|
||||
|
||||
{ private_key: Base64.encode64(object.private_key) }
|
||||
{ private_key: Base64.strict_encode64(object.private_key) }
|
||||
end
|
||||
|
||||
def mtls
|
||||
|
|
|
|||
|
|
@ -3353,9 +3353,6 @@ msgstr ""
|
|||
msgid "Add new webhook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add or edit reviewers"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add or remove a user."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -10879,9 +10876,6 @@ msgstr ""
|
|||
msgid "Change path"
|
||||
msgstr ""
|
||||
|
||||
msgid "Change reviewer"
|
||||
msgstr ""
|
||||
|
||||
msgid "Change reviewers"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -33748,6 +33742,9 @@ msgstr ""
|
|||
msgid "MergeRequests|started a thread on commit %{linkStart}%{commitDisplay}%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequest|Add or edit reviewers"
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequest|Awaiting review"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -33769,6 +33766,9 @@ msgstr ""
|
|||
msgid "MergeRequest|Can't show this merge request because the target branch %{branch_badge} is missing from project %{path_badge}. Close this merge request or update the target branch."
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequest|Change reviewer"
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequest|Compare %{target} and %{source}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -33784,6 +33784,9 @@ msgstr ""
|
|||
msgid "MergeRequest|No files found"
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequest|Quick add reviewers"
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequest|Remove reviewer"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -57475,6 +57478,9 @@ msgstr ""
|
|||
msgid "Unarchiving the project restores its members' ability to make commits, and create issues, comments, and other entities. %{strong_start}After you unarchive the project, it displays in the search and on the dashboard.%{strong_end} %{link_start}Learn more.%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unassign"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unassign from commenting user"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ function createComponent(
|
|||
projectPath: 'gitlab-org/gitlab',
|
||||
issuableId: '1',
|
||||
issuableIid: '1',
|
||||
directlyInviteMembers: true,
|
||||
},
|
||||
stubs: {
|
||||
UpdateReviewers,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('~/ml/model_registry/services/upload_model', () => ({
|
||||
uploadModel: jest.fn(),
|
||||
uploadModel: jest.fn(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
describe('ModelCreate', () => {
|
||||
|
|
@ -229,7 +229,6 @@ describe('ModelCreate', () => {
|
|||
expect(findImportArtifactZone().props()).toEqual({
|
||||
path: null,
|
||||
submitOnSelect: false,
|
||||
value: { file: null, subfolder: '' },
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -397,9 +396,7 @@ describe('ModelCreate', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('Visits the model versions page upon successful create mutation', async () => {
|
||||
createWrapper();
|
||||
await submitForm();
|
||||
it('Visits the model versions page upon successful create mutation', () => {
|
||||
expect(visitUrl).toHaveBeenCalledWith('/some/project/-/ml/models/1/versions/1');
|
||||
});
|
||||
});
|
||||
|
|
@ -414,9 +411,7 @@ describe('ModelCreate', () => {
|
|||
await submitForm();
|
||||
});
|
||||
|
||||
it('Visits the model page upon successful create mutation without a version', async () => {
|
||||
createWrapper();
|
||||
await submitForm();
|
||||
it('Visits the model page upon successful create mutation without a version', () => {
|
||||
expect(visitUrl).toHaveBeenCalledWith('/some/project/-/ml/models/1');
|
||||
});
|
||||
});
|
||||
|
|
@ -489,7 +484,6 @@ describe('ModelCreate', () => {
|
|||
});
|
||||
|
||||
it('Visits the model versions page upon successful create mutation', async () => {
|
||||
expect(findGlAlert().text()).toBe('Artifact import error.');
|
||||
await submitForm(); // retry submit
|
||||
expect(visitUrl).toHaveBeenCalledWith('/some/project/-/ml/models/1/versions/1');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('~/ml/model_registry/services/upload_model', () => ({
|
||||
uploadModel: jest.fn(),
|
||||
uploadModel: jest.fn(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
describe('ModelVersionCreate', () => {
|
||||
|
|
@ -124,7 +124,6 @@ describe('ModelVersionCreate', () => {
|
|||
expect(findImportArtifactZone().props()).toEqual({
|
||||
path: null,
|
||||
submitOnSelect: false,
|
||||
value: { file: null, subfolder: '' },
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -297,29 +296,12 @@ describe('ModelVersionCreate', () => {
|
|||
expect(findGlAlert().text()).toBe('Version is invalid');
|
||||
});
|
||||
|
||||
it('Displays an alert upon an exception', async () => {
|
||||
createWrapper();
|
||||
uploadModel.mockRejectedValueOnce('Runtime error');
|
||||
|
||||
await submitForm();
|
||||
|
||||
expect(findGlAlert().text()).toBe('Runtime error');
|
||||
});
|
||||
|
||||
it('Logs to sentry upon an exception', async () => {
|
||||
createWrapper();
|
||||
uploadModel.mockRejectedValueOnce('Runtime error');
|
||||
|
||||
await submitForm();
|
||||
|
||||
expect(Sentry.captureException).toHaveBeenCalledWith('Runtime error');
|
||||
});
|
||||
|
||||
describe('Failed flow with file upload retried', () => {
|
||||
beforeEach(async () => {
|
||||
createWrapper();
|
||||
findVersionInput().vm.$emit('input', '1.0.0');
|
||||
zone().vm.$emit('change', file);
|
||||
await nextTick();
|
||||
uploadModel.mockRejectedValueOnce('Artifact import error.');
|
||||
|
||||
await submitForm();
|
||||
|
|
|
|||
|
|
@ -88,10 +88,6 @@ describe('ml/model_registry/components/model_version_detail.vue', () => {
|
|||
expect(findImportArtifactZone().props()).toEqual({
|
||||
path: 'path/to/import',
|
||||
submitOnSelect: true,
|
||||
value: {
|
||||
file: null,
|
||||
subfolder: '',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ describe('ReviewerTitle component', () => {
|
|||
reviewerAssignDrawer,
|
||||
},
|
||||
},
|
||||
stubs: ['approval-summary'],
|
||||
stubs: ['approval-summary', 'ReviewerDropdown'],
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ describe('ReviewerTitle component', () => {
|
|||
editable: false,
|
||||
});
|
||||
|
||||
expect(wrapper.vm.$el.querySelector('.edit-link')).toBeNull();
|
||||
expect(findEditButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders edit link when editable', () => {
|
||||
|
|
@ -98,7 +98,7 @@ describe('ReviewerTitle component', () => {
|
|||
editable: true,
|
||||
});
|
||||
|
||||
expect(wrapper.vm.$el.querySelector('.edit-link')).not.toBeNull();
|
||||
expect(findEditButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('tracks the event when edit is clicked', () => {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ RSpec.describe API::Entities::Clusters::ReceptiveAgent, feature_category: :deplo
|
|||
it do
|
||||
is_expected.to include(
|
||||
id: config.agent_id,
|
||||
jwt: { private_key: Base64.encode64(config.private_key) }
|
||||
jwt: { private_key: Base64.strict_encode64(config.private_key) }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -166,16 +166,16 @@ RSpec.describe API::Clusters::AgentUrlConfigurations, feature_category: :deploym
|
|||
|
||||
context 'when providing client cert and key' do
|
||||
let_it_be(:client_cert) do
|
||||
Base64.encode64(File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')))
|
||||
File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem'))
|
||||
end
|
||||
|
||||
let_it_be(:client_key) { Base64.encode64(File.read(Rails.root.join('spec/fixtures/clusters/sample_key.key'))) }
|
||||
let_it_be(:client_key) { File.read(Rails.root.join('spec/fixtures/clusters/sample_key.key')) }
|
||||
|
||||
let_it_be(:params) do
|
||||
{
|
||||
url: 'grpcs://localhost:4242',
|
||||
client_cert: client_cert,
|
||||
client_key: client_key
|
||||
client_cert: Base64.encode64(client_cert),
|
||||
client_key: Base64.encode64(client_key)
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -187,17 +187,17 @@ RSpec.describe API::Clusters::AgentUrlConfigurations, feature_category: :deploym
|
|||
expect(json_response['agent_id']).to eq(agent.id)
|
||||
expect(json_response['url']).to eq(params[:url])
|
||||
expect(json_response['public_key']).to be_nil
|
||||
expect(json_response['client_cert']).to eq(client_cert)
|
||||
expect(Base64.decode64(json_response['client_cert'])).to eq(client_cert)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when providing ca cert' do
|
||||
let_it_be(:ca_cert) { Base64.encode64(File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem'))) }
|
||||
let_it_be(:ca_cert) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) }
|
||||
|
||||
let_it_be(:params) do
|
||||
{
|
||||
url: 'grpcs://localhost:4242',
|
||||
ca_cert: ca_cert
|
||||
ca_cert: Base64.encode64(ca_cert)
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -208,7 +208,7 @@ RSpec.describe API::Clusters::AgentUrlConfigurations, feature_category: :deploym
|
|||
expect(response).to match_response_schema('public_api/v4/agent_url_configuration')
|
||||
expect(json_response['agent_id']).to eq(agent.id)
|
||||
expect(json_response['url']).to eq(params[:url])
|
||||
expect(json_response['ca_cert']).to eq(ca_cert)
|
||||
expect(Base64.decode64(json_response['ca_cert'])).to eq(ca_cert)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue