Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/ml')
-rw-r--r--app/assets/javascripts/ml/model_registry/apps/index_ml_models.vue134
-rw-r--r--app/assets/javascripts/ml/model_registry/components/candidate_list.vue21
-rw-r--r--app/assets/javascripts/ml/model_registry/components/model_row.vue33
-rw-r--r--app/assets/javascripts/ml/model_registry/components/model_version_list.vue21
-rw-r--r--app/assets/javascripts/ml/model_registry/components/searchable_list.vue87
-rw-r--r--app/assets/javascripts/ml/model_registry/graphql/queries/get_models.query.graphql46
-rw-r--r--app/assets/javascripts/ml/model_registry/translations.js5
7 files changed, 276 insertions, 71 deletions
diff --git a/app/assets/javascripts/ml/model_registry/apps/index_ml_models.vue b/app/assets/javascripts/ml/model_registry/apps/index_ml_models.vue
index 59b68fc0063..7a04ccfe163 100644
--- a/app/assets/javascripts/ml/model_registry/apps/index_ml_models.vue
+++ b/app/assets/javascripts/ml/model_registry/apps/index_ml_models.vue
@@ -1,32 +1,29 @@
<script>
-import { isEmpty } from 'lodash';
-import { GlBadge, GlButton, GlTooltipDirective } from '@gitlab/ui';
-import Pagination from '~/vue_shared/components/incubation/pagination.vue';
+import { GlExperimentBadge, GlButton } from '@gitlab/ui';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
+import * as Sentry from '~/sentry/sentry_browser_wrapper';
import EmptyState from '../components/empty_state.vue';
import * as i18n from '../translations';
-import { BASE_SORT_FIELDS, MODEL_ENTITIES } from '../constants';
-import SearchBar from '../components/search_bar.vue';
+import { BASE_SORT_FIELDS, GRAPHQL_PAGE_SIZE, MODEL_ENTITIES } from '../constants';
import ModelRow from '../components/model_row.vue';
import ActionsDropdown from '../components/actions_dropdown.vue';
+import getModelsQuery from '../graphql/queries/get_models.query.graphql';
+import { makeLoadModelErrorMessage } from '../translations';
+import SearchableList from '../components/searchable_list.vue';
export default {
name: 'IndexMlModels',
components: {
- Pagination,
ModelRow,
- SearchBar,
MetadataItem,
TitleArea,
- GlBadge,
- EmptyState,
+ GlExperimentBadge,
GlButton,
+ EmptyState,
ActionsDropdown,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
+ SearchableList,
},
provide() {
return {
@@ -34,23 +31,14 @@ export default {
};
},
props: {
- models: {
- type: Array,
- required: true,
- },
- pageInfo: {
- type: Object,
+ projectPath: {
+ type: String,
required: true,
},
createModelPath: {
type: String,
required: true,
},
- modelCount: {
- type: Number,
- required: false,
- default: 0,
- },
canWriteModelRegistry: {
type: Boolean,
required: false,
@@ -62,9 +50,68 @@ export default {
default: '',
},
},
+ apollo: {
+ models: {
+ query: getModelsQuery,
+ variables() {
+ return this.queryVariables;
+ },
+ update(data) {
+ return data?.project?.mlModels ?? [];
+ },
+ error(error) {
+ this.handleError(error);
+ },
+ },
+ },
+ data() {
+ return {
+ models: [],
+ errorMessage: undefined,
+ };
+ },
computed: {
- hasModels() {
- return !isEmpty(this.models);
+ pageInfo() {
+ return this.models?.pageInfo ?? {};
+ },
+ items() {
+ return this.models?.nodes ?? [];
+ },
+ count() {
+ return this.models?.count ?? 0;
+ },
+ isLoading() {
+ return this.$apollo.queries.models.loading;
+ },
+ queryVariables() {
+ return {
+ fullPath: this.projectPath,
+ first: GRAPHQL_PAGE_SIZE,
+ };
+ },
+ },
+ methods: {
+ fetchPage(variables) {
+ const vars = {
+ ...this.queryVariables,
+ ...variables,
+ name: variables.name,
+ orderBy: variables.orderBy?.toUpperCase() || 'CREATED_AT',
+ sort: variables.sort?.toUpperCase() || 'DESC',
+ };
+
+ this.$apollo.queries.models
+ .fetchMore({
+ variables: vars,
+ updateQuery: (previousResult, { fetchMoreResult }) => {
+ return fetchMoreResult;
+ },
+ })
+ .catch(this.handleError);
+ },
+ handleError(error) {
+ this.errorMessage = makeLoadModelErrorMessage(error.message);
+ Sentry.captureException(error);
},
},
i18n,
@@ -80,28 +127,39 @@ export default {
<template #title>
<div class="gl-flex-grow-1 gl-display-flex gl-align-items-center">
<span>{{ $options.i18n.TITLE_LABEL }}</span>
- <gl-badge variant="neutral" class="gl-mx-4" size="lg" :href="$options.docHref">
- {{ __('Experiment') }}
- </gl-badge>
+ <gl-experiment-badge :help-page-url="$options.docHref" />
</div>
</template>
<template #metadata-models-count>
- <metadata-item icon="machine-learning" :text="$options.i18n.modelsCountLabel(modelCount)" />
+ <metadata-item icon="machine-learning" :text="$options.i18n.modelsCountLabel(count)" />
</template>
<template #right-actions>
- <gl-button v-if="canWriteModelRegistry" :href="createModelPath">{{
- $options.i18n.CREATE_MODEL_LABEL
- }}</gl-button>
+ <gl-button
+ v-if="canWriteModelRegistry"
+ :href="createModelPath"
+ data-testid="create-model-button"
+ >{{ $options.i18n.CREATE_MODEL_LABEL }}</gl-button
+ >
<actions-dropdown />
</template>
</title-area>
- <template v-if="hasModels">
- <search-bar :sortable-fields="$options.sortableFields" />
- <model-row v-for="model in models" :key="model.name" :model="model" />
- <pagination v-bind="pageInfo" />
- </template>
+ <searchable-list
+ show-search
+ :page-info="pageInfo"
+ :items="items"
+ :error-message="errorMessage"
+ :is-loading="isLoading"
+ :sortable-fields="$options.sortableFields"
+ @fetch-page="fetchPage"
+ >
+ <template #empty-state>
+ <empty-state :entity-type="$options.modelEntity" />
+ </template>
- <empty-state v-else :entity-type="$options.modelEntity" />
+ <template #item="{ item }">
+ <model-row :model="item" />
+ </template>
+ </searchable-list>
</div>
</template>
diff --git a/app/assets/javascripts/ml/model_registry/components/candidate_list.vue b/app/assets/javascripts/ml/model_registry/components/candidate_list.vue
index fca4462d7d2..d05a827c545 100644
--- a/app/assets/javascripts/ml/model_registry/components/candidate_list.vue
+++ b/app/assets/javascripts/ml/model_registry/components/candidate_list.vue
@@ -35,8 +35,7 @@ export default {
return data.mlModel?.candidates ?? {};
},
error(error) {
- this.errorMessage = makeLoadCandidatesErrorMessage(error.message);
- Sentry.captureException(error);
+ this.handleError(error);
},
},
},
@@ -67,12 +66,18 @@ export default {
...newPageInfo,
};
- this.$apollo.queries.candidates.fetchMore({
- variables,
- updateQuery: (previousResult, { fetchMoreResult }) => {
- return fetchMoreResult;
- },
- });
+ this.$apollo.queries.candidates
+ .fetchMore({
+ variables,
+ updateQuery: (previousResult, { fetchMoreResult }) => {
+ return fetchMoreResult;
+ },
+ })
+ .catch(this.handleError);
+ },
+ handleError(error) {
+ this.errorMessage = makeLoadCandidatesErrorMessage(error.message);
+ Sentry.captureException(error);
},
},
i18n: {
diff --git a/app/assets/javascripts/ml/model_registry/components/model_row.vue b/app/assets/javascripts/ml/model_registry/components/model_row.vue
index 15be7bd0b47..49f72c7cef2 100644
--- a/app/assets/javascripts/ml/model_registry/components/model_row.vue
+++ b/app/assets/javascripts/ml/model_registry/components/model_row.vue
@@ -1,11 +1,14 @@
<script>
-import { GlLink } from '@gitlab/ui';
+import { GlLink, GlTruncate } from '@gitlab/ui';
import { s__, n__ } from '~/locale';
+import ListItem from '~/vue_shared/components/registry/list_item.vue';
export default {
name: 'MlModelRow',
components: {
GlLink,
+ ListItem,
+ GlTruncate,
},
props: {
model: {
@@ -15,7 +18,7 @@ export default {
},
computed: {
hasVersions() {
- return this.model.version != null;
+ return this.model.versionCount > 0;
},
modelVersionCountMessage() {
if (!this.model.versionCount) return s__('MlModelRegistry|No registered versions');
@@ -31,15 +34,23 @@ export default {
</script>
<template>
- <div class="gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-py-3">
- <gl-link :href="model.path" class="gl-text-body gl-font-weight-bold gl-line-height-24">
- {{ model.name }}
- </gl-link>
+ <list-item v-bind="$attrs">
+ <template #left-primary>
+ <div class="gl-display-flex gl-align-items-center">
+ <gl-link class="gl-text-body" :href="model._links.showPath">
+ <gl-truncate :text="model.name" />
+ </gl-link>
+ </div>
+ </template>
- <div class="gl-text-secondary">
- <gl-link v-if="hasVersions" :href="model.versionPath">{{ model.version }}</gl-link>
+ <template #left-secondary>
+ <div class="gl-text-secondary">
+ <gl-link v-if="hasVersions" :href="model.latestVersion._links.showPath">{{
+ model.latestVersion.version
+ }}</gl-link>
- {{ modelVersionCountMessage }}
- </div>
- </div>
+ {{ modelVersionCountMessage }}
+ </div>
+ </template>
+ </list-item>
</template>
diff --git a/app/assets/javascripts/ml/model_registry/components/model_version_list.vue b/app/assets/javascripts/ml/model_registry/components/model_version_list.vue
index 5a649a9596a..ea5258a299e 100644
--- a/app/assets/javascripts/ml/model_registry/components/model_version_list.vue
+++ b/app/assets/javascripts/ml/model_registry/components/model_version_list.vue
@@ -36,8 +36,7 @@ export default {
return data.mlModel?.versions ?? {};
},
error(error) {
- this.errorMessage = makeLoadVersionsErrorMessage(error.message);
- Sentry.captureException(error);
+ this.handleError(error);
},
},
},
@@ -68,12 +67,18 @@ export default {
...pageInfo,
};
- this.$apollo.queries.modelVersions.fetchMore({
- variables,
- updateQuery: (previousResult, { fetchMoreResult }) => {
- return fetchMoreResult;
- },
- });
+ this.$apollo.queries.modelVersions
+ .fetchMore({
+ variables,
+ updateQuery: (previousResult, { fetchMoreResult }) => {
+ return fetchMoreResult;
+ },
+ })
+ .catch(this.handleError);
+ },
+ handleError(error) {
+ this.errorMessage = makeLoadVersionsErrorMessage(error.message);
+ Sentry.captureException(error);
},
},
modelVersionEntity: MODEL_ENTITIES.modelVersion,
diff --git a/app/assets/javascripts/ml/model_registry/components/searchable_list.vue b/app/assets/javascripts/ml/model_registry/components/searchable_list.vue
index 05062ae6fbf..1ff8cc578a1 100644
--- a/app/assets/javascripts/ml/model_registry/components/searchable_list.vue
+++ b/app/assets/javascripts/ml/model_registry/components/searchable_list.vue
@@ -2,11 +2,14 @@
import { GlAlert } from '@gitlab/ui';
import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
-import { GRAPHQL_PAGE_SIZE } from '~/ml/model_registry/constants';
+import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
+import { GRAPHQL_PAGE_SIZE, LIST_KEY_CREATED_AT } from '~/ml/model_registry/constants';
+import { queryToObject, setUrlParams, updateHistory } from '~/lib/utils/url_utility';
+import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
export default {
name: 'SearchableList',
- components: { PackagesListLoader, RegistryList, GlAlert },
+ components: { PackagesListLoader, RegistryList, RegistrySearch, GlAlert },
props: {
items: {
type: Array,
@@ -26,30 +29,92 @@ export default {
required: false,
default: '',
},
+ showSearch: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ sortableFields: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ },
+ data() {
+ const query = queryToObject(window.location.search);
+
+ const filter = query.name ? [{ value: { data: query.name }, type: FILTERED_SEARCH_TERM }] : [];
+
+ const orderBy = query.orderBy || LIST_KEY_CREATED_AT;
+
+ return {
+ filters: filter,
+ sorting: {
+ orderBy,
+ sort: (query.sort || 'desc').toLowerCase(),
+ },
+ };
},
computed: {
isListEmpty() {
return this.items.length === 0;
},
+ parsedQuery() {
+ const name = this.filters
+ .map((f) => f.value.data)
+ .join(' ')
+ .trim();
+
+ const filterByQuery = name === '' ? {} : { name };
+
+ return { ...filterByQuery, ...this.sorting };
+ },
+ },
+ created() {
+ this.nextPage();
},
methods: {
prevPage() {
- const pageInfo = {
+ const variables = {
first: null,
last: GRAPHQL_PAGE_SIZE,
before: this.pageInfo.startCursor,
+ ...this.parsedQuery,
};
- this.$emit('fetch-page', pageInfo);
+ this.fetchPage(variables);
},
nextPage() {
- const pageInfo = {
+ const variables = {
first: GRAPHQL_PAGE_SIZE,
last: null,
after: this.pageInfo.endCursor,
+ ...this.parsedQuery,
};
- this.$emit('fetch-page', pageInfo);
+ this.fetchPage(variables);
+ },
+ fetchPage(variables) {
+ updateHistory({
+ url: setUrlParams(variables, window.location.href, true),
+ title: document.title,
+ replace: true,
+ });
+
+ this.$emit('fetch-page', variables);
+ },
+ submitFilters() {
+ this.fetchPage(this.parsedQuery);
+ },
+ updateFilters(newValue) {
+ this.filters = newValue;
+ },
+ updateSorting(newValue) {
+ this.sorting = { ...this.sorting, ...newValue };
+ },
+ updateSortingAndEmitUpdate(newValue) {
+ this.updateSorting(newValue);
+ this.submitFilters();
},
},
};
@@ -57,6 +122,16 @@ export default {
<template>
<div>
+ <registry-search
+ v-if="showSearch"
+ :filters="filters"
+ :sorting="sorting"
+ :sortable-fields="sortableFields"
+ @sorting:changed="updateSortingAndEmitUpdate"
+ @filter:changed="updateFilters"
+ @filter:submit="submitFilters"
+ @filter:clear="filters = []"
+ />
<packages-list-loader v-if="isLoading" />
<gl-alert v-else-if="errorMessage" variant="danger" :dismissible="false">
{{ errorMessage }}
diff --git a/app/assets/javascripts/ml/model_registry/graphql/queries/get_models.query.graphql b/app/assets/javascripts/ml/model_registry/graphql/queries/get_models.query.graphql
new file mode 100644
index 00000000000..a9559bd7f5d
--- /dev/null
+++ b/app/assets/javascripts/ml/model_registry/graphql/queries/get_models.query.graphql
@@ -0,0 +1,46 @@
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
+
+query getModels(
+ $fullPath: ID!
+ $name: String
+ $orderBy: MlModelsOrderBy
+ $sort: SortDirectionEnum
+ $first: Int
+ $last: Int
+ $after: String
+ $before: String
+) {
+ project(fullPath: $fullPath) {
+ id
+ mlModels(
+ name: $name
+ orderBy: $orderBy
+ sort: $sort
+ after: $after
+ before: $before
+ first: $first
+ last: $last
+ ) {
+ count
+ nodes {
+ id
+ name
+ versionCount
+ createdAt
+ latestVersion {
+ id
+ version
+ _links {
+ showPath
+ }
+ }
+ _links {
+ showPath
+ }
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/ml/model_registry/translations.js b/app/assets/javascripts/ml/model_registry/translations.js
index 006142979e2..9d3e1e7badb 100644
--- a/app/assets/javascripts/ml/model_registry/translations.js
+++ b/app/assets/javascripts/ml/model_registry/translations.js
@@ -47,6 +47,11 @@ export const makeLoadVersionsErrorMessage = (message) =>
message,
});
+export const makeLoadModelErrorMessage = (message) =>
+ sprintf(s__('MlModelRegistry|Failed to load model with error: %{message}'), {
+ message,
+ });
+
export const NO_CANDIDATES_LABEL = s__('MlModelRegistry|This model has no candidates');
export const makeLoadCandidatesErrorMessage = (message) =>
sprintf(s__('MlModelRegistry|Failed to load model candidates with error: %{message}'), {