Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
cb9d96285c
commit
f73fa6daff
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
@ -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>
|
||||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
c2ac99a5b648b8f369ca84f4270b393d650de1e42834545e736faaf7fc2029b2
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue