Skip to content

Commit f16f05a

Browse files
authored
Merge branch 'main' into fix/debuging-query-members
2 parents ce87f0d + b6f71e8 commit f16f05a

17 files changed

Lines changed: 652 additions & 95 deletions

File tree

frontend/src/modules/contributor/components/edit/identity/contributor-identity-add.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,10 @@ const isModalOpen = computed<boolean>({
166166
167167
const addIdentities = () => {
168168
sending.value = true;
169-
createContributorIdentities(props.contributor.id, form)
169+
createContributorIdentities(
170+
props.contributor.id,
171+
form.map(({ id, ...identity }) => identity),
172+
)
170173
.then(() => {
171174
ToastStore.success('Identities successfully added');
172175
isModalOpen.value = false;

frontend/src/modules/contributor/services/contributor.identities.api.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import authAxios from '@/shared/axios/auth-axios';
2-
import { ContributorIdentity, UpdateContributorIdentityPayload } from '@/modules/contributor/types/Contributor';
2+
import { CreateContributorIdentity, UpdateContributorIdentityPayload } from '@/modules/contributor/types/Contributor';
33
import { storeToRefs } from 'pinia';
44
import { useLfSegmentsStore } from '@/modules/lf/segments/store';
55

@@ -22,7 +22,7 @@ export class ContributorIdentitiesApiService {
2222
).then(({ data }) => Promise.resolve(data));
2323
}
2424

25-
static async createMultiple(memberId: string, identities: ContributorIdentity[]) {
25+
static async createMultiple(memberId: string, identities: CreateContributorIdentity[]) {
2626
return authAxios.put(
2727
`/member/${memberId}/identity`,
2828
{

frontend/src/modules/contributor/store/contributor.actions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useLfSegmentsStore } from '@/modules/lf/segments/store';
22
import { storeToRefs } from 'pinia';
33
import { ContributorApiService } from '@/modules/contributor/services/contributor.api.service';
44
import {
5-
Contributor, ContributorAffiliation, ContributorIdentity, UpdateContributorIdentityPayload,
5+
Contributor, ContributorAffiliation, ContributorIdentity, CreateContributorIdentity, UpdateContributorIdentityPayload,
66
} from '@/modules/contributor/types/Contributor';
77
import { ContributorIdentitiesApiService } from '@/modules/contributor/services/contributor.identities.api.service';
88
import { MergeActionsService } from '@/shared/modules/merge/services/merge-actions.service';
@@ -70,7 +70,7 @@ export default {
7070
return ContributorIdentitiesApiService.list(id, [selectedProjectGroup.value?.id as string])
7171
.then(this.setIdentities);
7272
},
73-
createContributorIdentities(memberId: string, identities: ContributorIdentity[]): Promise<ContributorIdentity[]> {
73+
createContributorIdentities(memberId: string, identities: CreateContributorIdentity[]): Promise<ContributorIdentity[]> {
7474
return ContributorIdentitiesApiService.createMultiple(memberId, identities)
7575
.then(this.setIdentities);
7676
},

frontend/src/modules/contributor/types/Contributor.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ export interface ContributorIdentity {
7373
duplicatedIdentities?: ContributorIdentity[];
7474
}
7575

76+
export type CreateContributorIdentity = Omit<
77+
ContributorIdentity,
78+
'id' | 'platforms' | 'url' | 'duplicatedIdentities'
79+
>;
80+
7681
export interface UpdateContributorIdentityPayload {
7782
value?: string;
7883
type?: string;

scripts/cli

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,14 +1182,14 @@ while test $# -gt 0; do
11821182
exit
11831183
;;
11841184
service-restart-fe-dev)
1185-
IGNORED_SERVICES=("frontend" "python-worker" "job-generator" "webhook-api" "profiles-worker" "organizations-enrichment-worker" "merge-suggestions-worker" "members-enrichment-worker" "exports-worker" "entity-merging-worker")
1185+
IGNORED_SERVICES=("frontend" "python-worker" "job-generator" "webhook-api" "profiles-worker" "organizations-enrichment-worker" "merge-suggestions-worker" "members-enrichment-worker" "exports-worker" "entity-merging-worker" "automatic-projects-discovery-worker" "pcc-sync-worker" "projects-evaluation-worker" "bq-dataset-ingest" "cargo-worker" "dockerhub-sync" "github-repos-enricher" "go-worker" "maven-worker" "npm-worker" "nuget-worker" "osv-worker" "pypi-worker" "rubygems-worker" "security-contacts-worker")
11861186
DEV=1
11871187
kill_all_containers
11881188
service_start
11891189
exit
11901190
;;
11911191
clean-start-fe-dev)
1192-
IGNORED_SERVICES=("frontend" "python-worker" "job-generator" "webhook-api" "profiles-worker" "organizations-enrichment-worker" "merge-suggestions-worker" "members-enrichment-worker" "exports-worker" "entity-merging-worker" "cache-worker" "categorization-worker" "cron-service" "data-sink-worker" "git-integration" "integration-run-worker" "integration-stream-worker" "nango-webhook-api" "nango-worker" "script-executor-worker" "search-sync-api" "search-sync-worker" "security-best-practices-worker" "snowflake-connectors-worker")
1192+
IGNORED_SERVICES=("frontend" "python-worker" "job-generator" "webhook-api" "profiles-worker" "organizations-enrichment-worker" "merge-suggestions-worker" "members-enrichment-worker" "exports-worker" "entity-merging-worker" "cache-worker" "categorization-worker" "cron-service" "data-sink-worker" "git-integration" "integration-run-worker" "integration-stream-worker" "nango-webhook-api" "nango-worker" "script-executor-worker" "search-sync-api" "search-sync-worker" "security-best-practices-worker" "snowflake-connectors-worker" "automatic-projects-discovery-worker" "pcc-sync-worker" "projects-evaluation-worker" "bq-dataset-ingest" "cargo-worker" "dockerhub-sync" "github-repos-enricher" "go-worker" "maven-worker" "npm-worker" "nuget-worker" "osv-worker" "pypi-worker" "rubygems-worker" "security-contacts-worker")
11931193
CLEAN_START=1
11941194
DEV=1
11951195
start

scripts/services/docker/Dockerfile.security_best_practices_worker

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ARG PVTR_VERSION=v0.23.2
1+
ARG PVTR_VERSION=v0.24.0
22

33
FROM alpine:3.21 AS core
44
RUN apk add --no-cache wget tar unzip
@@ -10,13 +10,13 @@ ARG PLATFORM=Linux_x86_64
1010
RUN wget https://github.com/privateerproj/privateer/releases/download/v${VERSION}/privateer_${PLATFORM}.tar.gz
1111
RUN tar -xzf privateer_${PLATFORM}.tar.gz
1212

13-
FROM golang:1.26.3-alpine3.23 AS plugin
13+
FROM golang:1.26.4-alpine3.22 AS plugin
1414
RUN apk add --no-cache make git
1515
WORKDIR /plugin
16-
ARG PVTR_COMMIT=c7bd9538d64f7eaab94a05c9b5fd05458a387b1c
16+
ARG PVTR_COMMIT=c1095c95a1b399ec63e4f4e2b7880b0ef55e604f
1717
ARG PVTR_VERSION
1818
# To run the latest version of the plugin, we need to use the latest commit of the pvtr-github-repo-scanner repository.
19-
# Currently using v0.23.2: https://github.com/ossf/pvtr-github-repo-scanner/commit/c7bd9538d64f7eaab94a05c9b5fd05458a387b1c
19+
# Currently using v0.24.0+2fixes: https://github.com/ossf/pvtr-github-repo-scanner/commit/c1095c95a1b399ec63e4f4e2b7880b0ef55e604f
2020
RUN git clone https://github.com/ossf/pvtr-github-repo-scanner.git && cd pvtr-github-repo-scanner && git checkout ${PVTR_COMMIT}
2121
RUN cd pvtr-github-repo-scanner && make binary && cp github-repo ../github-repo
2222

services/apps/packages_worker/src/maven/__tests__/normalize.test.ts

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ describe('parseRepoUrl', () => {
103103
expect(result?.host).toBe('other')
104104
})
105105

106+
it('keeps the full path as name (no owner) for known SVN hosts', () => {
107+
expect(parseRepoUrl('https://svn.apache.org/repos/asf/geronimo/server')).toEqual({
108+
host: 'svn',
109+
owner: null,
110+
name: 'repos/asf/geronimo/server',
111+
})
112+
})
113+
106114
it('returns null for invalid URLs', () => {
107115
expect(parseRepoUrl('not-a-url')).toBeNull()
108116
})
@@ -111,6 +119,22 @@ describe('parseRepoUrl', () => {
111119
const result = parseRepoUrl('https://github.com/')
112120
expect(result).toEqual({ host: 'github', owner: null, name: null })
113121
})
122+
123+
// The canonical OpenDaylight link keeps repo identity in ?p=, not the (fixed)
124+
// pathname — a generic parts[0]/parts[1] split would collapse every OpenDaylight
125+
// repo to the same owner='gerrit', name='gitweb'.
126+
it('derives owner=null and name from the ?p= query for OpenDaylight Gerrit links', () => {
127+
expect(parseRepoUrl('https://git.opendaylight.org/gerrit/gitweb?p=netconf.git')).toEqual({
128+
host: 'gerrit',
129+
owner: null,
130+
name: 'netconf',
131+
})
132+
expect(parseRepoUrl('https://git.opendaylight.org/gerrit/gitweb?p=aaa.git')).toEqual({
133+
host: 'gerrit',
134+
owner: null,
135+
name: 'aaa',
136+
})
137+
})
114138
})
115139

116140
describe('normalizeScmUrl', () => {
@@ -160,8 +184,57 @@ describe('normalizeScmUrl', () => {
160184
)
161185
})
162186

163-
it('returns null for non-https result', () => {
164-
expect(normalizeScmUrl('svn://svn.apache.org/repos/commons-lang')).toBeNull()
187+
it('returns null for non-https, non-SVN schemes', () => {
188+
expect(normalizeScmUrl('ftp://ftp.apache.org/repos/commons-lang')).toBeNull()
189+
})
190+
191+
// SVN has no owner/repo shape and is mapped even though the hosts are largely
192+
// unreachable today — see normalizeSvnUrl / normalizeSvnScmUrl docstrings.
193+
it('maps svn:// URLs on known SVN hosts to a canonical link', () => {
194+
expect(normalizeScmUrl('svn://svn.apache.org/repos/asf/commons-lang/trunk')).toBe(
195+
'https://svn.apache.org/repos/asf/commons-lang',
196+
)
197+
})
198+
199+
it('maps scm:svn: connection strings, unifying viewvc/repos-asf to the same link', () => {
200+
expect(
201+
normalizeScmUrl(
202+
'scm:svn:http://svn.apache.org/repos/asf/geronimo/server/tags/geronimo-3.0.1',
203+
),
204+
).toBe('https://svn.apache.org/repos/asf/geronimo/server')
205+
expect(
206+
normalizeScmUrl('http://svn.apache.org/viewvc/geronimo/server/tags/geronimo-3.0.1'),
207+
).toBe('https://svn.apache.org/repos/asf/geronimo/server')
208+
})
209+
210+
it('maps svn+ssh: with an embedded user and SourceForge /p/.../code paths', () => {
211+
expect(
212+
normalizeScmUrl('scm:svn:svn+ssh://albertil@svn.forge.objectweb.org/svnroot/myproj/trunk'),
213+
).toBe('https://svn.forge.objectweb.org/svnroot/myproj')
214+
expect(normalizeScmUrl('http://svn.code.sf.net/p/myproj/code/trunk/foo')).toBe(
215+
'https://svn.code.sf.net/p/myproj/code',
216+
)
217+
})
218+
219+
it('returns null for a marker-only SVN root with no project segment', () => {
220+
// No trailing slash after "repos/asf" — must not fall through to being
221+
// mis-parsed as project segments ["repos", "asf"] and duplicated.
222+
expect(normalizeScmUrl('https://svn.apache.org/repos/asf')).toBeNull()
223+
})
224+
225+
// java.net requires an explicit SVN marker because the bare host is shared with
226+
// non-SVN content (forums, wikis, JIRA) — this must not accidentally widen to
227+
// treat every java.net URL as SVN.
228+
it('treats java.net as SVN only with an explicit scm:svn:/svn:// marker', () => {
229+
expect(normalizeScmUrl('https://java.net/projects/foo/sources/svn')).toBeNull()
230+
expect(normalizeScmUrl('scm:svn:https://java.net/svn/foo/trunk')).toBe(
231+
'https://java.net/svn/foo',
232+
)
233+
expect(normalizeScmUrl('svn://java.net/svn/foo/trunk')).toBe('https://java.net/svn/foo')
234+
})
235+
236+
it('does not map dead services that incidentally match an "svn" text filter', () => {
237+
expect(normalizeScmUrl('http://specs.googlecode.com/svn/trunk')).toBeNull()
165238
})
166239

167240
// Gap B — recover repository_url from inputs that were previously dropped
@@ -362,6 +435,21 @@ describe('normalizeScmUrl', () => {
362435
expect(normalizeScmUrl('https://android.googlesource.com/')).toBeNull()
363436
})
364437

438+
// cs.android.com is the Android Code Search UI, backed by the same Gitiles
439+
// repos as android.googlesource.com — same identity passthrough applies.
440+
it('passes through cs.android.com urls unchanged, mirroring the googlesource identity form', () => {
441+
expect(normalizeScmUrl('https://cs.android.com/androidx/platform/frameworks/support')).toBe(
442+
'https://cs.android.com/androidx/platform/frameworks/support',
443+
)
444+
})
445+
446+
it('returns null for a bare OpenDaylight Gerrit root with no repo path', () => {
447+
// /gerrit/ alone (as opposed to /gerrit/gitweb?p=...) has nothing to extract —
448+
// same "no derivable identity" case as the bare gerrit.onosproject.org root
449+
// tested above, confirmed against real declared_repository_url values.
450+
expect(normalizeScmUrl('https://git.opendaylight.org/gerrit/')).toBeNull()
451+
})
452+
365453
it('recovers GitHub Pages project-page urls (owner.github.io/repo)', () => {
366454
expect(normalizeScmUrl('https://silentbalanceyh.github.io/vertx-zero/')).toBe(
367455
'https://github.com/silentbalanceyh/vertx-zero',
@@ -480,9 +568,19 @@ describe('normalizeScmUrl', () => {
480568
expect(normalizeScmUrl('https://git.iem.at/owner/repo')).toBe('https://git.iem.at/owner/repo')
481569
})
482570

483-
it('still returns null for internal or non-allowlisted hosts', () => {
484-
expect(normalizeScmUrl('https://git.corp.adobe.com/team/project')).toBeNull()
485-
expect(normalizeScmUrl('https://gitlab.alibaba-inc.com/team/project')).toBeNull()
571+
// Internal-only hosts are mapped despite being unreachable for external
572+
// consumers — see the SCM_HOSTS docstring.
573+
it('maps internal-only hosts (git.corp.adobe.com, gitlab.alibaba-inc.com)', () => {
574+
expect(normalizeScmUrl('https://git.corp.adobe.com/team/project')).toBe(
575+
'https://git.corp.adobe.com/team/project',
576+
)
577+
expect(normalizeScmUrl('https://gitlab.alibaba-inc.com/team/project')).toBe(
578+
'https://gitlab.alibaba-inc.com/team/project',
579+
)
580+
})
581+
582+
it('still returns null for genuinely non-allowlisted hosts', () => {
583+
expect(normalizeScmUrl('https://git.some-random-unlisted-host.example/team/project')).toBeNull()
486584
})
487585

488586
it('preserves nested GitLab group namespaces instead of truncating to the group', () => {
@@ -515,6 +613,33 @@ describe('normalizeScmUrl', () => {
515613
it('returns null for a GitLab namespace with no project segment', () => {
516614
expect(normalizeScmUrl('https://gitlab.com/onlygroup')).toBeNull()
517615
})
616+
617+
// OpenDaylight's Gerrit: both the gitweb browse query and the SSH clone form
618+
// (port, no git@ user) unify to the same gitweb browse link.
619+
it('maps OpenDaylight Gerrit gitweb query and SSH clone URLs to the same link', () => {
620+
expect(
621+
normalizeScmUrl('https://git.opendaylight.org/gerrit/gitweb?p=netconf.git;a=summary'),
622+
).toBe('https://git.opendaylight.org/gerrit/gitweb?p=netconf.git')
623+
expect(normalizeScmUrl('scm:git:ssh://git.opendaylight.org:29418/aaa.git')).toBe(
624+
'https://git.opendaylight.org/gerrit/gitweb?p=aaa.git',
625+
)
626+
})
627+
628+
it('returns null for Gerrit shapes with no derivable repo identity', () => {
629+
// Bare Gerrit root — no path at all, nothing to extract.
630+
expect(normalizeScmUrl('http://gerrit.onosproject.org/')).toBeNull()
631+
// Gerrit admin-UI fragment path — not a clone shape.
632+
expect(
633+
normalizeScmUrl('https://gerrit.wikimedia.org/r/#/admin/projects/wikidata/query/rdf'),
634+
).toBeNull()
635+
})
636+
637+
it('returns null for non-SCM OpenDaylight URLs instead of mistaking them for a repo', () => {
638+
// A `p` query on some unrelated path is not a gitweb browse link.
639+
expect(normalizeScmUrl('https://git.opendaylight.org/somewhere?p=netconf.git')).toBeNull()
640+
// Single path segment with no .git suffix — e.g. a static asset, not a clone URL.
641+
expect(normalizeScmUrl('https://git.opendaylight.org/favicon.ico')).toBeNull()
642+
})
518643
})
519644

520645
describe('interpolateProperties', () => {

0 commit comments

Comments
 (0)