213 lines
6.0 KiB
Vue
213 lines
6.0 KiB
Vue
<script>
|
|
import { GlTable, GlLink, GlPagination, GlTooltipDirective } from '@gitlab/ui';
|
|
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
|
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
|
|
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
|
|
import {
|
|
LIST_KEY_CREATED_AT,
|
|
BASE_SORT_FIELDS,
|
|
METRIC_KEY_PREFIX,
|
|
} from '~/ml/experiment_tracking/constants';
|
|
import { s__ } from '~/locale';
|
|
import { queryToObject, setUrlParams, visitUrl } from '~/lib/utils/url_utility';
|
|
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
|
|
import IncubationAlert from './incubation_alert.vue';
|
|
|
|
export default {
|
|
name: 'MlExperiment',
|
|
components: {
|
|
GlTable,
|
|
GlLink,
|
|
TimeAgo,
|
|
IncubationAlert,
|
|
GlPagination,
|
|
RegistrySearch,
|
|
},
|
|
directives: {
|
|
GlTooltip: GlTooltipDirective,
|
|
},
|
|
inject: ['candidates', 'metricNames', 'paramNames', 'pagination'],
|
|
data() {
|
|
const query = queryToObject(window.location.search);
|
|
|
|
const filter = query.name ? [{ value: { data: query.name }, type: FILTERED_SEARCH_TERM }] : [];
|
|
|
|
let orderBy = query.orderBy || LIST_KEY_CREATED_AT;
|
|
|
|
if (query.orderByType === 'metric') {
|
|
orderBy = `${METRIC_KEY_PREFIX}${orderBy}`;
|
|
}
|
|
|
|
return {
|
|
page: parseInt(query.page, 10) || 1,
|
|
filters: filter,
|
|
sorting: {
|
|
orderBy,
|
|
sort: (query.sort || 'desc').toLowerCase(),
|
|
},
|
|
};
|
|
},
|
|
computed: {
|
|
fields() {
|
|
if (this.candidates.length === 0) return [];
|
|
|
|
return [
|
|
{ key: 'name', label: this.$options.i18n.nameLabel },
|
|
{ key: 'created_at', label: this.$options.i18n.createdAtLabel },
|
|
{ key: 'user', label: this.$options.i18n.userLabel },
|
|
...this.paramNames,
|
|
...this.metricNames,
|
|
{ key: 'details', label: '' },
|
|
{ key: 'artifact', label: '' },
|
|
];
|
|
},
|
|
displayPagination() {
|
|
return this.candidates.length > 0;
|
|
},
|
|
prevPage() {
|
|
return this.pagination.page > 1 ? this.pagination.page - 1 : null;
|
|
},
|
|
nextPage() {
|
|
return !this.pagination.isLastPage ? this.pagination.page + 1 : null;
|
|
},
|
|
sortableFields() {
|
|
return [
|
|
...BASE_SORT_FIELDS,
|
|
...this.metricNames.map((name) => ({
|
|
orderBy: `${METRIC_KEY_PREFIX}${name}`,
|
|
label: capitalizeFirstCharacter(name),
|
|
})),
|
|
];
|
|
},
|
|
parsedQuery() {
|
|
const name = this.filters
|
|
.map((f) => f.value.data)
|
|
.join(' ')
|
|
.trim();
|
|
|
|
const filterByQuery = name === '' ? {} : { name };
|
|
|
|
let orderByType = 'column';
|
|
let { orderBy } = this.sorting;
|
|
const { sort } = this.sorting;
|
|
|
|
if (orderBy.startsWith(METRIC_KEY_PREFIX)) {
|
|
orderBy = this.sorting.orderBy.slice(METRIC_KEY_PREFIX.length);
|
|
orderByType = 'metric';
|
|
}
|
|
|
|
return { ...filterByQuery, orderBy, orderByType, sort };
|
|
},
|
|
},
|
|
methods: {
|
|
generateLink(page) {
|
|
return setUrlParams({ ...this.parsedQuery, page });
|
|
},
|
|
submitFilters() {
|
|
return visitUrl(setUrlParams({ ...this.parsedQuery, page: this.page }));
|
|
},
|
|
updateFilters(newValue) {
|
|
this.filters = newValue;
|
|
},
|
|
updateSorting(newValue) {
|
|
this.sorting = { ...this.sorting, ...newValue };
|
|
},
|
|
updateSortingAndEmitUpdate(newValue) {
|
|
this.updateSorting(newValue);
|
|
this.submitFilters();
|
|
},
|
|
},
|
|
i18n: {
|
|
titleLabel: s__('MlExperimentTracking|Experiment candidates'),
|
|
emptyStateLabel: s__('MlExperimentTracking|No candidates to display'),
|
|
artifactsLabel: s__('MlExperimentTracking|Artifacts'),
|
|
detailsLabel: s__('MlExperimentTracking|Details'),
|
|
userLabel: s__('MlExperimentTracking|User'),
|
|
createdAtLabel: s__('MlExperimentTracking|Created at'),
|
|
nameLabel: s__('MlExperimentTracking|Name'),
|
|
noDataContent: s__('MlExperimentTracking|-'),
|
|
filterCandidatesLabel: s__('MlExperimentTracking|Filter candidates'),
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<incubation-alert />
|
|
|
|
<h3>
|
|
{{ $options.i18n.titleLabel }}
|
|
</h3>
|
|
|
|
<registry-search
|
|
:filters="filters"
|
|
:sorting="sorting"
|
|
:sortable-fields="sortableFields"
|
|
@sorting:changed="updateSortingAndEmitUpdate"
|
|
@filter:changed="updateFilters"
|
|
@filter:submit="submitFilters"
|
|
@filter:clear="filters = []"
|
|
/>
|
|
|
|
<gl-table
|
|
:fields="fields"
|
|
:items="candidates"
|
|
:empty-text="$options.i18n.emptyStateLabel"
|
|
show-empty
|
|
small
|
|
class="gl-mt-0! ml-candidate-table"
|
|
>
|
|
<template #cell()="data">
|
|
<div v-gl-tooltip.hover :title="data.value">{{ data.value }}</div>
|
|
</template>
|
|
|
|
<template #cell(artifact)="data">
|
|
<gl-link
|
|
v-if="data.value"
|
|
v-gl-tooltip.hover
|
|
:href="data.value"
|
|
target="_blank"
|
|
:title="$options.i18n.artifactsLabel"
|
|
>{{ $options.i18n.artifactsLabel }}</gl-link
|
|
>
|
|
<div v-else v-gl-tooltip.hover :title="$options.i18n.artifactsLabel">
|
|
{{ $options.i18n.noDataContent }}
|
|
</div>
|
|
</template>
|
|
|
|
<template #cell(details)="data">
|
|
<gl-link v-gl-tooltip.hover :href="data.value" :title="$options.i18n.detailsLabel">{{
|
|
$options.i18n.detailsLabel
|
|
}}</gl-link>
|
|
</template>
|
|
|
|
<template #cell(created_at)="data">
|
|
<time-ago v-gl-tooltip.hover :time="data.value" :title="data.value" />
|
|
</template>
|
|
|
|
<template #cell(user)="data">
|
|
<gl-link
|
|
v-if="data.value"
|
|
v-gl-tooltip.hover
|
|
:href="data.value.path"
|
|
:title="data.value.username"
|
|
>@{{ data.value.username }}</gl-link
|
|
>
|
|
<div v-else>{{ $options.i18n.noDataContent }}</div>
|
|
</template>
|
|
</gl-table>
|
|
|
|
<gl-pagination
|
|
v-if="displayPagination"
|
|
v-model="pagination.page"
|
|
:prev-page="prevPage"
|
|
:next-page="nextPage"
|
|
:total-items="pagination.totalItems"
|
|
:per-page="pagination.perPage"
|
|
:link-gen="generateLink"
|
|
align="center"
|
|
class="w-100"
|
|
/>
|
|
</div>
|
|
</template>
|