Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .licenserc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ header:
- 'frontend/src/assets/svg/operator-reuse-cache-invalid.svg'
- 'frontend/src/assets/svg/operator-reuse-cache-valid.svg'
- 'frontend/src/assets/notebook_migration_tool/jupyter-logo.svg'
# Third-party icon glyph - see LICENSE file for attribution
- 'frontend/src/app/common/icon/model-icon.ts'
- 'frontend/src/app/common/type/proto/org/apache/texera/amber/core/virtualidentity.ts'
- 'frontend/src/app/common/type/proto/org/apache/texera/amber/core/workflow.ts'
- 'frontend/src/app/common/type/proto/google/protobuf/descriptor.ts'
Expand Down
6 changes: 6 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,12 @@ This product bundles code derived from TypeFox monaco-languageclient:
Source: https://github.com/TypeFox/monaco-languageclient
License: MIT License (licenses-3rd-party-code/monaco-languageclient.txt)

This product includes an icon from Google's Material Symbols:
- frontend/src/app/common/icon/model-icon.ts ("hub")
Copyright (c) Google LLC
Source: https://github.com/google/material-design-icons
License: Apache License 2.0 (this LICENSE file)

This product includes SVG icons from SVGRepo:
- frontend/src/assets/svg/operator-view-result.svg
- frontend/src/assets/svg/operator-reuse-cache-valid.svg
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,8 @@ class ModelResource extends LazyLogging {
MODEL_RESOURCE,
uid,
classOf[Model],
(model: Model) => model.getMid
(model: Model) => model.getMid,
includePublic = false
)(
fromGrant = (model, ownerEmail, privilege, isOwner) =>
Some(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ object ResourceAccess {
resource: ResourceTables[R, A],
uid: Integer,
pojoClass: Class[P],
idOf: P => Integer
idOf: P => Integer,
includePublic: Boolean = true
)(
fromGrant: (P, String, PrivilegeEnum, Boolean) => Option[D],
fromPublic: (P, String) => Option[D]
Expand Down Expand Up @@ -216,22 +217,25 @@ object ResourceAccess {

val grantedIds = granted.map(_._1).toSet

val public = ctx
.select()
.from(
resource.table
.leftJoin(USER)
.on(USER.UID.eq(resource.ownerUidField))
)
.where(resource.isPublicField.eq(true))
.fetch()
.asScala
.toList
.flatMap { record =>
val entity = record.into(resource.table).into(pojoClass)
if (grantedIds.contains(idOf(entity))) None
else fromPublic(entity, record.into(USER).getEmail)
}
val public =
if (!includePublic) Nil
else
ctx
.select()
.from(
resource.table
.leftJoin(USER)
.on(USER.UID.eq(resource.ownerUidField))
)
.where(resource.isPublicField.eq(true))
.fetch()
.asScala
.toList
.flatMap { record =>
val entity = record.into(resource.table).into(pojoClass)
if (grantedIds.contains(idOf(entity))) None
else fromPublic(entity, record.into(USER).getEmail)
}

granted.map(_._2) ++ public
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ class ModelResourceSpec
}
}

"listModels" should "include public models owned by another user" in {
"listModels" should "omit public models owned by another user" in {
val othersPublic = modelResource.createModel(
ModelResource.CreateModelRequest(
modelName = "others-public-model",
Expand All @@ -505,10 +505,14 @@ class ModelResourceSpec
sessionUser2
)

val listed = modelResource.listModels(sessionUser)
val entry = listed.find(_.model.getMid == othersPublic.model.getMid)
entry should not be empty
entry.get.isOwner shouldBe false
entry.get.accessPrivilege shouldEqual PrivilegeEnum.READ
// /model/list backs the "Your Work" page, so it lists only what the caller was granted.
// Public models are discovered through the hub and fetched with getPublicModel.
modelResource
.listModels(sessionUser)
.find(_.model.getMid == othersPublic.model.getMid) shouldBe empty
modelResource
.getPublicModel(othersPublic.model.getMid)
.model
.getMid shouldEqual othersPublic.model.getMid
}
}
5 changes: 5 additions & 0 deletions frontend/proxy.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
"secure": false,
"changeOrigin": true
},
"/api/model/**": {
"target": "http://localhost:9092",
"secure": false,
"changeOrigin": true
},
"/api/access/dataset/**": {
"target": "http://localhost:9092",
"secure": false,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/app/app-routing.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const USER_WORKSPACE = `${USER}/workflow`;
export const USER_WORKFLOW = `${USER}/workflow`;
export const USER_DATASET = `${USER}/dataset`;
export const USER_DATASET_CREATE = `${USER_DATASET}/create`;
export const USER_MODEL = `${USER}/model`;
export const USER_COMPUTING_UNIT = `${USER}/compute`;
export const USER_PYTHON_VENV = `${USER}/python-venv`;
export const USER_QUOTA = `${USER}/quota`;
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { FeedbackComponent } from "./dashboard/component/user/feedback/feedback.
import { AdminGmailComponent } from "./dashboard/component/admin/gmail/admin-gmail.component";
import { DatasetDetailComponent } from "./dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component";
import { UserDatasetComponent } from "./dashboard/component/user/user-dataset/user-dataset.component";
import { UserModelComponent } from "./dashboard/component/user/user-model/user-model.component";
import { HubWorkflowDetailComponent } from "./hub/component/workflow/detail/hub-workflow-detail.component";
import { LandingPageComponent } from "./hub/component/landing-page/landing-page.component";
import { USER_WORKFLOW } from "./app-routing.constant";
Expand Down Expand Up @@ -135,6 +136,10 @@ routes.push({
path: "dataset/create",
component: DatasetDetailComponent,
},
{
path: "model",
component: UserModelComponent,
},
{
path: "compute",
component: UserComputingUnitComponent,
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { GuiConfigService } from "./common/service/gui-config.service";
import { DeploymentVersionService } from "./common/service/deployment-version/deployment-version.service";
import { Version } from "../environments/version";
import { UntilDestroy } from "@ngneat/until-destroy";
import { NzIconService } from "ng-zorro-antd/icon";
import { MODEL_ICON, MODEL_ICON_SVG } from "./common/icon/model-icon";

@UntilDestroy()
@Component({
Expand All @@ -44,8 +46,12 @@ export class AppComponent {

constructor(
private configService: GuiConfigService,
private deploymentVersionService: DeploymentVersionService
private deploymentVersionService: DeploymentVersionService,
iconService: NzIconService
) {
// ng-zorro has no icon that reads as an ML model, so this one is registered from Material Symbols.
iconService.addIconLiteral(MODEL_ICON, MODEL_ICON_SVG);

// determine whether configuration was successfully loaded by APP_INITIALIZER
try {
// accessing env will throw if not loaded
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/app/common/icon/model-icon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Material Symbols "hub", from Google's material-design-icons.
* Apache License 2.0 — see the LICENSE file at the repository root for attribution.
* https://github.com/google/material-design-icons
*/

/** Namespaced name to pass as `nzType`, once registered with NzIconService. */
export const MODEL_ICON = "texera:model";

export const MODEL_ICON_SVG =
'<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M240-40q-50 0-85-35t-35-85q0-50 35-85t85-35q14 0 26 3t23 8l57-71q-28-31-39-70t-5-78l-81-27q-17 25-43 40t-58 15q-50 0-85-35T0-580q0-50 35-85t85-35q50 0 85 35t35 85v8l81 28q20-36 53.5-61t75.5-32v-87q-39-11-64.5-42.5T360-840q0-50 35-85t85-35q50 0 85 35t35 85q0 42-26 73.5T510-724v87q42 7 75.5 32t53.5 61l81-28v-8q0-50 35-85t85-35q50 0 85 35t35 85q0 50-35 85t-85 35q-32 0-58.5-15T739-515l-81 27q6 39-5 77.5T614-340l57 70q11-5 23-7.5t26-2.5q50 0 85 35t35 85q0 50-35 85t-85 35q-50 0-85-35t-35-85q0-20 6.5-38.5T624-232l-57-71q-41 23-87.5 23T392-303l-56 71q11 15 17.5 33.5T360-160q0 50-35 85t-85 35ZM120-540q17 0 28.5-11.5T160-580q0-17-11.5-28.5T120-620q-17 0-28.5 11.5T80-580q0 17 11.5 28.5T120-540Zm120 420q17 0 28.5-11.5T280-160q0-17-11.5-28.5T240-200q-17 0-28.5 11.5T200-160q0 17 11.5 28.5T240-120Zm240-680q17 0 28.5-11.5T520-840q0-17-11.5-28.5T480-880q-17 0-28.5 11.5T440-840q0 17 11.5 28.5T480-800Zm0 440q42 0 71-29t29-71q0-42-29-71t-71-29q-42 0-71 29t-29 71q0 42 29 71t71 29Zm240 240q17 0 28.5-11.5T760-160q0-17-11.5-28.5T720-200q-17 0-28.5 11.5T680-160q0 17 11.5 28.5T720-120Zm120-420q17 0 28.5-11.5T880-580q0-17-11.5-28.5T840-620q-17 0-28.5 11.5T800-580q0 17 11.5 28.5T840-540ZM480-840ZM120-580Zm360 120Zm360-120ZM240-160Zm480 0Z"/></svg>';
1 change: 1 addition & 0 deletions frontend/src/app/common/type/gui-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export interface SidebarTabs {
workflows_enabled: boolean;
compute_enabled: boolean;
datasets_enabled: boolean;
models_enabled: boolean;
quota_enabled: boolean;
forum_enabled: boolean;
about_enabled: boolean;
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/app/common/type/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* 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.
*/

export interface Model {
mid: number | undefined;
ownerUid: number | undefined;
name: string;
repositoryName: string | undefined;
isPublic: boolean;
isDownloadable: boolean;
description: string;
creationTime: number | undefined;
coverImage: string | undefined;
framework: string | undefined;
format: string | undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,15 @@ <h2 class="page-title">General Settings</h2>
</nz-switch>
</div>

<div class="submenu-item">
<span>Models</span>
<nz-switch
class="tab-switch"
[(ngModel)]="sidebarTabs.models_enabled"
[nzDisabled]="!sidebarTabs.your_work_enabled">
</nz-switch>
</div>

<div class="submenu-item">
<span>Compute</span>
<nz-switch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ describe("AdminSettingsComponent wiring", () => {
"projects_enabled",
"workflows_enabled",
"datasets_enabled",
"models_enabled",
"compute_enabled",
"quota_enabled",
"forum_enabled",
Expand Down Expand Up @@ -647,7 +648,18 @@ describe("AdminSettingsComponent wiring", () => {
});

it("locks the Your Work children until Your Work itself is on", () => {
const yourWorkChildren = [5, 6, 7, 8, 9, 10];
// Indexed by key, not position: adding a tab shifts these and would silently drop one.
const yourWorkChildren = (
[
"projects_enabled",
"workflows_enabled",
"datasets_enabled",
"models_enabled",
"compute_enabled",
"quota_enabled",
"forum_enabled",
] as const
).map(key => SWITCH_KEYS.indexOf(key));

component.sidebarTabs.your_work_enabled = false;
fixture.detectChanges();
Expand All @@ -664,7 +676,10 @@ describe("AdminSettingsComponent wiring", () => {
component.sidebarTabs.your_work_enabled = false;
fixture.detectChanges();

[0, 4, 11].forEach(i => expect(switches()[i].componentInstance.nzDisabled).toBeFalsy());
// Indexed by key, not position, so adding a tab does not silently retarget this.
(["hub_enabled", "your_work_enabled", "about_enabled"] as const).forEach(key =>
expect(switches()[SWITCH_KEYS.indexOf(key)].componentInstance.nzDisabled).toBeFalsy()
);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export class AdminSettingsComponent implements OnInit {
workflows_enabled: false,
compute_enabled: false,
datasets_enabled: false,
models_enabled: false,
quota_enabled: false,
forum_enabled: false,
about_enabled: false,
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/app/dashboard/component/dashboard.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@
nzType="database"></span>
<span>Datasets</span>
</li>
<li
*ngIf="sidebarTabs.models_enabled"
nz-menu-item
nz-tooltip="Look up for models"
nzMatchRouter="true"
nzTooltipPlacement="right"
[routerLink]="USER_MODEL">
<span
nz-icon
[nzType]="MODEL_ICON"></span>
<span>Models</span>
</li>
<li
*ngIf="sidebarTabs.compute_enabled"
nz-menu-item
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,15 +287,17 @@ describe("DashboardComponent", () => {
projects_enabled: true,
workflows_enabled: true,
datasets_enabled: true,
models_enabled: true,
compute_enabled: true,
quota_enabled: true,
forum_enabled: true,
about_enabled: true,
};
fixture.detectChanges();

// 7 "Your Work" links (incl. Python Venvs) + 4 admin links + 1 about link + 1 feedback link = 13
expect(fixture.debugElement.queryAll(By.directive(RouterLink)).length).toBe(13);
// 8 "Your Work" links (incl. Python Venvs and Models) + 4 admin links + 1 about link
// + 1 feedback link = 14
expect(fixture.debugElement.queryAll(By.directive(RouterLink)).length).toBe(14);
});

describe("sidebar active-route highlighting (#3490)", () => {
Expand All @@ -308,6 +310,7 @@ describe("DashboardComponent", () => {
projects_enabled: true,
workflows_enabled: true,
datasets_enabled: true,
models_enabled: true,
compute_enabled: true,
quota_enabled: true,
forum_enabled: true,
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/app/dashboard/component/dashboard.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
ADMIN_USER,
USER_COMPUTING_UNIT,
USER_DATASET,
USER_MODEL,
USER_DISCUSSION,
USER_PROJECT,
USER_PYTHON_VENV,
Expand All @@ -56,6 +57,7 @@ import { NzIconDirective } from "ng-zorro-antd/icon";
import { NzButtonComponent } from "ng-zorro-antd/button";
import { SearchBarComponent } from "./user/search-bar/search-bar.component";
import { UserIconComponent } from "./user/user-icon/user-icon.component";
import { MODEL_ICON } from "../../common/icon/model-icon";

@Component({
selector: "texera-dashboard",
Expand Down Expand Up @@ -106,6 +108,7 @@ export class DashboardComponent implements OnInit {
projects_enabled: false,
workflows_enabled: false,
datasets_enabled: false,
models_enabled: false,
compute_enabled: false,
quota_enabled: false,
forum_enabled: false,
Expand All @@ -116,6 +119,8 @@ export class DashboardComponent implements OnInit {
protected readonly USER_PROJECT = USER_PROJECT;
protected readonly USER_WORKFLOW = USER_WORKFLOW;
protected readonly USER_DATASET = USER_DATASET;
protected readonly USER_MODEL = USER_MODEL;
protected readonly MODEL_ICON = MODEL_ICON;
protected readonly USER_COMPUTING_UNIT = USER_COMPUTING_UNIT;
protected readonly USER_PYTHON_VENV = USER_PYTHON_VENV;
protected readonly USER_QUOTA = USER_QUOTA;
Expand Down
Loading
Loading