Skip to content

Commit 4490778

Browse files
Merge pull request #225 from canonical/WD-24286-render-assets-on-webpage
[WD-24286] feat: Render paginated assets on a web page
2 parents 6fcb5a5 + faba226 commit 4490778

File tree

13 files changed

+212
-11
lines changed

13 files changed

+212
-11
lines changed

static/client/App.scss

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@
6868
position: sticky !important;
6969
top: -1.5rem;
7070
z-index: 9;
71-
72-
& button[aria-expanded="true"]{
71+
72+
& button[aria-expanded="true"] {
7373
background: var(--vf-color-background-neutral-hover) !important;
7474
}
7575
}
@@ -78,6 +78,10 @@
7878
}
7979
}
8080

81-
.p-table__row--highlight{
81+
.p-table__row--highlight {
8282
background: var(--vf-color-background-neutral-hover) !important;
83-
}
83+
}
84+
85+
.p-asset__image {
86+
background: $color-mid-x-light;
87+
}

static/client/components/Views/TableView/ProjectTitle.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ const ProjectTitle: React.FC<ProjectTitleProps> = ({ project }) => {
2020
<span className="p-muted-heading" style={{ margin: "0 0.5rem" }}>
2121
{project.name}
2222
</span>
23-
<Badge className="u-no-padding--top" value={countPages(project.templates) - (isFilterApplied ? 1 : 0)} />
23+
<Badge
24+
className="u-no-padding--top u-no-margin--bottom"
25+
value={countPages(project.templates) - (isFilterApplied ? 1 : 0)}
26+
/>
2427
</span>
2528
);
2629
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React, { useMemo } from "react";
2+
3+
import config from "@/config";
4+
import type { IAsset } from "@/services/api/types/assets";
5+
6+
const Asset: React.FC<{ asset: IAsset }> = ({ asset }) => {
7+
const assetName = useMemo(() => {
8+
return asset.url.split("/v1/")[1];
9+
}, [asset.url]);
10+
11+
const isImgFile = useMemo(() => {
12+
return [".jpg", ".jpeg", ".png", ".gif", ".svg"].includes(asset.type.toLowerCase());
13+
}, [asset.type]);
14+
15+
return (
16+
<>
17+
<div className="p-image-container--3-2 p-asset__image">
18+
<img
19+
alt=""
20+
className="p-image-container__image"
21+
src={isImgFile ? asset.url : "https://assets.ubuntu.com/v1/fd84bbdc-Document-open.svg"}
22+
/>
23+
</div>
24+
<div className="asset-name u-truncate">
25+
<b>{assetName}</b>
26+
</div>
27+
<div className="asset-type">
28+
<p>
29+
File type: <b>{asset.type}</b>
30+
</p>
31+
</div>
32+
<div className="asset-cta">
33+
<div className="p-cta-block">
34+
<a
35+
className="p-button--positive"
36+
href={`${config.assetsManagerUrl}/details?file-path=${assetName}`}
37+
rel="noreferrer"
38+
target="_blank"
39+
>
40+
View
41+
</a>
42+
<a
43+
className="p-button"
44+
href={`${config.assetsManagerUrl}/update?file-path=${assetName}`}
45+
rel="noreferrer"
46+
target="_blank"
47+
>
48+
Edit
49+
</a>
50+
</div>
51+
</div>
52+
</>
53+
);
54+
};
55+
56+
export default Asset;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React, { useCallback, useState } from "react";
2+
3+
import { Pagination, Spinner } from "@canonical/react-components";
4+
5+
import Asset from "./Asset";
6+
7+
import { useWebpageAssets } from "@/services/api/hooks/assets";
8+
import type { IPage } from "@/services/api/types/pages";
9+
10+
const WebpageAssets: React.FC<{ page: IPage }> = ({ page }) => {
11+
const [currentPage, setCurrentPage] = useState(1);
12+
const { data: assetsData, isLoading } = useWebpageAssets(page.url ?? "", page.project?.name ?? "", currentPage, 12);
13+
14+
const paginate = useCallback((pageNumber: number) => {
15+
setCurrentPage(pageNumber);
16+
document.querySelector("#webpage-assets")?.scrollIntoView({
17+
block: "start",
18+
behavior: "smooth",
19+
});
20+
}, []);
21+
22+
if (isLoading) return <Spinner text="Loading assets ..." />;
23+
if (!assetsData?.assets?.length) return null;
24+
25+
return (
26+
<div id="webpage-assets">
27+
<section className="p-section">
28+
<p className="p-text--small-caps u-sv1">Assets used: {assetsData.total}</p>
29+
<div className="grid-row--25-25-25-25">
30+
{assetsData.assets?.map((asset) => {
31+
return (
32+
<div className="grid-col" key={asset.id}>
33+
<div className="p-section--shallow">
34+
<Asset asset={asset} />
35+
</div>
36+
</div>
37+
);
38+
})}
39+
</div>
40+
<Pagination
41+
centered
42+
currentPage={assetsData.page}
43+
itemsPerPage={assetsData.page_size}
44+
paginate={paginate}
45+
totalItems={assetsData.total}
46+
/>
47+
</section>
48+
</div>
49+
);
50+
};
51+
52+
export default WebpageAssets;

