Skip to content

Commit

Permalink
Merge branch 'refs/heads/feature/1319-promotion-subscriptions' into r…
Browse files Browse the repository at this point in the history
…elease/4.9
  • Loading branch information
dcoraboeuf committed Jul 10, 2024
2 parents 7e72483 + bf1dfc9 commit adb7778
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,13 @@ class EntitySubscriptionStore(
// Total count for THIS entity
val count = entityStore.getCountByFilter(entity, ENTITY_STORE, jsonFilter)
// Loading & converting the records
val items = entityStore.getByFilter<SubscriptionRecord>(entity, ENTITY_STORE, jsonFilter)
val items = entityStore.getByFilter<SubscriptionRecord>(
entity = entity,
store = ENTITY_STORE,
offset = offset,
size = size,
jsonFilter = jsonFilter
)
// OK
return count to items
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ internal class GQLRootQueryEventSubscriptionsIT : AbstractNotificationTestSuppor
EventFactory.NEW_PROMOTION_RUN
)
// Query
asUser().withProjectFunction(this, ProjectSubscriptionsRead::class.java).call {
asUser().withView(this).withProjectFunction(this, ProjectSubscriptionsRead::class.java).call {
run(
"""
query {
Expand All @@ -138,7 +138,7 @@ internal class GQLRootQueryEventSubscriptionsIT : AbstractNotificationTestSuppor
}
}
// Query
asUser().withProjectFunction(this, ProjectSubscriptionsRead::class.java)
asUser().withView(this).withProjectFunction(this, ProjectSubscriptionsRead::class.java)
.withProjectFunction(this, ProjectSubscriptionsWrite::class.java)
.call {
run(
Expand Down Expand Up @@ -277,6 +277,53 @@ internal class GQLRootQueryEventSubscriptionsIT : AbstractNotificationTestSuppor
}
}

@Test
fun `Getting the first subscriptions for an entity`() {
project {
// Creating 10 subscriptions
(1..10).forEach {
eventSubscriptionService.subscribe(
name = uid("p"),
channel = mockNotificationChannel,
channelConfig = MockNotificationChannelConfig("#channel-$it"),
projectEntity = this,
keywords = null,
origin = "test",
contentTemplate = "Subscription #$it",
EventFactory.NEW_PROMOTION_RUN
)
}
// Query
run(
"""
query {
eventSubscriptions(
size: 2,
filter: {
entity: {
type: PROJECT,
id: $id
}
}
) {
pageInfo {
totalSize
}
pageItems {
contentTemplate
}
}
}
"""
) { data ->
val eventSubscriptions = data.path("eventSubscriptions")
assertEquals(10, eventSubscriptions.path("pageInfo").path("totalSize").asInt())
val items = eventSubscriptions.path("pageItems")
assertEquals(2, items.size())
}
}
}

@Test
fun `Getting a global subscription with a content template`() {
asAdmin {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ interface EntityStore {
fun <T : Any> getByFilter(
entity: ProjectEntity,
store: String,
offset: Int = 0,
size: Int = 10,
filter: EntityStoreFilter,
type: KClass<T>
): List<T>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ inline fun <reified T : Any> EntityStore.findByName(entity: ProjectEntity, entit
inline fun <reified T : Any> EntityStore.getByFilter(
entity: ProjectEntity,
store: String,
offset: Int = 0,
size: Int = 10,
jsonFilter: EntityStoreFilter
): List<T> =
getByFilter(entity, store, jsonFilter, T::class)
getByFilter(entity, store, offset, size, jsonFilter, T::class)

inline fun <reified T : Any> EntityStore.forEachByFilter(
entity: ProjectEntity,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package net.nemerosa.ontrack.model.pagination

import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

class PaginatedListExtensionsTest {

@Test
fun `Spanning pagination with one seed`() {
val items = (1..3).map { "Item $it" }
val seed: (offset: Int, size: Int) -> Pair<Int, List<String>> = { offset, size: Int ->
3 to items.drop(offset).take(size)
}
val pl = spanningPaginatedList(
offset = 0,
size = 2,
seeds = listOf(seed)
)
assertEquals(3, pl.pageInfo.totalSize)
assertNotNull(pl.pageInfo.nextPage)
assertEquals(
listOf(
"Item 1",
"Item 2"
),
pl.pageItems
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,21 @@ class EntityStoreJdbcRepository(dataSource: DataSource) : AbstractJdbcRepository
override fun <T : Any> getByFilter(
entity: ProjectEntity,
store: String,
offset: Int,
size: Int,
filter: EntityStoreFilter,
type: KClass<T>
): List<T> {
val context = StringBuilder()
val criteria = StringBuilder()
val params = mutableMapOf<String, Any?>()
buildCriteria(entity, store, filter, context, criteria, params)
params["offset"] = offset
params["size"] = size
// Runs the query
@Suppress("SqlSourceToSinkFlow")
return namedParameterJdbcTemplate!!.query(
"SELECT DATA FROM ENTITY_STORE $context WHERE $criteria",
"SELECT DATA FROM ENTITY_STORE $context WHERE $criteria ORDER BY ID DESC OFFSET :offset LIMIT :size",
params
) { rs: ResultSet, _ ->
readJson(rs, "DATA").parseInto(type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class EntityStoreJdbcRepositoryIT : AbstractRepositoryTestSupport() {

assertEquals(
listOf(r1),
repository.getByFilter(branch, STORE, filter)
repository.getByFilter(branch, STORE, jsonFilter = filter)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Head from "next/head";
import {subBranchTitle} from "@components/common/Titles";
import {downToBranchBreadcrumbs} from "@components/common/Breadcrumbs";
import MainPage from "@components/layouts/MainPage";
import {List, Skeleton, Space} from "antd";
import {List, Skeleton, Space, Typography} from "antd";
import {gql} from "graphql-request";
import {CloseCommand} from "@components/common/Commands";
import {branchUri} from "@components/common/Links";
Expand All @@ -15,6 +15,7 @@ import {isAuthorized} from "@components/common/authorizations";
import PromotionLevelCreateCommand from "@components/promotionLevels/PromotionLevelCreateCommand";
import {EventsContext, useEventForRefresh} from "@components/common/EventsContext";
import SortableList, {SortableItem, SortableKnob} from "react-easy-sort";
import EntitySubscriptions from "@components/extension/notifications/EntitySubscriptions";

export default function BranchPromotionLevelsView({id}) {

Expand Down Expand Up @@ -148,6 +149,9 @@ export default function BranchPromotionLevelsView({id}) {
}
description={pl.description}
/>
<div style={{width: '50%'}}>
<EntitySubscriptions type="PROMOTION_LEVEL" id={pl.id}/>
</div>
</List.Item>
</SortableItem>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {useGraphQLClient} from "@components/providers/ConnectionContextProvider";
import {useEffect, useState} from "react";
import {gql} from "graphql-request";
import {List, Space, Tag, Typography} from "antd";
import SubscriptionLink from "@components/extension/notifications/SubscriptionLink";
import Link from "next/link";
import SubscriptionsLink from "@components/extension/notifications/SubscriptionsLink";

/**
* This component displays a list of the subscriptions attached to a given entity.
*
* Each subscription can be navigated to and a general link allows to go the list
* of all subscriptions for this entity.
*
* @param type Project entity type
* @param id Project entity ID
*/
export default function EntitySubscriptions({type, id}) {

const client = useGraphQLClient()
const [subscriptions, setSubscriptions] = useState([])
const [pageInfo, setPageInfo] = useState({})

useEffect(() => {
if (client) {
client.request(
gql`
query GetEntitySubscriptions($entity: ProjectEntityIDInput!) {
eventSubscriptions(size: 10, filter: {
entity: $entity,
}) {
pageInfo {
totalSize
nextPage {
offset
}
}
pageItems {
name
channel
}
}
}
`,
{entity: {type, id}}
).then(data => {
setSubscriptions(data.eventSubscriptions.pageItems)
setPageInfo(data.eventSubscriptions.pageInfo)
})
}
}, [client, type, id])

return (
<>
<List
dataSource={subscriptions}
header={
<Typography.Title type="secondary" level={5}>Subscriptions (<SubscriptionsLink entity={{type, id}}
text={pageInfo.totalSize}
/>)</Typography.Title>
}
size="small"
renderItem={(subscription) => (
<List.Item>
<List.Item.Meta
title={<SubscriptionLink entity={{type, id}} subscription={subscription}/>}
avatar={<Tag>{subscription.channel}</Tag>}
/>
</List.Item>
)}
footer={
<div style={{paddingLeft: 16}}>
{
pageInfo.nextPage &&
<SubscriptionsLink entity={{type, id}}
text="More..."
/>
}
</div>
}
/>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,10 @@ export default function SubscriptionName({subscription, entity, managePermission
<>
{contextHolder}
<Typography.Text
editable={{
editable: managePermission,
editable={managePermission ? {
onChange: onChange,
icon: changing ? <Spin size="small"/> : <FaPencilAlt size={12}/>,
}}
} : false}
>
{subscription.name}
</Typography.Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ export const subscriptionsLink = (entity) => {
}
}

export default function SubscriptionsLink({entity}) {
return <Link href={subscriptionsLink(entity)}>Subscriptions</Link>
export default function SubscriptionsLink({entity, text = 'Subscriptions'}) {
return <Link title="List of all subscriptions" href={subscriptionsLink(entity)}>{text}</Link>
}

0 comments on commit adb7778

Please sign in to comment.