diff --git a/app/services/katello/repository_type.rb b/app/services/katello/repository_type.rb
index 9511cb69438..5792a715dbc 100644
--- a/app/services/katello/repository_type.rb
+++ b/app/services/katello/repository_type.rb
@@ -76,7 +76,7 @@ def default_managed_content_type(label = nil)
def content_type(model_class, options = {})
@content_types ||= []
- @content_types << ContentType.new(options.merge(:model_class => model_class))
+ @content_types << ContentType.new(options.merge(:model_class => model_class, :content_type => model_class::CONTENT_TYPE))
end
def generic_content_type(content_type, options = {})
@@ -127,10 +127,11 @@ def pulp3_api(smart_proxy)
class ContentType
attr_accessor :model_class, :priority, :pulp2_service_class, :pulp3_service_class, :index, :uploadable, :removable,
- :primary_content, :index_on_pulp3, :generic_browser
+ :primary_content, :index_on_pulp3, :generic_browser, :content_type
def initialize(options)
self.model_class = options[:model_class]
+ self.content_type = options[:content_type]
self.priority = options[:priority] || 99
self.pulp2_service_class = options[:pulp2_service_class]
self.pulp3_service_class = options[:pulp3_service_class]
@@ -157,7 +158,7 @@ def as_json(_options = {})
end
class GenericContentType < ContentType
- attr_accessor :pulp3_api, :pulp3_model, :content_type, :filename_key, :duplicates_allowed, :pluralized_name,
+ attr_accessor :pulp3_api, :pulp3_model, :filename_key, :duplicates_allowed, :pluralized_name,
:model_name, :model_version, :model_filename
def initialize(options)
diff --git a/app/services/katello/repository_type_manager.rb b/app/services/katello/repository_type_manager.rb
index c4b0e6d0dab..d79b4e750f5 100644
--- a/app/services/katello/repository_type_manager.rb
+++ b/app/services/katello/repository_type_manager.rb
@@ -87,6 +87,14 @@ def generic_content_types(enabled_only = true)
list.flatten.map(&:content_type)
end
+ def generic_ui_content_types(enabled_only = true)
+ repo_types = enabled_only ? enabled_repository_types : defined_repository_types
+ list = repo_types.values.map do |type|
+ type.content_types.select { |ct| ct.generic_browser }
+ end
+ list.flatten.map(&:content_type)
+ end
+
def generic_content_type?(content_type, enabled_only = true)
types = generic_content_types(enabled_only)
types.include?(content_type)
diff --git a/config/routes.rb b/config/routes.rb
index 53f5c5d34c1..ee444deb9f7 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -24,15 +24,15 @@
match '/module_streams' => 'react#index', :via => [:get]
match '/module_streams/*page' => 'react#index', :via => [:get]
- match '/ansible_collections' => 'react#index', :via => [:get]
- match '/ansible_collections/*page' => 'react#index', :via => [:get]
+ match '/legacy_ansible_collections' => 'react#index', :via => [:get]
+ match '/legacy_ansible_collections/*page' => 'react#index', :via => [:get]
match '/content_views' => 'react#index', :via => [:get]
match '/content_views/*page' => 'react#index', :via => [:get]
match '/content' => 'react#index', :via => [:get]
- Katello::RepositoryTypeManager.generic_content_types.each do |type|
+ Katello::RepositoryTypeManager.generic_ui_content_types.each do |type|
get "/#{type.pluralize}", to: redirect("/content/#{type.pluralize}")
get "/#{type.pluralize}/:page", to: redirect("/content/#{type.pluralize}/%{page}")
match "/content/#{type.pluralize}" => 'react#index', :via => [:get]
diff --git a/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/repositories.routes.js b/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/repositories.routes.js
index 0c5604f79d3..e73e80cd9c6 100644
--- a/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/repositories.routes.js
+++ b/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/repositories.routes.js
@@ -91,7 +91,7 @@
}
})
.state('product.repository.manage-content.docker-manifests', {
- url: '/content/docker_manifests',
+ url: '/docker_manifests',
permission: 'view_products',
templateUrl: 'products/details/repositories/details/views/repository-manage-docker-manifests.html',
ncyBreadcrumb: {
@@ -100,7 +100,7 @@
}
})
.state('product.repository.manage-content.docker-tags', {
- url: '/content/docker_tags',
+ url: '/docker_tags',
permission: 'view_products',
templateUrl: 'products/details/repositories/details/views/repository-manage-docker-tags.html',
ncyBreadcrumb: {
@@ -109,7 +109,7 @@
}
})
.state('product.repository.manage-content.docker-manifest-lists', {
- url: '/content/docker_manifest_lists',
+ url: '/docker_manifest_lists',
permission: 'view_products',
templateUrl: 'products/details/repositories/details/views/repository-manage-docker-manifest-lists.html',
ncyBreadcrumb: {
@@ -118,7 +118,7 @@
}
})
.state('product.repository.manage-content.files', {
- url: '/content/files',
+ url: '/files',
permission: 'view_products',
templateUrl: 'products/details/repositories/details/views/repository-manage-files.html',
ncyBreadcrumb: {
@@ -127,7 +127,7 @@
}
})
.state('product.repository.manage-content.ostree-branches', {
- url: '/content/ostree_branches',
+ url: '/ostree_branches',
permission: 'view_products',
templateUrl: 'products/details/repositories/details/views/repository-manage-ostree-branches.html',
ncyBreadcrumb: {
@@ -145,7 +145,7 @@
}
})
.state('product.repository.manage-content.debs', {
- url: '/content/debs',
+ url: '/debs',
permission: 'view_products',
templateUrl: 'products/details/repositories/details/views/repository-manage-debs.html',
ncyBreadcrumb: {
@@ -154,7 +154,7 @@
}
})
.state('product.repository.manage-content.ansible-collections', {
- url: '/content/ansible_collections',
+ url: '/ansible_collections',
permission: 'view_products',
templateUrl: 'products/details/repositories/details/views/repository-manage-ansible-collections.html',
ncyBreadcrumb: {
diff --git a/lib/katello/repository_types/ansible_collection.rb b/lib/katello/repository_types/ansible_collection.rb
index c96a7053f80..1e8a58aaa67 100644
--- a/lib/katello/repository_types/ansible_collection.rb
+++ b/lib/katello/repository_types/ansible_collection.rb
@@ -16,5 +16,6 @@
distribution_class PulpAnsibleClient::AnsibleAnsibleDistribution
repo_sync_url_class PulpAnsibleClient::AnsibleRepositorySyncURL
- content_type Katello::AnsibleCollection, :pulp3_service_class => ::Katello::Pulp3::AnsibleCollection, :user_removable => true
+ content_type Katello::AnsibleCollection, :pulp3_service_class => ::Katello::Pulp3::AnsibleCollection, :user_removable => true, generic_browser: true
+ default_managed_content_type :ansible_collections
end
diff --git a/webpack/components/RoutedTabs/index.js b/webpack/components/RoutedTabs/index.js
index 3e2469a72ce..744c9910a93 100644
--- a/webpack/components/RoutedTabs/index.js
+++ b/webpack/components/RoutedTabs/index.js
@@ -45,7 +45,7 @@ const RoutedTabs = ({
{content}
))}
-
+
diff --git a/webpack/containers/Application/config.js b/webpack/containers/Application/config.js
index 1248fcc758f..206b273b923 100644
--- a/webpack/containers/Application/config.js
+++ b/webpack/containers/Application/config.js
@@ -47,11 +47,11 @@ export const links = [
component: WithOrganization(withHeader(ModuleStreamDetails, { title: __('Module Stream Details') })),
},
{
- path: 'ansible_collections',
+ path: 'legacy_ansible_collections',
component: WithOrganization(withHeader(AnsibleCollections, { title: __('Ansible Collections') })),
},
{
- path: 'ansible_collections/:id([0-9]+)',
+ path: 'legacy_ansible_collections/:id([0-9]+)',
component: WithOrganization(withHeader(AnsibleCollectionDetails, { title: __('Ansible Collection Details') })),
},
{
@@ -65,14 +65,14 @@ export const links = [
},
{
path: 'content',
- component: WithOrganization(withHeader(Content, { title: __('Other Content Types') })),
+ component: WithOrganization(withHeader(Content, { title: __('Content') })),
},
{
path: 'content/:content_type([a-z_]+)',
- component: WithOrganization(withHeader(Content, { title: __('Other Content Types') })),
+ component: WithOrganization(withHeader(Content, { title: __('Content') })),
},
{
path: 'content/:content_type([a-z_]+)/:id([0-9]+)',
- component: WithOrganization(withHeader(ContentDetails, { title: __('Other Content Type Details') })),
+ component: WithOrganization(withHeader(ContentDetails, { title: __('Content Details') })),
},
];
diff --git a/webpack/scenes/AnsibleCollections/AnsibleCollectionsTableSchema.js b/webpack/scenes/AnsibleCollections/AnsibleCollectionsTableSchema.js
index df2f20be91a..d02a521c692 100644
--- a/webpack/scenes/AnsibleCollections/AnsibleCollectionsTableSchema.js
+++ b/webpack/scenes/AnsibleCollections/AnsibleCollectionsTableSchema.js
@@ -18,7 +18,7 @@ const TableSchema = [
formatters: [
(value, { rowData }) => (
- {rowData.name}
+ {rowData.name}
|
),
],
diff --git a/webpack/scenes/Content/ContentConfig.js b/webpack/scenes/Content/ContentConfig.js
index f7705b32cd8..373dbddb644 100644
--- a/webpack/scenes/Content/ContentConfig.js
+++ b/webpack/scenes/Content/ContentConfig.js
@@ -47,7 +47,72 @@ export default () => [
{
title: __('Product'),
getProperty: unit =>
- {unit?.product.name},
+ {unit?.product.name},
+ },
+ {
+ title: __('Sync Status'),
+ getProperty: unit =>
+ ,
+ },
+ {
+ title: __('Content Count'),
+ getProperty: (unit, singularLabel) =>
+ (),
+ },
+ ],
+ },
+ ],
+ },
+ {
+ names: {
+ pluralTitle: __('Ansible Collections'),
+ singularTitle: __('Ansible Collection'),
+ pluralLowercase: __('Ansible collections'),
+ singularLowercase: __('Ansible collection'),
+ pluralLabel: 'ansible_collections',
+ singularLabel: 'ansible_collection',
+ },
+ columnHeaders: [
+ { title: __('Name'), getProperty: unit => ({unit?.name}) },
+ { title: __('Author'), getProperty: unit => unit?.namespace },
+ { title: __('Version'), getProperty: unit => unit?.version },
+ { title: __('Checksum'), getProperty: unit => unit?.checksum },
+
+ ],
+ tabs: [
+ {
+ tabKey: 'details',
+ title: __('Details'),
+ getContent: (contentType, id, tabKey) => ,
+ columnHeaders: [
+ { title: __('Name'), getProperty: unit => unit?.name },
+ { title: __('Description'), getProperty: unit => unit?.description },
+ { title: __('Author'), getProperty: unit => unit?.namespace },
+ { title: __('Version'), getProperty: unit => unit?.version },
+ { title: __('Checksum'), getProperty: unit => unit?.checksum },
+ { title: __('Tags'), getProperty: unit => unit?.tags?.join() },
+ ],
+ },
+ {
+ tabKey: 'repositories',
+ title: __('Repositories'),
+ getContent: (contentType, id, tabKey) =>
+ ,
+ columnHeaders: [
+ {
+ title: __('Name'),
+ getProperty: unit =>
+ {unit?.name},
+ },
+ {
+ title: __('Product'),
+ getProperty: unit =>
+ {unit?.product.name},
},
{
title: __('Sync Status'),
diff --git a/webpack/scenes/Content/ContentPage.js b/webpack/scenes/Content/ContentPage.js
index 5d126df8fa6..f6d04fea9dd 100644
--- a/webpack/scenes/Content/ContentPage.js
+++ b/webpack/scenes/Content/ContentPage.js
@@ -17,6 +17,7 @@ const ContentPage = () => {
const contentTypesResponse = useSelector(selectContentTypes);
const contentTypesStatus = useSelector(selectContentTypesStatus);
const [selectedContentType, setSelectedContentType] = useState(null);
+ const [showContentTypeSelector, setShowContentTypeSelector] = useState(true);
const [contentTypes, setContentTypes] = useState(null);
const { content_type: contentType } = useParams();
@@ -45,6 +46,7 @@ const ContentPage = () => {
Object.entries(enabledContentTypes).forEach(([key, value]) => {
if (value.includes(contentType)) {
setSelectedContentType(key);
+ setShowContentTypeSelector(false);
}
});
}
@@ -75,7 +77,10 @@ const ContentPage = () => {
-
+
);
diff --git a/webpack/scenes/Content/Details/ContentDetails.js b/webpack/scenes/Content/Details/ContentDetails.js
index 14061ab9848..0936a7f43c3 100644
--- a/webpack/scenes/Content/Details/ContentDetails.js
+++ b/webpack/scenes/Content/Details/ContentDetails.js
@@ -58,7 +58,7 @@ const ContentDetails = () => {
-
+
);
diff --git a/webpack/scenes/Content/Details/__tests__/ansibleCollectionDetails.fixtures.json b/webpack/scenes/Content/Details/__tests__/ansibleCollectionDetails.fixtures.json
new file mode 100644
index 00000000000..3e079f5692e
--- /dev/null
+++ b/webpack/scenes/Content/Details/__tests__/ansibleCollectionDetails.fixtures.json
@@ -0,0 +1,20 @@
+{
+ "id": 51,
+ "name": "linux_system_roles",
+ "namespace": "fedora",
+ "version": "1.5.0",
+ "checksum": "edd27bbc1f6ba805760bf85a05efacca989492570e41d263fd804358ce9cef92",
+ "description": "Ansible roles for linux system components management",
+ "tags": [
+ "collection",
+ "system"
+ ],
+ "repositories": [
+ {
+ "id": 698,
+ "name": "lin_collections",
+ "product_id": 296,
+ "product_name": "test_all_types"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/webpack/scenes/Content/Details/__tests__/ansibleCollectionRepositoryDetails.fixtures.json b/webpack/scenes/Content/Details/__tests__/ansibleCollectionRepositoryDetails.fixtures.json
new file mode 100644
index 00000000000..d2bfd9d58c5
--- /dev/null
+++ b/webpack/scenes/Content/Details/__tests__/ansibleCollectionRepositoryDetails.fixtures.json
@@ -0,0 +1,71 @@
+{
+ "total": 1,
+ "subtotal": 1,
+ "page": "1",
+ "per_page": "20",
+ "error": null,
+ "search": null,
+ "sort": {
+ "by": "name",
+ "order": "asc"
+ },
+ "results": [
+ {
+ "backend_identifier": "b7e662ed-fd91-44ed-94c0-60fc9c5b2ccc",
+ "relative_path": "Default_Organization/Library/custom/test_all_types/lin_collections",
+ "container_repository_name": null,
+ "full_path": "https://centos7-devel4.samir.example.com/pulp_ansible/galaxy/Default_Organization/Library/custom/test_all_types/lin_collections/api/",
+ "library_instance_id": null,
+ "version_href": "/pulp/api/v3/repositories/ansible/ansible/59832f57-b06d-49aa-ab3e-ac07c847fb55/versions/1/",
+ "remote_href": "/pulp/api/v3/remotes/ansible/collection/66badf55-97c1-42be-9e55-c30fdfdd182c/",
+ "publication_href": null,
+ "content_counts": {
+ "ansible_collection": 17
+ },
+ "id": 698,
+ "name": "lin_collections",
+ "label": "lin_collections",
+ "description": null,
+ "last_sync": {
+ "id": "5b6b544a-767f-4558-926d-4c7c828afccc",
+ "username": "admin",
+ "started_at": "2021-10-15 11:34:51 -0400",
+ "ended_at": "2021-10-15 11:35:10 -0400",
+ "state": "stopped",
+ "result": "success",
+ "progress": 1
+ },
+ "content_view": {
+ "id": 1,
+ "name": "Default Organization View"
+ },
+ "content_view_version": {
+ "id": 1,
+ "name": "Default Organization View 1.0",
+ "content_view_id": 1
+ },
+ "kt_environment": {
+ "id": 1,
+ "name": "Library"
+ },
+ "content_type": "ansible_collection",
+ "url": "http://galaxy.ansible.com/",
+ "arch": "noarch",
+ "os_versions": null,
+ "content_id": "1634312081893",
+ "generic_remote_options": null,
+ "major": null,
+ "minor": null,
+ "product": {
+ "id": 296,
+ "cp_id": "292121829980",
+ "name": "test_all_types",
+ "orphaned": false,
+ "redhat": false,
+ "sync_plan": null
+ },
+ "content_label": "Default_Organization_test_all_types_lin_collections",
+ "last_sync_words": "12 days"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/webpack/scenes/Content/Details/__tests__/contentDetail.test.js b/webpack/scenes/Content/Details/__tests__/contentDetail.test.js
index 9f95eeb7204..eab3c64a704 100644
--- a/webpack/scenes/Content/Details/__tests__/contentDetail.test.js
+++ b/webpack/scenes/Content/Details/__tests__/contentDetail.test.js
@@ -6,12 +6,14 @@ import api from '../../../../services/api';
import { CONTENT_ID_KEY } from '../../ContentConstants';
import ContentDetails from '../ContentDetails';
import ContentRepositories from '../ContentRepositories';
-
-const pythonPackageDetailsResponse = require('./pythonPackageDetails.fixtures.json');
-const pythonPackageRepositoryDetailsResponse = require('./pythonPackageRepositoryDetails.fixtures.json');
+import pythonPackageDetailsResponse from './pythonPackageDetails.fixtures.json';
+import pythonPackageRepositoryDetailsResponse from './pythonPackageRepositoryDetails.fixtures.json';
+import ansibleCollectionDetailsResponse from './ansibleCollectionDetails.fixtures.json';
+import ansibleCollectionRepositoryDetailsResponse from './ansibleCollectionRepositoryDetails.fixtures.json';
const pythonPackageDetailsPath = api.getApiUrl('/python_packages/1491');
-const pythonPackageRepositoryDetailsPath = api.getApiUrl('/repositories');
+const ansibleCollectionDetailsPath = api.getApiUrl('/ansible_collections/51');
+const contentRepositoryDetailsPath = api.getApiUrl('/repositories');
const withContentRoute = component => {component};
@@ -60,7 +62,7 @@ test('Can call API for Python package repository details and show repositories t
const contentCountWords = '1489 Python packages';
const pythonPackageRepositoryDetailsScope = nockInstance
- .get(pythonPackageRepositoryDetailsPath)
+ .get(contentRepositoryDetailsPath)
.query(true)
.reply(200, pythonPackageRepositoryDetailsResponse);
@@ -80,3 +82,66 @@ test('Can call API for Python package repository details and show repositories t
assertNockRequest(autocompleteScope);
assertNockRequest(pythonPackageRepositoryDetailsScope, done);
});
+
+test('Can call API for Ansible collection details and show details tab on page load', async (done) => {
+ const renderOptions = {
+ apiNamespace: CONTENT_ID_KEY,
+ routerParams: {
+ initialEntries: [{ pathname: '/content/ansible_collections/51', hash: '#/details' }],
+ initialIndex: 1,
+ },
+ };
+
+ const { name, version } = ansibleCollectionDetailsResponse;
+ const ansibleCollectionsScope = nockInstance
+ .get(ansibleCollectionDetailsPath)
+ .query(true)
+ .reply(200, ansibleCollectionDetailsResponse);
+
+ const { queryByText, getAllByText, getByLabelText } =
+ renderWithRedux(withContentRoute(), renderOptions);
+
+ expect(queryByText(name)).toBeNull();
+ await patientlyWaitFor(() => {
+ expect(getAllByText(name)[0]).toBeInTheDocument();
+ expect(getAllByText(version)[0]).toBeInTheDocument();
+ expect(getByLabelText('content_breadcrumb')).toBeInTheDocument();
+ expect(getByLabelText('content_breadcrumb_content')).toHaveTextContent(name);
+ });
+
+ assertNockRequest(ansibleCollectionsScope, done);
+});
+
+test('Can call API for Ansible collection repository details and show repositories tab', async (done) => {
+ const autocompleteUrl = '/repositories/auto_complete_search';
+ const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl);
+ searchDelayScope = mockSetting(nockInstance, 'autosearch_delay', 500);
+ autoSearchScope = mockSetting(nockInstance, 'autosearch_while_typing', true);
+
+ const results = ansibleCollectionRepositoryDetailsResponse.results[0];
+ const repoName = results.name;
+ const productName = results.product.name;
+ const lastSyncWords = `${results.last_sync_words} ago`;
+ const contentCountWords = '17 Ansible collections';
+
+ const ansibleCollectionRepositoryDetailsScope = nockInstance
+ .get(contentRepositoryDetailsPath)
+ .query(true)
+ .reply(200, ansibleCollectionRepositoryDetailsResponse);
+
+ const { queryByText, getAllByText } =
+ renderWithRedux();
+
+ expect(queryByText(repoName)).toBeNull();
+ await patientlyWaitFor(() => {
+ expect(getAllByText(repoName)[0]).toBeInTheDocument();
+ expect(getAllByText(productName)[0]).toBeInTheDocument();
+ expect(getAllByText(lastSyncWords)[0]).toBeInTheDocument();
+ expect(getAllByText(contentCountWords)[0]).toBeInTheDocument();
+ });
+
+ assertNockRequest(autoSearchScope);
+ assertNockRequest(searchDelayScope);
+ assertNockRequest(autocompleteScope);
+ assertNockRequest(ansibleCollectionRepositoryDetailsScope, done);
+});
diff --git a/webpack/scenes/Content/Table/ContentTable.js b/webpack/scenes/Content/Table/ContentTable.js
index 02f23464934..203cf9ad41b 100644
--- a/webpack/scenes/Content/Table/ContentTable.js
+++ b/webpack/scenes/Content/Table/ContentTable.js
@@ -9,7 +9,9 @@ import { selectContent, selectContentStatus, selectContentError } from '../Conte
import SelectableDropdown from '../../../components/SelectableDropdown';
import contentConfig from '../ContentConfig';
/* eslint-disable react/no-array-index-key */
-const ContentTable = ({ selectedContentType, setSelectedContentType, contentTypes }) => {
+const ContentTable = ({
+ selectedContentType, setSelectedContentType, contentTypes, showContentTypeSelector,
+}) => {
const status = useSelector(selectContentStatus);
const error = useSelector(selectContentError);
const response = useSelector(selectContent);
@@ -41,6 +43,7 @@ const ContentTable = ({ selectedContentType, setSelectedContentType, contentType
[contentTypes, selectedContentType],
)}
actionButtons={
+ showContentTypeSelector &&
{
+ const mockContentTypes = { 'Ansible Collections': ['ansible_collection', 'ansible_collections'] };
+ const autocompleteUrl = '/ansible_collections/auto_complete_search';
+ const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl);
+
+ const { results } = ansibleCollectionsResponse;
+ const [firstPackage] = results;
+
+ const ansibleCollections = nockInstance
+ .get(ansibleCollectionsPath)
+ .query(true)
+ .reply(200, ansibleCollectionsResponse);
+
+ const { queryByText, getAllByText } =
+ renderWithRedux( {}}
+ showContentTypeSelector={false}
+ />);
+
+ expect(queryByText(firstPackage.name)).toBeNull();
+ await patientlyWaitFor(() => {
+ expect(getAllByText(firstPackage.name)[0]).toBeInTheDocument();
+ expect(getAllByText(firstPackage.version)[0]).toBeInTheDocument();
+ expect(getAllByText(firstPackage.checksum)[0]).toBeInTheDocument();
+ });
+ assertNockRequest(autocompleteScope);
+ assertNockRequest(ansibleCollections, done);
+});
diff --git a/webpack/scenes/Content/__tests__/contentTypes.fixtures.json b/webpack/scenes/Content/__tests__/contentTypes.fixtures.json
index 11e2c98b0c8..3a68f1e2c44 100644
--- a/webpack/scenes/Content/__tests__/contentTypes.fixtures.json
+++ b/webpack/scenes/Content/__tests__/contentTypes.fixtures.json
@@ -1,7 +1,7 @@
[
{
"label": "ansible collection",
- "generic_browser": null
+ "generic_browser": true
},
{
"label": "deb",