Коммит 5688a917 создал по автору Eduardo Bonet's avatar Eduardo Bonet
Просмотр файлов

wip

владелец dfe07be4
......@@ -180,7 +180,7 @@ export default {
</template>
<template #cell(nameColumn)="data">
<gl-link :href="data.value.details_path">
<gl-link :href="data.value.detailsPath">
<span v-if="data.value.name"> {{ data.value.name }}</span>
<span v-else class="gl-font-style-italic">{{ $options.i18n.NO_CANDIDATE_NAME }}</span>
</gl-link>
......@@ -195,7 +195,7 @@ export default {
</div>
</template>
<template #cell(created_at)="data">
<template #cell(createdAt)="data">
<time-ago :time="data.value" />
</template>
......
import Vue from 'vue';
import { initSimpleApp } from '~/helpers/init_simple_app_helper';
import MlExperimentsShow from '~/ml/experiment_tracking/routes/experiments/show/ml_experiments_show.vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
const initShowExperiment = () => {
const element = document.querySelector('#js-show-ml-experiment');
if (!element) {
return undefined;
}
const props = {
candidates: JSON.parse(element.dataset.candidates),
metricNames: JSON.parse(element.dataset.metrics),
paramNames: JSON.parse(element.dataset.params),
pageInfo: convertObjectPropsToCamelCase(JSON.parse(element.dataset.pageInfo)),
};
return new Vue({
el: element,
render(h) {
return h(MlExperimentsShow, { props });
},
});
};
initShowExperiment();
initSimpleApp('#js-show-ml-experiment', MlExperimentsShow);
......@@ -29,13 +29,12 @@ def show
.transform_keys(&:underscore)
.permit(:name, :order_by, :sort, :order_by_type)
paginator = CandidateFinder
@paginator = CandidateFinder
.new(@experiment, find_params)
.execute
.keyset_paginate(cursor: params[:cursor], per_page: MAX_CANDIDATES_PER_PAGE)
@candidates = paginator.records.each(&:artifact_lazy)
@page_info = page_info(paginator)
@candidates = @paginator.records.each(&:artifact_lazy)
end
def destroy
......
......@@ -24,26 +24,6 @@ def show_candidate_view_model(candidate)
Gitlab::Json.generate(data)
end
def candidates_table_items(candidates)
items = candidates.map do |candidate|
{
**candidate.params.to_h { |p| [p.name, p.value] },
**candidate.latest_metrics.to_h { |m| [m.name, number_with_precision(m.value, precision: 4)] },
artifact: link_to_artifact(candidate),
details: link_to_details(candidate),
name: candidate.name,
created_at: candidate.created_at,
user: user_info(candidate)
}
end
Gitlab::Json.generate(items)
end
def unique_logged_names(candidates, &selector)
Gitlab::Json.generate(candidates.flat_map(&selector).map(&:name).uniq)
end
def experiments_as_data(project, experiments)
data = experiments.map do |exp|
{
......
# frozen_string_literal: true
module Projects
module Ml
class BasePresenter
include Rails.application.routes.url_helpers
def view_model
raise NotImplementedError, "#{self.class} does not implement #{__method__}"
end
def view_model_as_json
Gitlab::Json.generate(view_model.deep_transform_keys! { |key| key.to_s.camelize(:lower) })
end
def link_to_artifact(candidate)
artifact = candidate.artifact
return unless artifact.present?
project_package_path(candidate.project, artifact)
end
def link_to_details(candidate)
project_ml_candidate_path(candidate.project, candidate.iid)
end
def link_to_experiment(project, experiment)
project_ml_experiment_path(project, experiment.iid)
end
def page_info(paginator)
{
has_next_page: paginator.has_next_page?,
has_previous_page: paginator.has_previous_page?,
start_cursor: paginator.cursor_for_previous_page,
end_cursor: paginator.cursor_for_next_page
}
end
end
end
end
# frozen_string_literal: true
module Projects
module Ml
class ExperimentPresenter < BasePresenter
def initialize(experiment, candidates, paginator)
@experiment = experiment
@candidates = candidates
@paginator = paginator
end
def view_model
{
experiment: experiment_view_model,
candidates: candidates_view_model,
metric_names: unique_logged_names(&:latest_metrics),
param_names: unique_logged_names(&:params),
page_info: page_info(@paginator)
}
end
private
def experiment_view_model
{
name: @experiment.name,
path: link_to_experiment(@experiment.project, @experiment)
}
end
def candidates_view_model
@candidates.map do |candidate|
{
**candidate.params.to_h { |p| [p.name, p.value] },
**candidate.latest_metrics.to_h { |m| [m.name, number_with_precision(m.value, precision: 4)] },
artifact: link_to_artifact(candidate),
details: link_to_details(candidate),
name: candidate.name,
created_at: candidate.created_at,
user: user_info(candidate)
}
end
end
def unique_logged_names(&selector)
@candidates.flat_map(&selector).map(&:name).uniq
end
end
end
end
- add_to_breadcrumbs _("Experiments"), project_ml_experiments_path(@project)
- add_to_breadcrumbs s_("MlExperimentTracking|Model experiments"), project_ml_experiments_path(@project)
- breadcrumb_title @experiment.name
- page_title @experiment.name
- add_page_specific_style 'page_bundles/ml_experiment_tracking'
- items = candidates_table_items(@candidates)
- metrics = unique_logged_names(@candidates, &:latest_metrics)
- params = unique_logged_names(@candidates, &:params)
- page_info = formatted_page_info(@page_info)
.page-title-holder.d-flex.align-items-center
%h1.page-title.gl-font-size-h-display= @experiment.name
#js-show-ml-experiment{ data: {
candidates: items,
metrics: metrics,
params: params,
page_info: page_info
} }
#js-show-ml-experiment{ data: { view_model: show_experiment_view_model(@experiment, @candidates, @paginator)} }
......@@ -11,5 +11,15 @@
e.metadata = FactoryBot.create_list(:ml_experiment_metadata, 2, experiment: e) # rubocop:disable StrategyInCallback
end
end
factory :experiment_with_candidates do
transient do
candidates_count { 2 }
end
candidates do
Array.new(candidates_count) { association(:ml_candidates) }
end
end
end
end
# frozen_string_literal: true
require 'rspec'
require 'spec_helper'
require 'mime/types'
......@@ -26,57 +24,6 @@
let_it_be(:candidates) { [candidate0, candidate1] }
describe '#candidates_table_items' do
subject { Gitlab::Json.parse(helper.candidates_table_items(candidates)) }
it 'creates the correct model for the table', :aggregate_failures do
expected_values = [
{ 'param1' => 'p1', 'param2' => 'p2', 'metric1' => '0.1000', 'metric2' => '0.2000', 'metric3' => '0.3000',
'artifact' => "/#{project.full_path}/-/packages/#{candidate0.artifact.id}",
'details' => "/#{project.full_path}/-/ml/candidates/#{candidate0.iid}",
'name' => candidate0.name,
'created_at' => candidate0.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
'user' => { 'username' => candidate0.user.username, 'path' => "/#{candidate0.user.username}" } },
{ 'param2' => 'p3', 'param3' => 'p4', 'metric3' => '0.4000',
'artifact' => nil, 'details' => "/#{project.full_path}/-/ml/candidates/#{candidate1.iid}",
'name' => candidate1.name,
'created_at' => candidate1.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
'user' => { 'username' => candidate1.user.username, 'path' => "/#{candidate1.user.username}" } }
]
subject.sort_by! { |s| s[:name] }
expect(subject[0]).to eq(expected_values[0])
expect(subject[1]).to eq(expected_values[1])
end
context 'when candidate does not have user' do
let(:candidates) { [candidate0] }
before do
allow(candidate0).to receive(:user).and_return(nil)
end
it 'has the user property, but is nil' do
expect(subject[0]['user']).to be_nil
end
end
end
describe '#unique_logged_names' do
context 'when for params' do
subject { Gitlab::Json.parse(helper.unique_logged_names(candidates, &:params)) }
it { is_expected.to match_array(%w[param1 param2 param3]) }
end
context 'when latest_metrics is passed' do
subject { Gitlab::Json.parse(helper.unique_logged_names(candidates, &:latest_metrics)) }
it { is_expected.to match_array(%w[metric1 metric2 metric3]) }
end
end
describe '#show_candidate_view_model' do
let(:candidate) { candidate0 }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Ml::BasePresenter, feature_category: :mlops do
let_it_be(:project) { build_stubbed(:project, :private) }
let_it_be(:experiment) do
build_stubbed(:experiment_with_candidates, user_id: project.creator, project: project, iid: 1)
end
let_it_be(:candidate) { experiment.candidates[0] }
let_it_be(:artifact) do
FactoryBot.build_stubbed(:generic_package,
name: candidate.package_name,
version: candidate.package_version,
project: candidate.project)
end
let(:described_object) { described_class.new }
describe '.view_model' do
it 'raises error' do
expect { described_object.view_model }.to raise_error(NotImplementedError)
end
end
describe '.view_model_as_json' do
before do
allow(described_object).to receive(:view_model).and_return({ some_object: [{ another_object: 1 }] })
end
subject { described_object.view_model_as_json }
it 'transforms to json' do
expect(Gitlab::Json.parse(subject)).to eq({ 'someObject' => [{ "anotherObject" => 1 }] })
end
end
describe '.link_to_artifact' do
subject { described_object.link_to_artifact(candidate) }
context 'when candidate has artifact' do
before do
allow(candidate).to receive(:artifact).and_return(artifact)
end
it 'returns link to artifact' do
expect(subject).to eq("/#{project.full_path}/-/packages/#{artifact.id}")
end
end
context 'when there is not artifact' do
before do
allow(candidate).to receive(:artifact).and_return(nil)
end
it 'returns nil' do
expect(subject).to be_nil
end
end
end
describe '.link_to_details' do
subject { described_object.link_to_details(candidate) }
it 'returns path to the candidate' do
expect(subject).to eq("/#{project.full_path}/-/ml/candidates/#{candidate.iid}")
end
end
describe '.link_to_experiment' do
subject { described_object.link_to_experiment(project, experiment) }
it 'returns path to the experiment' do
expect(subject).to eq("/#{project.full_path}/-/ml/experiments/#{experiment.iid}")
end
end
describe '#page_info' do
let(:paginator) do
# Ideally we would test using experiment.candidates.keyset_paginate, but it doesn't work
# with build_stubbed
Class.new do
def has_previous_page?
true
end
def has_next_page?
true
end
def cursor_for_previous_page
"abc"
end
def cursor_for_next_page
"def"
end
end.new
end
subject { described_object.page_info(paginator) }
it 'generates the correct page_info' do
is_expected.to include({
has_next_page: true,
has_previous_page: true,
start_cursor: "abc",
end_cursor: "def"
})
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Ml::ExperimentPresenter, feature_category: :mlops do
let_it_be(:project) { build_stubbed(:project, :private) }
let_it_be(:experiment) do
build_stubbed(:experiment_with_candidates, user_id: project.creator, project: project, iid: 1)
end
let_it_be(:candidate) { experiment.candidates[0] }
let_it_be(:artifact) do
FactoryBot.build_stubbed(:generic_package,
name: candidate.package_name,
version: candidate.package_version,
project: candidate.project)
end
let(:described_object) { described_class.new(experiment, experiment.candidates, nil) }
describe '.view_model' do
subject { described_object.view_model }
it 'creates the correct model for the table', :aggregate_failures do
expected_values = [
{ 'param1' => 'p1', 'param2' => 'p2', 'metric1' => '0.1000', 'metric2' => '0.2000', 'metric3' => '0.3000',
'artifact' => "/#{project.full_path}/-/packages/#{candidate0.artifact.id}",
'details' => "/#{project.full_path}/-/ml/candidates/#{candidate0.iid}",
'name' => candidate0.name,
'created_at' => candidate0.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
'user' => { 'username' => candidate0.user.username, 'path' => "/#{candidate0.user.username}" } },
{ 'param2' => 'p3', 'param3' => 'p4', 'metric3' => '0.4000',
'artifact' => nil, 'details' => "/#{project.full_path}/-/ml/candidates/#{candidate1.iid}",
'name' => candidate1.name,
'created_at' => candidate1.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
'user' => { 'username' => candidate1.user.username, 'path' => "/#{candidate1.user.username}" } }
]
subject.sort_by! { |s| s[:name] }
expect(subject[0]).to eq(expected_values[0])
expect(subject[1]).to eq(expected_values[1])
end
context 'when candidate does not have user' do
let(:candidates) { [candidate0] }
before do
allow(candidate0).to receive(:user).and_return(nil)
end
it 'has the user property, but is nil' do
expect(subject[0]['user']).to be_nil
end
end
end
describe '#unique_logged_names' do
context 'when for params' do
subject { Gitlab::Json.parse(helper.unique_logged_names(candidates, &:params)) }
it { is_expected.to match_array(%w[param1 param2 param3]) }
end
context 'when latest_metrics is passed' do
subject { Gitlab::Json.parse(helper.unique_logged_names(candidates, &:latest_metrics)) }
it { is_expected.to match_array(%w[metric1 metric2 metric3]) }
end
end
end
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать