Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1372,4 +1372,44 @@ class DatasetResource {
Right(response)
}
}

@POST
@RolesAllowed(Array("REGULAR", "ADMIN"))
@Path("/{did}/update/cover")
def updateDatasetCoverImage(
@PathParam("did") did: Integer,
coverImage: String,
@Auth sessionUser: SessionUser
): Response = {
withTransaction(context) { ctx =>
val uid = sessionUser.getUid
val dataset = getDatasetByID(ctx, did)
if (!userHasWriteAccess(ctx, did, uid)) {
throw new ForbiddenException(ERR_USER_HAS_NO_ACCESS_TO_DATASET_MESSAGE)
}

val document = DocumentFactory
.openReadonlyDocument(
FileResolver.resolve(s"${getOwner(ctx, did).getEmail}/${dataset.getName}/$coverImage")
)
.asInstanceOf[OnDataset]

val file = LakeFSStorageClient.getFileFromRepo(
document.getRepositoryName(),
document.getVersionHash(),
document.getFileRelativePath()
)
val coverSizeLimit = 10 * 1024 * 1024 // 10 MB

if (file.length() > coverSizeLimit) {
throw new BadRequestException(
s"Cover image must be less than ${coverSizeLimit / (1024 * 1024)} MB"
)
}

dataset.setCoverImage(coverImage)
new DatasetDao(ctx.configuration()).update(dataset)
Response.ok().build()
}
}
}
1 change: 1 addition & 0 deletions frontend/src/app/common/type/dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ export interface Dataset {
storagePath: string | undefined;
description: string;
creationTime: number | undefined;
coverImage: string | undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,8 @@ <h6 style="font-weight: lighter; font-size: 0.9em">Choose a Version:</h6>
[fileTreeNodes]="fileTreeNodeList"
[isTreeNodeDeletable]="true"
(selectedTreeNode)="onVersionFileTreeNodeSelected($event)"
(deletedTreeNode)="onPreviouslyUploadedFileDeleted($event)">
(deletedTreeNode)="onPreviouslyUploadedFileDeleted($event)"
(setCoverImage)="onSetCoverImage($event)">
</texera-user-dataset-version-filetree>
</nz-collapse-panel>
</nz-collapse>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -657,4 +657,22 @@ export class DatasetDetailComponent implements OnInit {
changeViewDisplayStyle() {
this.displayPreciseViewCount = !this.displayPreciseViewCount;
}

onSetCoverImage(filePath: string): void {
if (!this.did || !this.selectedVersion) {
return;
}

this.datasetService
.updateDatasetCoverImage(this.did, `${this.selectedVersion.name}/${filePath}`)
.pipe(untilDestroyed(this))
.subscribe({
next: () => {
this.notificationService.success("Cover image set successfully");
},
error: (err: HttpErrorResponse) => {
this.notificationService.error(err.error?.message || "Failed to set cover image");
},
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export class UserDatasetVersionCreatorComponent implements OnInit {
ownerUid: undefined,
storagePath: undefined,
creationTime: undefined,
coverImage: undefined,
};
this.datasetService
.createDataset(ds)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,26 @@
nz-button
nzType="link"
*ngIf="isTreeNodeDeletable && !node.data.children"
class="delete-button"
class="icon-button"
(click)="onNodeDeleted(node.data)">
<i
nz-icon
nzType="delete"
nzTheme="outline"></i>
</button>

<button
nz-button
nzType="link"
*ngIf="!node.data.children && isImageFile(node.data.name)"
class="icon-button"
nz-tooltip="Set as cover"
(click)="onSetCover(node.data)">
<i
nz-icon
nzType="picture"
nzTheme="outline"></i>
</button>
</span>
</ng-template>
</tree-root>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
}

/* Styles for the delete button */
.delete-button {
.icon-button {
width: 15px;
margin-left: 5px;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@

import { UntilDestroy } from "@ngneat/until-destroy";
import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from "@angular/core";
import { DatasetFileNode } from "../../../../../../common/type/datasetVersionFileTree";
import {
DatasetFileNode,
getRelativePathFromDatasetFileNode,
} from "../../../../../../common/type/datasetVersionFileTree";
import { ITreeOptions, TREE_ACTIONS } from "@ali-hm/angular-tree-component";

@UntilDestroy()
Expand All @@ -40,6 +43,9 @@ export class UserDatasetVersionFiletreeComponent implements AfterViewInit {

@ViewChild("tree") tree: any;

@Output()
setCoverImage = new EventEmitter<string>();

public fileTreeDisplayOptions: ITreeOptions = {
displayField: "name",
hasChildrenField: "children",
Expand Down Expand Up @@ -74,4 +80,14 @@ export class UserDatasetVersionFiletreeComponent implements AfterViewInit {
this.tree.treeModel.expandAll();
}
}

isImageFile(fileName: string): boolean {
const imageExts = [".jpg", ".jpeg", ".png", ".gif", ".webp"];
return imageExts.some(ext => fileName.toLowerCase().endsWith(ext));
}

onSetCover(nodeData: DatasetFileNode): void {
const path = getRelativePathFromDatasetFileNode(nodeData);
this.setCoverImage.emit(path);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -539,4 +539,8 @@ export class DatasetService {
public retrieveOwners(): Observable<string[]> {
return this.http.get<string[]>(`${AppSettings.getApiEndpoint()}/${DATASET_GET_OWNERS_URL}`);
}

public updateDatasetCoverImage(did: number, coverImagePath: string): Observable<Response> {
return this.http.post<Response>(`${AppSettings.getApiEndpoint()}/dataset/${did}/update/cover`, coverImagePath);
}
}
2 changes: 2 additions & 0 deletions frontend/src/app/dashboard/type/dashboard-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class DashboardEntry {
likeCount: number;
isLiked: boolean;
accessibleUserIds: number[];
coverImageUrl?: string;

constructor(public value: DashboardWorkflow | DashboardProject | DashboardFile | DashboardDataset) {
if (isDashboardWorkflow(value)) {
Expand Down Expand Up @@ -122,6 +123,7 @@ export class DashboardEntry {
this.likeCount = 0;
this.isLiked = false;
this.accessibleUserIds = [];
this.coverImageUrl = value.dataset.coverImage;
} else {
throw new Error("Unexpected type in DashboardEntry.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ <h1 class="card-title">{{ entity.name }}</h1>
<img
alt="example"
class="card-cover-image"
[src]="defaultBackground" />
[src]="getCoverImage(entity)"
(error)="$any($event.target).src = defaultBackground" />
<nz-avatar
class="entity-avatar"
[ngStyle]="{ 'background-color': 'grey', 'vertical-align': 'middle' }"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,12 @@ export class BrowseSectionComponent implements OnInit, OnChanges {
throw new Error("Unexpected type in DashboardEntry.");
}
}

getCoverImage(entity: DashboardEntry): string {
if (entity.type === "dataset" && entity.coverImageUrl) {
const fullPath = `${entity.ownerEmail}/${entity.name}/${entity.coverImageUrl}`;
return `/api/dataset/file?path=${encodeURIComponent(fullPath)}`;
}
return this.defaultBackground;
}
}
1 change: 1 addition & 0 deletions sql/texera_ddl.sql
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ CREATE TABLE IF NOT EXISTS dataset
is_downloadable BOOLEAN NOT NULL DEFAULT TRUE,
description VARCHAR(512) NOT NULL,
creation_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
cover_image varchar(255),
FOREIGN KEY (owner_uid) REFERENCES "user"(uid) ON DELETE CASCADE
);

Expand Down
34 changes: 34 additions & 0 deletions sql/updates/16.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance
-- with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing,
-- software distributed under the License is distributed on an
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.

-- ============================================
-- 1. Connect to the texera_db database
-- ============================================
\c texera_db

SET search_path TO texera_db;

-- ============================================
-- 2. Update the table schema
-- ============================================
BEGIN;

-- 1. Add new column cover_image to dataset table.
ALTER TABLE dataset
ADD COLUMN IF NOT EXISTS cover_image varchar(255);

COMMIT;