Skip to content

Commit

Permalink
Improve client types around user + refactoring
Browse files Browse the repository at this point in the history
Refactor userStore method to use matchesCurrentUsername for ownership checks.
  • Loading branch information
davelopez committed Aug 5, 2024
1 parent 861a9d9 commit 01227d4
Show file tree
Hide file tree
Showing 13 changed files with 65 additions and 83 deletions.
28 changes: 12 additions & 16 deletions client/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,36 +215,32 @@ export function isHistoryItem(item: object): item is HistoryItemSummary {
return item && "history_content_type" in item;
}

type QuotaUsageResponse = components["schemas"]["UserQuotaUsage"];
type RegisteredUserModel = components["schemas"]["DetailedUserModel"];
type AnonymousUserModel = components["schemas"]["AnonUserModel"];

/** Represents a registered user.**/
export interface User extends QuotaUsageResponse {
id: string;
email: string;
tags_used: string[];
export interface RegisteredUser extends RegisteredUserModel {
isAnonymous: false;
is_admin?: boolean;
username?: string;
}

export interface AnonymousUser extends QuotaUsageResponse {
id?: string;
export interface AnonymousUser extends AnonymousUserModel {
isAnonymous: true;
is_admin?: false;
username?: string;
}

export type GenericUser = User | AnonymousUser;
export type GenericUser = RegisteredUser | AnonymousUser;

/** Represents any user, including anonymous users or session-less (null) users.**/
export type AnyUser = GenericUser | null;

export function isRegisteredUser(user: AnyUser): user is User {
return user !== null && !user?.isAnonymous;
export function isRegisteredUser(user: AnyUser): user is RegisteredUser {
return user !== null && "email" in user;
}

export function isAnonymousUser(user: AnyUser): user is AnonymousUser {
return user !== null && user.isAnonymous;
return user !== null && !isRegisteredUser(user);
}

export function isAdminUser(user: AnyUser): user is RegisteredUser {
return isRegisteredUser(user) && user.is_admin;
}

export function userOwnsHistory(user: AnyUser, history: AnyHistory) {
Expand Down
13 changes: 13 additions & 0 deletions client/src/api/workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,16 @@ export const invocationCountsFetcher = fetcher.path("/api/workflows/{workflow_id

export const sharing = fetcher.path("/api/workflows/{workflow_id}/sharing").method("get").create();
export const enableLink = fetcher.path("/api/workflows/{workflow_id}/enable_link_access").method("put").create();

//TODO: replace with generated schema model when available
export interface WorkflowSummary {
name: string;
owner: string;
[key: string]: unknown;
update_time: string;
license?: string;
tags?: string[];
creator?: {
[key: string]: unknown;
}[];
}
9 changes: 5 additions & 4 deletions client/src/components/Masthead/QuotaMeter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ import { BLink, BProgress, BProgressBar } from "bootstrap-vue";
import { storeToRefs } from "pinia";
import { computed } from "vue";
import { isRegisteredUser } from "@/api";
import { useConfig } from "@/composables/config";
import { useUserStore } from "@/stores/userStore";
import { bytesToString } from "@/utils/utils";
const { config } = useConfig();
const { currentUser, isAnonymous } = storeToRefs(useUserStore());
const hasQuota = computed(() => {
const hasQuota = computed<boolean>(() => {
const quotasEnabled = config.value.enable_quotas ?? false;
const quotaLimited = currentUser.value?.quota !== "unlimited" ?? false;
const quotaLimited = (isRegisteredUser(currentUser.value) && currentUser.value.quota !== "unlimited") ?? false;
return quotasEnabled && quotaLimited;
});
const quotaLimit = computed(() => currentUser.value?.quota ?? 0);
const quotaLimit = computed(() => (isRegisteredUser(currentUser.value) && currentUser.value.quota) ?? null);
const totalUsageString = computed(() => {
const total = currentUser.value?.total_disk_usage ?? 0;
Expand Down Expand Up @@ -56,7 +57,7 @@ const variant = computed(() => {
<span v-localize>Using</span>
<span v-if="hasQuota">
<span>{{ usage.toFixed(0) }}%</span>
<span v-if="quotaLimit !== 0">of {{ quotaLimit }}</span>
<span v-if="quotaLimit !== null">of {{ quotaLimit }}</span>
</span>
<span v-else>{{ totalUsageString }}</span>
</span>
Expand Down
7 changes: 2 additions & 5 deletions client/src/components/Workflow/List/WorkflowActions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,9 @@ const { confirm } = useConfirmDialog();
const bookmarkLoading = ref(false);
const shared = computed(() => {
if (userStore.currentUser) {
return userStore.currentUser.username !== props.workflow.owner;
} else {
return false;
}
return !userStore.matchesCurrentUsername(props.workflow.owner);
});
const sourceType = computed(() => {
if (props.workflow.source_metadata?.url) {
return "url";
Expand Down
7 changes: 2 additions & 5 deletions client/src/components/Workflow/List/WorkflowActionsExtend.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,9 @@ const { isAnonymous } = storeToRefs(useUserStore());
const downloadUrl = computed(() => {
return withPrefix(`/api/workflows/${props.workflow.id}/download?format=json-download`);
});
const shared = computed(() => {
if (userStore.currentUser) {
return userStore.currentUser.username !== props.workflow.owner;
} else {
return false;
}
return !userStore.matchesCurrentUsername(props.workflow.owner);
});
async function onCopy() {
Expand Down
8 changes: 3 additions & 5 deletions client/src/components/Workflow/List/WorkflowCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,11 @@ const showRename = ref(false);
const showPreview = ref(false);
const workflow = computed(() => props.workflow);
const shared = computed(() => {
if (userStore.currentUser) {
return userStore.currentUser.username !== workflow.value.owner;
} else {
return false;
}
return !userStore.matchesCurrentUsername(workflow.value.owner);
});
const description = computed(() => {
if (workflow.value.annotations && workflow.value.annotations.length > 0) {
return workflow.value.annotations[0].trim();
Expand Down
11 changes: 5 additions & 6 deletions client/src/components/Workflow/List/WorkflowIndicators.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,17 @@ const router = useRouter();
const userStore = useUserStore();
const publishedTitle = computed(() => {
if (userStore.currentUser?.username === props.workflow.owner) {
if (userStore.matchesCurrentUsername(props.workflow.owner)) {
return "Published by you. Click to view all published workflows by you";
} else {
return `Published by '${props.workflow.owner}'. Click to view all published workflows by '${props.workflow.owner}'`;
}
});
const shared = computed(() => {
if (userStore.currentUser) {
return userStore.currentUser.username !== props.workflow.owner;
} else {
return false;
}
return !userStore.matchesCurrentUsername(props.workflow.owner);
});
const sourceType = computed(() => {
if (props.workflow.source_metadata?.url) {
return "url";
Expand All @@ -51,6 +49,7 @@ const sourceType = computed(() => {
return "";
}
});
const sourceTitle = computed(() => {
if (sourceType.value.includes("trs")) {
return `Imported from TRS ID (version: ${props.workflow.source_metadata.trs_version_id}). Click to copy ID`;
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Workflow/List/WorkflowList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ async function load(overlayLoading = false, silent = false) {
: data;
if (props.activeList === "my") {
filteredWorkflows = filter(filteredWorkflows, (w: any) => w.owner === userStore.currentUser?.username);
filteredWorkflows = filter(filteredWorkflows, (w: any) => userStore.matchesCurrentUsername(w.owner));
}
workflowsLoaded.value = filteredWorkflows;
Expand Down
20 changes: 3 additions & 17 deletions client/src/components/Workflow/Published/WorkflowInformation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { computed } from "vue";
import { RouterLink } from "vue-router";
import { type WorkflowSummary } from "@/api/workflows";
import { useUserStore } from "@/stores/userStore";
import { getFullAppUrl } from "@/utils/utils";
Expand All @@ -16,19 +17,8 @@ import UtcDate from "@/components/UtcDate.vue";
library.add(faBuilding, faUser);
interface WorkflowInformation {
name: string;
[key: string]: unknown;
update_time: string;
license?: string;
tags?: string[];
creator?: {
[key: string]: unknown;
}[];
}
interface Props {
workflowInfo: WorkflowInformation;
workflowInfo: WorkflowSummary;
embedded?: boolean;
}
Expand All @@ -51,11 +41,7 @@ const fullLink = computed(() => {
});
const userOwned = computed(() => {
if (userStore.currentUser) {
return userStore.currentUser.username === props.workflowInfo.owner;
} else {
return false;
}
return userStore.matchesCurrentUsername(props.workflowInfo.owner);
});
</script>

Expand Down
19 changes: 5 additions & 14 deletions client/src/components/Workflow/Published/WorkflowPublished.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { type AxiosError } from "axios";
import { BAlert, BButton, BCard } from "bootstrap-vue";
import { computed, onMounted, ref, watch } from "vue";
import { type WorkflowSummary } from "@/api/workflows";
import { fromSimple } from "@/components/Workflow/Editor/modules/model";
import { getWorkflowFull, getWorkflowInfo } from "@/components/Workflow/workflows.services";
import { useDatatypesMapper } from "@/composables/datatypesMapper";
Expand All @@ -22,14 +23,6 @@ import WorkflowInformation from "@/components/Workflow/Published/WorkflowInforma
library.add(faBuilding, faDownload, faEdit, faPlay, faSpinner, faUser);
type WorkflowInfo = {
name: string;
[key: string]: unknown;
license?: string;
tags?: string[];
update_time: string;
};
interface Props {
id: string;
zoom?: number;
Expand Down Expand Up @@ -65,7 +58,7 @@ const { stateStore } = provideScopedWorkflowStores(props.id);
const loading = ref(true);
const errorMessage = ref("");
const workflowInfo = ref<WorkflowInfo>();
const workflowInfo = ref<WorkflowSummary>();
const workflow = ref<Workflow | null>(null);
const hasError = computed(() => !!errorMessage.value);
Expand All @@ -80,13 +73,11 @@ const initialPosition = computed(() => ({
}));
const viewUrl = computed(() => withPrefix(`/published/workflow?id=${props.id}`));
const sharedWorkflow = computed(() => {
if (userStore.currentUser) {
return userStore.currentUser.username !== workflowInfo.value?.owner;
} else {
return false;
}
return !userStore.matchesCurrentUsername(workflowInfo.value?.owner);
});
const editButtonTitle = computed(() => {
if (userStore.isAnonymous) {
return "Log in to edit Workflow";
Expand Down
5 changes: 2 additions & 3 deletions client/src/components/Workflow/Run/WorkflowRun.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { BAlert, BLink } from "bootstrap-vue";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref, watch } from "vue";
import { RouterLink } from "vue-router";
import { useRouter } from "vue-router/composables";
Expand All @@ -22,7 +21,7 @@ import WorkflowRunSuccess from "@/components/Workflow/Run/WorkflowRunSuccess.vue
const historyStore = useHistoryStore();
const historyItemsStore = useHistoryItemsStore();
const { currentUser } = storeToRefs(useUserStore());
const userStore = useUserStore();
const router = useRouter();
interface Props {
Expand Down Expand Up @@ -55,7 +54,7 @@ const editorLink = computed(
() => `/workflows/edit?id=${props.workflowId}${props.version ? `&version=${props.version}` : ""}`
);
const historyStatusKey = computed(() => `${currentHistoryId.value}_${lastUpdateTime.value}`);
const isOwner = computed(() => currentUser.value?.username === workflowModel.value.runData.owner);
const isOwner = computed(() => userStore.matchesCurrentUsername(workflowModel.value.runData.owner));
const lastUpdateTime = computed(() => historyItemsStore.lastUpdateTime);
const canRunOnHistory = computed(() => {
if (!currentHistoryId.value) {
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Workflow/workflows.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ export async function copyWorkflow(id: string, currentOwner: string, version?: s
const { data: workflowData } = await axios.get(withPrefix(path));

workflowData.name = `Copy of ${workflowData.name}`;
const currentUsername = useUserStore().currentUser?.username;
const userStore = useUserStore();

if (currentUsername !== currentOwner) {
if (!userStore.matchesCurrentUsername(currentOwner)) {
workflowData.name += ` shared by user ${currentOwner}`;
}

Expand Down
15 changes: 10 additions & 5 deletions client/src/stores/userStore.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineStore } from "pinia";
import { computed, ref } from "vue";

import { type AnonymousUser, type User } from "@/api";
import { type AnyUser, isAdminUser, isAnonymousUser, isRegisteredUser, type RegisteredUser } from "@/api";
import { useUserLocalStorage } from "@/composables/userLocalStorage";
import { useHistoryStore } from "@/stores/historyStore";
import {
Expand All @@ -19,7 +19,7 @@ interface Preferences {
type ListViewMode = "grid" | "list";

export const useUserStore = defineStore("userStore", () => {
const currentUser = ref<User | AnonymousUser | null>(null);
const currentUser = ref<AnyUser>(null);
const currentPreferences = ref<Preferences | null>(null);

// explicitly pass current User, because userStore might not exist yet
Expand All @@ -35,11 +35,11 @@ export const useUserStore = defineStore("userStore", () => {
}

const isAdmin = computed(() => {
return currentUser.value?.is_admin ?? false;
return isAdminUser(currentUser.value);
});

const isAnonymous = computed(() => {
return !("email" in (currentUser.value || []));
return isAnonymousUser(currentUser.value);
});

const currentTheme = computed(() => {
Expand All @@ -54,7 +54,11 @@ export const useUserStore = defineStore("userStore", () => {
}
});

function setCurrentUser(user: User) {
function matchesCurrentUsername(username?: string) {
return isRegisteredUser(currentUser.value) && currentUser.value.username === username;
}

function setCurrentUser(user: RegisteredUser) {
currentUser.value = user;
}

Expand Down Expand Up @@ -132,6 +136,7 @@ export const useUserStore = defineStore("userStore", () => {
toggledSideBar,
preferredListViewMode,
loadUser,
matchesCurrentUsername,
setCurrentUser,
setCurrentTheme,
setPreferredListViewMode,
Expand Down

0 comments on commit 01227d4

Please sign in to comment.