Skip to content

Commit 76bdd3b

Browse files
committed
[ui] Add view to load SBoM files
Adds a view to load an SPDX SBoM file and display a list of repositories found on it. The repositories can then be selected to be scheduled as tasks. Signed-off-by: Eva Millán <[email protected]>
1 parent b82ab17 commit 76bdd3b

File tree

8 files changed

+834
-1
lines changed

8 files changed

+834
-1
lines changed

.github/workflows/tests.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,27 @@ jobs:
5656
- name: Tests
5757
run: |
5858
poetry run python manage.py test --settings=config.settings.testing
59+
60+
frontend:
61+
62+
strategy:
63+
matrix:
64+
node-version: [18.x, 20.x]
65+
66+
runs-on: ubuntu-latest
67+
name: Node ${{ matrix.node-version }}
68+
steps:
69+
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
70+
- name: Setup Node ${{ matrix.node-version }}
71+
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # 4.0.1
72+
with:
73+
node-version: ${{ matrix.node-version }}
74+
- name: Install dependencies
75+
working-directory: ./ui
76+
run: yarn install
77+
- name: Run ESLint
78+
working-directory: ./ui
79+
run: yarn lint
80+
- name: Run unit tests
81+
working-directory: ./ui
82+
run: yarn test:unit

ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"serve": "vite",
88
"build": "vite build --emptyOutDir",
99
"preview": "vite preview",
10-
"test:unit": "vitest",
10+
"test:unit": "vitest run",
1111
"test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
1212
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
1313
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",

