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 && + + } + <> + +
+ +