diff --git a/CHANGES/2241.feature b/CHANGES/2241.feature new file mode 100644 index 0000000000..a9ba78be15 --- /dev/null +++ b/CHANGES/2241.feature @@ -0,0 +1 @@ +Expose legacy role download count in the UI diff --git a/src/api/collection.ts b/src/api/collection.ts index bed01436b0..425807a622 100644 --- a/src/api/collection.ts +++ b/src/api/collection.ts @@ -179,6 +179,12 @@ export class API extends HubAPI { `pulp/api/v3/content/ansible/collection_versions/`, ); } + + getDetail(distroBasePath, namespace, name) { + return this.http.get( + `v3/plugin/ansible/content/${distroBasePath}/collections/index/${namespace}/${name}/`, + ); + } } export const CollectionAPI = new API(); diff --git a/src/api/response-types/legacy-role.ts b/src/api/response-types/legacy-role.ts index e4289826dc..40ad6af321 100644 --- a/src/api/response-types/legacy-role.ts +++ b/src/api/response-types/legacy-role.ts @@ -44,6 +44,7 @@ export class LegacyRoleListType { commit: string; name: string; description: string; + download_count: number; summary_fields: { dependencies: string[]; namespace: { @@ -77,6 +78,7 @@ export class LegacyRoleDetailType { commit: string; name: string; description: string; + download_count: number; summary_fields: { dependencies: string[]; namespace: { diff --git a/src/components/headers/base-header.tsx b/src/components/headers/base-header.tsx index 6ed378d85a..3cd07bdcfa 100644 --- a/src/components/headers/base-header.tsx +++ b/src/components/headers/base-header.tsx @@ -47,11 +47,9 @@ export class BaseHeader extends React.Component { - {pageControls ? ( -
{pageControls}
- ) : null} + {pageControls || null} - {versionControl ? <>{versionControl} : null} + {versionControl || null} {children ? (
{children}
diff --git a/src/components/headers/collection-header.tsx b/src/components/headers/collection-header.tsx index ea097820ef..55e83da727 100644 --- a/src/components/headers/collection-header.tsx +++ b/src/components/headers/collection-header.tsx @@ -164,15 +164,15 @@ export class CollectionHeader extends React.Component { render() { const { + activeTab, + breadcrumbs, + className, + collection, collections, collectionsCount, - collection, content, params, updateParams, - breadcrumbs, - activeTab, - className, } = this.props; const { @@ -474,69 +474,71 @@ export class CollectionHeader extends React.Component { } breadcrumbs={} versionControl={ -
- {t`Version`} -
- +
+
+ {t`Version`} +
+ +
+ {latestVersion ? ( + + + Last updated + + + ) : null} + {display_signatures ? ( + + ) : null}
- {latestVersion ? ( - - - Last updated - - - ) : null} - {display_signatures ? ( - - ) : null}
} pageControls={ diff --git a/src/components/headers/header.scss b/src/components/headers/header.scss index 49d10f9972..06007cb791 100644 --- a/src/components/headers/header.scss +++ b/src/components/headers/header.scss @@ -12,10 +12,6 @@ $breakpoint-md: 1000px; .column-section { display: flex; justify-content: space-between; - - .header-right { - align-items: right; - } } .install-version-column { diff --git a/src/components/index.ts b/src/components/index.ts index 0ae7b6d2ef..aab00a2b10 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -111,6 +111,7 @@ export { ShaLabel } from './sha-label/sha-label'; export { DataForm } from './shared/data-form'; export { DetailList } from './shared/detail-list'; export { Details } from './shared/details'; +export { DownloadCount } from './shared/download-count'; export { LoginLink } from './shared/login-link'; export { UIVersion } from './shared/ui-version'; export { diff --git a/src/components/legacy-role-list/legacy-role-item.tsx b/src/components/legacy-role-list/legacy-role-item.tsx index 860a71832f..9c455f0a08 100644 --- a/src/components/legacy-role-list/legacy-role-item.tsx +++ b/src/components/legacy-role-list/legacy-role-item.tsx @@ -12,7 +12,7 @@ import { import * as React from 'react'; import { Link } from 'react-router-dom'; import { LegacyRoleDetailType } from 'src/api'; -import { DateComponent, Logo, Tag } from 'src/components'; +import { DateComponent, DownloadCount, Logo, Tag } from 'src/components'; import { Paths, formatPath } from 'src/paths'; import { chipGroupProps } from 'src/utilities'; import './legacy-role-item.scss'; @@ -105,6 +105,9 @@ export class LegacyRoleListItem extends React.Component {
{release_name}
+
+ +
, ); diff --git a/src/components/shared/download-count.tsx b/src/components/shared/download-count.tsx new file mode 100644 index 0000000000..f86a6b33a0 --- /dev/null +++ b/src/components/shared/download-count.tsx @@ -0,0 +1,27 @@ +import { Trans, t } from '@lingui/macro'; +import { DownloadIcon } from '@patternfly/react-icons'; +import React from 'react'; +import { Tooltip } from 'src/components'; +import { language } from 'src/l10n'; + +interface IProps { + item?: { download_count?: number }; +} + +export const DownloadCount = ({ item }: IProps) => { + if (!item?.download_count) { + return null; + } + + const downloadCount = new Intl.NumberFormat(language).format( + item.download_count, + ); + + return ( + + {downloadCount} Downloads + + ); +}; diff --git a/src/containers/collection-detail/base.ts b/src/containers/collection-detail/base.ts index 412d1d5b35..15622a6092 100644 --- a/src/containers/collection-detail/base.ts +++ b/src/containers/collection-detail/base.ts @@ -8,17 +8,17 @@ import { AlertType } from 'src/components'; import { Paths, formatPath } from 'src/paths'; export interface IBaseCollectionState { + alerts?: AlertType[]; + collection?: CollectionVersionSearch; + collections?: CollectionVersionSearch[]; + collectionsCount?: number; + content?: CollectionVersionContentType; + distroBasePath?: string; params: { version?: string; showing?: string; keywords?: string; }; - collections?: CollectionVersionSearch[]; - collectionsCount?: number; - collection?: CollectionVersionSearch; - content?: CollectionVersionContentType; - alerts?: AlertType[]; - distroBasePath?: string; } // Caches the collection data when matching, prevents redundant fetches between collection detail tabs @@ -28,9 +28,9 @@ const cache = { name: null, version: null, + collection: null, collections: [], collectionsCount: 0, - collection: null, content: null, }; @@ -110,9 +110,9 @@ export function loadCollection({ cache.name = name; cache.version = version; + cache.collection = collection; cache.collections = collections; cache.collectionsCount = collectionsCount; - cache.collection = collection; cache.content = content; }, ); diff --git a/src/containers/collection-detail/collection-content.tsx b/src/containers/collection-detail/collection-content.tsx index f8045757af..6d832f6cba 100644 --- a/src/containers/collection-detail/collection-content.tsx +++ b/src/containers/collection-detail/collection-content.tsx @@ -22,11 +22,11 @@ class CollectionContent extends React.Component< const params = ParamHelper.parseParamString(props.location.search); this.state = { + collection: null, collections: [], collectionsCount: 0, - collection: null, content: null, - params: params, + params, }; } @@ -35,7 +35,7 @@ class CollectionContent extends React.Component< } render() { - const { collections, collectionsCount, collection, params, content } = + const { collection, collections, collectionsCount, content, params } = this.state; if (collections.length <= 0) { @@ -66,17 +66,17 @@ class CollectionContent extends React.Component< return ( this.loadCollections(true)} + activeTab='contents' + breadcrumbs={breadcrumbs} + collection={collection} collections={collections} collectionsCount={collectionsCount} - collection={collection} content={content} params={params} + reload={() => this.loadCollections(true)} updateParams={(params) => this.updateParams(params, () => this.loadCollections(true)) } - breadcrumbs={breadcrumbs} - activeTab='contents' />
@@ -98,7 +98,12 @@ class CollectionContent extends React.Component< matchParams: this.props.routeParams, navigate: this.props.navigate, setCollection: (collections, collection, content, collectionsCount) => - this.setState({ collections, collection, content, collectionsCount }), + this.setState({ + collections, + collection, + content, + collectionsCount, + }), stateParams: this.state.params, }); } diff --git a/src/containers/collection-detail/collection-dependencies.tsx b/src/containers/collection-detail/collection-dependencies.tsx index ce9a296cff..d8bca088ce 100644 --- a/src/containers/collection-detail/collection-dependencies.tsx +++ b/src/containers/collection-detail/collection-dependencies.tsx @@ -52,16 +52,16 @@ class CollectionDependencies extends React.Component { params['sort'] = !params['sort'] ? '-collection' : 'collection'; this.state = { + alerts: [], + collection: null, collections: [], collectionsCount: 0, - collection: null, content: null, dependencies_repos: [], - params: params, + params, usedByDependencies: [], usedByDependenciesCount: 0, usedByDependenciesLoading: true, - alerts: [], }; } @@ -71,15 +71,15 @@ class CollectionDependencies extends React.Component { render() { const { + alerts, + collection, collections, collectionsCount, - collection, content, params, usedByDependencies, usedByDependenciesCount, usedByDependenciesLoading, - alerts, } = this.state; if (collections.length <= 0) { @@ -117,20 +117,20 @@ class CollectionDependencies extends React.Component { this.closeAlert(i)} /> this.loadData(true)} + activeTab='dependencies' + breadcrumbs={breadcrumbs} + collection={collection} collections={collections} collectionsCount={collectionsCount} - collection={collection} content={content} params={headerParams} + reload={() => this.loadData(true)} + repo={repository.name} updateParams={(p) => { this.updateParams(this.combineParams(this.state.params, p), () => this.loadData(true), ); }} - breadcrumbs={breadcrumbs} - activeTab='dependencies' - repo={repository.name} />
@@ -293,7 +293,12 @@ class CollectionDependencies extends React.Component { navigate: this.props.navigate, setCollection: (collections, collection, content, collectionsCount) => this.setState( - { collections, collection, content, collectionsCount }, + { + collections, + collection, + content, + collectionsCount, + }, callback, ), stateParams: this.state.params.version diff --git a/src/containers/collection-detail/collection-detail.tsx b/src/containers/collection-detail/collection-detail.tsx index 9461c65c4d..0964c8a947 100644 --- a/src/containers/collection-detail/collection-detail.tsx +++ b/src/containers/collection-detail/collection-detail.tsx @@ -25,13 +25,13 @@ class CollectionDetail extends React.Component< const params = ParamHelper.parseParamString(props.location.search); this.state = { + alerts: [], + collection: null, collections: [], collectionsCount: 0, - collection: null, content: null, distroBasePath: null, - params: params, - alerts: [], + params, }; } @@ -47,12 +47,12 @@ class CollectionDetail extends React.Component< render() { const { + alerts, + collection, collections, collectionsCount, - collection, content, params, - alerts, } = this.state; if (collections.length <= 0) { @@ -81,18 +81,18 @@ class CollectionDetail extends React.Component< closeAlert={(i) => this.closeAlert(i)} > this.loadCollections(true)} + activeTab='install' + breadcrumbs={breadcrumbs} + collection={collection} collections={collections} collectionsCount={collectionsCount} - collection={collection} content={content} params={params} + reload={() => this.loadCollections(true)} + repo={this.props.routeParams.repo} updateParams={(p) => this.updateParams(p, () => this.loadCollections(true)) } - breadcrumbs={breadcrumbs} - activeTab='install' - repo={this.props.routeParams.repo} />
diff --git a/src/containers/collection-detail/collection-distributions.tsx b/src/containers/collection-detail/collection-distributions.tsx index d6f0dc70eb..adeb55edaf 100644 --- a/src/containers/collection-detail/collection-distributions.tsx +++ b/src/containers/collection-detail/collection-distributions.tsx @@ -29,16 +29,14 @@ import { loadCollection } from './base'; const CollectionDistributions = (props: RouteProps) => { const routeParams = ParamHelper.parseParamString(props.location.search); + const [collection, setCollection] = useState(null); const [collections, setCollections] = useState([]); const [collectionsCount, setCollectionsCount] = useState(0); - const [collection, setCollection] = useState(null); const [content, setContent] = useState(null); - const [inputText, setInputText] = useState(''); - - const [distributions, setDistributions] = useState(null); const [count, setCount] = useState(0); + const [distributions, setDistributions] = useState(null); + const [inputText, setInputText] = useState(''); const [loading, setLoading] = useState(true); - const [params, setParams] = useState( Object.keys(routeParams).length ? routeParams @@ -46,15 +44,16 @@ const CollectionDistributions = (props: RouteProps) => { sort: '-pulp_created', }, ); + const loadCollections = (forceReload) => { loadCollection({ forceReload, matchParams: props.routeParams, navigate: props.navigate, setCollection: (collections, collection, content, collectionsCount) => { + setCollection(collection); setCollections(collections); setCollectionsCount(collectionsCount); - setCollection(collection); setContent(content); loadDistributions(collection.repository.pulp_href); @@ -201,19 +200,19 @@ const CollectionDistributions = (props: RouteProps) => { return ( loadCollections(true)} + activeTab='distributions' + breadcrumbs={breadcrumbs} + collection={collection} collections={collections} collectionsCount={collectionsCount} - collection={collection} content={content} params={params} + reload={() => loadCollections(true)} updateParams={(params) => { updateParamsMixin( ParamHelper.setParam(params, 'version', params.version), ); }} - breadcrumbs={breadcrumbs} - activeTab='distributions' />
diff --git a/src/containers/collection-detail/collection-docs.tsx b/src/containers/collection-detail/collection-docs.tsx index 0da1bee5ba..5e42f0461e 100644 --- a/src/containers/collection-detail/collection-docs.tsx +++ b/src/containers/collection-detail/collection-docs.tsx @@ -33,11 +33,11 @@ class CollectionDocs extends React.Component { const params = ParamHelper.parseParamString(props.location.search); this.state = { + collection: null, collections: [], collectionsCount: 0, - collection: null, content: null, - params: params, + params, }; this.docsRef = React.createRef(); this.searchBarRef = React.createRef(); @@ -48,7 +48,7 @@ class CollectionDocs extends React.Component { } render() { - const { params, collection, collections, collectionsCount, content } = + const { collection, collections, collectionsCount, content, params } = this.state; const urlFields = this.props.routeParams; @@ -117,30 +117,21 @@ class CollectionDocs extends React.Component { { name: t`Documentation` }, ]; - // scroll to top of page - - // if ( - // this.docsRef.current && - // this.searchBarRef.current !== window.document.activeElement - // ) { - // this.docsRef.current.scrollIntoView(); - // } - return ( this.loadCollection(true)} + activeTab='documentation' + breadcrumbs={breadcrumbs} + className='header' + collection={collection} collections={collections} collectionsCount={collectionsCount} - collection={collection} content={content} params={params} + reload={() => this.loadCollection(true)} updateParams={(p) => this.updateParams(p, () => this.loadCollection(true)) } - breadcrumbs={breadcrumbs} - activeTab='documentation' - className='header' />
@@ -306,7 +297,12 @@ class CollectionDocs extends React.Component { matchParams: this.props.routeParams, navigate: this.props.navigate, setCollection: (collections, collection, content, collectionsCount) => - this.setState({ collections, collection, content, collectionsCount }), + this.setState({ + collections, + collection, + content, + collectionsCount, + }), stateParams: this.state.params, }); } diff --git a/src/containers/collection-detail/collection-import-log.tsx b/src/containers/collection-detail/collection-import-log.tsx index 0bacc37635..61ed0c1344 100644 --- a/src/containers/collection-detail/collection-import-log.tsx +++ b/src/containers/collection-detail/collection-import-log.tsx @@ -26,15 +26,15 @@ class CollectionImportLog extends React.Component { const params = ParamHelper.parseParamString(props.location.search); this.state = { + apiError: undefined, collection: null, collections: [], collectionsCount: 0, content: null, - params: params, loadingImports: true, - selectedImportDetail: undefined, + params, selectedImport: undefined, - apiError: undefined, + selectedImportDetail: undefined, }; } @@ -44,15 +44,15 @@ class CollectionImportLog extends React.Component { render() { const { + apiError, collection, collections, collectionsCount, - params, + content, loadingImports, - selectedImportDetail, + params, selectedImport, - apiError, - content, + selectedImportDetail, } = this.state; if (!collection) { @@ -83,17 +83,17 @@ class CollectionImportLog extends React.Component { return ( this.loadData(true)} + activeTab='import-log' + breadcrumbs={breadcrumbs} + collection={collection} collections={collections} collectionsCount={collectionsCount} - collection={collection} content={content} params={params} + reload={() => this.loadData(true)} updateParams={(params) => this.updateParams(params, () => this.loadData(true)) } - breadcrumbs={breadcrumbs} - activeTab='import-log' />
@@ -158,7 +158,12 @@ class CollectionImportLog extends React.Component { navigate: this.props.navigate, setCollection: (collections, collection, content, collectionsCount) => this.setState( - { collections, collection, content, collectionsCount }, + { + collections, + collection, + content, + collectionsCount, + }, callback, ), stateParams: this.state.params, diff --git a/src/containers/legacy-roles/legacy-role.tsx b/src/containers/legacy-roles/legacy-role.tsx index 3d13f87248..9eca107630 100644 --- a/src/containers/legacy-roles/legacy-role.tsx +++ b/src/containers/legacy-roles/legacy-role.tsx @@ -27,6 +27,7 @@ import { Breadcrumbs, ClipboardCopy, DateComponent, + DownloadCount, LoadingPageWithHeader, Logo, Main, @@ -317,6 +318,9 @@ class LegacyRole extends React.Component { Github Repository
+
+ +
, ); } diff --git a/src/l10n.ts b/src/l10n.ts index fe8dd159f1..86542b32b6 100644 --- a/src/l10n.ts +++ b/src/l10n.ts @@ -57,7 +57,7 @@ const overrideLanguage = window.localStorage.override_l10n && availableLanguages.includes(window.localStorage.override_l10n) && window.localStorage.override_l10n; -const language = overrideLanguage || userLanguage || 'en'; +export const language = overrideLanguage || userLanguage || 'en'; const pseudolocalization = window.localStorage.test_l10n === 'true'; if (overrideLanguage) {