Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-11-21 09:15:06 +00:00
parent cb9d96285c
commit f73fa6daff
63 changed files with 577 additions and 653 deletions

View File

@ -1,45 +1,15 @@
<script>
import { GlAvatarLabeled, GlLink, GlTableLite } from '@gitlab/ui';
import { isEmpty, maxBy, range } from 'lodash';
import ModelExperimentsHeader from '~/ml/experiment_tracking/components/model_experiments_header.vue';
import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue';
import { __, sprintf } from '~/locale';
import DetailRow from './components/candidate_detail_row.vue';
import {
TITLE_LABEL,
INFO_LABEL,
ID_LABEL,
STATUS_LABEL,
EXPERIMENT_LABEL,
ARTIFACTS_LABEL,
PARAMETERS_LABEL,
METRICS_LABEL,
METADATA_LABEL,
DELETE_CANDIDATE_CONFIRMATION_MESSAGE,
DELETE_CANDIDATE_PRIMARY_ACTION_LABEL,
DELETE_CANDIDATE_MODAL_TITLE,
MLFLOW_ID_LABEL,
CI_SECTION_LABEL,
JOB_LABEL,
CI_USER_LABEL,
CI_MR_LABEL,
PERFORMANCE_LABEL,
NO_PARAMETERS_MESSAGE,
NO_METRICS_MESSAGE,
NO_METADATA_MESSAGE,
NO_CI_MESSAGE,
} from './translations';
import CandidateDetail from '~/ml/model_registry/components/candidate_detail.vue';
import { s__ } from '~/locale';
export default {
name: 'MlCandidatesShow',
components: {
ModelExperimentsHeader,
DeleteButton,
DetailRow,
GlAvatarLabeled,
GlLink,
GlTableLite,
CandidateDetail,
},
props: {
candidate: {
@ -47,70 +17,18 @@ export default {
required: true,
},
},
i18n: {
TITLE_LABEL,
INFO_LABEL,
ID_LABEL,
STATUS_LABEL,
EXPERIMENT_LABEL,
ARTIFACTS_LABEL,
DELETE_CANDIDATE_CONFIRMATION_MESSAGE,
DELETE_CANDIDATE_PRIMARY_ACTION_LABEL,
DELETE_CANDIDATE_MODAL_TITLE,
MLFLOW_ID_LABEL,
CI_SECTION_LABEL,
JOB_LABEL,
CI_USER_LABEL,
CI_MR_LABEL,
PARAMETERS_LABEL,
METRICS_LABEL,
METADATA_LABEL,
PERFORMANCE_LABEL,
NO_PARAMETERS_MESSAGE,
NO_METRICS_MESSAGE,
NO_METADATA_MESSAGE,
NO_CI_MESSAGE,
},
computed: {
info() {
return Object.freeze(this.candidate.info);
},
ciJob() {
return Object.freeze(this.info.ci_job);
},
hasMetadata() {
return !isEmpty(this.candidate.metadata);
},
hasParameters() {
return !isEmpty(this.candidate.params);
},
hasMetrics() {
return !isEmpty(this.candidate.metrics);
},
metricsTableFields() {
const maxStep = maxBy(this.candidate.metrics, 'step').step;
const rowClass = 'gl-p-3!';
const cssClasses = { thClass: rowClass, tdClass: rowClass };
const fields = range(maxStep + 1).map((step) => ({
key: step.toString(),
label: sprintf(__('Step %{step}'), { step }),
...cssClasses,
}));
return [{ key: 'name', label: __('Metric'), ...cssClasses }, ...fields];
},
metricsTableItems() {
const items = {};
this.candidate.metrics.forEach((metric) => {
const metricRow = items[metric.name] || { name: metric.name };
metricRow[metric.step] = metric.value;
items[metric.name] = metricRow;
});
return Object.values(items);
},
},
i18n: {
TITLE_LABEL: s__('MlExperimentTracking|Model candidate details'),
DELETE_CANDIDATE_CONFIRMATION_MESSAGE: s__(
'MlExperimentTracking|Deleting this candidate will delete the associated parameters, metrics, and metadata.',
),
DELETE_CANDIDATE_PRIMARY_ACTION_LABEL: s__('MlExperimentTracking|Delete candidate'),
DELETE_CANDIDATE_MODAL_TITLE: s__('MlExperimentTracking|Delete candidate?'),
},
};
</script>
@ -126,106 +44,6 @@ export default {
/>
</model-experiments-header>
<section class="gl-mb-6">
<table class="candidate-details">
<tbody>
<detail-row :label="$options.i18n.ID_LABEL">
{{ info.iid }}
</detail-row>
<detail-row :label="$options.i18n.MLFLOW_ID_LABEL">{{ info.eid }}</detail-row>
<detail-row :label="$options.i18n.STATUS_LABEL">{{ info.status }}</detail-row>
<detail-row :label="$options.i18n.EXPERIMENT_LABEL">
<gl-link :href="info.path_to_experiment">
{{ info.experiment_name }}
</gl-link>
</detail-row>
<detail-row v-if="info.path_to_artifact" :label="$options.i18n.ARTIFACTS_LABEL">
<gl-link :href="info.path_to_artifact">
{{ $options.i18n.ARTIFACTS_LABEL }}
</gl-link>
</detail-row>
</tbody>
</table>
</section>
<section class="gl-mb-6">
<h4>{{ $options.i18n.CI_SECTION_LABEL }}</h4>
<table v-if="ciJob" class="candidate-details">
<tbody>
<detail-row
:label="$options.i18n.JOB_LABEL"
:section-label="$options.i18n.CI_SECTION_LABEL"
>
<gl-link :href="ciJob.path">
{{ ciJob.name }}
</gl-link>
</detail-row>
<detail-row v-if="ciJob.user" :label="$options.i18n.CI_USER_LABEL">
<gl-avatar-labeled label="" :size="24" :src="ciJob.user.avatar">
<gl-link :href="ciJob.user.path">
{{ ciJob.user.name }}
</gl-link>
</gl-avatar-labeled>
</detail-row>
<detail-row v-if="ciJob.merge_request" :label="$options.i18n.CI_MR_LABEL">
<gl-link :href="ciJob.merge_request.path">
!{{ ciJob.merge_request.iid }} {{ ciJob.merge_request.title }}
</gl-link>
</detail-row>
</tbody>
</table>
<div v-else class="gl-text-secondary">{{ $options.i18n.NO_CI_MESSAGE }}</div>
</section>
<section class="gl-mb-6">
<h4>{{ $options.i18n.PARAMETERS_LABEL }}</h4>
<table v-if="hasParameters" class="candidate-details">
<tbody>
<detail-row v-for="item in candidate.params" :key="item.name" :label="item.name">
{{ item.value }}
</detail-row>
</tbody>
</table>
<div v-else class="gl-text-secondary">{{ $options.i18n.NO_PARAMETERS_MESSAGE }}</div>
</section>
<section class="gl-mb-6">
<h4>{{ $options.i18n.METADATA_LABEL }}</h4>
<table v-if="hasMetadata" class="candidate-details">
<tbody>
<detail-row v-for="item in candidate.metadata" :key="item.name" :label="item.name">
{{ item.value }}
</detail-row>
</tbody>
</table>
<div v-else class="gl-text-secondary">{{ $options.i18n.NO_METADATA_MESSAGE }}</div>
</section>
<section class="gl-mb-6">
<h4>{{ $options.i18n.PERFORMANCE_LABEL }}</h4>
<div v-if="hasMetrics" class="gl-overflow-x-auto">
<gl-table-lite
:items="metricsTableItems"
:fields="metricsTableFields"
class="gl-w-auto"
hover
/>
</div>
<div v-else class="gl-text-secondary">{{ $options.i18n.NO_METRICS_MESSAGE }}</div>
</section>
<candidate-detail :candidate="candidate" />
</div>
</template>

View File

@ -1,26 +0,0 @@
import { __, s__ } from '~/locale';
export const TITLE_LABEL = s__('MlExperimentTracking|Model candidate details');
export const INFO_LABEL = s__('MlExperimentTracking|Info');
export const ID_LABEL = s__('MlExperimentTracking|ID');
export const MLFLOW_ID_LABEL = s__('MlExperimentTracking|MLflow run ID');
export const STATUS_LABEL = s__('MlExperimentTracking|Status');
export const EXPERIMENT_LABEL = s__('MlExperimentTracking|Experiment');
export const ARTIFACTS_LABEL = s__('MlExperimentTracking|Artifacts');
export const PARAMETERS_LABEL = s__('MlExperimentTracking|Parameters');
export const METRICS_LABEL = s__('MlExperimentTracking|Metrics');
export const PERFORMANCE_LABEL = s__('MlExperimentTracking|Model performance');
export const METADATA_LABEL = s__('MlExperimentTracking|Metadata');
export const NO_PARAMETERS_MESSAGE = s__('MlExperimentTracking|No logged parameters');
export const NO_METRICS_MESSAGE = s__('MlExperimentTracking|No logged metrics');
export const NO_METADATA_MESSAGE = s__('MlExperimentTracking|No logged metadata');
export const NO_CI_MESSAGE = s__('MlExperimentTracking|Candidate not linked to a CI build');
export const DELETE_CANDIDATE_CONFIRMATION_MESSAGE = s__(
'MlExperimentTracking|Deleting this candidate will delete the associated parameters, metrics, and metadata.',
);
export const DELETE_CANDIDATE_PRIMARY_ACTION_LABEL = s__('MlExperimentTracking|Delete candidate');
export const DELETE_CANDIDATE_MODAL_TITLE = s__('MLExperimentTracking|Delete candidate?');
export const CI_SECTION_LABEL = s__('MLExperimentTracking|CI Info');
export const JOB_LABEL = __('Job');
export const CI_USER_LABEL = s__('MlExperimentTracking|Triggered by');
export const CI_MR_LABEL = __('Merge request');

View File

@ -0,0 +1,207 @@
<script>
import { GlAvatarLabeled, GlLink, GlTableLite } from '@gitlab/ui';
import { isEmpty, maxBy, range } from 'lodash';
import { __, sprintf } from '~/locale';
import {
INFO_LABEL,
ID_LABEL,
STATUS_LABEL,
EXPERIMENT_LABEL,
ARTIFACTS_LABEL,
PARAMETERS_LABEL,
METADATA_LABEL,
MLFLOW_ID_LABEL,
CI_SECTION_LABEL,
JOB_LABEL,
CI_USER_LABEL,
CI_MR_LABEL,
PERFORMANCE_LABEL,
NO_PARAMETERS_MESSAGE,
NO_METRICS_MESSAGE,
NO_METADATA_MESSAGE,
NO_CI_MESSAGE,
} from '../translations';
import DetailRow from './candidate_detail_row.vue';
export default {
name: 'MlCandidatesShow',
components: {
DetailRow,
GlAvatarLabeled,
GlLink,
GlTableLite,
},
props: {
candidate: {
type: Object,
required: true,
},
},
i18n: {
INFO_LABEL,
ID_LABEL,
STATUS_LABEL,
EXPERIMENT_LABEL,
ARTIFACTS_LABEL,
MLFLOW_ID_LABEL,
CI_SECTION_LABEL,
JOB_LABEL,
CI_USER_LABEL,
CI_MR_LABEL,
PARAMETERS_LABEL,
METADATA_LABEL,
PERFORMANCE_LABEL,
NO_PARAMETERS_MESSAGE,
NO_METRICS_MESSAGE,
NO_METADATA_MESSAGE,
NO_CI_MESSAGE,
},
computed: {
info() {
return Object.freeze(this.candidate.info);
},
ciJob() {
return Object.freeze(this.info.ci_job);
},
hasMetadata() {
return !isEmpty(this.candidate.metadata);
},
hasParameters() {
return !isEmpty(this.candidate.params);
},
hasMetrics() {
return !isEmpty(this.candidate.metrics);
},
metricsTableFields() {
const maxStep = maxBy(this.candidate.metrics, 'step').step;
const rowClass = 'gl-p-3!';
const cssClasses = { thClass: rowClass, tdClass: rowClass };
const fields = range(maxStep + 1).map((step) => ({
key: step.toString(),
label: sprintf(__('Step %{step}'), { step }),
...cssClasses,
}));
return [{ key: 'name', label: __('Metric'), ...cssClasses }, ...fields];
},
metricsTableItems() {
const items = {};
this.candidate.metrics.forEach((metric) => {
const metricRow = items[metric.name] || { name: metric.name };
metricRow[metric.step] = metric.value;
items[metric.name] = metricRow;
});
return Object.values(items);
},
},
};
</script>
<template>
<div>
<section class="gl-mb-6">
<table class="candidate-details">
<tbody>
<detail-row :label="$options.i18n.ID_LABEL">
{{ info.iid }}
</detail-row>
<detail-row :label="$options.i18n.MLFLOW_ID_LABEL">{{ info.eid }}</detail-row>
<detail-row :label="$options.i18n.STATUS_LABEL">{{ info.status }}</detail-row>
<detail-row :label="$options.i18n.EXPERIMENT_LABEL">
<gl-link :href="info.path_to_experiment">
{{ info.experiment_name }}
</gl-link>
</detail-row>
<detail-row v-if="info.path_to_artifact" :label="$options.i18n.ARTIFACTS_LABEL">
<gl-link :href="info.path_to_artifact">
{{ $options.i18n.ARTIFACTS_LABEL }}
</gl-link>
</detail-row>
</tbody>
</table>
</section>
<section class="gl-mb-6">
<h4>{{ $options.i18n.CI_SECTION_LABEL }}</h4>
<table v-if="ciJob" class="candidate-details">
<tbody>
<detail-row
:label="$options.i18n.JOB_LABEL"
:section-label="$options.i18n.CI_SECTION_LABEL"
>
<gl-link :href="ciJob.path">
{{ ciJob.name }}
</gl-link>
</detail-row>
<detail-row v-if="ciJob.user" :label="$options.i18n.CI_USER_LABEL">
<gl-avatar-labeled label="" :size="24" :src="ciJob.user.avatar">
<gl-link :href="ciJob.user.path">
{{ ciJob.user.name }}
</gl-link>
</gl-avatar-labeled>
</detail-row>
<detail-row v-if="ciJob.merge_request" :label="$options.i18n.CI_MR_LABEL">
<gl-link :href="ciJob.merge_request.path">
!{{ ciJob.merge_request.iid }} {{ ciJob.merge_request.title }}
</gl-link>
</detail-row>
</tbody>
</table>
<div v-else class="gl-text-secondary">{{ $options.i18n.NO_CI_MESSAGE }}</div>
</section>
<section class="gl-mb-6">
<h4>{{ $options.i18n.PARAMETERS_LABEL }}</h4>
<table v-if="hasParameters" class="candidate-details">
<tbody>
<detail-row v-for="item in candidate.params" :key="item.name" :label="item.name">
{{ item.value }}
</detail-row>
</tbody>
</table>
<div v-else class="gl-text-secondary">{{ $options.i18n.NO_PARAMETERS_MESSAGE }}</div>
</section>
<section class="gl-mb-6">
<h4>{{ $options.i18n.METADATA_LABEL }}</h4>
<table v-if="hasMetadata" class="candidate-details">
<tbody>
<detail-row v-for="item in candidate.metadata" :key="item.name" :label="item.name">
{{ item.value }}
</detail-row>
</tbody>
</table>
<div v-else class="gl-text-secondary">{{ $options.i18n.NO_METADATA_MESSAGE }}</div>
</section>
<section class="gl-mb-6">
<h4>{{ $options.i18n.PERFORMANCE_LABEL }}</h4>
<div v-if="hasMetrics" class="gl-overflow-x-auto">
<gl-table-lite
:items="metricsTableItems"
:fields="metricsTableFields"
class="gl-w-auto"
hover
/>
</div>
<div v-else class="gl-text-secondary">{{ $options.i18n.NO_METRICS_MESSAGE }}</div>
</section>
</div>
</template>

View File

@ -1,4 +1,4 @@
import { s__, n__ } from '~/locale';
import { __, s__, n__ } from '~/locale';
export const MODEL_DETAILS_TAB_LABEL = s__('MlModelRegistry|Details');
export const MODEL_OTHER_VERSIONS_TAB_LABEL = s__('MlModelRegistry|Versions');
@ -14,3 +14,21 @@ export const NO_MODELS_LABEL = s__('MlModelRegistry|No models registered in this
export const modelsCountLabel = (modelCount) =>
n__('MlModelRegistry|%d model', 'MlModelRegistry|%d models', modelCount);
export const INFO_LABEL = s__('MlModelRegistry|Info');
export const ID_LABEL = s__('MlModelRegistry|ID');
export const MLFLOW_ID_LABEL = s__('MlModelRegistry|MLflow run ID');
export const STATUS_LABEL = s__('MlModelRegistry|Status');
export const EXPERIMENT_LABEL = s__('MlModelRegistry|Experiment');
export const ARTIFACTS_LABEL = s__('MlModelRegistry|Artifacts');
export const PARAMETERS_LABEL = s__('MlModelRegistry|Parameters');
export const PERFORMANCE_LABEL = s__('MlModelRegistry|Model performance');
export const METADATA_LABEL = s__('MlModelRegistry|Metadata');
export const NO_PARAMETERS_MESSAGE = s__('MlModelRegistry|No logged parameters');
export const NO_METRICS_MESSAGE = s__('MlModelRegistry|No logged metrics');
export const NO_METADATA_MESSAGE = s__('MlModelRegistry|No logged metadata');
export const NO_CI_MESSAGE = s__('MlModelRegistry|Candidate not linked to a CI build');
export const CI_SECTION_LABEL = s__('MlModelRegistry|CI Info');
export const JOB_LABEL = __('Job');
export const CI_USER_LABEL = s__('MlModelRegistry|Triggered by');
export const CI_MR_LABEL = __('Merge request');

View File

@ -571,7 +571,7 @@ export default {
:disabled="!canChangeVisibilityLevel"
name="project[visibility_level]"
class="form-control select-control"
data-qa-selector="project_visibility_dropdown"
data-testid="project-visibility-dropdown"
>
<option
:value="$options.VISIBILITY_LEVEL_PRIVATE_INTEGER"
@ -1060,13 +1060,7 @@ export default {
data-testid="project-features-save-button"
@confirm="$emit('confirm')"
/>
<gl-button
v-else
type="submit"
variant="confirm"
data-testid="project-features-save-button"
data-qa-selector="visibility_features_permissions_save_button"
>
<gl-button v-else type="submit" variant="confirm" data-testid="project-features-save-button">
{{ $options.i18n.confirmButtonText }}
</gl-button>
</div>

View File

@ -148,6 +148,7 @@ export default {
:class="$options.userColorScheme"
data-type="simple"
:data-path="blob.path"
data-testid="blob-viewer-file-content"
>
<codeowners-validation
v-if="isCodeownersFile"

View File

@ -31,7 +31,7 @@ module Ci
span_class = 'gl-text-orange-500'
end
content_tag(:span, class: span_class, title: title, data: { toggle: 'tooltip', container: 'body', testid: 'runner_status_icon', qa_selector: "runner_status_#{status}_content" }) do
content_tag(:span, class: span_class, title: title, data: { toggle: 'tooltip', container: 'body', testid: 'runner-status-icon', qa_status: status }) do
sprite_icon(icon, size: size, css_class: icon_class)
end
end

View File

@ -21,15 +21,6 @@ module NotesHelper
Notes::QuickActionsService.supported?(note)
end
def noteable_json(noteable)
{
id: noteable.id,
class: noteable.class.name,
resources: noteable.class.table_name,
project_id: noteable.project.id
}.to_json
end
def diff_view_data
return {} unless @new_diff_note_attrs
@ -87,10 +78,6 @@ module NotesHelper
end
end
def note_max_access_for_user(note)
note.project.team.max_member_access(note.author_id)
end
def note_human_max_access(note)
note.project.team.human_max_access(note.author_id)
end

View File

@ -35,42 +35,6 @@ module NotificationsHelper
sprite_icon(icon)
end
def notification_title(level)
# Can be anything in `NotificationSetting.level:
case level.to_sym
when :participating
s_('NotificationLevel|Participate')
when :mention
s_('NotificationLevel|On mention')
else
N_('NotificationLevel|Global')
N_('NotificationLevel|Watch')
N_('NotificationLevel|Disabled')
N_('NotificationLevel|Custom')
level = "NotificationLevel|#{level.to_s.humanize}"
s_(level)
end
end
def notification_description(level)
case level.to_sym
when :participating
_('You will only receive notifications for threads you have participated in')
when :mention
_('You will receive notifications only for comments in which you were @mentioned')
when :watch
_('You will receive notifications for any activity')
when :disabled
_('You will not get any notifications via email')
when :global
_('Use your global notification setting')
when :custom
_('You will only receive notifications for the events you choose')
when :owner_disabled
_('Notifications have been disabled by the project or group owner')
end
end
def show_unsubscribe_title?(noteable)
can?(current_user, "read_#{noteable.to_ability_name}".to_sym, noteable)
end

View File

@ -3,10 +3,6 @@
module PackagesHelper
include ::API::Helpers::RelatedResourcesHelpers
def package_sort_path(options = {})
"#{request.path}?#{options.to_param}"
end
def nuget_package_registry_url(project_id)
expose_url(api_v4_projects_packages_nuget_index_path(id: project_id, format: '.json'))
end

View File

@ -27,10 +27,6 @@ module ProfilesHelper
params[:controller] == 'users'
end
def availability_values
Types::AvailabilityEnum.enum
end
def middle_dot_divider_classes(stacking, breakpoint)
['gl-mb-3'].tap do |classes|
if stacking

View File

@ -171,14 +171,6 @@ module TabHelper
current_controller?(c) && current_action?(a)
end
def branches_tab_class
if current_controller?(:protected_branches) ||
current_controller?(:branches) ||
current_page?(project_repository_path(@project))
'active'
end
end
private
def route_matches_paths?(paths)

View File

@ -13,7 +13,7 @@
%br
= _("And this registration token:")
%br
%code#registration_token{ data: {testid: 'registration_token' } }= registration_token
%code#registration_token= registration_token
= deprecated_clipboard_button(target: '#registration_token', title: _("Copy token"))
.gl-mt-3.gl-mb-3

View File

@ -1,8 +0,0 @@
---
name: adherence_report_ui
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122374
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414495
milestone: '16.1'
type: development
group: group::compliance
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: approval_rules_disable_joins
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136588
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/431564
milestone: '16.7'
type: development
group: group::source code
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: compliance_adherence_report
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124167
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/416988
milestone: '16.2'
type: development
group: group::compliance
default_enabled: true

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class PrepareIndexForOrgIdAndIdOnProjects < Gitlab::Database::Migration[2.2]
milestone '16.7'
INDEX_NAME = 'index_projects_on_organization_id_and_id'
def up
prepare_async_index :projects, [:organization_id, :id], name: INDEX_NAME
end
def down
unprepare_async_index :projects, [:organization_id, :id], name: INDEX_NAME
end
end

View File

@ -0,0 +1 @@
c2ac99a5b648b8f369ca84f4270b393d650de1e42834545e736faaf7fc2029b2

View File

@ -27,9 +27,11 @@ In GitLab 14.8 and earlier, projects in personal namespaces have an `access_leve
## Limitations
The `group_saml_identity` attribute is only visible to a group owner for [SSO enabled groups](../user/group/saml_sso/index.md).
The `group_saml_identity` attribute is only visible to group owners for [SSO-enabled groups](../user/group/saml_sso/index.md).
The `email` attribute is only visible to group Owners for any [enterprise user](../user/enterprise_user/index.md).
The `email` attribute is only visible to group owners for users provisioned by the group with [SCIM](../user/group/saml_sso/scim_setup.md).
In GitLab 16.7 and later, the attribute is only visible to group owners for all [enterprise users](../user/enterprise_user/index.md).
For more information, see [issue 391453](https://gitlab.com/gitlab-org/gitlab/-/issues/391453).
## List all members of a group or project

View File

@ -87,19 +87,13 @@ For features that use the embedding database, additional setup is needed.
1. Run `gdk reconfigure`
1. Run database migrations to create the embedding database
### Setup for GitLab documentation chat (legacy chat)
To populate the embedding database for GitLab chat:
1. Open a rails console
1. Run [this script](https://gitlab.com/gitlab-com/gl-infra/production/-/issues/10588#note_1373586079) to populate the embedding database
### Configure GCP Vertex access
In order to obtain a GCP service key for local development, please follow the steps below:
- Create a sandbox GCP project by visiting [this page](https://about.gitlab.com/handbook/infrastructure-standards/#individual-environment) and following the instructions, or by requesting access to our existing group GCP project by using [this template](https://gitlab.com/gitlab-com/it/infra/issue-tracker/-/issues/new?issuable_template=gcp_group_account_iam_update_request).
- If you are using an individual GCP project, you may also need to enable the Vertex AI API:
1. Visit [welcome page](https://console.cloud.google.com/welcome), choose your project (e.g. jdoe-5d23dpe).
1. Go to **APIs & Services > Enabled APIs & services**.
1. Select **+ Enable APIs and Services**.
1. Search for `Vertex AI API`.
@ -141,7 +135,7 @@ we can add a few selected embeddings to the table from a pre-generated fixture.
For instance, to test that the question "How can I reset my password" is correctly
retrieving the relevant embeddings and answered, we can extract the top N closet embeddings
to the question into a fixture and only restore a small number of embeddings quickly.
To faciliate an extraction process, a Rake task been written.
To facilitate an extraction process, a Rake task has been written.
You can add or remove the questions needed to be tested in the Rake task and run the task to generate a new fixture.
```shell

View File

@ -30,6 +30,17 @@ View pipeline duration history:
## View CI/CD analytics
You can view CI/CD analytics for a group or project.
### For a group **(ULTIMATE ALL)**
To view CI/CD analytics:
1. On the left sidebar, select **Search or go to** and find your group.
1. Select **Analyze > CI/CD analytics**.
### For a project **(FREE ALL)**
To view CI/CD analytics:
1. On the left sidebar, select **Search or go to** and find your project.
@ -44,7 +55,7 @@ frequency to the `production` environment. The environment must be part of the
[production deployment tier](../../ci/environments/index.md#deployment-tier-of-environments)
for its deployment information to appear on the graphs.
Deployment frequency is one of the four DORA metrics that DevOps teams use for measuring excellence in software delivery.
Deployment frequency is one of the four DORA metrics that DevOps teams use for measuring excellence in software delivery.
The deployment frequency chart is available for groups and projects.
@ -68,7 +79,7 @@ merge requests to be deployed to a production environment. This chart is availab
- For time periods in which no merge requests were deployed, the charts render a
red, dashed line.
Lead time for changes is one of the four DORA metrics that DevOps teams use for measuring excellence in software delivery.
Lead time for changes is one of the four DORA metrics that DevOps teams use for measuring excellence in software delivery.
To view the lead time for changes chart:

View File

@ -16,11 +16,7 @@ See report and manage standards adherence, violations, and compliance frameworks
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125875) GraphQL APIs in GitLab 16.2 [with a flag](../../../administration/feature_flags.md) named `compliance_adherence_report`. Disabled by default.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125444) standards adherence dashboard in GitLab 16.3 [with a flag](../../../administration/feature_flags.md) named `adherence_report_ui`. Disabled by default.
> - [Enabled](https://gitlab.com/gitlab-org/gitlab/-/issues/414495) in GitLab 16.5.
FLAG:
On self-managed GitLab, by default this feature is available. To hide the feature per project or for your entire instance, an administrator can
[disable the feature flags](../../../administration/feature_flags.md) named `compliance_adherence_report` and `adherence_report_ui`. On GitLab.com,
this feature is available.
> - [Feature flag `compliance_adherence_report` and `adherence_report_ui`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137398) removed in GitLab 16.7.
Standards adherence dashboard lists the adherence status of projects complying to GitLab standard.

View File

@ -28822,12 +28822,6 @@ msgstr ""
msgid "MD5"
msgstr ""
msgid "MLExperimentTracking|CI Info"
msgstr ""
msgid "MLExperimentTracking|Delete candidate?"
msgstr ""
msgid "MLExperimentTracking|Delete experiment?"
msgstr ""
@ -30696,9 +30690,6 @@ msgstr ""
msgid "MlExperimentTracking|CI Job"
msgstr ""
msgid "MlExperimentTracking|Candidate not linked to a CI build"
msgstr ""
msgid "MlExperimentTracking|Candidate removed"
msgstr ""
@ -30714,6 +30705,9 @@ msgstr ""
msgid "MlExperimentTracking|Delete candidate"
msgstr ""
msgid "MlExperimentTracking|Delete candidate?"
msgstr ""
msgid "MlExperimentTracking|Delete experiment"
msgstr ""
@ -30744,36 +30738,18 @@ msgstr ""
msgid "MlExperimentTracking|Get started with model experiments!"
msgstr ""
msgid "MlExperimentTracking|ID"
msgstr ""
msgid "MlExperimentTracking|Info"
msgstr ""
msgid "MlExperimentTracking|Logged candidates for experiment"
msgstr ""
msgid "MlExperimentTracking|MLflow run ID"
msgstr ""
msgid "MlExperimentTracking|Machine learning experiment tracking"
msgstr ""
msgid "MlExperimentTracking|Metadata"
msgstr ""
msgid "MlExperimentTracking|Metrics"
msgstr ""
msgid "MlExperimentTracking|Model candidate details"
msgstr ""
msgid "MlExperimentTracking|Model experiments"
msgstr ""
msgid "MlExperimentTracking|Model performance"
msgstr ""
msgid "MlExperimentTracking|Model removed"
msgstr ""
@ -30789,27 +30765,9 @@ msgstr ""
msgid "MlExperimentTracking|No candidates logged for the query. Create new candidates using the MLflow client."
msgstr ""
msgid "MlExperimentTracking|No logged metadata"
msgstr ""
msgid "MlExperimentTracking|No logged metrics"
msgstr ""
msgid "MlExperimentTracking|No logged parameters"
msgstr ""
msgid "MlExperimentTracking|No name"
msgstr ""
msgid "MlExperimentTracking|Parameters"
msgstr ""
msgid "MlExperimentTracking|Status"
msgstr ""
msgid "MlExperimentTracking|Triggered by"
msgstr ""
msgid "MlModelRegistry|%d model"
msgid_plural "MlModelRegistry|%d models"
msgstr[0] ""
@ -30820,24 +30778,69 @@ msgid_plural "MlModelRegistry|%d versions"
msgstr[0] ""
msgstr[1] ""
msgid "MlModelRegistry|Artifacts"
msgstr ""
msgid "MlModelRegistry|CI Info"
msgstr ""
msgid "MlModelRegistry|Candidate not linked to a CI build"
msgstr ""
msgid "MlModelRegistry|Details"
msgstr ""
msgid "MlModelRegistry|Experiment"
msgstr ""
msgid "MlModelRegistry|ID"
msgstr ""
msgid "MlModelRegistry|Info"
msgstr ""
msgid "MlModelRegistry|Latest version"
msgstr ""
msgid "MlModelRegistry|MLflow run ID"
msgstr ""
msgid "MlModelRegistry|Metadata"
msgstr ""
msgid "MlModelRegistry|Model performance"
msgstr ""
msgid "MlModelRegistry|Model registry"
msgstr ""
msgid "MlModelRegistry|No logged metadata"
msgstr ""
msgid "MlModelRegistry|No logged metrics"
msgstr ""
msgid "MlModelRegistry|No logged parameters"
msgstr ""
msgid "MlModelRegistry|No models registered in this project"
msgstr ""
msgid "MlModelRegistry|No registered versions"
msgstr ""
msgid "MlModelRegistry|Parameters"
msgstr ""
msgid "MlModelRegistry|Status"
msgstr ""
msgid "MlModelRegistry|This model has no versions"
msgstr ""
msgid "MlModelRegistry|Triggered by"
msgstr ""
msgid "MlModelRegistry|Version candidates"
msgstr ""

View File

@ -5,27 +5,12 @@ module QA
module Project
module Settings
class Runners < Page::Base
view 'app/views/ci/runner/_how_to_setup_runner.html.haml' do
element :registration_token, '%code#registration_token' # rubocop:disable QA/ElementWithPattern
element :coordinator_address, '%code#coordinator_address' # rubocop:disable QA/ElementWithPattern
end
view 'app/helpers/ci/runners_helper.rb' do
# rubocop:disable Lint/InterpolationCheck
element :runner_status_icon, 'qa_selector: "runner_status_#{status}_content"' # rubocop:disable QA/ElementWithPattern
# rubocop:enable Lint/InterpolationCheck
end
def registration_token
find('code#registration_token').text
end
def coordinator_address
find('code#coordinator_address').text
element 'runner-status-icon'
end
def has_online_runner?
has_element?(:runner_status_online_content)
has_element?('runner-status-icon', status: 'online')
end
end
end

View File

@ -6,13 +6,13 @@ module QA
module Settings
class VisibilityFeaturesPermissions < Page::Base
view 'app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue' do
element :project_visibility_dropdown
element :visibility_features_permissions_save_button
element 'project-visibility-dropdown'
element 'project-features-save-button'
end
def set_project_visibility(visibility)
select_element(:project_visibility_dropdown, visibility)
click_element :visibility_features_permissions_save_button
select_element('project-visibility-dropdown', visibility)
click_element 'project-features-save-button'
end
end
end

View File

@ -1,206 +1,39 @@
import { GlAvatarLabeled, GlLink, GlTableLite } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { shallowMount } from '@vue/test-utils';
import MlCandidatesShow from '~/ml/experiment_tracking/routes/candidates/show';
import DetailRow from '~/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row.vue';
import {
TITLE_LABEL,
NO_PARAMETERS_MESSAGE,
NO_METRICS_MESSAGE,
NO_METADATA_MESSAGE,
NO_CI_MESSAGE,
} from '~/ml/experiment_tracking/routes/candidates/show/translations';
import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue';
import CandidateDetail from '~/ml/model_registry/components/candidate_detail.vue';
import ModelExperimentsHeader from '~/ml/experiment_tracking/components/model_experiments_header.vue';
import { stubComponent } from 'helpers/stub_component';
import { newCandidate } from './mock_data';
import { newCandidate } from 'jest/ml/model_registry/mock_data';
describe('MlCandidatesShow', () => {
let wrapper;
const CANDIDATE = newCandidate();
const USER_ROW = 1;
const INFO_SECTION = 0;
const CI_SECTION = 1;
const PARAMETER_SECTION = 2;
const METADATA_SECTION = 3;
const createWrapper = (createCandidate = () => CANDIDATE) => {
wrapper = shallowMountExtended(MlCandidatesShow, {
propsData: { candidate: createCandidate() },
stubs: {
GlTableLite: { ...stubComponent(GlTableLite), props: ['items', 'fields'] },
},
const createWrapper = () => {
wrapper = shallowMount(MlCandidatesShow, {
propsData: { candidate: CANDIDATE },
});
};
const findDeleteButton = () => wrapper.findComponent(DeleteButton);
const findHeader = () => wrapper.findComponent(ModelExperimentsHeader);
const findSection = (section) => wrapper.findAll('section').at(section);
const findRowInSection = (section, row) =>
findSection(section).findAllComponents(DetailRow).at(row);
const findLinkAtRow = (section, rowIndex) =>
findRowInSection(section, rowIndex).findComponent(GlLink);
const findNoDataMessage = (label) => wrapper.findByText(label);
const findLabel = (label) => wrapper.find(`[label='${label}']`);
const findCiUserDetailRow = () => findRowInSection(CI_SECTION, USER_ROW);
const findCiUserAvatar = () => findCiUserDetailRow().findComponent(GlAvatarLabeled);
const findCiUserAvatarNameLink = () => findCiUserAvatar().findComponent(GlLink);
const findMetricsTable = () => wrapper.findComponent(GlTableLite);
const findCandidateDetail = () => wrapper.findComponent(CandidateDetail);
describe('Header', () => {
beforeEach(() => createWrapper());
beforeEach(() => createWrapper());
it('shows delete button', () => {
expect(findDeleteButton().exists()).toBe(true);
});
it('passes the delete path to delete button', () => {
expect(findDeleteButton().props('deletePath')).toBe('path_to_candidate');
});
it('passes the right title', () => {
expect(findHeader().props('pageTitle')).toBe(TITLE_LABEL);
});
it('shows delete button', () => {
expect(findDeleteButton().exists()).toBe(true);
});
describe('Detail Table', () => {
describe('All info available', () => {
beforeEach(() => createWrapper());
it('passes the delete path to delete button', () => {
expect(findDeleteButton().props('deletePath')).toBe('path_to_candidate');
});
const mrText = `!${CANDIDATE.info.ci_job.merge_request.iid} ${CANDIDATE.info.ci_job.merge_request.title}`;
const expectedTable = [
[INFO_SECTION, 0, 'ID', CANDIDATE.info.iid],
[INFO_SECTION, 1, 'MLflow run ID', CANDIDATE.info.eid],
[INFO_SECTION, 2, 'Status', CANDIDATE.info.status],
[INFO_SECTION, 3, 'Experiment', CANDIDATE.info.experiment_name],
[INFO_SECTION, 4, 'Artifacts', 'Artifacts'],
[CI_SECTION, 0, 'Job', CANDIDATE.info.ci_job.name],
[CI_SECTION, 1, 'Triggered by', 'CI User'],
[CI_SECTION, 2, 'Merge request', mrText],
[PARAMETER_SECTION, 0, CANDIDATE.params[0].name, CANDIDATE.params[0].value],
[PARAMETER_SECTION, 1, CANDIDATE.params[1].name, CANDIDATE.params[1].value],
[METADATA_SECTION, 0, CANDIDATE.metadata[0].name, CANDIDATE.metadata[0].value],
[METADATA_SECTION, 1, CANDIDATE.metadata[1].name, CANDIDATE.metadata[1].value],
];
it('passes the right title', () => {
expect(findHeader().props('pageTitle')).toBe('Model candidate details');
});
it.each(expectedTable)('row %s is created correctly', (section, rowIndex, label, text) => {
const row = findRowInSection(section, rowIndex);
expect(row.props()).toMatchObject({ label });
expect(row.text()).toBe(text);
});
describe('Table links', () => {
const linkRows = [
[INFO_SECTION, 3, CANDIDATE.info.path_to_experiment],
[INFO_SECTION, 4, CANDIDATE.info.path_to_artifact],
[CI_SECTION, 0, CANDIDATE.info.ci_job.path],
[CI_SECTION, 2, CANDIDATE.info.ci_job.merge_request.path],
];
it.each(linkRows)('row %s is created correctly', (section, rowIndex, href) => {
expect(findLinkAtRow(section, rowIndex).attributes().href).toBe(href);
});
});
describe('Metrics table', () => {
it('computes metrics table items correctly', () => {
expect(findMetricsTable().props('items')).toEqual([
{ name: 'AUC', 0: '.55' },
{ name: 'Accuracy', 1: '.99', 2: '.98', 3: '.97' },
{ name: 'F1', 3: '.1' },
]);
});
it('computes metrics table fields correctly', () => {
expect(findMetricsTable().props('fields')).toEqual([
expect.objectContaining({ key: 'name', label: 'Metric' }),
expect.objectContaining({ key: '0', label: 'Step 0' }),
expect.objectContaining({ key: '1', label: 'Step 1' }),
expect.objectContaining({ key: '2', label: 'Step 2' }),
expect.objectContaining({ key: '3', label: 'Step 3' }),
]);
});
});
describe('CI triggerer', () => {
it('renders user row', () => {
const avatar = findCiUserAvatar();
expect(avatar.props()).toMatchObject({
label: '',
});
expect(avatar.attributes().src).toEqual('/img.png');
});
it('renders user name', () => {
const nameLink = findCiUserAvatarNameLink();
expect(nameLink.attributes().href).toEqual('path/to/ci/user');
expect(nameLink.text()).toEqual('CI User');
});
});
});
describe('No artifact path', () => {
beforeEach(() =>
createWrapper(() => {
const candidate = newCandidate();
delete candidate.info.path_to_artifact;
return candidate;
}),
);
it('does not render artifact row', () => {
expect(findLabel('Artifacts').exists()).toBe(false);
});
});
describe('No params, metrics, ci or metadata available', () => {
beforeEach(() =>
createWrapper(() => {
const candidate = newCandidate();
delete candidate.params;
delete candidate.metrics;
delete candidate.metadata;
delete candidate.info.ci_job;
return candidate;
}),
);
it('does not render params', () => {
expect(findNoDataMessage(NO_PARAMETERS_MESSAGE).exists()).toBe(true);
});
it('does not render metadata', () => {
expect(findNoDataMessage(NO_METADATA_MESSAGE).exists()).toBe(true);
});
it('does not render metrics', () => {
expect(findNoDataMessage(NO_METRICS_MESSAGE).exists()).toBe(true);
});
it('does not render CI info', () => {
expect(findNoDataMessage(NO_CI_MESSAGE).exists()).toBe(true);
});
});
describe('Has CI, but no user or mr', () => {
beforeEach(() =>
createWrapper(() => {
const candidate = newCandidate();
delete candidate.info.ci_job.user;
delete candidate.info.ci_job.merge_request;
return candidate;
}),
);
it('does not render MR info', () => {
expect(findLabel('Merge request').exists()).toBe(false);
});
it('does not render CI user info', () => {
expect(findLabel('Triggered by').exists()).toBe(false);
});
});
it('creates the candidate detail section', () => {
expect(findCandidateDetail().props('candidate')).toBe(CANDIDATE);
});
});

View File

@ -1,41 +0,0 @@
export const newCandidate = () => ({
params: [
{ name: 'Algorithm', value: 'Decision Tree' },
{ name: 'MaxDepth', value: '3' },
],
metrics: [
{ name: 'AUC', value: '.55', step: 0 },
{ name: 'Accuracy', value: '.99', step: 1 },
{ name: 'Accuracy', value: '.98', step: 2 },
{ name: 'Accuracy', value: '.97', step: 3 },
{ name: 'F1', value: '.1', step: 3 },
],
metadata: [
{ name: 'FileName', value: 'test.py' },
{ name: 'ExecutionTime', value: '.0856' },
],
info: {
iid: 'candidate_iid',
eid: 'abcdefg',
path_to_artifact: 'path_to_artifact',
experiment_name: 'The Experiment',
path_to_experiment: 'path/to/experiment',
status: 'SUCCESS',
path: 'path_to_candidate',
ci_job: {
name: 'test',
path: 'path/to/job',
merge_request: {
path: 'path/to/mr',
iid: 1,
title: 'Some MR',
},
user: {
path: 'path/to/ci/user',
name: 'CI User',
username: 'ciuser',
avatar: '/img.png',
},
},
},
});

View File

@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import DetailRow from '~/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row.vue';
import DetailRow from '~/ml/model_registry/components/candidate_detail_row.vue';
describe('CandidateDetailRow', () => {
const ROW_LABEL_CELL = 0;

View File

@ -0,0 +1,183 @@
import { GlAvatarLabeled, GlLink, GlTableLite } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import CandidateDetail from '~/ml/model_registry/components/candidate_detail.vue';
import DetailRow from '~/ml/model_registry/components/candidate_detail_row.vue';
import {
NO_PARAMETERS_MESSAGE,
NO_METRICS_MESSAGE,
NO_METADATA_MESSAGE,
NO_CI_MESSAGE,
} from '~/ml/model_registry/translations';
import { stubComponent } from 'helpers/stub_component';
import { newCandidate } from '../mock_data';
describe('ml/model_registry/components/candidate_detail.vue', () => {
let wrapper;
const CANDIDATE = newCandidate();
const USER_ROW = 1;
const INFO_SECTION = 0;
const CI_SECTION = 1;
const PARAMETER_SECTION = 2;
const METADATA_SECTION = 3;
const createWrapper = (createCandidate = () => CANDIDATE) => {
wrapper = shallowMountExtended(CandidateDetail, {
propsData: { candidate: createCandidate() },
stubs: {
GlTableLite: { ...stubComponent(GlTableLite), props: ['items', 'fields'] },
},
});
};
const findSection = (section) => wrapper.findAll('section').at(section);
const findRowInSection = (section, row) =>
findSection(section).findAllComponents(DetailRow).at(row);
const findLinkAtRow = (section, rowIndex) =>
findRowInSection(section, rowIndex).findComponent(GlLink);
const findNoDataMessage = (label) => wrapper.findByText(label);
const findLabel = (label) => wrapper.find(`[label='${label}']`);
const findCiUserDetailRow = () => findRowInSection(CI_SECTION, USER_ROW);
const findCiUserAvatar = () => findCiUserDetailRow().findComponent(GlAvatarLabeled);
const findCiUserAvatarNameLink = () => findCiUserAvatar().findComponent(GlLink);
const findMetricsTable = () => wrapper.findComponent(GlTableLite);
describe('All info available', () => {
beforeEach(() => createWrapper());
const mrText = `!${CANDIDATE.info.ci_job.merge_request.iid} ${CANDIDATE.info.ci_job.merge_request.title}`;
const expectedTable = [
[INFO_SECTION, 0, 'ID', CANDIDATE.info.iid],
[INFO_SECTION, 1, 'MLflow run ID', CANDIDATE.info.eid],
[INFO_SECTION, 2, 'Status', CANDIDATE.info.status],
[INFO_SECTION, 3, 'Experiment', CANDIDATE.info.experiment_name],
[INFO_SECTION, 4, 'Artifacts', 'Artifacts'],
[CI_SECTION, 0, 'Job', CANDIDATE.info.ci_job.name],
[CI_SECTION, 1, 'Triggered by', 'CI User'],
[CI_SECTION, 2, 'Merge request', mrText],
[PARAMETER_SECTION, 0, CANDIDATE.params[0].name, CANDIDATE.params[0].value],
[PARAMETER_SECTION, 1, CANDIDATE.params[1].name, CANDIDATE.params[1].value],
[METADATA_SECTION, 0, CANDIDATE.metadata[0].name, CANDIDATE.metadata[0].value],
[METADATA_SECTION, 1, CANDIDATE.metadata[1].name, CANDIDATE.metadata[1].value],
];
it.each(expectedTable)('row %s is created correctly', (section, rowIndex, label, text) => {
const row = findRowInSection(section, rowIndex);
expect(row.props()).toMatchObject({ label });
expect(row.text()).toBe(text);
});
describe('Table links', () => {
const linkRows = [
[INFO_SECTION, 3, CANDIDATE.info.path_to_experiment],
[INFO_SECTION, 4, CANDIDATE.info.path_to_artifact],
[CI_SECTION, 0, CANDIDATE.info.ci_job.path],
[CI_SECTION, 2, CANDIDATE.info.ci_job.merge_request.path],
];
it.each(linkRows)('row %s is created correctly', (section, rowIndex, href) => {
expect(findLinkAtRow(section, rowIndex).attributes().href).toBe(href);
});
});
describe('Metrics table', () => {
it('computes metrics table items correctly', () => {
expect(findMetricsTable().props('items')).toEqual([
{ name: 'AUC', 0: '.55' },
{ name: 'Accuracy', 1: '.99', 2: '.98', 3: '.97' },
{ name: 'F1', 3: '.1' },
]);
});
it('computes metrics table fields correctly', () => {
expect(findMetricsTable().props('fields')).toEqual([
expect.objectContaining({ key: 'name', label: 'Metric' }),
expect.objectContaining({ key: '0', label: 'Step 0' }),
expect.objectContaining({ key: '1', label: 'Step 1' }),
expect.objectContaining({ key: '2', label: 'Step 2' }),
expect.objectContaining({ key: '3', label: 'Step 3' }),
]);
});
});
describe('CI triggerer', () => {
it('renders user row', () => {
const avatar = findCiUserAvatar();
expect(avatar.props()).toMatchObject({
label: '',
});
expect(avatar.attributes().src).toEqual('/img.png');
});
it('renders user name', () => {
const nameLink = findCiUserAvatarNameLink();
expect(nameLink.attributes().href).toEqual('path/to/ci/user');
expect(nameLink.text()).toEqual('CI User');
});
});
});
describe('No artifact path', () => {
beforeEach(() =>
createWrapper(() => {
const candidate = newCandidate();
delete candidate.info.path_to_artifact;
return candidate;
}),
);
it('does not render artifact row', () => {
expect(findLabel('Artifacts').exists()).toBe(false);
});
});
describe('No params, metrics, ci or metadata available', () => {
beforeEach(() =>
createWrapper(() => {
const candidate = newCandidate();
delete candidate.params;
delete candidate.metrics;
delete candidate.metadata;
delete candidate.info.ci_job;
return candidate;
}),
);
it('does not render params', () => {
expect(findNoDataMessage(NO_PARAMETERS_MESSAGE).exists()).toBe(true);
});
it('does not render metadata', () => {
expect(findNoDataMessage(NO_METADATA_MESSAGE).exists()).toBe(true);
});
it('does not render metrics', () => {
expect(findNoDataMessage(NO_METRICS_MESSAGE).exists()).toBe(true);
});
it('does not render CI info', () => {
expect(findNoDataMessage(NO_CI_MESSAGE).exists()).toBe(true);
});
});
describe('Has CI, but no user or mr', () => {
beforeEach(() =>
createWrapper(() => {
const candidate = newCandidate();
delete candidate.info.ci_job.user;
delete candidate.info.ci_job.merge_request;
return candidate;
}),
);
it('does not render MR info', () => {
expect(findLabel('Merge request').exists()).toBe(false);
});
it('does not render CI user info', () => {
expect(findLabel('Triggered by').exists()).toBe(false);
});
});
});

View File

@ -46,3 +46,45 @@ export const defaultPageInfo = Object.freeze({
hasNextPage: true,
hasPreviousPage: true,
});
export const newCandidate = () => ({
params: [
{ name: 'Algorithm', value: 'Decision Tree' },
{ name: 'MaxDepth', value: '3' },
],
metrics: [
{ name: 'AUC', value: '.55', step: 0 },
{ name: 'Accuracy', value: '.99', step: 1 },
{ name: 'Accuracy', value: '.98', step: 2 },
{ name: 'Accuracy', value: '.97', step: 3 },
{ name: 'F1', value: '.1', step: 3 },
],
metadata: [
{ name: 'FileName', value: 'test.py' },
{ name: 'ExecutionTime', value: '.0856' },
],
info: {
iid: 'candidate_iid',
eid: 'abcdefg',
path_to_artifact: 'path_to_artifact',
experiment_name: 'The Experiment',
path_to_experiment: 'path/to/experiment',
status: 'SUCCESS',
path: 'path_to_candidate',
ci_job: {
name: 'test',
path: 'path/to/job',
merge_request: {
path: 'path/to/mr',
iid: 1,
title: 'Some MR',
},
user: {
path: 'path/to/ci/user',
name: 'CI User',
username: 'ciuser',
avatar: '/img.png',
},
},
},
});

View File

@ -55,23 +55,6 @@ RSpec.describe NotesHelper, feature_category: :team_planning do
end
end
describe "#notes_max_access_for_users" do
it 'returns access levels' do
expect(helper.note_max_access_for_user(owner_note)).to eq(Gitlab::Access::OWNER)
expect(helper.note_max_access_for_user(maintainer_note)).to eq(Gitlab::Access::MAINTAINER)
expect(helper.note_max_access_for_user(reporter_note)).to eq(Gitlab::Access::REPORTER)
end
it 'handles access in different projects' do
second_project = create(:project)
second_project.add_reporter(maintainer)
other_note = create(:note, author: maintainer, project: second_project)
expect(helper.note_max_access_for_user(maintainer_note)).to eq(Gitlab::Access::MAINTAINER)
expect(helper.note_max_access_for_user(other_note)).to eq(Gitlab::Access::REPORTER)
end
end
describe '#discussion_path' do
let_it_be(:project) { create(:project, :repository) }

View File

@ -13,12 +13,6 @@ RSpec.describe NotificationsHelper do
it { expect(notification_icon(:custom)).to equal('') }
end
describe 'notification_title' do
it { expect(notification_title(:watch)).to match('Watch') }
it { expect(notification_title(:mention)).to match('On mention') }
it { expect(notification_title(:global)).to match('Global') }
end
describe '#notification_icon_level' do
let(:user) { create(:user) }
let(:global_setting) { user.global_notification_setting }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.