Skip to content

Commit d555390

Browse files
authored
Merge pull request #21067 from mvdbeek/merge_25_1_into_dev
Merge 25 1 into dev
2 parents 17b6b12 + 6633b11 commit d555390

File tree

79 files changed

+8452
-139
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+8452
-139
lines changed

client/src/api/schema/schema.ts

Lines changed: 645 additions & 0 deletions
Large diffs are not rendered by default.

client/src/api/tools.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ export type NestedElementItem = NestedElementItems[number];
1111
export type FetchTargets = FetchDataPayload["targets"];
1212
export type AnyFetchTarget = FetchTargets[number];
1313

14+
export interface ToolIdentifier {
15+
toolId: string;
16+
toolVersion: string;
17+
}
18+
19+
export function getToolKey(toolId: string, toolVersion: string): string {
20+
return `${toolId}@${toolVersion}`;
21+
}
22+
1423
export function urlDataElement(identifier: string, uri: string): UrlDataElement {
1524
const element: UrlDataElement = {
1625
src: "url",

client/src/api/userCredentials.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/**
2+
* User Credentials API
3+
*
4+
* This module provides type definitions, interfaces, and utility functions
5+
* for managing user service credentials. It includes types for credential
6+
* groups, service definitions, and helper functions for credential management.
7+
*
8+
* @module userCredentials
9+
*/
10+
11+
import type { components } from "@/api";
12+
import { useToolsServiceCredentialsDefinitionsStore } from "@/stores/toolsServiceCredentialsDefinitionsStore";
13+
14+
/**
15+
* Just an alias for a string that represents a unique key for a service credentials identifier.
16+
* The key is a combination of the service name and version, formatted as "name-version".
17+
*/
18+
type ServiceCredentialsIdentifierKey = string;
19+
20+
/** Type for credential field types. */
21+
export type CredentialType = "variable" | "secret";
22+
/** Service credential group response from API. */
23+
export type ServiceCredentialGroupResponse = components["schemas"]["ServiceCredentialGroupResponse"];
24+
/** Payload for creating source credentials. */
25+
export type CreateSourceCredentialsPayload = components["schemas"]["CreateSourceCredentialsPayload"];
26+
/** User service credentials response from API. */
27+
export type UserServiceCredentialsResponse = components["schemas"]["UserServiceCredentialsResponse"];
28+
/** Service credential payload for API requests. */
29+
export type ServiceCredentialPayload = components["schemas"]["ServiceCredentialPayload"];
30+
/** Service credential group payload for API requests. */
31+
export type ServiceCredentialGroupPayload = components["schemas"]["ServiceCredentialGroupPayload"];
32+
/** User service credentials with definition response from API. */
33+
export type UserServiceCredentialsWithDefinitionResponse =
34+
components["schemas"]["UserServiceCredentialsWithDefinitionResponse"];
35+
/** Payload for selecting current credential group. */
36+
export type SelectCurrentGroupPayload = components["schemas"]["SelectCurrentGroupPayload"];
37+
/** Service parameter definition from API. */
38+
export type ServiceParameterDefinition = components["schemas"]["ServiceParameterDefinition"];
39+
/** Service credentials definition from API. */
40+
export type ServiceCredentialsDefinition = components["schemas"]["ServiceCredentialsDefinition"];
41+
42+
/**
43+
* Service credentials identifier interface.
44+
* @interface ServiceCredentialsIdentifier
45+
*/
46+
export interface ServiceCredentialsIdentifier {
47+
/** Service name. */
48+
name: string;
49+
/** Service version. */
50+
version: string;
51+
}
52+
53+
/**
54+
* Represents the definition of credentials for a particular source.
55+
* A source can be an entity using a service that uses credentials, for example, a tool.
56+
* A source may accept multiple services, each with its own credentials.
57+
*
58+
* The `services` map is indexed by the service name and version using the `getKeyFromCredentialsIdentifier` function.
59+
* @interface SourceCredentialsDefinition
60+
*/
61+
export interface SourceCredentialsDefinition {
62+
/** Type of the source (e.g., "tool"). */
63+
sourceType: string;
64+
/** Unique identifier for the source. */
65+
sourceId: string;
66+
/** Map of services indexed by service identifier key. */
67+
services: Map<ServiceCredentialsIdentifierKey, ServiceCredentialsDefinition>;
68+
}
69+
70+
/**
71+
* Service credentials context interface.
72+
* @interface ServiceCredentialsContext
73+
* @todo Replace with proper API schema model when available.
74+
*/
75+
export interface ServiceCredentialsContext {
76+
/** User credentials ID or null if not set. */
77+
user_credentials_id: string | null;
78+
/** Service name. */
79+
name: string;
80+
/** Service version. */
81+
version: string;
82+
/** Selected credential group information. */
83+
selected_group: {
84+
/** Group ID or null if not selected. */
85+
id: string | null;
86+
/** Group name. */
87+
name: string;
88+
};
89+
}
90+
91+
/**
92+
* Generates a unique key from service credentials identifier
93+
* @param {ServiceCredentialsIdentifier} credentialsIdentifier - Service credentials identifier
94+
* @returns {ServiceCredentialsIdentifierKey} Unique key in format "name-version"
95+
*/
96+
export function getKeyFromCredentialsIdentifier(
97+
credentialsIdentifier: ServiceCredentialsIdentifier,
98+
): ServiceCredentialsIdentifierKey {
99+
return `${credentialsIdentifier.name}-${credentialsIdentifier.version}`;
100+
}
101+
102+
/**
103+
* Transforms tool information into source credentials definition
104+
* @param {string} toolId - The id of the tool
105+
* @param {string} toolVersion - The version of the tool
106+
* @returns {SourceCredentialsDefinition} Source credentials definition for the tool
107+
*/
108+
export function transformToSourceCredentials(toolId: string, toolVersion: string): SourceCredentialsDefinition {
109+
const { getToolServiceCredentialsDefinitionsFor } = useToolsServiceCredentialsDefinitionsStore();
110+
111+
const toolCredentialsDefinitions = getToolServiceCredentialsDefinitionsFor(toolId, toolVersion);
112+
113+
const services = new Map(
114+
toolCredentialsDefinitions.map((service) => [getKeyFromCredentialsIdentifier(service), service]),
115+
);
116+
117+
return {
118+
sourceType: "tool",
119+
sourceId: toolId,
120+
services,
121+
};
122+
}

client/src/components/BaseComponents/GButton.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,13 @@ const buttonElementRef = useResolveElement(buttonRef);
317317
}
318318
}
319319
</style>
320+
321+
<style lang="scss">
322+
// Fix for GButton inside Bootstrap input-group-append
323+
// Prevents horizontal scrolling issues caused by flex layout conflicts
324+
// This must be unscoped to target Bootstrap's input-group-append
325+
.input-group-append .g-button {
326+
flex-shrink: 0;
327+
overflow: hidden;
328+
}
329+
</style>