ui/src/assets/main.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@
1111
font-weight: 400;
1212
font-style: normal;
1313
}
14+
15+
a {
16+
color: #1f2328;
17+
font-weight: 500;
18+
text-decoration: none;
19+
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
<template>
2+
<v-data-table-virtual
3+
v-if="items.length > 0"
4+
v-model="selected"
5+
:fixed-header="true"
6+
:headers="headers"
7+
:items="items"
8+
:loading="isLoading"
9+
color="primary"
10+
item-value="url"
11+
return-object
12+
show-select
13+
@update:model-value="$emit('update:selected', selectedByCategory)"
14+
>
15+
<template #top>
16+
<div class="d-flex align-center justify-space-between pa-1">
17+
<p class="text-subtitle-2 mb-4">Select the data you want to analyse</p>
18+
<div v-if="showFilters" class="d-flex">
19+
<v-switch
20+
v-model="filters.archived"
21+
class="mr-4"
22+
color="primary"
23+
label="Show archived repositories"
24+
hide-details
25+
/>
26+
<v-switch
27+
v-model="filters.fork"
28+
color="primary"
29+
label="Show forked repositories"
30+
hide-details
31+
></v-switch>
32+
</div>
33+
</div>
34+
</template>
35+
<template #[`item.url`]="{ item }">
36+
{{ item.url }}
37+
<v-chip v-if="item.fork" class="ml-2" color="primary" density="comfortable" size="small">
38+
<v-icon size="small" start>mdi-source-fork</v-icon>
39+
fork
40+
</v-chip>
41+
<v-chip v-if="item.archived" class="ml-2" color="warning" density="comfortable" size="small">
42+
<v-icon size="small" start>mdi-archive-outline</v-icon>
43+
archived
44+
</v-chip>
45+
</template>
46+
<template
47+
v-for="header in headers"
48+
:key="header.value"
49+
#[`header.${header.value}`]="{ column }"
50+
>
51+
<div v-if="header.value !== 'url'" class="d-flex align-center">
52+
<v-checkbox-btn
53+
v-model="selectedColumns[column.value]"
54+
:true-value="true"
55+
:false-value="false"
56+
:indeterminate="indeterminate[column.value]"
57+
color="primary"
58+
density="compact"
59+
@change="selectAll(column.value)"
60+
/>
61+
<span class="ml-2">{{ column.title }}</span>
62+
</div>
63+
<span v-else>{{ column.title }}</span>
64+
</template>
65+
<template #[`item.commit`]="{ item }">
66+
<v-checkbox-btn
67+
v-model="item.form.commit"
68+
color="primary"
69+
density="compact"
70+
@update:model-value="$emit('update:selected', selectedByCategory)"
71+
/>
72+
</template>
73+
<template #[`item.issue`]="{ item }">
74+
<v-checkbox-btn
75+
v-model="item.form.issue"
76+
:disabled="!item.has_issues"
77+
color="primary"
78+
density="compact"
79+
@update:model-value="$emit('update:selected', selectedByCategory)"
80+
/>
81+
</template>
82+
<template #[`item.pull_request`]="{ item }">
83+
<v-checkbox-btn
84+
v-model="item.form.pull_request"
85+
:disabled="!item.has_pull_requests"
86+
color="primary"
87+
density="compact"
88+
@update:model-value="$emit('update:selected', selectedByCategory)"
89+
/>
90+
</template>
91+
</v-data-table-virtual>
92+
</template>
93+
<script>
94+
export default {
95+
name: 'RepositoryTable',
96+
emits: ['update:selected'],
97+
props: {
98+
repositories: {
99+
type: Array,
100+
required: true
101+
},
102+
showFilters: {
103+
type: Boolean,
104+
required: false,
105+
default: false
106+
}
107+
},
108+
data() {
109+
return {
110+
isLoading: false,
111+
items: [],
112+
headers: [
113+
{ title: 'Repository URL', value: 'url' },
114+
{ title: 'Commits', value: 'commit' },
115+
{ title: 'Issues', value: 'issue' },
116+
{ title: 'Pull Requests', value: 'pull_request' }
117+
],
118+
selectedColumns: {
119+
commit: true,
120+
issue: true,
121+
pull_request: true
122+
},
123+
selected: [],
124+
filters: {
125+
archived: true,
126+
fork: true
127+
}
128+
}
129+
},
130+
computed: {
131+
selectedByCategory() {
132+
return this.selected.reduce((list, current) => {
133+
Object.entries(current.form).forEach((category) => {
134+
const [key, value] = category
135+
if (value) {
136+
list.push({
137+
category: key,
138+
datasource: current.datasource,
139+
url: key === 'commit' ? `${current.url}.git` : current.url
140+
})
141+
}
142+
})
143+
return list
144+
}, [])
145+
},
146+
indeterminate() {
147+
return {
148+
commit: this.areSomeSelected('commit'),
149+
issue: this.areSomeSelected('issue'),
150+
pull_request: this.areSomeSelected('pull_request')
151+
}
152+
}
153+
},
154+
methods: {
155+
applyFilters(array, filters) {
156+
this.selected = []
157+
const filterKeys = Object.keys(filters)
158+
return array.reduce((acc, item) => {
159+
const match = filterKeys.every((key) => {
160+
if (filters[key]) return true
161+
return filters[key] === item[key]
162+
})
163+
if (match) {
164+
acc.push({
165+
...item,
166+
form: {
167+
commit: true,
168+
pull_request: item.has_pull_requests,
169+
issue: item.has_issues
170+
}
171+
})
172+
}
173+
return acc
174+
}, [])
175+
},
176+
selectAll(column) {
177+
this.items.forEach((item) => (item.form[column] = this.selectedColumns[column]))
178+
this.$emit('update:selected', this.selectedByCategory)
179+
},
180+
areSomeSelected(column) {
181+
return (
182+
this.items.some((item) => item.form[column] === true) &&
183+
!this.items.every((item) => item.form[column] === true)
184+
)
185+
}
186+
},
187+
watch: {
188+
filters: {
189+
handler(value) {
190+
this.items = this.applyFilters(this.repositories, value)
191+
},
192+
deep: true
193+
},
194+
repositories(value) {
195+
this.items = this.applyFilters(value, this.filters)
196+
}
197+
},
198+
mounted() {
199+
this.items = this.applyFilters(this.repositories, this.filters)
200+
}
201+
}
202+
</script>
203+
<style lang="scss" scoped>
204+
:deep(.v-switch) {
205+
.v-selection-control {
206+
min-height: auto;
207+
}
208+
.v-label {
209+
font-size: 0.875rem;
210+
letter-spacing: normal;
211+
font-weight: 500;
212+
}
213+
}
214+
215+
:deep(.v-data-table__th) {
216+
.v-selection-control {
217+
flex: 0;
218+
}
219+
}
220+
</style>

0 commit comments

Comments
 (0)