Skip to content

Commit

Permalink
e 2498 bug le filtre has good first issues de la liste projet ne (#1310)
Browse files Browse the repository at this point in the history
* Returns live-ODHack issues in project/good-first-issues endpoint

* Iso project_contributions_data

* Add live_hackathon_issue_count to project_contributions_data

* Add hasGfiOrLiveHackathonIssues query param to GET /projects

* Fix project_contributions_data

* Better index

* Update dumps

* Fix flakiness

* Fix IT
  • Loading branch information
ofux authored Nov 25, 2024
1 parent fc3330f commit 0ea59c6
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import onlydust.com.marketplace.api.contract.model.ProjectPageItemResponse;
import onlydust.com.marketplace.api.contract.model.ProjectPageResponse;
import onlydust.com.marketplace.api.helper.DatabaseHelper;
import onlydust.com.marketplace.api.postgres.adapter.PostgresBiProjectorAdapter;
import onlydust.com.marketplace.api.postgres.adapter.PostgresProjectAdapter;
import onlydust.com.marketplace.api.postgres.adapter.entity.read.ProjectViewEntity;
import onlydust.com.marketplace.api.postgres.adapter.entity.write.ProjectCategoryEntity;
Expand All @@ -16,14 +17,18 @@
import onlydust.com.marketplace.api.postgres.adapter.repository.bi.BiProjectGlobalDataRepository;
import onlydust.com.marketplace.api.postgres.adapter.repository.old.ProjectLeaderInvitationRepository;
import onlydust.com.marketplace.api.suites.tags.TagProject;
import onlydust.com.marketplace.kernel.model.ContributionUUID;
import onlydust.com.marketplace.kernel.model.ProjectId;
import onlydust.com.marketplace.project.domain.model.Hackathon;
import onlydust.com.marketplace.project.domain.model.Project;
import onlydust.com.marketplace.project.domain.port.output.HackathonStoragePort;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -4154,131 +4159,58 @@ void should_get_projects_with_good_first_issues() {
.expectBody()
.json("""
{
"totalPageNumber": 0,
"totalItemNumber": 0,
"totalPageNumber": 1,
"totalItemNumber": 4,
"hasMore": false,
"nextPageIndex": 0,
"projects": [],
"languages": [
{
"id": "f57d0866-89f3-4613-aaa2-32f4f4ecc972",
"slug": "cairo",
"name": "Cairo",
"logoUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-logo-cairo.png",
"bannerUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-banner-cairo.png"
},
{
"id": "1109d0a2-1143-4915-a9c1-69e8be6c1bea",
"slug": "javascript",
"name": "Javascript",
"logoUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-logo-javascript.png",
"bannerUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-banner-javascript.png"
},
{
"id": "e1842c39-fcfa-4289-9b5e-61bf50386a72",
"slug": "python",
"name": "Python",
"logoUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-logo-python.png",
"bannerUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-banner-python.png"
},
{
"id": "e0321e59-8633-46ee-bfbc-b1d84d845f83",
"slug": "ruby",
"name": "Ruby",
"logoUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-logo-ruby.png",
"bannerUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-banner-ruby.png"
},
{
"id": "ca600cac-0f45-44e9-a6e8-25e21b0c6887",
"slug": "rust",
"name": "Rust",
"logoUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-logo-rust.png",
"bannerUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-banner-rust.png"
},
{
"id": "d69b6d3e-f583-4c98-92d0-99a56f6f884a",
"slug": "solidity",
"name": "Solidity",
"logoUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-logo-solidity.png",
"bannerUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-banner-solidity.png"
},
{
"id": "75ce6b37-8610-4600-8d2d-753b50aeda1e",
"slug": "typescript",
"name": "Typescript",
"logoUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-logo-typescript.png",
"bannerUrl": "https://od-metadata-assets-develop.s3.eu-west-1.amazonaws.com/languages-banner-Typescript.png"
}
],
"ecosystems": [
{
"id": "9f82bdb4-22c2-455a-91a8-e3c7d96c47d7",
"name": "Aptos",
"url": "https://aptosfoundation.org/",
"logoUrl": "https://onlydust-app-images.s3.eu-west-1.amazonaws.com/8106946702216548210.png",
"bannerUrl": null,
"slug": "aptos",
"hidden": null
},
{
"id": "397df411-045d-4d9f-8d65-8284c88f9208",
"name": "Avail",
"url": "https://www.availproject.org/",
"logoUrl": "https://onlydust-app-images.s3.eu-west-1.amazonaws.com/12011103528231014365.png",
"bannerUrl": null,
"slug": "avail",
"hidden": null
},
{
"id": "ed314d31-f5f2-40e5-9cfc-a962b35c572e",
"name": "Aztec",
"url": "https://aztec.network/",
"logoUrl": "https://onlydust-app-images.s3.eu-west-1.amazonaws.com/2431172990485257518.jpg",
"bannerUrl": null,
"slug": "aztec",
"hidden": null
},
{
"id": "6ab7fa6c-c418-4997-9c5f-55fb021a8e5c",
"name": "Ethereum",
"url": "https://ethereum.foundation/",
"logoUrl": "https://onlydust-app-images.s3.eu-west-1.amazonaws.com/8506434858363286425.png",
"bannerUrl": null,
"slug": "ethereum",
"hidden": null
},
{
"id": "99b6c284-f9bb-4f89-8ce7-03771465ef8e",
"name": "Starknet",
"url": "https://www.starknet.io/en",
"logoUrl": "https://onlydust-app-images.s3.eu-west-1.amazonaws.com/12429671188779981103.png",
"bannerUrl": null,
"slug": "starknet",
"hidden": null
},
{
"id": "b599313c-a074-440f-af04-a466529ab2e7",
"name": "Zama",
"url": "https://www.zama.ai/",
"logoUrl": "https://onlydust-app-images.s3.eu-west-1.amazonaws.com/599423013682223091.png",
"bannerUrl": null,
"slug": "zama",
"hidden": null
}
],
"categories": [
{
"id": "b151c7e4-1493-4927-bb0f-8647ec98a9c5",
"slug": "ai",
"name": "AI",
"description": "AI is cool",
"iconSlug": "brain"
}
]
"nextPageIndex": 0
}
""");
}

@Autowired
HackathonStoragePort hackathonStoragePort;
@Autowired
PostgresBiProjectorAdapter postgresBiProjectorAdapter;

@Test
@Order(16)
void should_get_projects_with_hasGfiOrLiveHackathonIssues() {
// Given
final var antho = userAuthHelper.authenticateAntho();
final var label = "Awesome ODHack 42";
final var hackathon = Hackathon.builder()
.id(Hackathon.Id.random())
.title("Hackathon 1")
.startDate(ZonedDateTime.now().minusDays(1))
.endDate(ZonedDateTime.now().plusDays(1))
.status(Hackathon.Status.PUBLISHED)
.build();
hackathon.githubLabels().add(label);
hackathon.projectIds().add(UUID.fromString("a0c91aee-9770-4000-a893-953ddcbd62a7"));
hackathonStoragePort.save(hackathon);
final var hackathonIssueId = githubHelper.createIssue(99600159L, ZonedDateTime.now().minusDays(3), null, "OPEN", antho);
githubHelper.addLabelToIssue(hackathonIssueId, label, ZonedDateTime.now().minusSeconds(10));
postgresBiProjectorAdapter.onContributionsChanged(99600159L, ContributionUUID.of(hackathonIssueId));

// When
client.get()
.uri(getApiURI(PROJECTS_GET, Map.of("pageIndex", "0", "pageSize", "5", "hasGfiOrLiveHackathonIssues", "true")))
// Then
.exchange()
.expectStatus()
.is2xxSuccessful()
.expectBody()
.json("""
{
"totalPageNumber": 1,
"totalItemNumber": 5,
"hasMore": false,
"nextPageIndex": 0
}
""");
}


private static final String GET_PROJECTS_AFTER_TAGS_UPDATE_JSON_RESPONSE = """
{
"totalPageNumber": 3,
Expand Down
4 changes: 2 additions & 2 deletions bootstrap/src/test/resources/database/dumps/IT_data.dump
Git LFS file not shown
4 changes: 2 additions & 2 deletions bootstrap/src/test/resources/database/dumps/IT_schema.dump
Git LFS file not shown
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ public int refreshByProject(final ProjectId projectId) {

@Trace(operationName = "pseudo_projection.refresh", resourceName = "refresh:bi.project_contributions_data:repo_id")
public int refreshByRepo(final Long repoId) {
return refreshUnsafe("%d = any(repo_ids)".formatted(repoId));
return refreshUnsafe("project_id = (select pgr.project_id from project_github_repos pgr where pgr.github_repo_id = %d limit 1)".formatted(repoId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
call drop_pseudo_projection('bi', 'project_contributions_data');

call create_pseudo_projection('bi', 'project_contributions_data', $$
SELECT p.id as project_id,
count(distinct unnested.contributor_ids) as contributor_count,
count(distinct cd.contribution_uuid) filter ( where cd.is_good_first_issue and
coalesce(array_length(ccd.assignee_ids, 1), 0) = 0 and
cd.contribution_status != 'COMPLETED' and
cd.contribution_status != 'CANCELLED') as good_first_issue_count,
count(distinct cd.contribution_uuid) filter ( where gl.id is not null and
coalesce(array_length(ccd.assignee_ids, 1), 0) = 0 and
cd.contribution_status != 'COMPLETED' and
cd.contribution_status != 'CANCELLED') as live_hackathon_issue_count
FROM projects p
LEFT JOIN bi.p_contribution_data cd ON cd.project_id = p.id
LEFT JOIN bi.p_contribution_contributors_data ccd ON ccd.contribution_uuid = cd.contribution_uuid
LEFT JOIN unnest(ccd.contributor_ids) unnested(contributor_ids) ON true
LEFT JOIN hackathon_projects hp ON hp.project_id = p.id
LEFT JOIN hackathons h ON h.id = hp.hackathon_id AND
h.status = 'PUBLISHED' AND
h.start_date <= now() AND
h.end_date >= now()
LEFT JOIN indexer_exp.github_labels gl ON gl.name = any (h.github_labels) AND gl.id = any (cd.github_label_ids)
GROUP BY p.id
$$, 'project_id');

create unique index on bi.p_project_contributions_data (project_id, contributor_count, good_first_issue_count, live_hackathon_issue_count);
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@ databaseChangeLog:
file: db/changelog/changelogs/00000028_add_indexes.sql
- include:
file: db/changelog/changelogs/00000029_fix_some_projections_perf.sql
- include:
file: db/changelog/changelogs/00000030_add_hackathon_issues_to_project_contributions_data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2010,6 +2010,11 @@ paths:
required: false
schema:
type: boolean
- in: query
name: hasGfiOrLiveHackathonIssues
required: false
schema:
type: boolean
- in: query
name: sort
description: Sort order
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public ResponseEntity<ProjectPageResponse> getProjects(final Integer pageIndex,
final List<String> languageSlugs,
final List<String> categorySlugs,
final Boolean hasGoodFirstIssues,
final Boolean hasGfiOrLiveHackathonIssues,
final ProjectListSort sort
) {
final var user = authenticatedAppUserService.tryGetAuthenticatedUser();
Expand All @@ -128,6 +129,7 @@ public ResponseEntity<ProjectPageResponse> getProjects(final Integer pageIndex,
isNull(ecosystemSlugs) ? null : ecosystemSlugs.toArray(String[]::new),
isNull(tags) ? null : tags.stream().map(ProjectTag::name).toArray(String[]::new),
hasGoodFirstIssues,
hasGfiOrLiveHackathonIssues,
PageRequest.of(pageIndex, pageSize, isNull(sort) ? JpaSort.unsafe(ASC, "project_name") :
switch (sort) {
case RANK -> JpaSort.unsafe(DESC, "coalesce(is_invited_as_project_lead.value, false)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ LEFT JOIN LATERAL (
and (cast(:hasGoodFirstIssues as boolean) is null or
cast(:hasGoodFirstIssues as boolean) is true and pcd.good_first_issue_count > 0 or
cast(:hasGoodFirstIssues as boolean) is false and pcd.good_first_issue_count = 0)
and (cast(:hasGfiOrLiveHackathonIssues as boolean) is null or
cast(:hasGfiOrLiveHackathonIssues as boolean) is true and (pcd.good_first_issue_count > 0 or pcd.live_hackathon_issue_count > 0) or
cast(:hasGfiOrLiveHackathonIssues as boolean) is false and (pcd.good_first_issue_count = 0 and pcd.live_hackathon_issue_count = 0))
and (cast(:search as text) is null or p.search ilike '%' || cast(:search as text) || '%')
""", nativeQuery = true)
Page<ProjectPageItemQueryEntity> findAll(UUID userId,
Expand All @@ -75,6 +78,7 @@ Page<ProjectPageItemQueryEntity> findAll(UUID userId,
String[] ecosystemSlugs,
String[] tags,
Boolean hasGoodFirstIssues,
Boolean hasGfiOrLiveHackathonIssues,
Pageable pageable);

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package onlydust.com.marketplace.user.domain.service;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.*;
import onlydust.com.marketplace.kernel.model.UserId;
import onlydust.com.marketplace.kernel.model.notification.NotificationCategory;
import onlydust.com.marketplace.kernel.model.notification.NotificationChannel;
Expand Down Expand Up @@ -31,6 +28,7 @@ class NotificationServiceTest {
NotificationService notificationService = new NotificationService(notificationSettingsStoragePort, notificationStoragePort, userStoragePort,
new AsyncNotificationEmailProcessor(notificationEmailSender, notificationStoragePort));

@SneakyThrows
@Test
void push() {
// Given
Expand All @@ -44,6 +42,7 @@ void push() {
final var notification = notificationService.push(recipientId, new TestNotification(1, NotificationCategory.CONTRIBUTOR_REWARD));

// Then
Thread.sleep(1000); // Wait for async processing
verify(notificationStoragePort).save(eq(notification));
verify(notificationEmailSender).send(any(SendableNotification.class));
verify(notificationStoragePort).markAsSent(NotificationChannel.EMAIL, List.of(notification.id()));
Expand Down

0 comments on commit 0ea59c6

Please sign in to comment.