Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-09-02 09:07:42 +00:00
parent 5ed22fd289
commit 826a7475c3
28 changed files with 278 additions and 301 deletions

View File

@ -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"
>

View File

@ -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>

View File

@ -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" />

View File

@ -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"
/>

View File

@ -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() {

View File

@ -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"
/>

View File

@ -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: '',
};

View File

@ -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 }"

View File

@ -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, {

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -85,18 +85,7 @@ page:
![abuse-report-blocked-user-image](img/abuse_report_blocked_user.png)
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)

View File

@ -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)

156
doc/api/user_moderation.md Normal file
View File

@ -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)

View File

@ -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)

View File

@ -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`.

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -77,6 +77,7 @@ function createComponent(
projectPath: 'gitlab-org/gitlab',
issuableId: '1',
issuableIid: '1',
directlyInviteMembers: true,
},
stubs: {
UpdateReviewers,

View File

@ -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');
});

View File

@ -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();

View File

@ -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: '',
},
});
});

View File

@ -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', () => {

View File

@ -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

View File

@ -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