Skip to content

Commit

Permalink
✨ Show flaw labels on issue list
Browse files Browse the repository at this point in the history
  • Loading branch information
MrMarble committed Jan 9, 2025
1 parent 1bc4834 commit 7b8d467
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 55 deletions.
72 changes: 31 additions & 41 deletions src/components/IssueQueue/IssueQueue.vue
Original file line number Diff line number Diff line change
@@ -1,39 +1,43 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref, onUnmounted, watch } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import { DateTime } from 'luxon';
import { useElementVisibility } from '@vueuse/core';
import IssueQueueItem from '@/components/IssueQueueItem/IssueQueueItem.vue';
import IssueQueueItem from '@/components/IssueQueue/IssueQueueItem.vue';
import LabelCheckbox from '@/widgets/LabelCheckbox/LabelCheckbox.vue';
import { useUserStore } from '@/stores/UserStore';
import { FlawClassificationStateEnum } from '@/generated-client';
import type { ZodFlawType } from '@/types';
const props = defineProps<{
isFinalPageFetched: boolean;
isLoading: boolean;
issues: any[];
issues: ZodFlawType[];
total: number;
}>();
const emit = defineEmits(['flaws:fetch', 'flaws:load-more']);
const userStore = useUserStore();
type FilteredIssue = {
issue: any;
selected: boolean;
};
export type FilteredIssue = ReturnType<typeof relevantFields>;
// Temporarily hiding 'Source' column to avoid displaying incorrect information.
// TODO: unhide it once final issue sources are defined. [OSIDB-2424]
// type ColumnField = 'id' | 'impact' | 'source' | 'created_dt' | 'title' | 'state' | 'owner';
type ColumnField = 'created_dt' | 'id' | 'impact' | 'owner' | 'state' | 'title';
const issues = computed<any[]>(() => props.issues.map(relevantFields));
const issues = computed<FilteredIssue[]>(() => props.issues.map(relevantFields));
const selectedSortField = ref<ColumnField | null>('created_dt');
const isSortedByAscending = ref(false);
const isMyIssuesSelected = ref(false);
const isOpenIssuesSelected = ref(false);
const tableContainerEl = ref<HTMLElement | null>(null);
const tableContainerEl = ref<HTMLDivElement | null>(null);
const buttonRef = ref<HTMLButtonElement | null>(null);
const isButtonVisible = useElementVisibility(buttonRef,
{ rootMargin: '0px 0px 500px 0px', scrollTarget: tableContainerEl },
);
const filteredStates = computed(() => {
const allStates = Object.values(FlawClassificationStateEnum);
Expand Down Expand Up @@ -80,11 +84,6 @@ const columnsFieldsMap: Record<string, ColumnField> = {
Owner: 'owner',
};
const relevantIssues = computed<FilteredIssue[]>(() => {
return issues.value
.map(issue => reactive({ issue, selected: false }));
});
function selectSortField(field: ColumnField) {
// Toggle between descending, ascending and no ordering
// If selecting a new field, set to descending
Expand All @@ -100,48 +99,38 @@ function selectSortField(field: ColumnField) {
}
}
function relevantFields(issue: any) {
function relevantFields(issue: ZodFlawType) {
return {
id: issue.cve_id || issue.uuid,
impact: issue.impact,
// source: issue.source,
created_dt: issue.created_dt,
title: issue.title,
workflowState: issue.classification.state,
workflowState: issue.classification?.state,
unembargo_dt: issue.unembargo_dt,
embargoed: issue.embargoed,
owner: issue.owner,
formattedDate: DateTime.fromISO(issue.created_dt).toUTC().toFormat('yyyy-MM-dd'),
formattedDate: DateTime.fromISO(issue.created_dt!).toUTC().toFormat('yyyy-MM-dd'),
labels: issue.labels,
};
}
function emitLoadMore() {
emit('flaws:load-more', params);
}
onMounted(() => {
tableContainerEl.value?.addEventListener('scroll', handleScroll); // AutoScroll Option
emit('flaws:fetch', params);
});
onUnmounted(() => {
tableContainerEl.value?.removeEventListener('scroll', handleScroll);
watch(params, () => {
emit('flaws:fetch', params);
});
// AutoScroll Method. Maybe have this as a user configurable option in the future?
function handleScroll() {
if (props.isLoading || !tableContainerEl.value) return; // Do not load more if already loading
const totalHeight = tableContainerEl.value.scrollHeight;
const scrollPosition = tableContainerEl.value.scrollTop + tableContainerEl.value.clientHeight;
// Trigger loading more content when the user has scrolled to 99% of the container's height,
if (scrollPosition >= totalHeight * 0.99) {
watch(isButtonVisible, (isVisible) => {
if (isVisible) {
emitLoadMore();
}
}
function emitLoadMore() {
emit('flaws:load-more', params);
}
watch(params, () => {
emit('flaws:fetch', params);
});
</script>

Expand Down Expand Up @@ -189,9 +178,9 @@ watch(params, () => {
</tr>
</thead>
<tbody class="table-group-divider">
<template v-for="(relevantIssue, index) of relevantIssues" :key="relevantIssue.id">
<template v-for="(relevantIssue, index) of issues" :key="relevantIssue.id">
<IssueQueueItem
:issue="relevantIssue.issue"
:issue="relevantIssue"
:class="{
'osim-shaded': index % 2 === 0,
}"
Expand All @@ -200,7 +189,8 @@ watch(params, () => {
</tbody>
</table>
<button
v-if="relevantIssues.length !== 0 && !isFinalPageFetched"
v-if="issues.length !== 0 && !isFinalPageFetched"
ref="buttonRef"
class="btn btn-primary"
type="button"
:disabled="isLoading"
Expand All @@ -216,7 +206,7 @@ watch(params, () => {
<span v-if="isLoading"> Loading Flaws&hellip; </span>
<span v-else> Load More Flaws </span>
</button>
<p v-if="relevantIssues.length === 0">No results.</p>
<p v-if="issues.length === 0">No results.</p>
<span v-if="isFinalPageFetched" role="status">Nothing else to load.</span>
</div>
</div>
Expand All @@ -233,7 +223,7 @@ watch(params, () => {
// tbody
div.osim-incident-list {
display: block;
max-height: calc(100vh - 164px);
max-height: calc(100vh - 245px);
overflow-y: auto;
&:hover::-webkit-scrollbar-thumb {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,46 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
issue: any;
import type { FilteredIssue } from './IssueQueue.vue';
const { issue } = defineProps<{
issue: FilteredIssue;
}>();
// Temporarily hiding 'Source' column to avoid displaying incorrect information.
// TODO: unhide it once final issue sources are defined. [OSIDB-2424]
// and update the CSS for the column width in IssueQueue
// const nonIdFields = ['impact', 'source', 'formattedDate', 'title', 'workflowState', 'owner'];
const nonIdFields = ['impact', 'formattedDate', 'title', 'workflowState', 'owner'];
const isEmbargoed = computed(() => props.issue.embargoed);
const nonIdFields: Exclude<keyof FilteredIssue, 'id'>[] =
['impact', 'formattedDate', 'title', 'workflowState', 'owner'] as const;
const hasBadges = computed(() => issue.embargoed || issue.labels);
</script>

<template>
<tr class="osim-issue-queue-item" :class="$attrs.class">
<td class="osim-issue-title" :class="{ 'pb-0': isEmbargoed }">
<td class="osim-issue-title" :class="{ 'pb-0': hasBadges }">
<RouterLink :to="{ name: 'flaw-details', params: { id: issue.id } }">
{{ issue.id }}
</RouterLink>
</td>
<td v-for="field in nonIdFields" :key="field" :class="{ 'pb-0': isEmbargoed }">
<td v-for="field in nonIdFields" :key="field" :class="{ 'pb-0': hasBadges }">
{{ issue[field] }}
</td>
<!--<td>{{ issue.assigned }}</td>-->
</tr>
<tr v-if="isEmbargoed" class="osim-badge-lane" :class="$attrs.class">
<td colspan="100%" class="pt-0">
<span v-if="isEmbargoed">
<span class="badge rounded-pill text-bg-danger">Embargoed</span>
</span>
<tr v-if="hasBadges" class="osim-badge-lane" :class="$attrs.class">
<td colspan="100%">
<div class="gap-1 d-flex">
<span v-if="issue.embargoed" class="badge rounded-pill text-bg-danger">Embargoed</span>
<span
v-for="label in issue.labels"
:key="label.label"
class="badge rounded-pill text-capitalize"
:class="[label.state == 'REQ' ? 'text-bg-warning fw-bold' : 'text-bg-info']"
:title="label.state == 'REQ' ? 'Requested' :''"
>{{ label.label }}</span>
</div>
</td>
</tr>
</template>
Expand All @@ -43,6 +53,10 @@ td.osim-issue-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
div {
display: block;
}
}
tr td {
Expand Down
5 changes: 4 additions & 1 deletion src/components/__tests__/IssueQueue.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ref } from 'vue';

import { describe, it, expect, vi } from 'vitest';
import { Settings } from 'luxon';

import IssueQueueItem from '@/components/IssueQueueItem/IssueQueueItem.vue';
import IssueQueueItem from '@/components/IssueQueue/IssueQueueItem.vue';
import IssueQueue from '@/components/IssueQueue/IssueQueue.vue';

import { mountWithConfig } from '@/__tests__/helpers';
Expand Down Expand Up @@ -36,6 +38,7 @@ vi.mock('@vueuse/core', () => ({
},
}[key];
}),
useElementVisibility: vi.fn(() => ref(false)),
}));

vi.mock('jwt-decode', () => ({
Expand Down
3 changes: 2 additions & 1 deletion src/composables/useFlawsFetching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ref } from 'vue';

import { getFlaws } from '@/services/FlawService';
import { allowedEmptyFieldMapping } from '@/constants/flawFields';
import type { ZodFlawType } from '@/types';

function finializeRequestParams(params: Record<string, 'isempty' | 'nonempty' | string> = {}) {
const requestParams: Record<string, any> = {};
Expand All @@ -18,7 +19,7 @@ function finializeRequestParams(params: Record<string, 'isempty' | 'nonempty' |
export function useFlawsFetching() {
const isFinalPageFetched = ref(false);
const isLoading = ref(false);
const issues = ref<any[]>([]);
const issues = ref<ZodFlawType[]>([]);
const offset = ref(0);
const pagesize = 20;
const total = ref(0);
Expand Down
1 change: 1 addition & 0 deletions src/services/FlawService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const FLAW_LIST_FIELDS = [
'unembargo_dt',
'embargoed',
'owner',
'labels',
];

export async function getFlaws(offset = 0, limit = 20, args = {}) {
Expand Down
6 changes: 6 additions & 0 deletions src/types/zodFlaw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
SourceBe0Enum,
IssuerEnum,
FlawReferenceType,
StateEnum,
} from '../generated-client';
import { zodOsimDateTime, ImpactEnumWithBlank, ZodFlawClassification, ZodAlertSchema } from './zodShared';
import { ZodAffectSchema, type ZodAffectType } from './zodAffect';
Expand Down Expand Up @@ -175,6 +176,11 @@ export const ZodFlawSchema = z.object({
affects: z.array(ZodAffectSchema), // read-only
comments: z.array(ZodFlawCommentSchema),
cvss_scores: z.array(FlawCVSSSchema),
labels: z.array(z.object({
label: z.string(),
state: z.nativeEnum(StateEnum),
collaborator: z.string().nullish(),
})).nullish(),
references: z.array(FlawReferenceSchema),
acknowledgments: z.array(FlawAcknowledgmentSchema),
embargoed: z.boolean(),
Expand Down

0 comments on commit 7b8d467

Please sign in to comment.