diff --git a/ontrack-extension-environments/src/main/java/net/nemerosa/ontrack/extension/environments/storage/EnvironmentRepository.kt b/ontrack-extension-environments/src/main/java/net/nemerosa/ontrack/extension/environments/storage/EnvironmentRepository.kt
index 41ee467c3f..26400a900d 100644
--- a/ontrack-extension-environments/src/main/java/net/nemerosa/ontrack/extension/environments/storage/EnvironmentRepository.kt
+++ b/ontrack-extension-environments/src/main/java/net/nemerosa/ontrack/extension/environments/storage/EnvironmentRepository.kt
@@ -95,7 +95,7 @@ class EnvironmentRepository(
}
var sql = """
- SELECT E.ID, E.NAME, E."ORDER", E.DESCRIPTION, E.TAGS, (E.IMAGE IS NOT NULL) AS HAS_IMAGE
+ SELECT DISTINCT E.ID, E.NAME, E."ORDER", E.DESCRIPTION, E.TAGS, (E.IMAGE IS NOT NULL) AS HAS_IMAGE
FROM ENVIRONMENTS E
""".trimIndent()
diff --git a/ontrack-extension-environments/src/test/java/net/nemerosa/ontrack/extension/environments/EnvironmentServiceIT.kt b/ontrack-extension-environments/src/test/java/net/nemerosa/ontrack/extension/environments/EnvironmentServiceIT.kt
index 2039f08244..96f65d5ae6 100644
--- a/ontrack-extension-environments/src/test/java/net/nemerosa/ontrack/extension/environments/EnvironmentServiceIT.kt
+++ b/ontrack-extension-environments/src/test/java/net/nemerosa/ontrack/extension/environments/EnvironmentServiceIT.kt
@@ -94,9 +94,11 @@ class EnvironmentServiceIT : AbstractDSLTestSupport() {
val tag1 = uid("t1-")
val tag2 = uid("t2-")
val env1 = EnvironmentTestFixtures.testEnvironment(
+ order = 10,
tags = listOf(tag1, tag2)
).apply { environmentService.save(this) }
val env2 = EnvironmentTestFixtures.testEnvironment(
+ order = 20,
tags = listOf(tag1)
).apply { environmentService.save(this) }
@@ -129,6 +131,39 @@ class EnvironmentServiceIT : AbstractDSLTestSupport() {
}
}
+ @Test
+ fun `Selecting environments based on projects with qualifiers`() {
+ asAdmin {
+ val project1 = project(NameDescription(uid("p1-"), ""))
+ val project2 = project(NameDescription(uid("p2-"), ""))
+
+ val env1 = environmentTestSupport.withEnvironment(order = 10) {}
+ val env2 = environmentTestSupport.withEnvironment(order = 20) {}
+ val env3 = environmentTestSupport.withEnvironment(order = 30) {}
+
+ slotTestSupport.withSlot(environment = env1, project = project1) {}
+ slotTestSupport.withSlot(environment = env1, project = project2) {}
+ slotTestSupport.withSlot(environment = env1, project = project2, qualifier = "demo") {}
+
+ slotTestSupport.withSlot(environment = env2, project = project1) {}
+ slotTestSupport.withSlot(environment = env2, project = project2) {}
+ slotTestSupport.withSlot(environment = env2, project = project2, qualifier = "demo") {}
+
+ slotTestSupport.withSlot(environment = env3, project = project1) {}
+ slotTestSupport.withSlot(environment = env3, project = project2) {}
+ slotTestSupport.withSlot(environment = env3, project = project2, qualifier = "demo") {}
+
+ assertEquals(
+ listOf(env1, env2, env3),
+ environmentService.findAll(
+ filter = EnvironmentFilter(
+ projects = listOf(project2.name)
+ )
+ )
+ )
+ }
+ }
+
@Test
fun `Selecting environments based on tags and projects`() {
asAdmin {
@@ -137,9 +172,9 @@ class EnvironmentServiceIT : AbstractDSLTestSupport() {
val tag1 = uid("t1-")
val tag2 = uid("t2-")
- val env1 = environmentTestSupport.withEnvironment(tags = listOf(tag1)) {}
- val env2 = environmentTestSupport.withEnvironment(tags = listOf(tag1, tag2)) {}
- val env3 = environmentTestSupport.withEnvironment(tags = listOf(tag2)) {}
+ val env1 = environmentTestSupport.withEnvironment(order = 10, tags = listOf(tag1)) {}
+ val env2 = environmentTestSupport.withEnvironment(order = 20, tags = listOf(tag1, tag2)) {}
+ val env3 = environmentTestSupport.withEnvironment(order = 30, tags = listOf(tag2)) {}
slotTestSupport.withSlot(environment = env1, project = project1) {}
slotTestSupport.withSlot(environment = env2, project = project1) {}
diff --git a/ontrack-web-core/components/extension/environments/EnvironmentList.js b/ontrack-web-core/components/extension/environments/EnvironmentList.js
index 5c7efedac9..0a89b1b5dd 100644
--- a/ontrack-web-core/components/extension/environments/EnvironmentList.js
+++ b/ontrack-web-core/components/extension/environments/EnvironmentList.js
@@ -1,39 +1,162 @@
-import {Col, Empty, Row, Space} from "antd";
+import {Button, Card, Col, Empty, Form, Row, Select, Space} from "antd";
import EnvironmentCard from "@components/extension/environments/EnvironmentCard";
import SlotCard from "@components/extension/environments/SlotCard";
+import {useEffect, useState} from "react";
+import {FaSearch} from "react-icons/fa";
+import LoadingContainer from "@components/common/LoadingContainer";
+import {useGraphQLClient} from "@components/providers/ConnectionContextProvider";
+import {useEventForRefresh} from "@components/common/EventsContext";
+import {gql} from "graphql-request";
+import {gqlSlotData} from "@components/extension/environments/EnvironmentGraphQL";
+import SelectProject from "@components/projects/SelectProject";
-export default function EnvironmentList({environments}) {
- return (
- <>
-
+export default function EnvironmentList() {
+
+ const client = useGraphQLClient()
+
+ const [loading, setLoading] = useState(false)
+ const [environments, setEnvironments] = useState([])
+
+ const environmentCreated = useEventForRefresh("environment.created")
+ const slotCreated = useEventForRefresh("slot.created")
+
+ const [filter, setFilter] = useState({
+ projects: null,
+ tags: null,
+ })
+
+ useEffect(() => {
+ if (client) {
+ setLoading(true)
+ client.request(
+ gql`
+ query EnvironmentList(
+ $filterProjects: [String!],
+ $filterTags: [String!],
+ ) {
+ environments(filter: {
+ projects: $filterProjects,
+ tags: $filterTags,
+ }) {
+ id
+ name
+ description
+ order
+ tags
+ image
+ slots(projects: $filterProjects) {
+ ...SlotData
+ }
+ }
+ }
+
+ ${gqlSlotData}
+ `,
{
- environments.length === 0 &&
-
+ filterProjects: filter.projects,
+ filterTags: filter.tags,
}
- {
- environments.map(environment => (
-
-
-
-
- {
- environment.slots.map(slot => (
-
-
+ ).then(data => {
+ setEnvironments(data.environments)
+ }).finally(() => {
+ setLoading(false)
+ })
+ }
+ }, [client, environmentCreated, slotCreated, filter])
+
+ const [form] = Form.useForm()
+
+ const onFilter = () => {
+ const values = form.getFieldsValue()
+ setFilter({
+ projects: values.project ? [values.project] : null,
+ tags: values.tags ? values.tags : null,
+ })
+ }
+
+ const onClearFilter = () => {
+ form.resetFields()
+ onFilter()
+ }
+
+ return (
+ <>
+
+
+ {
+ environments.length === 0 &&
+
+ }
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ environments.map(environment => (
+
+
+
- ))
- }
-
- ))
- }
-
+ {
+ environment.slots.map(slot => (
+
+
+
+ ))
+ }
+
+ ))
+ }
+ >
+
+
>
)
}
\ No newline at end of file
diff --git a/ontrack-web-core/components/extension/environments/EnvironmentsView.js b/ontrack-web-core/components/extension/environments/EnvironmentsView.js
index 46d67b3c93..1926381c11 100644
--- a/ontrack-web-core/components/extension/environments/EnvironmentsView.js
+++ b/ontrack-web-core/components/extension/environments/EnvironmentsView.js
@@ -4,61 +4,11 @@ import {homeBreadcrumbs} from "@components/common/Breadcrumbs";
import {CloseCommand} from "@components/common/Commands";
import {homeUri} from "@components/common/Links";
import MainPage from "@components/layouts/MainPage";
-import {useGraphQLClient} from "@components/providers/ConnectionContextProvider";
-import {useEffect, useState} from "react";
-import {gql} from "graphql-request";
import EnvironmentCreateCommand from "@components/extension/environments/EnvironmentCreateCommand";
-import LoadingContainer from "@components/common/LoadingContainer";
-import {useEventForRefresh} from "@components/common/EventsContext";
import EnvironmentList from "@components/extension/environments/EnvironmentList";
import SlotCreateCommand from "@components/extension/environments/SlotCreateCommand";
-import {gqlSlotData} from "@components/extension/environments/EnvironmentGraphQL";
export default function EnvironmentsView() {
-
- const client = useGraphQLClient()
-
- const [loading, setLoading] = useState(false)
- const [environments, setEnvironments] = useState([])
- const [commands, setCommands] = useState([])
-
- const environmentCreated = useEventForRefresh("environment.created")
- const slotCreated = useEventForRefresh("slot.created")
-
- useEffect(() => {
- if (client) {
- setLoading(true)
- client.request(
- gql`
- query EnvironmentList {
- environments {
- id
- name
- description
- order
- tags
- image
- slots {
- ...SlotData
- }
- }
- }
-
- ${gqlSlotData}
- `
- ).then(data => {
- setEnvironments(data.environments)
- setCommands([
- ,
- ,
- ,
- ])
- }).finally(() => {
- setLoading(false)
- })
- }
- }, [client, environmentCreated, slotCreated])
-
return (
<>
@@ -67,11 +17,13 @@ export default function EnvironmentsView() {
,
+ ,
+ ,
+ ]}
>
-
-
-
+
>
)