Skip to content

Commit

Permalink
#1236 Filtering environments
Browse files Browse the repository at this point in the history
  • Loading branch information
dcoraboeuf committed Dec 13, 2024
1 parent ae2bb76 commit a694b4b
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) }

Expand Down Expand Up @@ -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 {
Expand All @@ -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) {}
Expand Down
183 changes: 153 additions & 30 deletions ontrack-web-core/components/extension/environments/EnvironmentList.js
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Space direction="vertical" className="ot-line">
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 &&
<Empty
description="No environment has been created yet."
/>
filterProjects: filter.projects,
filterTags: filter.tags,
}
{
environments.map(environment => (
<Row data-testid={`environment-row-${environment.id}`} gutter={[16, 16]} key={environment.id}
wrap={false}>
<Col span={4}>
<EnvironmentCard environment={environment}/>
</Col>
{
environment.slots.map(slot => (
<Col key={slot.id} span={6}>
<SlotCard
slot={slot}
showLastDeployed={true}
showLastDeployedInTitle={true}
/>
).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 (
<>
<LoadingContainer loading={loading}>
<Space direction="vertical" className="ot-line">
{
environments.length === 0 &&
<Empty
description="No environment has been created yet or the filter is too restrictive."
/>
}
<>
<Card
>
<Form
form={form}
layout="inline"
onSubmit={onFilter}
onValuesChange={onFilter}
>
<Form.Item
label="Tags"
name="tags"
>
<Select
mode="tags"
style={{width: "20em"}}
/>
</Form.Item>
<Form.Item
label="Project"
name="project"
>
<SelectProject
idAsValue={false}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
<Space>
<FaSearch/>
Filter
</Space>
</Button>
</Form.Item>
<Form.Item>
<Button type="link" onClick={onClearFilter}>
<Space>
Reset
</Space>
</Button>
</Form.Item>
</Form>
</Card>
{
environments.map(environment => (
<Row data-testid={`environment-row-${environment.id}`} gutter={[16, 16]}
key={environment.id}
wrap={false}>
<Col span={4}>
<EnvironmentCard environment={environment}/>
</Col>
))
}
</Row>
))
}
</Space>
{
environment.slots.map(slot => (
<Col key={slot.id} span={6}>
<SlotCard
slot={slot}
showLastDeployed={true}
showLastDeployedInTitle={true}
/>
</Col>
))
}
</Row>
))
}
</>
</Space>
</LoadingContainer>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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([
<EnvironmentCreateCommand key="create-environment"/>,
<SlotCreateCommand key="create-slot"/>,
<CloseCommand key="close" href={homeUri()}/>,
])
}).finally(() => {
setLoading(false)
})
}
}, [client, environmentCreated, slotCreated])

return (
<>
<Head>
Expand All @@ -67,11 +17,13 @@ export default function EnvironmentsView() {
<MainPage
title="Environments"
breadcrumbs={homeBreadcrumbs()}
commands={commands}
commands={[
<EnvironmentCreateCommand key="create-environment"/>,
<SlotCreateCommand key="create-slot"/>,
<CloseCommand key="close" href={homeUri()}/>,
]}
>
<LoadingContainer loading={loading}>
<EnvironmentList environments={environments}/>
</LoadingContainer>
<EnvironmentList/>
</MainPage>
</>
)
Expand Down

0 comments on commit a694b4b

Please sign in to comment.