static/client/config/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const config = {
3131
],
3232
pageRefreshes: ["Changing or adding to the page layout", "Modifications that change the layout"],
3333
},
34+
assetsManagerUrl: "https://assets.ubuntu.com/manager",
3435
api: {
3536
path: "/",
3637
FETCH_OPTIONS: {

static/client/pages/Webpage/Webpage.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import JiraTasks from "@/components/JiraTasks";
99
import OwnerAndReviewers from "@/components/OwnerAndReviewers";
1010
import Products from "@/components/Products";
1111
import RequestTaskModal from "@/components/RequestTaskModal";
12+
import WebpageAssets from "@/components/WebpageAssets";
1213
import config from "@/config";
1314
import { ChangeRequestType, PageStatus } from "@/services/api/types/pages";
1415

@@ -135,6 +136,7 @@ const Webpage = ({ page, project }: IWebpageProps): ReactNode => {
135136
<JiraTasks tasks={page.jira_tasks} />
136137
</div>
137138
) : null}
139+
<WebpageAssets page={page} />
138140
{modalOpen && (
139141
<RequestTaskModal
140142
changeType={changeType}

static/client/services/api/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const ENDPOINTS = {
99
currentUser: "/api/current-user",
1010
getProducts: "/api/get-products",
1111
setProducts: "/api/set-product",
12+
getWebpageAssets: "/api/get-webpage-assets",
1213
};
1314

1415
export const REST_TYPES = {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useQuery } from "react-query";
2+
3+
import { PagesServices } from "@/services/api/services/pages";
4+
import type { IAssetsResponse } from "@/services/api/types/assets";
5+
import type { IApiBasicError, IUseQueryHookRest } from "@/services/api/types/query";
6+
7+
export function useWebpageAssets(
8+
pageUrl: string,
9+
projectName: string,
10+
page: number = 1,
11+
perPage: number = 12,
12+
): IUseQueryHookRest<IAssetsResponse["data"]> {
13+
const result = useQuery<IAssetsResponse["data"], IApiBasicError>({
14+
queryKey: ["webpageAssets", pageUrl, projectName, page, perPage],
15+
queryFn: () =>
16+
PagesServices.getWebpageAssets({ pageUrl, projectName, page, perPage }).then(
17+
(response: IAssetsResponse) => response.data,
18+
),
19+
});
20+
21+
const error = result.error;
22+
const data = result.data;
23+
const isLoading = result.isFetching || result.isLoading;
24+
25+
return { data, error, isLoading };
26+
}

static/client/services/api/partials/PagesApiClass.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BasicApiClass } from "./BasicApiClass";
22

33
import { ENDPOINTS, REST_TYPES } from "@/services/api/constants";
4+
import type { IAssetsResponse, IGetWebpageAssets } from "@/services/api/types/assets";
45
import type {
56
INewPage,
67
INewPageResponse,
@@ -45,4 +46,11 @@ export class PagesApiClass extends BasicApiClass {
4546
public setProducts(body: ISetProducts) {
4647
return this.callApi(ENDPOINTS.setProducts, REST_TYPES.POST, body);
4748
}
49+
50+
public getWebpageAssets(body: IGetWebpageAssets): Promise<IAssetsResponse> {
51+
return this.callApi(`${ENDPOINTS.getWebpageAssets}?page=${body.page}&per_page=${body.perPage}`, REST_TYPES.POST, {
52+
webpage_url: body.pageUrl,
53+
project_name: body.projectName,
54+
});
55+
}
4856
}

static/client/services/api/services/pages.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { api } from "@/services/api";
2+
import type { IAssetsResponse, IGetWebpageAssets } from "@/services/api/types/assets";
23
import type {
34
INewPage,
45
INewPageResponse,
@@ -37,4 +38,8 @@ export const setProducts = async (body: ISetProducts) => {
3738
return api.pages.setProducts(body);
3839
};
3940

41+
export const getWebpageAssets = async (body: IGetWebpageAssets): Promise<IAssetsResponse> => {
42+
return api.pages.getWebpageAssets(body);
43+
};
44+
4045
export * as PagesServices from "./pages";

0 commit comments

Comments
 (0)