diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-21 12:15:06 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-21 12:15:06 +0300 |
commit | f73fa6daff38ea21d33a71f7bdcba34a86421333 (patch) | |
tree | 2efcadb09b56150b0f01209d217f167f446c02d6 | |
parent | cb9d96285c52d95a49782688e4ec8dd3f3942c89 (diff) |
Add latest changes from gitlab-org/gitlab@master
63 files changed, 576 insertions, 652 deletions
diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/ml_candidates_show.vue b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/ml_candidates_show.vue index 43d28e3d699..ea942012af3 100644 --- a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/ml_candidates_show.vue +++ b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/ml_candidates_show.vue @@ -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> diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/translations.js b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/translations.js deleted file mode 100644 index 98988e1db35..00000000000 --- a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/translations.js +++ /dev/null @@ -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'); diff --git a/app/assets/javascripts/ml/model_registry/components/candidate_detail.vue b/app/assets/javascripts/ml/model_registry/components/candidate_detail.vue new file mode 100644 index 00000000000..8c32fb3a2c6 --- /dev/null +++ b/app/assets/javascripts/ml/model_registry/components/candidate_detail.vue @@ -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> diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row.vue b/app/assets/javascripts/ml/model_registry/components/candidate_detail_row.vue index 8c7460940a0..8c7460940a0 100644 --- a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row.vue +++ b/app/assets/javascripts/ml/model_registry/components/candidate_detail_row.vue diff --git a/app/assets/javascripts/ml/model_registry/translations.js b/app/assets/javascripts/ml/model_registry/translations.js index 89b3f45ed94..689eeb45b00 100644 --- a/app/assets/javascripts/ml/model_registry/translations.js +++ b/app/assets/javascripts/ml/model_registry/translations.js @@ -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'); diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index 6ff48b7de95..c6d18a1328b 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -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> diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer_new.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer_new.vue index 7ced12952dd..bc46f11ab2d 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer_new.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer_new.vue @@ -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" diff --git a/app/helpers/ci/runners_helper.rb b/app/helpers/ci/runners_helper.rb index 7cc554bbeeb..c5d62b49a3c 100644 --- a/app/helpers/ci/runners_helper.rb +++ b/app/helpers/ci/runners_helper.rb @@ -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 diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 75e89a7d7bc..aad3e07ea28 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -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 diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index ddaef4652b4..b6e435986ce 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -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 diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb index fefc19d7c1a..887f63ce05d 100644 --- a/app/helpers/packages_helper.rb +++ b/app/helpers/packages_helper.rb @@ -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 diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb index 8d260d5e455..c115e4c594a 100644 --- a/app/helpers/profiles_helper.rb +++ b/app/helpers/profiles_helper.rb @@ -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 diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 137b24102e0..90f3c1e6ae6 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -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) diff --git a/app/views/ci/runner/_how_to_setup_runner.html.haml b/app/views/ci/runner/_how_to_setup_runner.html.haml index c46aabf2604..29c2e364c37 100644 --- a/app/views/ci/runner/_how_to_setup_runner.html.haml +++ b/app/views/ci/runner/_how_to_setup_runner.html.haml @@ -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 diff --git a/config/feature_flags/development/adherence_report_ui.yml b/config/feature_flags/development/adherence_report_ui.yml deleted file mode 100644 index 7db4fbb756f..00000000000 --- a/config/feature_flags/development/adherence_report_ui.yml +++ /dev/null @@ -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 diff --git a/config/feature_flags/development/approval_rules_disable_joins.yml b/config/feature_flags/development/approval_rules_disable_joins.yml deleted file mode 100644 index 1ee33b45ba7..00000000000 --- a/config/feature_flags/development/approval_rules_disable_joins.yml +++ /dev/null @@ -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 diff --git a/config/feature_flags/development/compliance_adherence_report.yml b/config/feature_flags/development/compliance_adherence_report.yml deleted file mode 100644 index f67ff7bdec3..00000000000 --- a/config/feature_flags/development/compliance_adherence_report.yml +++ /dev/null @@ -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 diff --git a/db/post_migrate/20231120161159_prepare_index_for_org_id_and_id_on_projects.rb b/db/post_migrate/20231120161159_prepare_index_for_org_id_and_id_on_projects.rb new file mode 100644 index 00000000000..1a49f8ee43f --- /dev/null +++ b/db/post_migrate/20231120161159_prepare_index_for_org_id_and_id_on_projects.rb @@ -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 diff --git a/db/schema_migrations/20231120161159 b/db/schema_migrations/20231120161159 new file mode 100644 index 00000000000..96a59184bde --- /dev/null +++ b/db/schema_migrations/20231120161159 @@ -0,0 +1 @@ +c2ac99a5b648b8f369ca84f4270b393d650de1e42834545e736faaf7fc2029b2
\ No newline at end of file diff --git a/doc/api/members.md b/doc/api/members.md index de59c66890d..af9d47a005b 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -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 diff --git a/doc/development/ai_features/index.md b/doc/development/ai_features/index.md index 9a980b89757..87c8abea405 100644 --- a/doc/development/ai_features/index.md +++ b/doc/development/ai_features/index.md @@ -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 diff --git a/doc/user/analytics/ci_cd_analytics.md b/doc/user/analytics/ci_cd_analytics.md index 61bc77e4469..e86e773caca 100644 --- a/doc/user/analytics/ci_cd_analytics.md +++ b/doc/user/analytics/ci_cd_analytics.md @@ -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: diff --git a/doc/user/compliance/compliance_center/index.md b/doc/user/compliance/compliance_center/index.md index 4a42a70a7e7..e17f5c1ec39 100644 --- a/doc/user/compliance/compliance_center/index.md +++ b/doc/user/compliance/compliance_center/index.md @@ -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. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 61eded19625..8487440719f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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,55 +30765,82 @@ msgstr "" msgid "MlExperimentTracking|No candidates logged for the query. Create new candidates using the MLflow client." msgstr "" -msgid "MlExperimentTracking|No logged metadata" +msgid "MlExperimentTracking|No name" +msgstr "" + +msgid "MlModelRegistry|%d model" +msgid_plural "MlModelRegistry|%d models" +msgstr[0] "" +msgstr[1] "" + +msgid "MlModelRegistry|%d version" +msgid_plural "MlModelRegistry|%d versions" +msgstr[0] "" +msgstr[1] "" + +msgid "MlModelRegistry|Artifacts" msgstr "" -msgid "MlExperimentTracking|No logged metrics" +msgid "MlModelRegistry|CI Info" msgstr "" -msgid "MlExperimentTracking|No logged parameters" +msgid "MlModelRegistry|Candidate not linked to a CI build" msgstr "" -msgid "MlExperimentTracking|No name" +msgid "MlModelRegistry|Details" msgstr "" -msgid "MlExperimentTracking|Parameters" +msgid "MlModelRegistry|Experiment" msgstr "" -msgid "MlExperimentTracking|Status" +msgid "MlModelRegistry|ID" msgstr "" -msgid "MlExperimentTracking|Triggered by" +msgid "MlModelRegistry|Info" msgstr "" -msgid "MlModelRegistry|%d model" -msgid_plural "MlModelRegistry|%d models" -msgstr[0] "" -msgstr[1] "" +msgid "MlModelRegistry|Latest version" +msgstr "" -msgid "MlModelRegistry|%d version" -msgid_plural "MlModelRegistry|%d versions" -msgstr[0] "" -msgstr[1] "" +msgid "MlModelRegistry|MLflow run ID" +msgstr "" -msgid "MlModelRegistry|Details" +msgid "MlModelRegistry|Metadata" msgstr "" -msgid "MlModelRegistry|Latest version" +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 "" diff --git a/qa/qa/page/project/settings/runners.rb b/qa/qa/page/project/settings/runners.rb index aa1ac216ae2..ce3bc1a5396 100644 --- a/qa/qa/page/project/settings/runners.rb +++ b/qa/qa/page/project/settings/runners.rb @@ -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 diff --git a/qa/qa/page/project/settings/visibility_features_permissions.rb b/qa/qa/page/project/settings/visibility_features_permissions.rb index 60cea6de7f5..a0b21fd1698 100644 --- a/qa/qa/page/project/settings/visibility_features_permissions.rb +++ b/qa/qa/page/project/settings/visibility_features_permissions.rb @@ -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 diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js b/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js index 296728af46a..3999e906cec 100644 --- a/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js +++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js @@ -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); - - describe('Header', () => { - beforeEach(() => createWrapper()); - - it('shows delete button', () => { - expect(findDeleteButton().exists()).toBe(true); - }); + const findCandidateDetail = () => wrapper.findComponent(CandidateDetail); - it('passes the delete path to delete button', () => { - expect(findDeleteButton().props('deletePath')).toBe('path_to_candidate'); - }); + beforeEach(() => createWrapper()); - 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()); - - 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('passes the delete path to delete button', () => { + expect(findDeleteButton().props('deletePath')).toBe('path_to_candidate'); + }); - it('does not render MR info', () => { - expect(findLabel('Merge request').exists()).toBe(false); - }); + it('passes the right title', () => { + expect(findHeader().props('pageTitle')).toBe('Model candidate details'); + }); - 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); }); }); diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/mock_data.js b/spec/frontend/ml/experiment_tracking/routes/candidates/show/mock_data.js deleted file mode 100644 index 4ea23ed2513..00000000000 --- a/spec/frontend/ml/experiment_tracking/routes/candidates/show/mock_data.js +++ /dev/null @@ -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', - }, - }, - }, -}); diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row_spec.js b/spec/frontend/ml/model_registry/components/candidate_detail_row_spec.js index cd252560590..24b18b6b42d 100644 --- a/spec/frontend/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row_spec.js +++ b/spec/frontend/ml/model_registry/components/candidate_detail_row_spec.js @@ -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; diff --git a/spec/frontend/ml/model_registry/components/candidate_detail_spec.js b/spec/frontend/ml/model_registry/components/candidate_detail_spec.js new file mode 100644 index 00000000000..f894af105ac --- /dev/null +++ b/spec/frontend/ml/model_registry/components/candidate_detail_spec.js @@ -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); + }); + }); +}); diff --git a/spec/frontend/ml/model_registry/mock_data.js b/spec/frontend/ml/model_registry/mock_data.js index a820c323103..c214194af9e 100644 --- a/spec/frontend/ml/model_registry/mock_data.js +++ b/spec/frontend/ml/model_registry/mock_data.js @@ -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', + }, + }, + }, +}); diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb index 62c0d1b1ff7..58d39caa90c 100644 --- a/spec/helpers/notes_helper_spec.rb +++ b/spec/helpers/notes_helper_spec.rb @@ -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) } diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb index a5338659659..8acd72b76a1 100644 --- a/spec/helpers/notifications_helper_spec.rb +++ b/spec/helpers/notifications_helper_spec.rb @@ -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 } diff --git a/vendor/project_templates/android.tar.gz b/vendor/project_templates/android.tar.gz Binary files differindex ee0689a6e45..6ce41349e39 100644 --- a/vendor/project_templates/android.tar.gz +++ b/vendor/project_templates/android.tar.gz diff --git a/vendor/project_templates/astro_tailwind.tar.gz b/vendor/project_templates/astro_tailwind.tar.gz Binary files differindex 4724b01d3f6..343edbda86b 100644 --- a/vendor/project_templates/astro_tailwind.tar.gz +++ b/vendor/project_templates/astro_tailwind.tar.gz diff --git a/vendor/project_templates/bridgetown.tar.gz b/vendor/project_templates/bridgetown.tar.gz Binary files differindex 8b4c63e1be5..8295ebfbe76 100644 --- a/vendor/project_templates/bridgetown.tar.gz +++ b/vendor/project_templates/bridgetown.tar.gz diff --git a/vendor/project_templates/cluster_management.tar.gz b/vendor/project_templates/cluster_management.tar.gz Binary files differindex 252fb2da75a..a76ca916691 100644 --- a/vendor/project_templates/cluster_management.tar.gz +++ b/vendor/project_templates/cluster_management.tar.gz diff --git a/vendor/project_templates/dotnetcore.tar.gz b/vendor/project_templates/dotnetcore.tar.gz Binary files differindex 63b86320cb1..d798b602595 100644 --- a/vendor/project_templates/dotnetcore.tar.gz +++ b/vendor/project_templates/dotnetcore.tar.gz diff --git a/vendor/project_templates/express.tar.gz b/vendor/project_templates/express.tar.gz Binary files differindex 3babc60a5bc..7d0b0c8fcf3 100644 --- a/vendor/project_templates/express.tar.gz +++ b/vendor/project_templates/express.tar.gz diff --git a/vendor/project_templates/gatsby.tar.gz b/vendor/project_templates/gatsby.tar.gz Binary files differindex 1c0e413b2ec..be2c30c500e 100644 --- a/vendor/project_templates/gatsby.tar.gz +++ b/vendor/project_templates/gatsby.tar.gz diff --git a/vendor/project_templates/gitpod_spring_petclinic.tar.gz b/vendor/project_templates/gitpod_spring_petclinic.tar.gz Binary files differindex 76308dc73df..9d146dcd4b7 100644 --- a/vendor/project_templates/gitpod_spring_petclinic.tar.gz +++ b/vendor/project_templates/gitpod_spring_petclinic.tar.gz diff --git a/vendor/project_templates/gomicro.tar.gz b/vendor/project_templates/gomicro.tar.gz Binary files differindex 50899e661c7..b0d286365fa 100644 --- a/vendor/project_templates/gomicro.tar.gz +++ b/vendor/project_templates/gomicro.tar.gz diff --git a/vendor/project_templates/hexo.tar.gz b/vendor/project_templates/hexo.tar.gz Binary files differindex cc30ff0a67e..57c6cf7a4d1 100644 --- a/vendor/project_templates/hexo.tar.gz +++ b/vendor/project_templates/hexo.tar.gz diff --git a/vendor/project_templates/hugo.tar.gz b/vendor/project_templates/hugo.tar.gz Binary files differindex 3d037bbf1df..14154a92b46 100644 --- a/vendor/project_templates/hugo.tar.gz +++ b/vendor/project_templates/hugo.tar.gz diff --git a/vendor/project_templates/iosswift.tar.gz b/vendor/project_templates/iosswift.tar.gz Binary files differindex 80a71b432b5..b95d9ee828f 100644 --- a/vendor/project_templates/iosswift.tar.gz +++ b/vendor/project_templates/iosswift.tar.gz diff --git a/vendor/project_templates/jekyll.tar.gz b/vendor/project_templates/jekyll.tar.gz Binary files differindex 8dc68699baa..9dd47dfbd64 100644 --- a/vendor/project_templates/jekyll.tar.gz +++ b/vendor/project_templates/jekyll.tar.gz diff --git a/vendor/project_templates/jsonnet.tar.gz b/vendor/project_templates/jsonnet.tar.gz Binary files differindex 8da4227530a..b32b3f6a69b 100644 --- a/vendor/project_templates/jsonnet.tar.gz +++ b/vendor/project_templates/jsonnet.tar.gz diff --git a/vendor/project_templates/kotlin_native_linux.tar.gz b/vendor/project_templates/kotlin_native_linux.tar.gz Binary files differindex 6c50c96ae9b..639ce89f736 100644 --- a/vendor/project_templates/kotlin_native_linux.tar.gz +++ b/vendor/project_templates/kotlin_native_linux.tar.gz diff --git a/vendor/project_templates/laravel.tar.gz b/vendor/project_templates/laravel.tar.gz Binary files differindex 49324636f5e..163b76cb428 100644 --- a/vendor/project_templates/laravel.tar.gz +++ b/vendor/project_templates/laravel.tar.gz diff --git a/vendor/project_templates/middleman.tar.gz b/vendor/project_templates/middleman.tar.gz Binary files differindex b926aa92a20..2b2bd84a494 100644 --- a/vendor/project_templates/middleman.tar.gz +++ b/vendor/project_templates/middleman.tar.gz diff --git a/vendor/project_templates/nfgitbook.tar.gz b/vendor/project_templates/nfgitbook.tar.gz Binary files differindex f09a5f41171..cbb55129864 100644 --- a/vendor/project_templates/nfgitbook.tar.gz +++ b/vendor/project_templates/nfgitbook.tar.gz diff --git a/vendor/project_templates/nfhexo.tar.gz b/vendor/project_templates/nfhexo.tar.gz Binary files differindex 3a241f68df4..107c8947fbd 100644 --- a/vendor/project_templates/nfhexo.tar.gz +++ b/vendor/project_templates/nfhexo.tar.gz diff --git a/vendor/project_templates/nfhugo.tar.gz b/vendor/project_templates/nfhugo.tar.gz Binary files differindex 093ecdea96a..3f555584593 100644 --- a/vendor/project_templates/nfhugo.tar.gz +++ b/vendor/project_templates/nfhugo.tar.gz diff --git a/vendor/project_templates/nfjekyll.tar.gz b/vendor/project_templates/nfjekyll.tar.gz Binary files differindex f554181e1db..20275cf7744 100644 --- a/vendor/project_templates/nfjekyll.tar.gz +++ b/vendor/project_templates/nfjekyll.tar.gz diff --git a/vendor/project_templates/nfplainhtml.tar.gz b/vendor/project_templates/nfplainhtml.tar.gz Binary files differindex 13dd13a6830..ef6ccf12021 100644 --- a/vendor/project_templates/nfplainhtml.tar.gz +++ b/vendor/project_templates/nfplainhtml.tar.gz diff --git a/vendor/project_templates/pelican.tar.gz b/vendor/project_templates/pelican.tar.gz Binary files differindex bc87d498ced..9d144ef0bad 100644 --- a/vendor/project_templates/pelican.tar.gz +++ b/vendor/project_templates/pelican.tar.gz diff --git a/vendor/project_templates/plainhtml.tar.gz b/vendor/project_templates/plainhtml.tar.gz Binary files differindex dc0354172be..e8b3be52107 100644 --- a/vendor/project_templates/plainhtml.tar.gz +++ b/vendor/project_templates/plainhtml.tar.gz diff --git a/vendor/project_templates/rails.tar.gz b/vendor/project_templates/rails.tar.gz Binary files differindex 0d82bf639de..b8a0d0be235 100644 --- a/vendor/project_templates/rails.tar.gz +++ b/vendor/project_templates/rails.tar.gz diff --git a/vendor/project_templates/salesforcedx.tar.gz b/vendor/project_templates/salesforcedx.tar.gz Binary files differindex 9486b410507..58746f89766 100644 --- a/vendor/project_templates/salesforcedx.tar.gz +++ b/vendor/project_templates/salesforcedx.tar.gz diff --git a/vendor/project_templates/serverless_framework.tar.gz b/vendor/project_templates/serverless_framework.tar.gz Binary files differindex 6b5def03ed3..f8d86acdfc9 100644 --- a/vendor/project_templates/serverless_framework.tar.gz +++ b/vendor/project_templates/serverless_framework.tar.gz diff --git a/vendor/project_templates/spring.tar.gz b/vendor/project_templates/spring.tar.gz Binary files differindex 12960e91f85..9cf97f09c2a 100644 --- a/vendor/project_templates/spring.tar.gz +++ b/vendor/project_templates/spring.tar.gz diff --git a/vendor/project_templates/tencent_serverless_framework.tar.gz b/vendor/project_templates/tencent_serverless_framework.tar.gz Binary files differindex f243dc30c63..dcbb0ec16a5 100644 --- a/vendor/project_templates/tencent_serverless_framework.tar.gz +++ b/vendor/project_templates/tencent_serverless_framework.tar.gz diff --git a/vendor/project_templates/typo3_distribution.tar.gz b/vendor/project_templates/typo3_distribution.tar.gz Binary files differindex 6c29993224b..dd4024fce96 100644 --- a/vendor/project_templates/typo3_distribution.tar.gz +++ b/vendor/project_templates/typo3_distribution.tar.gz |