Skip to content

Commit

Permalink
Refactor ToolCredentials and related UI components
Browse files Browse the repository at this point in the history
  • Loading branch information
davelopez committed Dec 20, 2024
1 parent 0d4e8b6 commit 971fa21
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 90 deletions.
70 changes: 48 additions & 22 deletions client/src/components/Tool/ToolCredentials.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
import { BAlert, BButton, BModal } from "bootstrap-vue";
import { computed, ref } from "vue";
import type { ToolCredentialsDefinition, UserCredentials } from "@/api/users";
import { isRegisteredUser } from "@/api";
import {
type CreateSourceCredentialsPayload,
type ServiceCredentialsDefinition,
type SourceCredentialsDefinition,
transformToSourceCredentials,
type UserCredentials,
} from "@/api/users";
import { useUserCredentialsStore } from "@/stores/userCredentials";
import { useUserStore } from "@/stores/userStore";
Expand All @@ -12,38 +19,55 @@ import ManageToolCredentials from "@/components/User/Credentials/ManageToolCrede
interface Props {
toolId: string;
toolVersion: string;
toolCredentialsDefinition: ToolCredentialsDefinition[];
toolCredentialsDefinition: ServiceCredentialsDefinition[];
}
const props = defineProps<Props>();
const userStore = useUserStore();
const userCredentialsStore = useUserCredentialsStore();
const userCredentialsStore = useUserCredentialsStore(
isRegisteredUser(userStore.currentUser) ? userStore.currentUser.id : "anonymous"
);
const isBusy = ref(true);
const busyMessage = ref<string>("");
const userCredentials = ref<UserCredentials[] | undefined>(undefined);
const credentialsDefinition = computed<SourceCredentialsDefinition>(() => {
return transformToSourceCredentials(props.toolId, props.toolCredentialsDefinition);
});
const hasUserProvidedRequiredCredentials = computed<boolean>(() => {
if (!userCredentials.value) {
if (!userCredentials.value || userCredentials.value.length === 0) {
return false;
}
return userCredentials.value.every((credentials) => credentials.optional || areSetByUser(credentials));
return userCredentials.value.every((credentials) => areOptional(credentials) || areSetByUser(credentials));
});
const hasUserProvidedAllCredentials = computed<boolean>(() => {
if (!userCredentials.value) {
if (!userCredentials.value || userCredentials.value.length === 0) {
return false;
}
return userCredentials.value.every(areSetByUser);
});
const hasSomeOptionalCredentials = computed<boolean>(() => {
return props.toolCredentialsDefinition.some((credentials) => credentials.optional);
for (const credentials of credentialsDefinition.value.services.values()) {
if (credentials.optional) {
return true;
}
}
return false;
});
const hasSomeRequiredCredentials = computed<boolean>(() => {
return props.toolCredentialsDefinition.some((credentials) => !credentials.optional);
for (const credentials of credentialsDefinition.value.services.values()) {
if (!credentials.optional) {
return true;
}
}
return false;
});
const provideCredentialsButtonTitle = computed(() => {
Expand Down Expand Up @@ -75,10 +99,7 @@ async function checkUserCredentials(providedCredentials?: UserCredentials[]) {
if (!providedCredentials) {
providedCredentials =
userCredentialsStore.getAllUserCredentialsForTool(props.toolId) ??
(await userCredentialsStore.fetchAllUserCredentialsForTool(
props.toolId,
props.toolCredentialsDefinition
));
(await userCredentialsStore.fetchAllUserCredentialsForTool(props.toolId));
}
userCredentials.value = providedCredentials;
Expand All @@ -91,25 +112,29 @@ async function checkUserCredentials(providedCredentials?: UserCredentials[]) {
}
function areSetByUser(credentials: UserCredentials): boolean {
return (
credentials.variables.every((variable) => variable.value) &&
credentials.secrets.every((secret) => secret.alreadySet)
);
return Object.values(credentials.groups).every((set) => {
return set.variables.every((variable) => variable.value) && set.secrets.every((secret) => secret.already_set);
});
}
function areOptional(credentials: UserCredentials): boolean {
const matchingDefinition = credentialsDefinition.value.services.get(credentials.reference);
if (!matchingDefinition) {
return false;
}
return matchingDefinition.optional;
}
function provideCredentials() {
showModal.value = true;
}
async function onSavedCredentials(providedCredentials: UserCredentials[]) {
async function onSavedCredentials(providedCredentials: CreateSourceCredentialsPayload) {
showModal.value = false;
busyMessage.value = "Saving your credentials...";
try {
isBusy.value = true;
userCredentials.value = await userCredentialsStore.saveUserCredentialsForTool(
props.toolId,
providedCredentials
);
userCredentials.value = await userCredentialsStore.saveUserCredentialsForTool(providedCredentials);
} catch (error) {
// TODO: Implement error handling.
console.error("Error saving user credentials", error);
Expand Down Expand Up @@ -170,7 +195,8 @@ checkUserCredentials();
<ManageToolCredentials
:tool-id="props.toolId"
:tool-version="props.toolVersion"
:credentials="userCredentials"
:tool-credentials-definition="credentialsDefinition"
:user-tool-credentials="userCredentials"
@save-credentials="onSavedCredentials" />
</BModal>
</div>
Expand Down
55 changes: 0 additions & 55 deletions client/src/components/User/Credentials/CredentialsInput.vue

This file was deleted.

115 changes: 102 additions & 13 deletions client/src/components/User/Credentials/ManageToolCredentials.vue
Original file line number Diff line number Diff line change
@@ -1,44 +1,133 @@
<script setup lang="ts">
import { ref } from "vue";
import type { UserCredentials } from "@/api/users";
import type {
CreateSourceCredentialsPayload,
ServiceCredentialPayload,
ServiceCredentialsDefinition,
ServiceGroupPayload,
SourceCredentialsDefinition,
UserCredentials,
} from "@/api/users";
import CredentialsInput from "@/components/User/Credentials/CredentialsInput.vue";
import ServiceCredentials from "@/components/User/Credentials/ServiceCredentials.vue";
interface ManageToolCredentialsProps {
toolId: string;
toolVersion: string;
credentials?: UserCredentials[];
toolCredentialsDefinition: SourceCredentialsDefinition;
userToolCredentials?: UserCredentials[];
}
const props = defineProps<ManageToolCredentialsProps>();
const props = withDefaults(defineProps<ManageToolCredentialsProps>(), {
userToolCredentials: () => [],
});
const providedCredentials = ref<UserCredentials[]>(initializeCredentials());
const providedCredentials = ref<CreateSourceCredentialsPayload>(initializeCredentials());
const emit = defineEmits<{
(e: "save-credentials", credentials: UserCredentials[]): void;
(e: "save-credentials", credentials: CreateSourceCredentialsPayload): void;
}>();
function saveCredentials() {
emit("save-credentials", providedCredentials.value);
}
function initializeCredentials(): UserCredentials[] {
// If credentials are provided, clone them to avoid modifying the original data
return props.credentials ? JSON.parse(JSON.stringify(props.credentials)) : [];
function initializeCredentials(): CreateSourceCredentialsPayload {
const serviceCredentials = [];
for (const reference of props.toolCredentialsDefinition.services.keys()) {
const userProvidedCredentialForService = props.userToolCredentials.find((c) => c.reference === reference);
const currentGroup = userProvidedCredentialForService?.current_group_name ?? "default";
const definition = getServiceCredentialsDefinition(reference);
const groups = buildGroupsFromUserCredentials(definition, userProvidedCredentialForService);
const credential: ServiceCredentialPayload = {
reference,
current_group: currentGroup,
groups,
};
serviceCredentials.push(credential);
}
const providedCredentials: CreateSourceCredentialsPayload = {
source_type: "tool",
source_id: props.toolId,
credentials: serviceCredentials,
};
return providedCredentials;
}
function buildGroupsFromUserCredentials(
definition: ServiceCredentialsDefinition,
userCredentials?: UserCredentials
): ServiceGroupPayload[] {
const groups: ServiceGroupPayload[] = [];
if (userCredentials) {
const existingGroups = Object.values(userCredentials.groups);
for (const group of existingGroups) {
const newGroup: ServiceGroupPayload = {
name: group.name,
variables: group.variables,
secrets: group.secrets.map((secret) => ({
name: secret.name,
value: null,
})),
};
groups.push(newGroup);
}
} else {
const defaultGroup: ServiceGroupPayload = {
name: "default",
variables: definition.variables.map((variable) => ({
name: variable.name,
value: null,
})),
secrets: definition.secrets.map((secret) => ({
name: secret.name,
value: null,
})),
};
groups.push(defaultGroup);
}
return groups;
}
function onNewCredentialsSet(credential: ServiceCredentialPayload, newSet: ServiceGroupPayload) {
const credentialFound = providedCredentials.value.credentials.find((c) => c.reference === credential.reference);
if (credentialFound) {
credentialFound.groups.push(newSet);
}
}
function onCurrentSetChange(credential: ServiceCredentialPayload, newSet: ServiceGroupPayload) {
const credentialFound = providedCredentials.value.credentials.find((c) => c.reference === credential.reference);
if (credentialFound) {
credentialFound.current_group = newSet.name;
}
}
function getServiceCredentialsDefinition(reference: string): ServiceCredentialsDefinition {
const definition = props.toolCredentialsDefinition.services.get(reference);
if (!definition) {
throw new Error(`No definition found for credential reference ${reference} in tool ${props.toolId}`);
}
return definition;
}
</script>

<template>
<div>
<p>
Here you can manage your credentials for the tool <strong>{{ toolId }}</strong> version
<strong> {{ toolVersion }} </strong>.
<strong> {{ toolVersion }}</strong
>.
</p>
<CredentialsInput
v-for="credential in providedCredentials"
<ServiceCredentials
v-for="credential in providedCredentials.credentials"
:key="credential.reference"
:credential="credential" />
:credential-definition="getServiceCredentialsDefinition(credential.reference)"
:credential-payload="credential"
@new-credentials-set="onNewCredentialsSet"
@update-current-set="onCurrentSetChange" />
<button @click="saveCredentials">Save Credentials</button>
</div>
</template>
Expand Down
Loading

0 comments on commit 971fa21

Please sign in to comment.