client/src/components/Common/GCard.vue

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ interface Props {
176176
* @default "Last updated"
177177
*/
178178
updateTimeTitle?: string;
179+
180+
/** Whether this card is highlighted (for example, as a range selection anchor)
181+
* @default false
182+
*/
183+
highlighted?: boolean;
179184
}
180185
181186
const props = withDefaults(defineProps<Props>(), {
@@ -207,6 +212,7 @@ const props = withDefaults(defineProps<Props>(), {
207212
updateTime: "",
208213
updateTimeIcon: () => faEdit,
209214
updateTimeTitle: "Last updated",
215+
highlighted: false,
210216
});
211217
212218
/**
@@ -266,7 +272,7 @@ async function toggleBookmark() {
266272
bookmarkLoading.value = false;
267273
}
268274
269-
const { renderMarkdown } = useMarkdown({ openLinksInNewPage: true });
275+
const { renderMarkdown } = useMarkdown({ noMargin: true, openLinksInNewPage: true });
270276
271277
/**
272278
* Helper functions for generating consistent element IDs
@@ -283,8 +289,10 @@ const allowedTitleLines = computed(() => props.titleNLines);
283289
284290
function onKeyDown(event: KeyboardEvent) {
285291
if ((props.clickable && event.key === "Enter") || event.key === " ") {
292+
event.stopPropagation();
286293
emit("click", event);
287294
} else if (props.clickable) {
295+
event.stopPropagation();
288296
emit("keydown", event);
289297
}
290298
}
@@ -310,7 +318,7 @@ function onKeyDown(event: KeyboardEvent) {
310318
<div
311319
:id="`g-card-content-${props.id}`"
312320
class="g-card-content d-flex flex-column justify-content-between h-100 p-2"
313-
:class="contentClass">
321+
:class="[{ 'g-card-highlighted': props.highlighted }, contentClass]">
314322
<slot>
315323
<div class="d-flex flex-column flex-gapy-1">
316324
<div
@@ -335,12 +343,13 @@ function onKeyDown(event: KeyboardEvent) {
335343
:id="getElementId(props.id, 'title')"
336344
bold
337345
inline
338-
class="align-items-baseline"
346+
class="d-block"
339347
:size="props.titleSize">
340348
<FontAwesomeIcon
341349
v-if="props.titleIcon?.icon"
342-
:icon="props.titleIcon.icon"
350+
class="mr-1"
343351
:class="props.titleIcon.class"
352+
:icon="props.titleIcon.icon"
344353
:title="props.titleIcon.title"
345354
:size="props.titleIcon.size"
346355
fixed-width />
@@ -516,7 +525,7 @@ function onKeyDown(event: KeyboardEvent) {
516525
<BButton
517526
v-if="(indicator.visible ?? true) && !indicator.disabled"
518527
:id="getIndicatorId(props.id, indicator.id)"
519-
:key="indicator.id"
528+
:key="`${indicator.id}-button`"
520529
v-b-tooltip.hover.noninteractive
521530
class="inline-icon-button"
522531
:title="localize(indicator.title)"
@@ -536,7 +545,7 @@ function onKeyDown(event: KeyboardEvent) {
536545
<FontAwesomeIcon
537546
v-else-if="(indicator.visible ?? true) && indicator.disabled"
538547
:id="getIndicatorId(props.id, indicator.id)"
539-
:key="indicator.id"
548+
:key="`${indicator.id}-icon`"
540549
v-b-tooltip.hover.noninteractive
541550
:title="localize(indicator.title)"
542551
:icon="indicator.icon"
@@ -549,16 +558,15 @@ function onKeyDown(event: KeyboardEvent) {
549558
</div>
550559
</div>
551560

552-
<div :id="getElementId(props.id, 'description')">
561+
<div :id="getElementId(props.id, 'description')" class="g-card-description">
553562
<slot name="description">
554-
<TextSummary
555-
v-if="props.description && !props.fullDescription"
556-
:id="getElementId(props.id, 'text-summary')"
557-
:description="props.description" />
558-
<div
559-
v-else-if="props.description && props.fullDescription"
560-
class="mb-2"
561-
v-html="renderMarkdown(props.description)" />
563+
<template v-if="props.description">
564+
<TextSummary
565+
v-if="!props.fullDescription"
566+
:id="getElementId(props.id, 'text-summary')"
567+
:description="props.description" />
568+
<div v-else v-html="renderMarkdown(props.description)" />
569+
</template>
562570
</slot>
563571
</div>
564572
</div>
@@ -598,9 +606,13 @@ function onKeyDown(event: KeyboardEvent) {
598606
</div>
599607
</slot>
600608

601-
<div class="align-items-center d-flex flex-gapx-1 justify-content-end ml-auto mt-1">
609+
<div class="align-items-center d-flex flex-gapx-1 justify-content-end ml-auto">
602610
<slot name="secondary-actions">
603-
<BButtonGroup :id="getElementId(props.id, 'secondary-actions')" size="sm">
611+
<BButtonGroup
612+
v-if="props.secondaryActions?.length"
613+
:id="getElementId(props.id, 'secondary-actions')"
614+
size="sm"
615+
class="mt-1">
604616
<template v-for="sa in props.secondaryActions">
605617
<BButton
606618
v-if="sa.visible ?? true"
@@ -630,30 +642,33 @@ function onKeyDown(event: KeyboardEvent) {
630642

631643
<div :id="getElementId(props.id, 'primary-actions')" class="d-flex flex-gapx-1">
632644
<slot name="primary-actions">
633-
<template v-for="pa in props.primaryActions">
634-
<BButton
635-
v-if="pa.visible ?? true"
636-
:id="getActionId(props.id, pa.id)"
637-
:key="pa.id"
638-
v-b-tooltip.hover.noninteractive
639-
:disabled="pa.disabled"
640-
:title="localize(pa.title)"
641-
:variant="pa.variant || 'primary'"
642-
:size="pa.size || 'sm'"
643-
:to="pa.to"
644-
:href="pa.href"
645-
:class="{
646-
'inline-icon-button': pa.inline,
647-
[String(pa.class)]: pa.class,
648-
}"
649-
@click.stop="pa.handler">
650-
<FontAwesomeIcon
651-
v-if="pa.icon"
652-
:icon="pa.icon"
653-
:size="pa.size || undefined"
654-
fixed-width />
655-
{{ localize(pa.label) }}
656-
</BButton>
645+
<template v-if="props.primaryActions?.length">
646+
<template v-for="pa in props.primaryActions">
647+
<BButton
648+
v-if="pa.visible ?? true"
649+
:id="getActionId(props.id, pa.id)"
650+
:key="pa.id"
651+
v-b-tooltip.hover.noninteractive
652+
class="mt-1"
653+
:disabled="pa.disabled"
654+
:title="localize(pa.title)"
655+
:variant="pa.variant || 'primary'"
656+
:size="pa.size || 'sm'"
657+
:to="pa.to"
658+
:href="pa.href"
659+
:class="{
660+
'inline-icon-button': pa.inline,
661+
[String(pa.class)]: pa.class,
662+
}"
663+
@click.stop="pa.handler">
664+
<FontAwesomeIcon
665+
v-if="pa.icon"
666+
:icon="pa.icon"
667+
:size="pa.size || undefined"
668+
fixed-width />
669+
{{ localize(pa.label) }}
670+
</BButton>
671+
</template>
657672
</template>
658673
</slot>
659674
</div>
@@ -701,6 +716,10 @@ function onKeyDown(event: KeyboardEvent) {
701716
border-left: 0.25rem solid $brand-primary;
702717
}
703718
719+
&.g-card-highlighted .g-card-content {
720+
box-shadow: 0 0 0 0.2rem transparentize($brand-primary, 0.75);
721+
}
722+
704723
&.g-card-clickable {
705724
cursor: pointer;
706725

client/src/components/Common/TextSummary.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface Props {
1313
oneLineSummary?: boolean;
1414
/** If `true`, doesn't show expand/collapse buttons */
1515
noExpand?: boolean;
16-
/** The component to use for the summary, default = `<p>` */
16+
/** The component to use for the summary, default = `<span>` */
1717
component?: string;
1818
/** If `true`, shows the full text */
1919
showExpandText?: boolean;

client/src/components/Form/FormCard.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<span class="portlet-title">
1818
<span v-if="icon" :class="['portlet-title-icon fa mr-1', icon]" />
1919
<b class="portlet-title-text" itemprop="name">{{ title }}</b>
20+
<slot name="title" />
2021
<span class="portlet-title-description" itemprop="description">{{ description }}</span>
2122
</span>
2223
</div>
@@ -25,6 +26,7 @@
2526
</div>
2627
</div>
2728
</template>
29+
2830
<script>
2931
import { library } from "@fortawesome/fontawesome-svg-core";
3032
import { faChevronDown, faChevronUp } from "@fortawesome/free-solid-svg-icons";

0 commit comments

Comments
 (0)