Skip to content

Commit

Permalink
Merge branch 'list-ordering' into 'main'
Browse files Browse the repository at this point in the history
List ordering

See merge request reportcreator/reportcreator!550
  • Loading branch information
MWedl committed May 16, 2024
2 parents 72c5928 + d1ffb38 commit 3bb4376
Show file tree
Hide file tree
Showing 13 changed files with 159 additions and 26 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Next
* Add sorting options to projects, templates, designs and users lists


## v2024.040 - 2024-05-15
* Collaborative editing in project findings and sections
* Collaborative editing: update notes list when import new notes
Expand Down
11 changes: 6 additions & 5 deletions api/src/reportcreator_api/pentests/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import_templates,
)
from reportcreator_api.archive.import_export.import_export import export_notes, import_notes
from reportcreator_api.pentests.consumers import send_collab_event_project
from reportcreator_api.pentests.customfields.predefined_fields import FINDING_FIELDS_PREDEFINED
from reportcreator_api.pentests.customfields.types import field_definition_to_dict
from reportcreator_api.pentests.models import (
Expand All @@ -62,7 +61,6 @@
UploadedTemplateImage,
UserPublicKey,
)
from reportcreator_api.pentests.models.collab import CollabEvent, CollabEventType
from reportcreator_api.pentests.permissions import (
ArchivedProjectKeyPartPermissions,
IsTemplateEditorOrReadOnly,
Expand Down Expand Up @@ -464,7 +462,7 @@ def filter_linked_project(self, queryset, name, value):


class ProjectTypeOrderingFilter(OrderingFilter):
ordering_fields = ['created', 'name', 'scope', 'status']
ordering_fields = ['created', 'updated', 'name', 'scope', 'status']

def get_queryset_ordering(self, request, queryset, view):
out = []
Expand Down Expand Up @@ -581,9 +579,10 @@ def asset_by_name(self, request, *arg, **kwargs):
class PentestProjectViewSet(CopyViewSetMixin, ExportImportViewSetMixin, HistoryTimelineViewSetMixin, viewsets.ModelViewSet, ViewSetAsync):
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES + [ProjectPermissions]
serializer_class = PentestProjectDetailSerializer
filter_backends = [SearchFilter, DjangoFilterBackend]
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
search_fields = ['name', 'tags', 'language']
filterset_fields = ['language', 'readonly']
ordering_fields = ['created', 'updated', 'name']

def get_serializer_class(self):
if self.action == 'list':
Expand Down Expand Up @@ -1155,7 +1154,7 @@ def to_html(self, request, queryset, view):


class FindingTemplateOrderingFilter(OrderingFilter):
ordering_fields = ['risk', 'usage']
ordering_fields = ['created', 'updated', 'risk', 'usage']

def get_queryset_ordering(self, request, queryset, view):
ordering_query = self.get_ordering(request, queryset, view)[0]
Expand All @@ -1174,6 +1173,8 @@ def get_queryset_ordering(self, request, queryset, view):
return ordering + ['usage_count', 'risk_level_number', 'main_translation__risk_score', 'created']
elif ordering_query == '-usage':
return ordering + ['-usage_count', 'risk_level_number', F('main_translation__risk_score').desc(nulls_last=True), '-created']
elif ordering_query in ['created', '-created', 'updated', '-updated']:
return ordering + [ordering_query]
else:
return None

Expand Down
2 changes: 1 addition & 1 deletion api/src/reportcreator_api/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class PentestUserViewSet(viewsets.ModelViewSet):
filter_backends = [filters.SearchFilter, DjangoFilterBackend, filters.OrderingFilter]
search_fields = ['username', 'email', 'first_name', 'last_name']
filterset_fields = ['username', 'email']
ordering_fields = ['created', 'username']
ordering_fields = ['created', 'updated', 'username']
ordering = ['-created']

def get_queryset(self):
Expand Down
54 changes: 42 additions & 12 deletions frontend/src/components/ListView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,24 @@
</div>
</h1>

<slot name="searchbar" :items="items">
<v-text-field
:model-value="items.search.value"
@update:model-value="updateSearch"
label="Search"
variant="underlined"
spellcheck="false"
hide-details="auto"
autofocus
class="mt-0 mb-2"
/>
<slot name="searchbar" :items="items" :ordering="ordering" :ordering-options="orderingOptions">
<div class="d-flex flex-row">
<v-text-field
:model-value="items.search.value"
@update:model-value="updateSearch"
label="Search"
variant="underlined"
spellcheck="false"
hide-details="auto"
autofocus
class="mt-0 mb-2"
/>
<s-select-ordering
:model-value="ordering"
@update:model-value="updateOrdering"
:ordering-options="props.orderingOptions"
/>
</div>
</slot>
<v-tabs v-if="$slots.tabs" height="30" selected-class="text-primary" class="list-header-tabs">
<slot name="tabs" />
Expand All @@ -48,15 +55,28 @@
<script setup lang="ts" generic="T = any">
import { useSearchableCursorPaginationFetcher } from "~/composables/api";
const orderingModel = defineModel<string|null>('ordering');
const props = defineProps<{
url: string|null;
orderingOptions?: OrderingOption[];
}>();
const ordering = computed(() => {
if (route.query.ordering) {
return props.orderingOptions?.find(o => o.value === route.query.ordering) || null;
} else {
return props.orderingOptions?.find(o => o.id === orderingModel.value) || props.orderingOptions?.[0] || null
}
});
const router = useRouter();
const route = useRoute();
const items = useSearchableCursorPaginationFetcher<T>({
baseURL: props.url,
query: { ...route.query },
query: {
ordering: ordering.value?.value,
...route.query
},
});
useLazyAsyncData(async () => {
await items.fetchNextPage()
Expand All @@ -75,9 +95,19 @@ function updateSearch(search: string) {
router.replace({ query: { ...route.query, search } });
}
function updateOrdering(ordering?: OrderingOption|null) {
orderingModel.value = ordering?.id || null;
if (!ordering) {
ordering = props.orderingOptions![0];
}
router.replace({ query: { ...route.query, ordering: ordering.value } });
items.applyFilters({ ...route.query });
}
defineExpose({
items,
updateSearch,
updateOrdering,
});
</script>

Expand Down
26 changes: 26 additions & 0 deletions frontend/src/components/S/SelectOrdering.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<template>
<s-btn-icon v-if="(props.orderingOptions?.length || 0) > 0">
<v-icon icon="mdi-sort" />
<v-menu activator="parent" location="bottom">
<v-list
:selected="[modelValue]"
@update:selected="modelValue = $event[0] || null"
>
<v-list-item
v-for="option in props.orderingOptions" :key="option.id"
prepend-icon="mdi-sort"
:title="option.title"
:value="option"
/>
</v-list>
</v-menu>
</s-btn-icon>
</template>
<script setup lang="ts">
const modelValue = defineModel<OrderingOption|null>();
const props = defineProps<{
orderingOptions?: OrderingOption[];
}>();
</script>
11 changes: 10 additions & 1 deletion frontend/src/pages/designs/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
<template>
<file-drop-area @drop="importBtnRef.performImport($event)" class="h-100">
<list-view url="/api/v1/projecttypes/?scope=global&ordering=name">
<list-view
url="/api/v1/projecttypes/?scope=global"
v-model:ordering="localSettings.designListOrdering"
:ordering-options="[
{id: 'name', title: 'Name', value: 'name'},
{id: 'created', title: 'Created', value: '-created'},
{id: 'updated', title: 'Updated', value: '-updated'},
]"
>
<template #title>Designs</template>
<template #actions>
<design-create-design-dialog :project-type-scope="ProjectTypeScope.GLOBAL" />
Expand All @@ -27,6 +35,7 @@ useHeadExtended({
});
const route = useRoute();
const localSettings = useLocalSettings();
const apiSettings = useApiSettings();
const importBtnRef = ref();
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/pages/designs/private.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
<template>
<file-drop-area @drop="importBtnRef.performImport($event)" class="h-100">
<list-view url="/api/v1/projecttypes/?scope=private&ordering=name">
<list-view
url="/api/v1/projecttypes/?scope=private"
v-model:ordering="localSettings.designListOrdering"
:ordering-options="[
{id: 'name', title: 'Name', value: 'name'},
{id: 'created', title: 'Created', value: '-created'},
{id: 'updated', title: 'Updated', value: '-updated'},
]"
>
<template #title>Designs</template>
<template #actions>
<design-create-design-dialog :project-type-scope="ProjectTypeScope.PRIVATE" />
Expand All @@ -27,6 +35,7 @@ useHeadExtended({
});
const route = useRoute();
const localSettings = useLocalSettings();
const apiSettings = useApiSettings();
const importBtnRef = ref();
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/pages/projects/finished.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
<template>
<list-view url="/api/v1/pentestprojects/?readonly=true">
<list-view
url="/api/v1/pentestprojects/?readonly=true"
v-model:ordering="localSettings.projectListOrdering"
:ordering-options="[
{id: 'created', title: 'Created', value: '-created'},
{id: 'updated', title: 'Updated', value: '-updated'},
{id: 'name', title: 'Name', value: 'name'},
]"
>
<template #title>Projects</template>
<template #tabs>
<v-tab :to="{path: '/projects/', query: route.query}" exact prepend-icon="mdi-file-document" text="Active" />
Expand All @@ -24,5 +32,6 @@ useHeadExtended({
});
const route = useRoute();
const localSettings = useLocalSettings();
const apiSettings = useApiSettings();
</script>
11 changes: 10 additions & 1 deletion frontend/src/pages/projects/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
<template>
<file-drop-area @drop="importBtn.performImport($event)" class="h-100">
<list-view url="/api/v1/pentestprojects/?readonly=false">
<list-view
url="/api/v1/pentestprojects/?readonly=false"
v-model:ordering="localSettings.projectListOrdering"
:ordering-options="[
{id: 'created', title: 'Created', value: '-created'},
{id: 'updated', title: 'Updated', value: '-updated'},
{id: 'name', title: 'Name', value: 'name'},
]"
>
<template #title>Projects</template>
<template #actions>
<btn-create to="/projects/new/" :disabled="!auth.permissions.value.create_projects" />
Expand Down Expand Up @@ -31,6 +39,7 @@ useHeadExtended({
const auth = useAuth();
const route = useRoute();
const localSettings = useLocalSettings();
const apiSettings = useApiSettings();
const importBtn = ref();
Expand Down
23 changes: 20 additions & 3 deletions frontend/src/pages/templates/index.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
<template>
<file-drop-area @drop="importBtnRef?.performImport($event)" class="h-100">
<list-view ref="listViewRef" url="/api/v1/findingtemplates/">
<list-view
ref="listViewRef"
url="/api/v1/findingtemplates/"
v-model:ordering="localSettings.templateListOrdering"
:ordering-options="[
{id: 'risk', title: 'Severity', value: '-risk'},
{id: 'created', title: 'Created', value: '-created'},
{id: 'updated', title: 'Updated', value: '-updated'},
]"
>
<template #title>Templates</template>
<template #searchbar="{items}">
<template #searchbar="{ items, ordering, orderingOptions }">
<v-row dense class="mb-2 w-100">
<v-col cols="12" md="10">
<v-col cols="12" md="auto" class="flex-grow-1">
<v-text-field
:model-value="items.search.value"
@update:model-value="listViewRef?.updateSearch"
Expand All @@ -24,6 +33,13 @@
class="ma-0"
/>
</v-col>
<v-col cols="auto">
<s-select-ordering
:model-value="ordering"
@update:model-value="listViewRef?.updateOrdering"
:ordering-options="orderingOptions"
/>
</v-col>
</v-row>
</template>
<template #actions>
Expand Down Expand Up @@ -60,6 +76,7 @@ useHeadExtended({
const route = useRoute();
const router = useRouter();
const localSettings = useLocalSettings();
const apiSettings = useApiSettings();
const auth = useAuth();
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/pages/users/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
<template>
<list-view url="/api/v1/pentestusers/">
<list-view
url="/api/v1/pentestusers/"
v-model:ordering="localSettings.userListOrdering"
:ordering-options="[
{id: 'created', title: 'Created', value: '-created'},
{id: 'updated', title: 'Updated', value: '-updated'},
{id: 'name', title: 'Name', value: 'username'},
]"
>
<template #title>Users</template>
<template #actions>
<btn-create
Expand Down Expand Up @@ -56,4 +64,5 @@ useHeadExtended({
});
const auth = useAuth();
const localSettings = useLocalSettings();
</script>
4 changes: 4 additions & 0 deletions frontend/src/stores/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export const useLocalSettings = defineStore('settings', {
reportFieldDefinitionMenuSize: 15,
findingFieldDefinitionMenuSize: 15,
defaultNotesDefinitionMenuSize: 15,
projectListOrdering: null as string|null,
designListOrdering: null as string|null,
templateListOrdering: null as string|null,
userListOrdering: null as string|null,
templateFieldFilterDesign: 'all',
templateFieldFilterHiddenFields: [] as string[],
noteExpandStates: {} as {[noteId: string]: boolean},
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,3 +544,9 @@ export type CWE = {
description: string;
parent: number|null;
};

export type OrderingOption = {
id: string;
title: string;
value: string;
}

0 comments on commit 3bb4376

Please sign in to comment.