Repository: opossum-tool/OpossumUI
Pull request #2481 (2024-01-09 15:41:56)

feat: offer multiple sorting options for attributions
Summary of changes

add toggle state for critical signals progress bar to store
extend sorting algorithm to consider criticality
extend test of sorting
Context and reason for change

There is highlighting for critical signals but before this change it was still hard to find critical signals in long lists of signals coming from e.g. ScanCode.

How can the changes be tested

Open an input file that contains critical signals and look at the sorting.



Patch:
diff --git a/package.json b/package.json
index 7d7d2bee..27b73879 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,7 @@
   },
   "devDependencies": {
     "@babel/core": "^7.23.7",
-    "@babel/preset-env": "^7.23.8",
+    "@babel/preset-env": "^7.23.7",
     "@babel/preset-react": "^7.23.3",
     "@babel/preset-typescript": "^7.23.3",
     "@commitlint/cli": "^18.4.4",
@@ -52,7 +52,7 @@
     "@faker-js/faker": "^8.3.1",
     "@playwright/test": "^1.40.1",
     "@tanstack/eslint-plugin-query": "^5.17.7",
-    "@testing-library/dom": "^9.3.4",
+    "@testing-library/dom": "^9.3.3",
     "@testing-library/jest-dom": "^6.2.0",
     "@testing-library/react": "^14.1.2",
     "@testing-library/user-event": "^14.5.2",
@@ -70,8 +70,8 @@
     "@types/recharts": "^1.8.29",
     "@types/stream-json": "^1.7.7",
     "@types/uuid": "^9.0.7",
-    "@typescript-eslint/eslint-plugin": "^6.18.1",
-    "@typescript-eslint/parser": "^6.18.1",
+    "@typescript-eslint/eslint-plugin": "^6.18.0",
+    "@typescript-eslint/parser": "^6.18.0",
     "@vitejs/plugin-react": "^4.2.1",
     "babel-jest": "^29.7.0",
     "babel-plugin-transform-import-meta": "^2.2.1",
diff --git a/src/Frontend/Components/AggregatedAttributionsPanel/AccordionPanel.util.ts b/src/Frontend/Components/AggregatedAttributionsPanel/AccordionPanel.util.ts
index c1ff4a69..4f964f38 100644
--- a/src/Frontend/Components/AggregatedAttributionsPanel/AccordionPanel.util.ts
+++ b/src/Frontend/Components/AggregatedAttributionsPanel/AccordionPanel.util.ts
@@ -5,6 +5,7 @@
 import {
   Attributions,
   AttributionsToHashes,
+  Criticality,
   DisplayPackageInfo,
   PackageInfo,
 } from '../../../shared/shared-types';
@@ -27,6 +28,7 @@ export function getContainedExternalDisplayPackageInfosWithCount(args: {
   resolvedExternalAttributions: Readonly<Set<string>>;
   attributionsToHashes: Readonly<AttributionsToHashes>;
   panelTitle: PackagePanelTitle;
+  sortByCriticality: boolean;
 }): [Array<string>, DisplayPackageInfosWithCount] {
   const externalAttributionIdsWithCount = getContainedExternalPackages(
     args.selectedResourceId,
@@ -39,6 +41,7 @@ export function getContainedExternalDisplayPackageInfosWithCount(args: {
     args.externalData.attributions,
     args.attributionsToHashes,
     args.panelTitle,
+    args.sortByCriticality,
   );
 }
 
@@ -46,6 +49,7 @@ export function getContainedManualDisplayPackageInfosWithCount(args: {
   selectedResourceId: string;
   manualData: Readonly<PanelAttributionData>;
   panelTitle: PackagePanelTitle;
+  sortByCriticality: boolean;
 }): [Array<string>, DisplayPackageInfosWithCount] {
   const manualAttributionIdsWithCount = getContainedManualPackages(
     args.selectedResourceId,
@@ -68,8 +72,9 @@ export function getContainedManualDisplayPackageInfosWithCount(args: {
   );
 
   packageCardIds.sort(
-    sortDisplayPackageInfosWithCountByCountAndPackageName(
+    sortDisplayPackageInfosWithCountByCriticalityAndCountAndPackageName(
       displayPackageInfosWithCount,
+      args.sortByCriticality,
     ),
   );
 
@@ -81,6 +86,7 @@ export function getExternalDisplayPackageInfosWithCount(
   attributions: Attributions,
   externalAttributionsToHashes: AttributionsToHashes,
   panelTitle: PackagePanelTitle,
+  sortByCriticality: boolean,
 ): [Array<string>, DisplayPackageInfosWithCount] {
   const packageCardIds: Array<string> = [];
   const displayPackageInfosWithCount: DisplayPackageInfosWithCount = {};
@@ -118,8 +124,9 @@ export function getExternalDisplayPackageInfosWithCount(
   );
 
   packageCardIds.sort(
-    sortDisplayPackageInfosWithCountByCountAndPackageName(
+    sortDisplayPackageInfosWithCountByCriticalityAndCountAndPackageName(
       displayPackageInfosWithCount,
+      sortByCriticality,
     ),
   );
 
@@ -144,11 +151,37 @@ function addMergedSignals(
   });
 }
 
+export function getNumericalCriticalityValue(
+  criticality: Criticality | undefined,
+) {
+  switch (criticality) {
+    case Criticality.High:
+      return 2;
+    case Criticality.Medium:
+      return 1;
+    default:
+      return 0;
+  }
+}
+
 //exported for testing
-export function sortDisplayPackageInfosWithCountByCountAndPackageName(
+export function sortDisplayPackageInfosWithCountByCriticalityAndCountAndPackageName(
   displayPackageInfosWithCount: DisplayPackageInfosWithCount,
+  sortByCriticality: boolean = false,
 ) {
   return function (id1: string, id2: string): number {
+    const p1: DisplayPackageInfo =
+      displayPackageInfosWithCount[id1].displayPackageInfo;
+    const p2: DisplayPackageInfo =
+      displayPackageInfosWithCount[id2].displayPackageInfo;
+
+    if (sortByCriticality && p1?.criticality !== p2?.criticality) {
+      return (
+        getNumericalCriticalityValue(p2?.criticality) -
+        getNumericalCriticalityValue(p1?.criticality)
+      );
+    }
+
     if (
       displayPackageInfosWithCount[id1].count !==
       displayPackageInfosWithCount[id2].count
@@ -159,10 +192,6 @@ export function sortDisplayPackageInfosWithCountByCountAndPackageName(
       );
     }
 
-    const p1: DisplayPackageInfo =
-      displayPackageInfosWithCount[id1].displayPackageInfo;
-    const p2: DisplayPackageInfo =
-      displayPackageInfosWithCount[id2].displayPackageInfo;
     if (p1?.packageName && p2?.packageName) {
       return p1.packageName.toLowerCase() < p2.packageName.toLowerCase()
         ? -1
diff --git a/src/Frontend/Components/AggregatedAttributionsPanel/SyncAccordionPanel.tsx b/src/Frontend/Components/AggregatedAttributionsPanel/SyncAccordionPanel.tsx
index 7222daba..8283a5f7 100644
--- a/src/Frontend/Components/AggregatedAttributionsPanel/SyncAccordionPanel.tsx
+++ b/src/Frontend/Components/AggregatedAttributionsPanel/SyncAccordionPanel.tsx
@@ -7,8 +7,10 @@ import {
   Attributions,
   AttributionsToHashes,
 } from '../../../shared/shared-types';
+import { text } from '../../../shared/text';
 import { PackagePanelTitle } from '../../enums/enums';
 import { AttributionIdWithCount } from '../../types/types';
+import { useActiveSortingInAuditView } from '../../util/use-active-sorting';
 import { AccordionPanel } from './AccordionPanel';
 import { getExternalDisplayPackageInfosWithCount } from './AccordionPanel.util';
 
@@ -24,12 +26,15 @@ interface SyncAccordionPanelProps {
 export function SyncAccordionPanel(
   props: SyncAccordionPanelProps,
 ): ReactElement {
+  const [activeSorting] = useActiveSortingInAuditView();
+
   const [sortedPackageCardIds, displayPackageInfosWithCount] =
     getExternalDisplayPackageInfosWithCount(
       props.getAttributionIdsWithCount(),
       props.attributions,
       props.attributionsToHashes,
       props.title,
+      activeSorting === text.auditViewSorting.byCriticality,
     );
 
   return (
diff --git a/src/Frontend/Components/AllAttributionsPanel/AllAttributionsPanel.tsx b/src/Frontend/Components/AllAttributionsPanel/AllAttributionsPanel.tsx
index b3f6f5ed..9e9cfa03 100644
--- a/src/Frontend/Components/AllAttributionsPanel/AllAttributionsPanel.tsx
+++ b/src/Frontend/Components/AllAttributionsPanel/AllAttributionsPanel.tsx
@@ -5,6 +5,7 @@
 import MuiPaper from '@mui/material/Paper';
 import { ReactElement } from 'react';
 
+import { text } from '../../../shared/text';
 import { PackagePanelTitle } from '../../enums/enums';
 import { OpossumColors } from '../../shared-styles';
 import { selectPackageCardInAuditViewOrOpenUnsavedPopup } from '../../state/actions/popup-actions/popup-actions';
@@ -12,6 +13,8 @@ import { addToSelectedResource } from '../../state/actions/resource-actions/save
 import { useAppDispatch } from '../../state/hooks';
 import { DisplayPackageInfos, PackageCardConfig } from '../../types/types';
 import { convertDisplayPackageInfoToPackageInfo } from '../../util/convert-package-info';
+import { getAlphabeticalComparerForAttributions } from '../../util/get-alphabetical-comparer';
+import { useActiveSortingInAuditView } from '../../util/use-active-sorting';
 import { PackageCard } from '../PackageCard/PackageCard';
 import { PackageList } from '../PackageList/PackageList';
 
@@ -35,6 +38,7 @@ export function AllAttributionsPanel(
   props: AllAttributionsPanelProps,
 ): ReactElement {
   const dispatch = useAppDispatch();
+  const [activeSorting] = useActiveSortingInAuditView();
 
   function getPackageCard(packageCardId: string): ReactElement | null {
     const displayPackageInfo = props.displayPackageInfos[packageCardId];
@@ -74,11 +78,18 @@ export function AllAttributionsPanel(
     );
   }
 
+  const sortedPackageCardIds = Object.keys(props.displayPackageInfos).sort(
+    getAlphabeticalComparerForAttributions(
+      props.displayPackageInfos,
+      activeSorting === text.auditViewSorting.byCriticality,
+    ),
+  );
+
   return (
     <MuiPaper sx={classes.root} elevation={0} square={true}>
       <PackageList
         displayPackageInfos={props.displayPackageInfos}
-        sortedPackageCardIds={Object.keys(props.displayPackageInfos)}
+        sortedPackageCardIds={sortedPackageCardIds}
         getAttributionCard={getPackageCard}
         listTitle={PackagePanelTitle.AllAttributions}
         fullHeight
diff --git a/src/Frontend/Components/AttributionColumn/AuditingOptions.tsx b/src/Frontend/Components/AttributionColumn/AuditingOptions.tsx
index 9f9e2ccb..c4b4c6f4 100644
--- a/src/Frontend/Components/AttributionColumn/AuditingOptions.tsx
+++ b/src/Frontend/Components/AttributionColumn/AuditingOptions.tsx
@@ -1,83 +1,452 @@
+/* eslint-disable @typescript-eslint/no-magic-numbers */
 // SPDX-FileCopyrightText: Meta Platforms, Inc. and its affiliates
 // SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
 //
 // SPDX-License-Identifier: Apache-2.0
 import AddIcon from '@mui/icons-material/Add';
+import CheckIcon from '@mui/icons-material/Check';
+import SentimentDissatisfiedIcon from '@mui/icons-material/SentimentDissatisfied';
+import SentimentSatisfiedIcon from '@mui/icons-material/SentimentSatisfied';
+import SentimentSatisfiedAltIcon from '@mui/icons-material/SentimentSatisfiedAltOutlined';
+import SentimentVeryDissatisfiedIcon from '@mui/icons-material/SentimentVeryDissatisfied';
+import SentimentVerySatisfiedIcon from '@mui/icons-material/SentimentVerySatisfied';
 import MuiBox from '@mui/material/Box';
 import MuiChip from '@mui/material/Chip';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import MuiListItemText from '@mui/material/ListItemText';
+import MuiMenu from '@mui/material/Menu';
+import MuiMenuItem from '@mui/material/MenuItem';
+import MuiRating from '@mui/material/Rating';
 import { SxProps } from '@mui/system';
-import { useState } from 'react';
+import { useMemo, useState } from 'react';
 
-import { DisplayPackageInfo } from '../../../shared/shared-types';
+import { DisplayPackageInfo, FollowUp } from '../../../shared/shared-types';
 import { text } from '../../../shared/text';
-import { baseIcon } from '../../shared-styles';
-import { SelectMenu } from '../SelectMenu/SelectMenu';
-import { useAuditingOptions } from './AuditingOptions.util';
+import { baseIcon, OpossumColors } from '../../shared-styles';
+import { setTemporaryDisplayPackageInfo } from '../../state/actions/resource-actions/all-views-simple-actions';
+import { useAppDispatch, useAppSelector, useAppStore } from '../../state/hooks';
+import {
+  getExternalAttributions,
+  getExternalAttributionSources,
+  getIsPreferenceFeatureEnabled,
+  getTemporaryDisplayPackageInfo,
+} from '../../state/selectors/all-views-resource-selectors';
+import { prettifySource } from '../../util/prettify-source';
+import { useUserSetting } from '../../util/use-user-setting';
+import {
+  ExcludeFromNoticeIcon,
+  FollowUpIcon,
+  NeedsReviewIcon,
+  PreferredIcon,
+  PreSelectedIcon,
+  SourceIcon,
+  WasPreferredIcon,
+} from '../Icons/Icons';
 
 const classes = {
   container: { display: 'flex', gap: '8px', flexWrap: 'wrap' },
+  menuPaper: {
+    overflow: 'visible',
+    filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
+    mt: 1.5,
+    '&:before': {
+      content: '""',
+      display: 'block',
+      position: 'absolute',
+      top: 0,
+      left: '50%',
+      width: 10,
+      height: 10,
+      bgcolor: 'background.paper',
+      transform: 'translateY(-50%) rotate(45deg)',
+      zIndex: 0,
+    },
+  },
 } satisfies SxProps;
 
+interface ChipDatum {
+  active: boolean;
+  option: string;
+  icon?: React.ReactElement;
+  interactive: boolean;
+  label: React.ReactNode;
+  onAdd?(): void;
+  onDelete?(): void;
+}
+
 interface Props {
   packageInfo: DisplayPackageInfo;
   isEditable: boolean;
 }
 
-export function AuditingOptions({ packageInfo, isEditable }: Props) {
-  const options = useAuditingOptions({ packageInfo, isEditable });
+export function AuditingOptions({
+  packageInfo,
+  isEditable,
+}: Props): React.ReactNode {
+  const chips = useChips({ packageInfo, isEditable });
   const [anchorEl, setAnchorEl] = useState<HTMLElement>();
-  const unselectedOptions = options.filter(
-    ({ selected, interactive }) => !selected && interactive,
+  const [pendingOptions, setPendingOptions] = useState<Array<string>>([]);
+  const inactiveChips = chips.filter(
+    ({ active, interactive }) => !active && interactive,
   );
 
-  return options.length ? (
+  return chips.length ? (
     <>
       <MuiBox sx={classes.container}>
         {renderTriggerButton()}
-        {renderSelectedOptions()}
+        {renderActiveChips()}
       </MuiBox>
-      <SelectMenu
-        anchorEl={anchorEl}
-        hideSelected
-        setAnchorEl={setAnchorEl}
-        options={options.filter(({ interactive }) => interactive)}
-        sx={{ marginTop: '8px' }}
-      />
+      {renderMenu()}
     </>
   ) : null;
 
   function renderTriggerButton() {
     return (
       isEditable &&
-      !!unselectedOptions.length && (
+      !!inactiveChips.length && (
         <MuiChip
           label={text.auditingOptions.add}
           color={'primary'}
           icon={<AddIcon color="primary" sx={baseIcon} />}
           size={'small'}
           onClick={(event) => setAnchorEl(event.currentTarget)}
+          aria-controls={anchorEl ? 'attribution-options-menu' : undefined}
+          aria-haspopup={'true'}
+          aria-expanded={anchorEl ? 'true' : undefined}
         />
       )
     );
   }
 
-  function renderSelectedOptions() {
-    return options.map(
-      (
-        { label, icon, deleteIcon, selected, onDelete, interactive, id },
-        index,
-      ) =>
-        selected ? (
+  function renderActiveChips() {
+    return chips.map(
+      ({ label, icon, active, onDelete, interactive, option }, index) =>
+        active ? (
           <MuiChip
             key={index}
             label={label}
             size={'small'}
             icon={icon}
             onDelete={interactive ? onDelete : undefined}
-            data-testid={`auditing-option-${id}`}
-            deleteIcon={deleteIcon}
+            data-testid={`auditing-option-${option}`}
           />
         ) : null,
     );
   }
+
+  function renderMenu() {
+    return (
+      <MuiMenu
+        anchorEl={anchorEl}
+        id={'attribution-options-menu'}
+        open={!!anchorEl}
+        onClose={() => {
+          setAnchorEl(undefined);
+          pendingOptions.forEach((option) => {
+            chips.find((chip) => chip.option === option)?.onAdd?.();
+          });
+          setPendingOptions([]);
+        }}
+        slotProps={{ paper: { elevation: 0, sx: classes.menuPaper } }}
+        transformOrigin={{ horizontal: 'center', vertical: 'top' }}
+        anchorOrigin={{ horizontal: 'center', vertical: 'bottom' }}
+        MenuListProps={{ variant: 'menu', sx: { padding: 0 } }}
+      >
+        {renderInactiveChips()}
+      </MuiMenu>
+    );
+
+    function renderInactiveChips() {
+      return inactiveChips.map(({ label, icon, option }, index) => (
+        <MuiMenuItem
+          sx={{ padding: '12px' }}
+          key={index}
+          onClick={() => {
+            option &&
+              setPendingOptions((prev) =>
+                prev.includes(option)
+                  ? prev.filter((value) => value !== option)
+                  : prev.concat(option),
+              );
+          }}
+          divider={index + 1 !== inactiveChips.length}
+          disableRipple
+        >
+          <ListItemIcon>{icon}</ListItemIcon>
+          <MuiListItemText
+            primary={label}
+            primaryTypographyProps={{ sx: { marginTop: '2px' } }}
+          />
+          <CheckIcon
+            sx={{
+              width: '20px',
+              height: '20px',
+              marginLeft: '16px',
+              visibility:
+                option && pendingOptions.includes(option)
+                  ? undefined
+                  : 'hidden',
+            }}
+          />
+        </MuiMenuItem>
+      ));
+    }
+  }
+}
+
+function useChips({
+  packageInfo,
+  isEditable,
+}: {
+  packageInfo: DisplayPackageInfo;
+  isEditable: boolean;
+}) {
+  const dispatch = useAppDispatch();
+  const store = useAppStore();
+  const [qaMode] = useUserSetting({ defaultValue: false, key: 'qaMode' });
+  const attributionSources = useAppSelector(getExternalAttributionSources);
+  const isPreferenceFeatureEnabled = useAppSelector(
+    getIsPreferenceFeatureEnabled,
+  );
+  const externalAttributions = useAppSelector(getExternalAttributions);
+  const source = useMemo(() => {
+    const sourceName =
+      packageInfo.source?.additionalName || packageInfo.source?.name;
+
+    if (sourceName) {
+      return {
+        sourceName,
+        fromOrigin: false,
+      };
+    }
+
+    const originSource = (
+      packageInfo.originIds?.length
+        ? Object.values(externalAttributions).find(
+            (signal) =>
+              signal.originIds?.some(
+                (id) => packageInfo.originIds?.includes(id),
+              ),
+          )
+        : undefined
+    )?.source;
+
+    return {
+      sourceName: originSource?.additionalName || originSource?.name,
+      fromOrigin: true,
+    };
+  }, [
+    externalAttributions,
+    packageInfo.originIds,
+    packageInfo.source?.additionalName,
+    packageInfo.source?.name,
+  ]);
+
+  return useMemo<Array<ChipDatum>>(
+    () => [
+      {
+        option: 'preferred',
+        label: text.auditingOptions.currentlyPreferred,
+        icon: <PreferredIcon noTooltip />,
+        active: !!packageInfo.preferred,
+        onAdd: () =>
+          dispatch(
+            setTemporaryDisplayPackageInfo({
+              ...getTemporaryDisplayPackageInfo(store.getState()),
+              preferred: true,
+            }),
+          ),
+        onDelete: () =>
+          dispatch(
+            setTemporaryDisplayPackageInfo({
+              ...getTemporaryDisplayPackageInfo(store.getState()),
+              preferred: false,
+            }),
+          ),
+        interactive: isPreferenceFeatureEnabled && qaMode,
+      },
+      {
+        option: 'was-preferred',
+        label: text.auditingOptions.previouslyPreferred,
+        icon: <WasPreferredIcon noTooltip />,
+        active: !!packageInfo.wasPreferred,
+        interactive: false,
+      },
+      {
+        option: 'pre-selected',
+        label: text.auditingOptions.preselected,
+        icon: <PreSelectedIcon noTooltip />,
+        active: !!packageInfo.preSelected,
+        interactive: false,
+      },
+      {
+        option: 'follow-up',
+        label: text.auditingOptions.followUp,
+        icon: <FollowUpIcon noTooltip />,
+        active: !!packageInfo.followUp,
+        onAdd: () =>
+          dispatch(
+            setTemporaryDisplayPackageInfo({
+              ...getTemporaryDisplayPackageInfo(store.getState()),
+              followUp: FollowUp,
+            }),
+          ),
+        onDelete: () =>
+          dispatch(
+            setTemporaryDisplayPackageInfo({
+              ...getTemporaryDisplayPackageInfo(store.getState()),
+              followUp: undefined,
+            }),
+          ),
+        interactive: true,
+      },
+      {
+        option: 'needs-review',
+        label: text.auditingOptions.needsReview,
+        icon: <NeedsReviewIcon noTooltip />,
+        active: !!packageInfo.needsReview,
+        onAdd: () =>
+          dispatch(
+            setTemporaryDisplayPackageInfo({
+              ...getTemporaryDisplayPackageInfo(store.getState()),
+              needsReview: true,
+            }),
+          ),
+        onDelete: () =>
+          dispatch(
+            setTemporaryDisplayPackageInfo({
+              ...getTemporaryDisplayPackageInfo(store.getState()),
+              needsReview: false,
+            }),
+          ),
+        interactive: true,
+      },
+      {
+        option: 'excluded-from-notice',
+        label: text.auditingOptions.excludedFromNotice,
+        icon: <ExcludeFromNoticeIcon noTooltip />,
+        active: !!packageInfo.excludeFromNotice,
+        onAdd: () =>
+          dispatch(
+            setTemporaryDisplayPackageInfo({
+              ...getTemporaryDisplayPackageInfo(store.getState()),
+              excludeFromNotice: true,
+            }),
+          ),
+        onDelete: () =>
+          dispatch(
+            setTemporaryDisplayPackageInfo({
+              ...getTemporaryDisplayPackageInfo(store.getState()),
+              excludeFromNotice: false,
+            }),
+          ),
+        interactive: true,
+      },
+      {
+        option: 'source',
+        label: `${
+          source.fromOrigin ? text.attributionColumn.originallyFrom : ''
+        }${prettifySource(source.sourceName, attributionSources)}`,
+        icon: <SourceIcon noTooltip />,
+        active: !!source.sourceName,
+        interactive: false,
+      },
+      {
+        option: 'confidence',
+        label: text.auditingOptions.confidence,
+        icon: (
+          <MuiRating
+            size={'small'}
+            sx={{
+              '& .MuiRating-iconEmpty .MuiSvgIcon-root': {
+                color: OpossumColors.grey,
+              },
+            }}
+            value={((packageInfo.attributionConfidence || 0) / 100) * 5}
+            onChange={(_, newValue) =>
+              newValue &&
+              dispatch(
+                setTemporaryDisplayPackageInfo({
+                  ...getTemporaryDisplayPackageInfo(store.getState()),
+                  attributionConfidence: newValue * 20,
+                }),
+              )
+            }
+            IconContainerComponent={({ value, ...rest }) => (
+              <span
+                aria-disabled={
+                  Math.round(
+                    ((packageInfo.attributionConfidence || 0) / 100) * 5,
+                  ) !== value
+                }
+                aria-label={`confidence of ${value}`}
+                {...rest}
+              >
+                {getSatisfaction(value)}
+              </span>
+            )}
+            readOnly={!isEditable}
+            highlightSelectedOnly
+          />
+        ),
+        active: true,
+        interactive: false,
+      },
+    ],
+    [
+      attributionSources,
+      dispatch,
+      isEditable,
+      isPreferenceFeatureEnabled,
+      packageInfo.attributionConfidence,
+      packageInfo.excludeFromNotice,
+      packageInfo.followUp,
+      packageInfo.needsReview,
+      packageInfo.preSelected,
+      packageInfo.preferred,
+      packageInfo.wasPreferred,
+      qaMode,
+      source,
+      store,
+    ],
+  );
+}
+
+function getSatisfaction(value: number): React.ReactNode {
+  if (value <= 1) {
+    return (
+      <SentimentVeryDissatisfiedIcon
+        color={'error'}
+        sx={{ width: '19px', height: '19px' }}
+      />
+    );
+  } else if (value === 2) {
+    return (
+      <SentimentDissatisfiedIcon
+        color={'error'}
+        sx={{ width: '19px', height: '19px' }}
+      />
+    );
+  } else if (value === 3) {
+    return (
+      <SentimentSatisfiedIcon
+        color={'warning'}
+        sx={{ width: '19px', height: '19px' }}
+      />
+    );
+  } else if (value === 4) {
+    return (
+      <SentimentSatisfiedAltIcon
+        color={'success'}
+        sx={{ width: '19px', height: '19px' }}
+      />
+    );
+  }
+
+  return (
+    <SentimentVerySatisfiedIcon
+      color={'success'}
+      sx={{ width: '19px', height: '19px' }}
+    />
+  );
 }
diff --git a/src/Frontend/Components/AttributionColumn/AuditingOptions.util.tsx b/src/Frontend/Components/AttributionColumn/AuditingOptions.util.tsx
deleted file mode 100644
index 705dbbcf..00000000
--- a/src/Frontend/Components/AttributionColumn/AuditingOptions.util.tsx
+++ /dev/null
@@ -1,334 +0,0 @@
-// SPDX-FileCopyrightText: Meta Platforms, Inc. and its affiliates
-// SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
-//
-// SPDX-License-Identifier: Apache-2.0
-
-/* eslint-disable @typescript-eslint/no-magic-numbers */
-import ReplayIcon from '@mui/icons-material/Replay';
-import SentimentDissatisfiedIcon from '@mui/icons-material/SentimentDissatisfied';
-import SentimentSatisfiedIcon from '@mui/icons-material/SentimentSatisfied';
-import SentimentSatisfiedAltIcon from '@mui/icons-material/SentimentSatisfiedAltOutlined';
-import SentimentVeryDissatisfiedIcon from '@mui/icons-material/SentimentVeryDissatisfied';
-import SentimentVerySatisfiedIcon from '@mui/icons-material/SentimentVerySatisfied';
-import MuiRating from '@mui/material/Rating';
-import { useMemo } from 'react';
-
-import { DisplayPackageInfo, FollowUp } from '../../../shared/shared-types';
-import { text } from '../../../shared/text';
-import { OpossumColors } from '../../shared-styles';
-import { setTemporaryDisplayPackageInfo } from '../../state/actions/resource-actions/all-views-simple-actions';
-import { useAppDispatch, useAppSelector, useAppStore } from '../../state/hooks';
-import {
-  getExternalAttributions,
-  getExternalAttributionSources,
-  getIsPreferenceFeatureEnabled,
-  getTemporaryDisplayPackageInfo,
-} from '../../state/selectors/all-views-resource-selectors';
-import { convertPackageInfoToDisplayPackageInfo } from '../../util/convert-package-info';
-import { getStrippedPackageInfo } from '../../util/get-stripped-package-info';
-import { prettifySource } from '../../util/prettify-source';
-import { useUserSetting } from '../../util/use-user-setting';
-import {
-  ExcludeFromNoticeIcon,
-  FollowUpIcon,
-  ModifiedPreferredIcon,
-  NeedsReviewIcon,
-  PreferredIcon,
-  PreSelectedIcon,
-  SourceIcon,
-  WasPreferredIcon,
-} from '../Icons/Icons';
-import { SelectMenuOption } from '../SelectMenu/SelectMenu';
-
-interface AuditingOption extends SelectMenuOption {
-  deleteIcon?: React.ReactElement;
-  interactive: boolean;
-}
-
-export function useAuditingOptions({
-  packageInfo,
-  isEditable,
-}: {
-  packageInfo: DisplayPackageInfo;
-  isEditable: boolean;
-}) {
-  const dispatch = useAppDispatch();
-  const store = useAppStore();
-  const [qaMode] = useUserSetting({ defaultValue: false, key: 'qaMode' });
-  const attributionSources = useAppSelector(getExternalAttributionSources);
-  const isPreferenceFeatureEnabled = useAppSelector(
-    getIsPreferenceFeatureEnabled,
-  );
-  const externalAttributions = useAppSelector(getExternalAttributions);
-  const source = useMemo(() => {
-    const sourceName =
-      packageInfo.source?.additionalName || packageInfo.source?.name;
-
-    if (sourceName) {
-      return {
-        sourceName,
-        fromOrigin: false,
-      };
-    }
-
-    const originSource = (
-      packageInfo.originIds?.length
-        ? Object.values(externalAttributions).find(
-            ({ originIds }) =>
-              originIds?.some((id) => packageInfo.originIds?.includes(id)),
-          )
-        : undefined
-    )?.source;
-
-    return {
-      sourceName: originSource?.additionalName || originSource?.name,
-      fromOrigin: true,
-    };
-  }, [
-    externalAttributions,
-    packageInfo.originIds,
-    packageInfo.source?.additionalName,
-    packageInfo.source?.name,
-  ]);
-
-  const originalPreferred = useMemo(
-    () =>
-      !!packageInfo.originIds?.length && !packageInfo.wasPreferred
-        ? Object.values(externalAttributions).find(
-            ({ originIds, wasPreferred }) =>
-              wasPreferred &&
-              originIds?.some((id) => packageInfo.originIds?.includes(id)),
-          )
-        : undefined,
-    [externalAttributions, packageInfo.originIds, packageInfo.wasPreferred],
-  );
-
-  return useMemo<Array<AuditingOption>>(
-    () => [
-      {
-        id: 'preferred',
-        label: text.auditingOptions.currentlyPreferred,
-        icon: <PreferredIcon noTooltip />,
-        selected: !!packageInfo.preferred,
-        onAdd: () =>
-          dispatch(
-            setTemporaryDisplayPackageInfo({
-              ...getTemporaryDisplayPackageInfo(store.getState()),
-              preferred: true,
-            }),
-          ),
-        onDelete: () =>
-          dispatch(
-            setTemporaryDisplayPackageInfo({
-              ...getTemporaryDisplayPackageInfo(store.getState()),
-              preferred: false,
-            }),
-          ),
-        interactive: isPreferenceFeatureEnabled && qaMode,
-      },
-      {
-        id: 'was-preferred',
-        label: text.auditingOptions.previouslyPreferred,
-        icon: <WasPreferredIcon noTooltip />,
-        selected: !!packageInfo.wasPreferred,
-        interactive: false,
-      },
-      {
-        id: 'is-modified-preferred',
-        label: text.auditingOptions.modifiedPreferred,
-        icon: <ModifiedPreferredIcon noTooltip />,
-        selected: !!originalPreferred,
-        interactive: !!originalPreferred,
-        deleteIcon: <ReplayIcon aria-label={'undo modified preferred'} />,
-        onDelete: originalPreferred
-          ? () =>
-              dispatch(
-                setTemporaryDisplayPackageInfo(
-                  convertPackageInfoToDisplayPackageInfo(
-                    getStrippedPackageInfo(originalPreferred),
-                    [],
-                  ),
-                ),
-              )
-          : undefined,
-      },
-      {
-        id: 'pre-selected',
-        label: text.auditingOptions.preselected,
-        icon: <PreSelectedIcon noTooltip />,
-        selected: !!packageInfo.preSelected,
-        interactive: false,
-      },
-      {
-        id: 'follow-up',
-        label: text.auditingOptions.followUp,
-        icon: <FollowUpIcon noTooltip />,
-        selected: !!packageInfo.followUp,
-        onAdd: () =>
-          dispatch(
-            setTemporaryDisplayPackageInfo({
-              ...getTemporaryDisplayPackageInfo(store.getState()),
-              followUp: FollowUp,
-            }),
-          ),
-        onDelete: () =>
-          dispatch(
-            setTemporaryDisplayPackageInfo({
-              ...getTemporaryDisplayPackageInfo(store.getState()),
-              followUp: undefined,
-            }),
-          ),
-        interactive: true,
-      },
-      {
-        id: 'needs-review',
-        label: text.auditingOptions.needsReview,
-        icon: <NeedsReviewIcon noTooltip />,
-        selected: !!packageInfo.needsReview,
-        onAdd: () =>
-          dispatch(
-            setTemporaryDisplayPackageInfo({
-              ...getTemporaryDisplayPackageInfo(store.getState()),
-              needsReview: true,
-            }),
-          ),
-        onDelete: () =>
-          dispatch(
-            setTemporaryDisplayPackageInfo({
-              ...getTemporaryDisplayPackageInfo(store.getState()),
-              needsReview: false,
-            }),
-          ),
-        interactive: true,
-      },
-      {
-        id: 'excluded-from-notice',
-        label: text.auditingOptions.excludedFromNotice,
-        icon: <ExcludeFromNoticeIcon noTooltip />,
-        selected: !!packageInfo.excludeFromNotice,
-        onAdd: () =>
-          dispatch(
-            setTemporaryDisplayPackageInfo({
-              ...getTemporaryDisplayPackageInfo(store.getState()),
-              excludeFromNotice: true,
-            }),
-          ),
-        onDelete: () =>
-          dispatch(
-            setTemporaryDisplayPackageInfo({
-              ...getTemporaryDisplayPackageInfo(store.getState()),
-              excludeFromNotice: false,
-            }),
-          ),
-        interactive: true,
-      },
-      {
-        id: 'source',
-        label: `${
-          source.fromOrigin ? text.attributionColumn.originallyFrom : ''
-        }${prettifySource(source.sourceName, attributionSources)}`,
-        icon: <SourceIcon noTooltip />,
-        selected: !!source.sourceName,
-        interactive: false,
-      },
-      {
-        id: 'confidence',
-        label: text.auditingOptions.confidence,
-        icon: (
-          <MuiRating
-            size={'small'}
-            sx={{
-              '& .MuiRating-iconEmpty .MuiSvgIcon-root': {
-                color: OpossumColors.grey,
-              },
-            }}
-            value={((packageInfo.attributionConfidence || 0) / 100) * 5}
-            onChange={(_, newValue) =>
-              newValue &&
-              dispatch(
-                setTemporaryDisplayPackageInfo({
-                  ...getTemporaryDisplayPackageInfo(store.getState()),
-                  attributionConfidence: newValue * 20,
-                }),
-              )
-            }
-            IconContainerComponent={({ value, ...rest }) => (
-              <span
-                aria-disabled={
-                  Math.round(
-                    ((packageInfo.attributionConfidence || 0) / 100) * 5,
-                  ) !== value
-                }
-                aria-label={`confidence of ${value}`}
-                {...rest}
-              >
-                {getSatisfaction(value)}
-              </span>
-            )}
-            readOnly={!isEditable}
-            highlightSelectedOnly
-          />
-        ),
-        selected: true,
-        interactive: false,
-      },
-    ],
-    [
-      attributionSources,
-      dispatch,
-      isEditable,
-      isPreferenceFeatureEnabled,
-      originalPreferred,
-      packageInfo.attributionConfidence,
-      packageInfo.excludeFromNotice,
-      packageInfo.followUp,
-      packageInfo.needsReview,
-      packageInfo.preSelected,
-      packageInfo.preferred,
-      packageInfo.wasPreferred,
-      qaMode,
-      source.fromOrigin,
-      source.sourceName,
-      store,
-    ],
-  );
-}
-
-function getSatisfaction(value: number): React.ReactNode {
-  if (value <= 1) {
-    return (
-      <SentimentVeryDissatisfiedIcon
-        color={'error'}
-        sx={{ width: '19px', height: '19px' }}
-      />
-    );
-  } else if (value === 2) {
-    return (
-      <SentimentDissatisfiedIcon
-        color={'error'}
-        sx={{ width: '19px', height: '19px' }}
-      />
-    );
-  } else if (value === 3) {
-    return (
-      <SentimentSatisfiedIcon
-        color={'warning'}
-        sx={{ width: '19px', height: '19px' }}
-      />
-    );
-  } else if (value === 4) {
-    return (
-      <SentimentSatisfiedAltIcon
-        color={'success'}
-        sx={{ width: '19px', height: '19px' }}
-      />
-    );
-  }
-
-  return (
-    <SentimentVerySatisfiedIcon
-      color={'success'}
-      sx={{ width: '19px', height: '19px' }}
-    />
-  );
-}
diff --git a/src/Frontend/Components/AttributionList/AttributionList.tsx b/src/Frontend/Components/AttributionList/AttributionList.tsx
index bcda2621..22bd0dbb 100644
--- a/src/Frontend/Components/AttributionList/AttributionList.tsx
+++ b/src/Frontend/Components/AttributionList/AttributionList.tsx
@@ -3,10 +3,12 @@
 //
 // SPDX-License-Identifier: Apache-2.0
 import FilterAltIcon from '@mui/icons-material/FilterAlt';
+import SortIcon from '@mui/icons-material/Sort';
 import MuiBox from '@mui/material/Box';
-import { useState } from 'react';
+import { ChangeEvent, useState } from 'react';
 
 import { Attributions } from '../../../shared/shared-types';
+import { text } from '../../../shared/text';
 import { clickableIcon, disabledIcon } from '../../shared-styles';
 import { changeSelectedAttributionIdOrOpenUnsavedPopup } from '../../state/actions/popup-actions/popup-actions';
 import { useAppDispatch, useAppSelector } from '../../state/hooks';
@@ -14,15 +16,23 @@ import { getSelectedAttributionIdInAttributionView } from '../../state/selectors
 import { DisplayPackageInfos } from '../../types/types';
 import { convertPackageInfoToDisplayPackageInfo } from '../../util/convert-package-info';
 import { getAlphabeticalComparerForAttributions } from '../../util/get-alphabetical-comparer';
+import {
+  AttributionViewSorting,
+  useActiveSortingInAttributionView,
+} from '../../util/use-active-sorting';
 import { AttributionCountsPanel } from '../AttributionCountsPanel/AttributionCountsPanel';
 import { FilterMultiSelect } from '../Filter/FilterMultiSelect';
 import { useFilteredAttributions } from '../Filter/FilterMultiSelect.util';
 import { IconButton } from '../IconButton/IconButton';
+import { Dropdown, MenuItem } from '../InputElements/Dropdown';
 import { PackageCard } from '../PackageCard/PackageCard';
 import { AttributionsViewPackageList } from '../PackageList/AttributionsViewPackageList';
 import { ResizableBox } from '../ResizableBox/ResizableBox';
 
 const classes = {
+  dropdown: {
+    marginTop: '8px',
+  },
   topElements: {
     display: 'flex',
     alignItems: 'center',
@@ -36,6 +46,18 @@ const classes = {
   },
 };
 
+const SORTING_ALGORITHMS: Array<MenuItem> = [
+  {
+    value: text.attributionViewSorting.alphabetical,
+    name: text.attributionViewSorting.alphabetical,
+  },
+  {
+    value: text.attributionViewSorting.byCriticality,
+    name: text.attributionViewSorting.byCriticality,
+  },
+];
+const defaultSorting = text.attributionViewSorting.alphabetical;
+
 export function AttributionList() {
   const dispatch = useAppDispatch();
   const selectedPackageCardIdInAttributionView = useAppSelector(
@@ -46,8 +68,21 @@ export function AttributionList() {
   const hasActiveFilters = !!activeFilters.length;
   const [showMultiSelect, setShowMultiselect] = useState(hasActiveFilters);
 
+  const [activeSorting, setActiveSorting] = useActiveSortingInAttributionView();
+  const defaultSortingIsActive = activeSorting === defaultSorting;
+  const [showSortingSelect, setShowSortingSelect] = useState<boolean>(
+    !defaultSortingIsActive,
+  );
+
   const { filteredAndSortedPackageCardIds, filteredDisplayPackageInfos } =
-    getFilteredAndSortedPackageCardIdsAndDisplayPackageInfos(attributions);
+    getFilteredAndSortedPackageCardIdsAndDisplayPackageInfos(
+      attributions,
+      activeSorting === text.attributionViewSorting.byCriticality,
+    );
+
+  function onSortInputChange(event: ChangeEvent<HTMLInputElement>): void {
+    setActiveSorting(event.target.value as AttributionViewSorting);
+  }
 
   function getAttributionCard(
     packageCardId: string,
@@ -82,18 +117,42 @@ export function AttributionList() {
     >
       <MuiBox sx={classes.topElements}>
         <AttributionCountsPanel />
-        <IconButton
-          tooltipTitle={'Filters'}
-          tooltipPlacement={'right'}
-          onClick={() => setShowMultiselect((prev) => !prev)}
-          disabled={hasActiveFilters}
-          icon={
-            <FilterAltIcon
-              sx={hasActiveFilters ? disabledIcon : clickableIcon}
-            />
-          }
-        />
+        <MuiBox>
+          <IconButton
+            tooltipTitle={text.attributionColumn.sortTooltip}
+            tooltipPlacement="right"
+            onClick={(): void => setShowSortingSelect(!showSortingSelect)}
+            disabled={!defaultSortingIsActive}
+            icon={
+              <SortIcon
+                aria-label={'Sort icon'}
+                sx={!defaultSortingIsActive ? disabledIcon : clickableIcon}
+              />
+            }
+          />
+          <IconButton
+            tooltipTitle={text.attributionColumn.filterTooltip}
+            tooltipPlacement={'right'}
+            onClick={() => setShowMultiselect((prev) => !prev)}
+            disabled={hasActiveFilters}
+            icon={
+              <FilterAltIcon
+                sx={hasActiveFilters ? disabledIcon : clickableIcon}
+              />
+            }
+          />
+        </MuiBox>
       </MuiBox>
+      {showSortingSelect ? (
+        <Dropdown
+          sx={classes.dropdown}
+          isEditable={true}
+          title={text.attributionViewSorting.sorting}
+          value={activeSorting}
+          menuItems={SORTING_ALGORITHMS}
+          handleChange={onSortInputChange}
+        />
+      ) : null}
       {showMultiSelect ? (
         <FilterMultiSelect sx={{ marginTop: '8px' }} />
       ) : undefined}
@@ -108,12 +167,13 @@ export function AttributionList() {
 
 function getFilteredAndSortedPackageCardIdsAndDisplayPackageInfos(
   attributions: Attributions,
+  sortByCriticality: boolean,
 ): {
   filteredAndSortedPackageCardIds: Array<string>;
   filteredDisplayPackageInfos: DisplayPackageInfos;
 } {
   const sortedAttributionIds = Object.keys(attributions).sort(
-    getAlphabeticalComparerForAttributions(attributions),
+    getAlphabeticalComparerForAttributions(attributions, sortByCriticality),
   );
 
   const filteredAndSortedPackageCardIds: Array<string> = [];
diff --git a/src/Frontend/Components/Autocomplete/Listbox/Listbox.tsx b/src/Frontend/Components/Autocomplete/Listbox/Listbox.tsx
index ff44e2b3..6d6da3ed 100644
--- a/src/Frontend/Components/Autocomplete/Listbox/Listbox.tsx
+++ b/src/Frontend/Components/Autocomplete/Listbox/Listbox.tsx
@@ -158,7 +158,7 @@ export const Listbox = forwardRef(
           ref={virtuosoRef}
           style={{
             ...styles.virtuoso,
-            height,
+            height: height + Math.trunc(options.length / 2),
           }}
           data={options}
           itemContent={(index, option) => renderOption({ option, index })}
@@ -185,7 +185,6 @@ export const Listbox = forwardRef(
           disabled={optionProps['aria-disabled'] as boolean}
           key={getOptionKey?.(option) ?? key}
           sx={{ gap: '12px' }}
-          dense
         >
           {renderOptionStartIcon?.(option, { closePopper })}
           <MuiListItemText
diff --git a/src/Frontend/Components/Filter/FilterMultiSelect.util.tsx b/src/Frontend/Components/Filter/FilterMultiSelect.util.tsx
index 3c4ff1ed..5af6a72f 100644
--- a/src/Frontend/Components/Filter/FilterMultiSelect.util.tsx
+++ b/src/Frontend/Components/Filter/FilterMultiSelect.util.tsx
@@ -5,22 +5,17 @@
 import Filter3Icon from '@mui/icons-material/Filter3';
 import SentimentDissatisfiedIcon from '@mui/icons-material/SentimentDissatisfied';
 import { pickBy } from 'lodash';
-import { useMemo } from 'react';
 
-import { Attributions, PackageInfo } from '../../../shared/shared-types';
+import { PackageInfo } from '../../../shared/shared-types';
 import { text } from '../../../shared/text';
 import { baseIcon, OpossumColors } from '../../shared-styles';
 import { useAppSelector } from '../../state/hooks';
-import {
-  getExternalAttributions,
-  getManualAttributions,
-} from '../../state/selectors/all-views-resource-selectors';
+import { getManualAttributions } from '../../state/selectors/all-views-resource-selectors';
 import { useVariable } from '../../util/use-variable';
 import {
   ExcludeFromNoticeIcon,
   FirstPartyIcon,
   FollowUpIcon,
-  ModifiedPreferredIcon,
   NeedsReviewIcon,
   PreferredIcon,
   PreSelectedIcon,
@@ -35,10 +30,7 @@ export type Filter = (typeof filters)[number];
 
 export const FILTER_PROPS: Record<
   Filter,
-  {
-    eval: (packageInfo: PackageInfo, signals: Attributions) => boolean;
-    icon: React.ReactNode;
-  }
+  { eval: (packageInfo: PackageInfo) => boolean; icon: React.ReactNode }
 > = {
   'Currently Preferred': {
     eval: (packageInfo) => !!packageInfo.preferred,
@@ -58,17 +50,6 @@ export const FILTER_PROPS: Record<
       packageInfo.attributionConfidence <= LOW_CONFIDENCE_THRESHOLD,
     icon: <SentimentDissatisfiedIcon color={'error'} sx={baseIcon} />,
   },
-  'Modified Previously Preferred': {
-    eval: (packageInfo, signals) =>
-      !!packageInfo.originIds?.length &&
-      !packageInfo.wasPreferred &&
-      !!Object.values(signals).find(
-        ({ originIds, wasPreferred }) =>
-          wasPreferred &&
-          originIds?.some((id) => packageInfo.originIds?.includes(id)),
-      ),
-    icon: <ModifiedPreferredIcon noTooltip />,
-  },
   'Needs Follow-Up': {
     eval: (packageInfo) => !!packageInfo.followUp,
     icon: <FollowUpIcon noTooltip />,
@@ -92,8 +73,7 @@ export const FILTER_PROPS: Record<
 };
 
 export function useFilteredAttributions() {
-  const manualAttributions = useAppSelector(getManualAttributions);
-  const externalAttributions = useAppSelector(getExternalAttributions);
+  const attributions = useAppSelector(getManualAttributions);
   const [activeFilters] = useVariable<Array<Filter>>(
     ACTIVE_FILTERS_REDUX_KEY,
     [],
@@ -101,16 +81,12 @@ export function useFilteredAttributions() {
 
   return {
     activeFilters,
-    attributions: useMemo(
-      () =>
-        activeFilters.length
-          ? pickBy(manualAttributions, (attribution) =>
-              activeFilters.every((filter) =>
-                FILTER_PROPS[filter].eval(attribution, externalAttributions),
-              ),
-            )
-          : manualAttributions,
-      [activeFilters, externalAttributions, manualAttributions],
-    ),
+    attributions: activeFilters.length
+      ? pickBy(attributions, (attribution) =>
+          activeFilters.every((filter) =>
+            FILTER_PROPS[filter].eval(attribution),
+          ),
+        )
+      : attributions,
   };
 }
diff --git a/src/Frontend/Components/Icons/Icons.tsx b/src/Frontend/Components/Icons/Icons.tsx
index 759cec12..62f7dc25 100644
--- a/src/Frontend/Components/Icons/Icons.tsx
+++ b/src/Frontend/Components/Icons/Icons.tsx
@@ -17,11 +17,13 @@ import QuestionMarkIcon from '@mui/icons-material/QuestionMark';
 import RectangleIcon from '@mui/icons-material/Rectangle';
 import ReplayIcon from '@mui/icons-material/Replay';
 import SearchIcon from '@mui/icons-material/Search';
+import SortIcon from '@mui/icons-material/Sort';
 import StarIcon from '@mui/icons-material/Star';
 import WidgetsIcon from '@mui/icons-material/Widgets';
 import { SxProps } from '@mui/material';
 import MuiBox from '@mui/material/Box';
 import MuiTooltip from '@mui/material/Tooltip';
+import { ReactElement } from 'react';
 
 import { Criticality } from '../../../shared/shared-types';
 import { text } from '../../../shared/text';
@@ -249,6 +251,18 @@ export function SearchPackagesIcon(props: IconProps) {
   );
 }
 
+export function SortAttributionsIcon(props: IconProps): ReactElement {
+  return (
+    <SortIcon
+      sx={getSxFromPropsAndClasses({
+        styleClass: classes.nonClickableIcon,
+        sxProps: props.sx,
+      })}
+      aria-label={'Sort icon'}
+    />
+  );
+}
+
 export function IncompleteAttributionsIcon(props: IconProps) {
   return (
     <MuiTooltip sx={classes.tooltip} title="contains incomplete information">
@@ -342,24 +356,6 @@ export function WasPreferredIcon({ className, noTooltip, sx }: IconProps) {
   );
 }
 
-export function ModifiedPreferredIcon({ className, noTooltip, sx }: IconProps) {
-  return (
-    <MuiTooltip
-      title={noTooltip ? undefined : text.auditingOptions.modifiedPreferred}
-    >
-      <StarIcon
-        aria-label={'Modified preferred icon'}
-        sx={{
-          ...baseIcon,
-          color: `${OpossumColors.black} !important`,
-          ...sx,
-        }}
-        className={className}
-      />
-    </MuiTooltip>
-  );
-}
-
 export function SourceIcon({ className, noTooltip, sx }: IconProps) {
   return (
     <MuiTooltip title={noTooltip ? undefined : text.attributionColumn.source}>
diff --git a/src/Frontend/Components/ManualPackagePanel/ManualPackagePanel.tsx b/src/Frontend/Components/ManualPackagePanel/ManualPackagePanel.tsx
index e87960e9..a7717569 100644
--- a/src/Frontend/Components/ManualPackagePanel/ManualPackagePanel.tsx
+++ b/src/Frontend/Components/ManualPackagePanel/ManualPackagePanel.tsx
@@ -62,7 +62,6 @@ export function ManualPackagePanel(
   );
   const selectedResourceOrClosestParentAttributions: Attributions =
     useAppSelector(getAttributionsOfSelectedResourceOrClosestParent);
-
   const selectedResourceId: string = useAppSelector(getSelectedResourceId);
 
   const shownAttributionsOfResource: Attributions = props.overrideParentMode
@@ -131,7 +130,7 @@ function getSortedPackageCardIdsAndDisplayPackageInfos(
   displayPackageInfos: DisplayPackageInfos;
 } {
   const sortedAttributionIds = Object.keys(shownAttributionsOfResource).sort(
-    getAlphabeticalComparerForAttributions(shownAttributionsOfResource),
+    getAlphabeticalComparerForAttributions(shownAttributionsOfResource, false),
   );
 
   const sortedPackageCardIds: Array<string> = [];
diff --git a/src/Frontend/Components/ResourceDetailsTabs/ResourceDetailsTabs.tsx b/src/Frontend/Components/ResourceDetailsTabs/ResourceDetailsTabs.tsx
index 3ccb3d4c..3da90e78 100644
--- a/src/Frontend/Components/ResourceDetailsTabs/ResourceDetailsTabs.tsx
+++ b/src/Frontend/Components/ResourceDetailsTabs/ResourceDetailsTabs.tsx
@@ -6,9 +6,10 @@ import MuiBox from '@mui/material/Box';
 import MuiTab from '@mui/material/Tab';
 import MuiTabs from '@mui/material/Tabs';
 import { remove } from 'lodash';
-import { ReactElement, useEffect, useState } from 'react';
+import { ChangeEvent, ReactElement, useEffect, useState } from 'react';
 
 import { Attributions } from '../../../shared/shared-types';
+import { text } from '../../../shared/text';
 import { PackagePanelTitle } from '../../enums/enums';
 import { OpossumColors } from '../../shared-styles';
 import {
@@ -29,13 +30,22 @@ import {
 import { DisplayPackageInfos } from '../../types/types';
 import { createPackageCardId } from '../../util/create-package-card-id';
 import { getDisplayPackageInfoWithCountFromAttributions } from '../../util/get-display-attributions-with-count-from-attributions';
+import {
+  AuditViewSorting,
+  useActiveSortingInAuditView,
+} from '../../util/use-active-sorting';
 import { AggregatedAttributionsPanel } from '../AggregatedAttributionsPanel/AggregatedAttributionsPanel';
 import { AllAttributionsPanel } from '../AllAttributionsPanel/AllAttributionsPanel';
 import { IconButton } from '../IconButton/IconButton';
-import { SearchPackagesIcon } from '../Icons/Icons';
+import { SearchPackagesIcon, SortAttributionsIcon } from '../Icons/Icons';
+import { Dropdown, MenuItem } from '../InputElements/Dropdown';
 import { SearchTextField } from '../SearchTextField/SearchTextField';
 
 const classes = {
+  dropdown: {
+    marginTop: '10px',
+    marginBottom: '8px',
+  },
   container: {
     position: 'relative',
     height: '100%',
@@ -60,7 +70,7 @@ const classes = {
       color: OpossumColors.black,
     },
   },
-  searchToggle: {
+  icons: {
     position: 'absolute',
     right: '0px',
     top: '0px',
@@ -78,11 +88,30 @@ const classes = {
       background: OpossumColors.middleBlue,
     },
   },
+  disabledLargeClickableIcon: {
+    width: '26px',
+    height: '26px',
+    padding: '2px',
+    margin: '0 2px',
+    color: OpossumColors.disabledButtonGrey,
+  },
   searchBox: {
     marginTop: '10px',
   },
 };
 
+const SORTING_ALGORITHMS: Array<MenuItem> = [
+  {
+    value: text.auditViewSorting.byOccurrence,
+    name: text.auditViewSorting.byOccurrence,
+  },
+  {
+    value: text.auditViewSorting.byCriticality,
+    name: text.auditViewSorting.byCriticality,
+  },
+];
+const defaultSorting = text.auditViewSorting.byOccurrence;
+
 interface ResourceDetailsTabsProps {
   isGlobalTabEnabled: boolean;
   isAddToPackageEnabled: boolean;
@@ -92,7 +121,6 @@ export function ResourceDetailsTabs(
   props: ResourceDetailsTabsProps,
 ): ReactElement | null {
   const manualData = useAppSelector(getManualData);
-
   const selectedPackage = useAppSelector(getDisplayedPackage);
   const selectedResourceId = useAppSelector(getSelectedResourceId);
   const attributionIdsOfSelectedResource: Array<string> =
@@ -102,6 +130,12 @@ export function ResourceDetailsTabs(
   );
   const searchTerm = useAppSelector(getPackageSearchTerm);
 
+  const [activeSorting, setActiveSorting] = useActiveSortingInAuditView();
+  const defaultSortingIsActive = activeSorting === defaultSorting;
+  const [showSortingSelect, setShowSortingSelect] = useState<boolean>(
+    !defaultSortingIsActive,
+  );
+
   const dispatch = useAppDispatch();
 
   enum Tabs {
@@ -129,6 +163,14 @@ export function ResourceDetailsTabs(
     [Tabs.Global]: 'Global',
   };
 
+  function onSortToggleClick(): void {
+    setShowSortingSelect(!showSortingSelect);
+  }
+
+  function onSortInputChange(event: ChangeEvent<HTMLInputElement>): void {
+    setActiveSorting(event.target.value as AuditViewSorting);
+  }
+
   function onSearchToggleClick(): void {
     dispatch(toggleAccordionSearchField());
     dispatch(setPackageSearchTerm(''));
@@ -165,13 +207,39 @@ export function ResourceDetailsTabs(
           sx={classes.tab}
         />
       </MuiTabs>
-      <IconButton
-        tooltipTitle="Search signals by name, license name, copyright text and version"
-        tooltipPlacement="right"
-        onClick={onSearchToggleClick}
-        icon={<SearchPackagesIcon sx={classes.largeClickableIcon} />}
-        iconSx={classes.searchToggle}
-      />
+      <MuiBox sx={classes.icons}>
+        <IconButton
+          tooltipTitle={text.resourceDetails.sortTooltip}
+          tooltipPlacement="right"
+          onClick={onSortToggleClick}
+          disabled={!defaultSortingIsActive}
+          icon={
+            <SortAttributionsIcon
+              sx={
+                defaultSortingIsActive
+                  ? classes.largeClickableIcon
+                  : classes.disabledLargeClickableIcon
+              }
+            />
+          }
+        />
+        <IconButton
+          tooltipTitle={text.resourceDetails.searchTooltip}
+          tooltipPlacement="right"
+          onClick={onSearchToggleClick}
+          icon={<SearchPackagesIcon sx={classes.largeClickableIcon} />}
+        />
+      </MuiBox>
+      {showSortingSelect ? (
+        <Dropdown
+          sx={classes.dropdown}
+          isEditable={true}
+          title={text.auditViewSorting.sorting}
+          value={activeSorting}
+          menuItems={SORTING_ALGORITHMS}
+          handleChange={onSortInputChange}
+        />
+      ) : null}
       {isAccordionSearchFieldDisplayed ? (
         <SearchTextField
           onInputChange={onSearchInputChange}
diff --git a/src/Frontend/Components/SelectMenu/SelectMenu.style.tsx b/src/Frontend/Components/SelectMenu/SelectMenu.style.tsx
deleted file mode 100644
index 55ea5bbd..00000000
--- a/src/Frontend/Components/SelectMenu/SelectMenu.style.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-// SPDX-FileCopyrightText: Meta Platforms, Inc. and its affiliates
-// SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
-//
-// SPDX-License-Identifier: Apache-2.0
-import CheckIcon from '@mui/icons-material/Check';
-import MuiMenu, { MenuProps as MuiMenuProps } from '@mui/material/Menu';
-import MuiMenuItem from '@mui/material/MenuItem';
-import { styled } from '@mui/material/styles';
-
-import { shouldNotBeCalled } from '../../util/should-not-be-called';
-
-export const StyledMenu = styled(
-  ({
-    horizontal,
-    sx,
-    ...props
-  }: MuiMenuProps & { horizontal: 'left' | 'right' | 'center' }) => (
-    <MuiMenu
-      elevation={0}
-      transformOrigin={{ horizontal, vertical: 'top' }}
-      anchorOrigin={{ horizontal, vertical: 'bottom' }}
-      MenuListProps={{ variant: 'menu', sx: { padding: 0 } }}
-      slotProps={{
-        paper: {
-          elevation: 0,
-          sx: {
-            overflow: 'visible',
-            filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
-            marginTop: '4px',
-            '&:before': {
-              content: '""',
-              display: 'block',
-              position: 'absolute',
-              top: 0,
-              left: (() => {
-                switch (horizontal) {
-                  case 'left':
-                    return '24px';
-                  case 'right':
-                    return 'calc(100% - 24px)';
-                  case 'center':
-                    return '50%';
-                  default:
-                    return shouldNotBeCalled(horizontal);
-                }
-              })(),
-              width: 10,
-              height: 10,
-              bgcolor: 'background.paper',
-              transform: 'translateY(-50%) rotate(45deg)',
-              zIndex: 0,
-            },
-            ...sx,
-          },
-        },
-      }}
-      {...props}
-    />
-  ),
-)({});
-
-export const StyledMenuItem = styled(MuiMenuItem)({
-  padding: '8px',
-});
-
-export const StyledCheckIcon = styled(CheckIcon)<{
-  visibility: 'hidden' | 'visible';
-}>(({ visibility }) => ({
-  width: '20px',
-  height: '20px',
-  marginLeft: '16px',
-  visibility,
-}));
diff --git a/src/Frontend/Components/SelectMenu/SelectMenu.tsx b/src/Frontend/Components/SelectMenu/SelectMenu.tsx
deleted file mode 100644
index 3573a115..00000000
--- a/src/Frontend/Components/SelectMenu/SelectMenu.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-// SPDX-FileCopyrightText: Meta Platforms, Inc. and its affiliates
-// SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
-//
-// SPDX-License-Identifier: Apache-2.0
-import MuiListItemIcon from '@mui/material/ListItemIcon';
-import MuiListItemText from '@mui/material/ListItemText';
-import { SxProps } from '@mui/system';
-import { useMemo, useState } from 'react';
-
-import {
-  StyledCheckIcon,
-  StyledMenu,
-  StyledMenuItem,
-} from './SelectMenu.style';
-
-export interface SelectMenuProps {
-  anchorEl: HTMLElement | undefined;
-  hideSelected?: boolean;
-  horizontal?: 'left' | 'right' | 'center';
-  multiple?: boolean;
-  options: Array<SelectMenuOption>;
-  setAnchorEl: (anchorEl: HTMLElement | undefined) => void;
-  sx?: SxProps;
-}
-
-export interface SelectMenuOption {
-  selected: boolean;
-  icon?: React.ReactElement;
-  id: string;
-  label: React.ReactNode;
-  onAdd?(): void;
-  onDelete?(): void;
-}
-
-export const SelectMenu: React.FC<SelectMenuProps> = ({
-  anchorEl,
-  hideSelected,
-  horizontal = 'center',
-  multiple,
-  options,
-  setAnchorEl,
-  sx,
-}) => {
-  const [pendingOptionIds, setPendingOptionIds] = useState<Array<string>>(
-    hideSelected
-      ? []
-      : options.filter(({ selected }) => selected).map(({ id }) => id),
-  );
-  const visibleOptions = useMemo(
-    () =>
-      hideSelected ? options.filter(({ selected }) => !selected) : options,
-    [hideSelected, options],
-  );
-  const handleClose = () => {
-    setAnchorEl(undefined);
-
-    if (hideSelected) {
-      pendingOptionIds.forEach((id) => {
-        options.find((option) => option.id === id)?.onAdd?.();
-      });
-      setPendingOptionIds([]);
-    } else {
-      options.forEach(({ selected, id, onAdd, onDelete }) => {
-        if (selected && !pendingOptionIds.includes(id)) {
-          onDelete?.();
-        } else if (!selected && pendingOptionIds.includes(id)) {
-          onAdd?.();
-        }
-      });
-    }
-  };
-
-  return (
-    <StyledMenu
-      anchorEl={anchorEl}
-      open={!!anchorEl}
-      onClose={handleClose}
-      horizontal={horizontal}
-      sx={sx}
-    >
-      {renderVisibleOptions()}
-    </StyledMenu>
-  );
-
-  function renderVisibleOptions() {
-    return visibleOptions.map(({ label, icon, id, selected }, index) => (
-      <StyledMenuItem
-        aria-selected={selected}
-        key={index}
-        onClick={() => {
-          if (multiple) {
-            setPendingOptionIds((prev) =>
-              prev.includes(id)
-                ? prev.filter((value) => value !== id)
-                : prev.concat(id),
-            );
-          } else {
-            setPendingOptionIds([id]);
-            options.forEach((option) => {
-              if (option.id === id) {
-                option.onAdd?.();
-              } else {
-                option.onDelete?.();
-              }
-            });
-            setAnchorEl(undefined);
-          }
-        }}
-        divider={index + 1 !== visibleOptions.length}
-        disableRipple
-      >
-        <MuiListItemIcon>{icon}</MuiListItemIcon>
-        <MuiListItemText
-          primary={label}
-          primaryTypographyProps={{ sx: { marginTop: '3px' } }}
-        />
-        <StyledCheckIcon
-          visibility={pendingOptionIds.includes(id) ? 'visible' : 'hidden'}
-        />
-      </StyledMenuItem>
-    ));
-  }
-};
diff --git a/src/Frontend/state/helpers/action-and-reducer-helpers.ts b/src/Frontend/state/helpers/action-and-reducer-helpers.ts
index d1365db6..80d0ef77 100644
--- a/src/Frontend/state/helpers/action-and-reducer-helpers.ts
+++ b/src/Frontend/state/helpers/action-and-reducer-helpers.ts
@@ -231,6 +231,7 @@ export function getAttributionIdOfFirstPackageCardInManualPackagePanel(
     displayedAttributionId = attributionIds.sort(
       getAlphabeticalComparerForAttributions(
         state.allViews.manualData.attributions,
+        false,
       ),
     )[0];
   } else {
@@ -244,6 +245,7 @@ export function getAttributionIdOfFirstPackageCardInManualPackagePanel(
       displayedAttributionId = closestParentAttributionIds.sort(
         getAlphabeticalComparerForAttributions(
           state.allViews.manualData.attributions,
+          false,
         ),
       )[0];
     }
@@ -264,7 +266,7 @@ export function getIndexOfAttributionInManualPackagePanel(
   }
 
   const sortedAttributionIds = manualAttributionIdsOnResource.sort(
-    getAlphabeticalComparerForAttributions(manualData.attributions),
+    getAlphabeticalComparerForAttributions(manualData.attributions, false),
   );
 
   const packageCardIndex = sortedAttributionIds.findIndex(
diff --git a/src/Frontend/util/get-alphabetical-comparer.ts b/src/Frontend/util/get-alphabetical-comparer.ts
index ca5770fc..f6514b51 100644
--- a/src/Frontend/util/get-alphabetical-comparer.ts
+++ b/src/Frontend/util/get-alphabetical-comparer.ts
@@ -3,18 +3,32 @@
 //
 // SPDX-License-Identifier: Apache-2.0
 import { Attributions } from '../../shared/shared-types';
+import { getNumericalCriticalityValue } from '../Components/AggregatedAttributionsPanel/AccordionPanel.util';
+import { DisplayPackageInfos } from '../types/types';
 import { convertPackageInfoToDisplayPackageInfo } from './convert-package-info';
 import { getCardLabels } from './get-card-labels';
 
 const DEFAULT_NAME = '\u10FFFF'; // largest unicode character
 
 export function getAlphabeticalComparerForAttributions(
-  attributions: Attributions,
+  attributions: Attributions | DisplayPackageInfos,
+  compareCriticality: boolean,
 ) {
   return function compareFunction(
     element: string,
     otherElement: string,
   ): number {
+    if (
+      compareCriticality &&
+      attributions[element]?.criticality !==
+        attributions[otherElement]?.criticality
+    ) {
+      return (
+        getNumericalCriticalityValue(attributions[otherElement]?.criticality) -
+        getNumericalCriticalityValue(attributions[element]?.criticality)
+      );
+    }
+
     const elementCardLabels = getCardLabels(
       convertPackageInfoToDisplayPackageInfo(
         {
diff --git a/src/Frontend/util/use-active-sorting.ts b/src/Frontend/util/use-active-sorting.ts
new file mode 100644
index 00000000..2ba1238a
--- /dev/null
+++ b/src/Frontend/util/use-active-sorting.ts
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: Meta Platforms, Inc. and its affiliates
+// SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
+//
+// SPDX-License-Identifier: Apache-2.0
+import { text } from '../../shared/text';
+import { useVariable } from './use-variable';
+
+export const auditViewSorting = Object.values(text.auditViewSorting);
+export const attributionViewSorting = Object.values(
+  text.attributionViewSorting,
+);
+export type AuditViewSorting = (typeof auditViewSorting)[number];
+export type AttributionViewSorting = (typeof attributionViewSorting)[number];
+
+export function useActiveSortingInAuditView() {
+  const [activeSorting, setActiveSorting] = useVariable<AuditViewSorting>(
+    'active-sorting-audit-view',
+    text.auditViewSorting.byOccurrence,
+  );
+
+  return [activeSorting, setActiveSorting] as const;
+}
+
+export function useActiveSortingInAttributionView() {
+  const [activeSorting, setActiveSorting] = useVariable<AttributionViewSorting>(
+    'active-sorting-attribution-view',
+    text.attributionViewSorting.alphabetical,
+  );
+
+  return [activeSorting, setActiveSorting] as const;
+}
diff --git a/src/Frontend/web-workers/scripts/get-attributions-in-folder-content.ts b/src/Frontend/web-workers/scripts/get-attributions-in-folder-content.ts
index 785f7fd1..b47c072a 100644
--- a/src/Frontend/web-workers/scripts/get-attributions-in-folder-content.ts
+++ b/src/Frontend/web-workers/scripts/get-attributions-in-folder-content.ts
@@ -10,17 +10,20 @@ import { PanelAttributionData } from '../../util/get-contained-packages';
 interface Props {
   manualData: PanelAttributionData;
   resourceId: string;
+  sortByCriticality: boolean;
 }
 
 export function getAttributionsInFolderContent({
   manualData,
   resourceId,
+  sortByCriticality,
 }: Props): PanelData {
   const [sortedPackageCardIds, displayPackageInfosWithCount] =
     getContainedManualDisplayPackageInfosWithCount({
       selectedResourceId: resourceId,
       manualData,
       panelTitle: PackagePanelTitle.ContainedManualPackages,
+      sortByCriticality,
     });
 
   return {
diff --git a/src/Frontend/web-workers/scripts/get-signals-in-folder-content.ts b/src/Frontend/web-workers/scripts/get-signals-in-folder-content.ts
index 0f9d2382..4a2c84ba 100644
--- a/src/Frontend/web-workers/scripts/get-signals-in-folder-content.ts
+++ b/src/Frontend/web-workers/scripts/get-signals-in-folder-content.ts
@@ -13,6 +13,7 @@ interface Props {
   attributionsToHashes: AttributionsToHashes;
   resolvedExternalAttributions: Set<string>;
   resourceId: string;
+  sortByCriticality: boolean;
 }
 
 export function getSignalsInFolderContent({
@@ -20,6 +21,7 @@ export function getSignalsInFolderContent({
   attributionsToHashes,
   resolvedExternalAttributions,
   resourceId,
+  sortByCriticality,
 }: Props): PanelData {
   const [sortedPackageCardIds, displayAttributionIdsWithCount] =
     getContainedExternalDisplayPackageInfosWithCount({
@@ -28,6 +30,7 @@ export function getSignalsInFolderContent({
       resolvedExternalAttributions,
       attributionsToHashes,
       panelTitle: PackagePanelTitle.ContainedExternalPackages,
+      sortByCriticality,
     });
 
   return {
diff --git a/src/Frontend/web-workers/signals-worker.ts b/src/Frontend/web-workers/signals-worker.ts
index 1faeeb7d..ba426a93 100644
--- a/src/Frontend/web-workers/signals-worker.ts
+++ b/src/Frontend/web-workers/signals-worker.ts
@@ -27,7 +27,8 @@ export type SignalsWorkerInput =
   | { name: 'resolvedExternalAttributions'; data: Set<string> }
   | { name: 'resourceId'; data: string }
   | { name: 'resources'; data: Resources }
-  | { name: 'sources'; data: ExternalAttributionSources };
+  | { name: 'sources'; data: ExternalAttributionSources }
+  | { name: 'sortByCriticality'; data: boolean };
 
 export type SignalsWorkerOutput =
   | {
@@ -61,6 +62,7 @@ interface State {
   resourceId?: string;
   resources?: Resources;
   sources?: ExternalAttributionSources;
+  sortByCriticality?: boolean;
 }
 
 export class SignalsWorker {
@@ -72,6 +74,7 @@ export class SignalsWorker {
       resolvedExternalAttributions: undefined,
       sources: undefined,
       resourceId: undefined,
+      sortByCriticality: undefined,
     },
   ) {}
 
@@ -113,6 +116,9 @@ export class SignalsWorker {
       case 'filesWithChildren':
         this.state.filesWithChildren = input.data;
         break;
+      case 'sortByCriticality':
+        this.state.sortByCriticality = input.data;
+        break;
       default:
         shouldNotBeCalled(input);
     }
@@ -142,12 +148,19 @@ export class SignalsWorker {
   }
 
   private dispatchAttributionsInFolderContent(input: SignalsWorkerInput) {
-    if (this.isHydrated(this.state, input, ['resourceId', 'manualData'])) {
+    if (
+      this.isHydrated(this.state, input, [
+        'resourceId',
+        'manualData',
+        'sortByCriticality',
+      ])
+    ) {
       this.dispatch({
         name: 'attributionsInFolderContent',
         data: getAttributionsInFolderContent({
           resourceId: this.state.resourceId,
           manualData: this.state.manualData,
+          sortByCriticality: this.state.sortByCriticality,
         }),
       });
     }
@@ -160,6 +173,7 @@ export class SignalsWorker {
         'attributionsToHashes',
         'externalData',
         'resolvedExternalAttributions',
+        'sortByCriticality',
       ])
     ) {
       this.dispatch({
@@ -169,6 +183,7 @@ export class SignalsWorker {
           attributionsToHashes: this.state.attributionsToHashes,
           externalData: this.state.externalData,
           resolvedExternalAttributions: this.state.resolvedExternalAttributions,
+          sortByCriticality: this.state.sortByCriticality,
         }),
       });
     }
@@ -234,7 +249,7 @@ export class SignalsWorker {
   ): state is WithRequired<State, (typeof dependencies)[number]> {
     return (
       dependencies.includes(input.name) &&
-      dependencies.every((dependency) => !!state[dependency])
+      dependencies.every((dependency) => state[dependency] !== undefined)
     );
   }
 }
diff --git a/src/Frontend/web-workers/use-signals-worker.ts b/src/Frontend/web-workers/use-signals-worker.ts
index ff37e549..08017ecb 100644
--- a/src/Frontend/web-workers/use-signals-worker.ts
+++ b/src/Frontend/web-workers/use-signals-worker.ts
@@ -5,6 +5,7 @@
 import { useEffect, useState } from 'react';
 
 import { AutocompleteSignal } from '../../shared/shared-types';
+import { text } from '../../shared/text';
 import { useAppSelector } from '../state/hooks';
 import {
   getAttributionBreakpoints,
@@ -23,6 +24,7 @@ import {
 import { isAuditViewSelected } from '../state/selectors/view-selector';
 import { PanelData, ProgressBarData } from '../types/types';
 import { shouldNotBeCalled } from '../util/should-not-be-called';
+import { useActiveSortingInAuditView } from '../util/use-active-sorting';
 import { useVariable } from '../util/use-variable';
 import { SignalsWorkerInput, SignalsWorkerOutput } from './signals-worker';
 
@@ -99,6 +101,7 @@ export function useSignalsWorker() {
   const filesWithChildren = useAppSelector(getFilesWithChildren);
   const isAuditView = useAppSelector(isAuditViewSelected);
   const { projectId } = useAppSelector(getProjectMetadata);
+  const [activeSorting] = useActiveSortingInAuditView();
 
   const [worker, setWorker] = useState<Worker>();
   const [, setAutocompleteSignals] = useVariable<Array<AutocompleteSignal>>(
@@ -248,4 +251,11 @@ export function useSignalsWorker() {
       } satisfies SignalsWorkerInput);
     }
   }, [resources, worker]);
+
+  useEffect(() => {
+    worker?.postMessage({
+      name: 'sortByCriticality',
+      data: activeSorting === text.auditViewSorting.byCriticality,
+    } satisfies SignalsWorkerInput);
+  }, [activeSorting, worker]);
 }
diff --git a/src/shared/text.ts b/src/shared/text.ts
index 9d3c9380..9aecf917 100644
--- a/src/shared/text.ts
+++ b/src/shared/text.ts
@@ -44,6 +44,8 @@ export const text = {
     source: 'Source',
     useAutocompleteSuggestion:
       'Adopt all coordinates and legal information from suggestion',
+    sortTooltip: 'Sort',
+    filterTooltip: 'Filter',
   },
   changePreferredStatusGloballyPopup: {
     markAsPreferred: 'Do you really want to prefer the attribution globally?',
@@ -67,7 +69,6 @@ export const text = {
     currentlyPreferred: 'Currently Preferred',
     excludedFromNotice: 'Excluded from Notice',
     followUp: 'Needs Follow-Up',
-    modifiedPreferred: 'Modified Previously Preferred',
     needsReview: 'Needs Review by QA',
     preselected: 'Pre-selected',
     previouslyPreferred: 'Previously Preferred',
@@ -91,11 +92,25 @@ export const text = {
     excludedFromNotice: 'Excluded from Notice',
     firstParty: 'First Party',
     lowConfidence: 'Low Confidence',
-    modifiedPreferred: 'Modified Previously Preferred',
     needsFollowUp: 'Needs Follow-Up',
     needsReview: 'Needs Review by QA',
     preSelected: 'Pre-selected',
     previouslyPreferred: 'Previously Preferred',
     thirdParty: 'Third Party',
   },
+  attributionViewSorting: {
+    sorting: 'Sorting',
+    alphabetical: 'Alphabetical',
+    byCriticality: 'By Criticality',
+  },
+  auditViewSorting: {
+    sorting: 'Sorting',
+    byOccurrence: 'By Occurrence',
+    byCriticality: 'By Criticality',
+  },
+  resourceDetails: {
+    searchTooltip:
+      'Search signals by name, license name, copyright text and version',
+    sortTooltip: 'Choose sorting algorithm for the displayed signals',
+  },
 } as const;
diff --git a/yarn.lock b/yarn.lock
index 5c948db0..dba2f786 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -730,21 +730,22 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-classes@npm:^7.23.8":
-  version: 7.23.8
-  resolution: "@babel/plugin-transform-classes@npm:7.23.8"
+"@babel/plugin-transform-classes@npm:^7.23.5":
+  version: 7.23.5
+  resolution: "@babel/plugin-transform-classes@npm:7.23.5"
   dependencies:
     "@babel/helper-annotate-as-pure": "npm:^7.22.5"
-    "@babel/helper-compilation-targets": "npm:^7.23.6"
+    "@babel/helper-compilation-targets": "npm:^7.22.15"
     "@babel/helper-environment-visitor": "npm:^7.22.20"
     "@babel/helper-function-name": "npm:^7.23.0"
+    "@babel/helper-optimise-call-expression": "npm:^7.22.5"
     "@babel/helper-plugin-utils": "npm:^7.22.5"
     "@babel/helper-replace-supers": "npm:^7.22.20"
     "@babel/helper-split-export-declaration": "npm:^7.22.6"
     globals: "npm:^11.1.0"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 4bb4b19e7a39871c4414fb44fc5f2cc47c78f993b74c43238dfb99c9dac2d15cb99b43f8a3d42747580e1807d2b8f5e13ce7e95e593fd839bd176aa090bf9a23
+  checksum: f6c4fed2f48bdd46a4726b829ea2ddb5c9c97edd0e55dc53791d82927daad5725052b7e785a8b7e90a53b0606166b9c554469dc94f10fba59ca9642e997d97ee
   languageName: node
   linkType: hard
 
@@ -1310,9 +1311,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/preset-env@npm:^7.23.8":
-  version: 7.23.8
-  resolution: "@babel/preset-env@npm:7.23.8"
+"@babel/preset-env@npm:^7.23.7":
+  version: 7.23.7
+  resolution: "@babel/preset-env@npm:7.23.7"
   dependencies:
     "@babel/compat-data": "npm:^7.23.5"
     "@babel/helper-compilation-targets": "npm:^7.23.6"
@@ -1347,7 +1348,7 @@ __metadata:
     "@babel/plugin-transform-block-scoping": "npm:^7.23.4"
     "@babel/plugin-transform-class-properties": "npm:^7.23.3"
     "@babel/plugin-transform-class-static-block": "npm:^7.23.4"
-    "@babel/plugin-transform-classes": "npm:^7.23.8"
+    "@babel/plugin-transform-classes": "npm:^7.23.5"
     "@babel/plugin-transform-computed-properties": "npm:^7.23.3"
     "@babel/plugin-transform-destructuring": "npm:^7.23.3"
     "@babel/plugin-transform-dotall-regex": "npm:^7.23.3"
@@ -1396,7 +1397,7 @@ __metadata:
     semver: "npm:^6.3.1"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 74493faa8b3e4bdd07ab3fbde80d7b13cc05321877f997698177c0bb082669c9b6a87397bd79ca6656df3c80c05e514ffed6d81510e2d8719a6e9647c038b7b9
+  checksum: 2059dee350c39aba0a1f128d00ccfd7abf97f92a9b661f4db07a96c11d91c928a9f1df39477583f068090627bff571b4415f1a4f94008d29f6ad8b124e69804e
   languageName: node
   linkType: hard
 
@@ -3220,9 +3221,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@testing-library/dom@npm:^9.0.0, @testing-library/dom@npm:^9.3.4":
-  version: 9.3.4
-  resolution: "@testing-library/dom@npm:9.3.4"
+"@testing-library/dom@npm:^9.0.0, @testing-library/dom@npm:^9.3.3":
+  version: 9.3.3
+  resolution: "@testing-library/dom@npm:9.3.3"
   dependencies:
     "@babel/code-frame": "npm:^7.10.4"
     "@babel/runtime": "npm:^7.12.5"
@@ -3232,7 +3233,7 @@ __metadata:
     dom-accessibility-api: "npm:^0.5.9"
     lz-string: "npm:^1.5.0"
     pretty-format: "npm:^27.0.2"
-  checksum: 510da752ea76f4a10a0a4e3a77917b0302cf03effe576cd3534cab7e796533ee2b0e9fb6fb11b911a1ebd7c70a0bb6f235bf4f816c9b82b95b8fe0cddfd10975
+  checksum: 1ebd1672226600049ce16509d6964bdad8ee71b10f7e68f98126e00638c08ebefb6b7c729a0f2a41cffc77902c3081a95fc2bc1a097cae442ed4a5c481f348b7
   languageName: node
   linkType: hard
 
@@ -3831,15 +3832,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@typescript-eslint/eslint-plugin@npm:^6.18.1":
-  version: 6.18.1
-  resolution: "@typescript-eslint/eslint-plugin@npm:6.18.1"
+"@typescript-eslint/eslint-plugin@npm:^6.18.0":
+  version: 6.18.0
+  resolution: "@typescript-eslint/eslint-plugin@npm:6.18.0"
   dependencies:
     "@eslint-community/regexpp": "npm:^4.5.1"
-    "@typescript-eslint/scope-manager": "npm:6.18.1"
-    "@typescript-eslint/type-utils": "npm:6.18.1"
-    "@typescript-eslint/utils": "npm:6.18.1"
-    "@typescript-eslint/visitor-keys": "npm:6.18.1"
+    "@typescript-eslint/scope-manager": "npm:6.18.0"
+    "@typescript-eslint/type-utils": "npm:6.18.0"
+    "@typescript-eslint/utils": "npm:6.18.0"
+    "@typescript-eslint/visitor-keys": "npm:6.18.0"
     debug: "npm:^4.3.4"
     graphemer: "npm:^1.4.0"
     ignore: "npm:^5.2.4"
@@ -3852,25 +3853,25 @@ __metadata:
   peerDependenciesMeta:
     typescript:
       optional: true
-  checksum: 451abba1f784539ba38cbe40af26c461facd848e90cbf0c440a98ca2a770ff74a794bafe1df7240e52a97e7cd72bade97d5de751dffbac269643460ad65e3b19
+  checksum: 7810a84f6d9cb735f6848aa4a7ef64c77740812afc0248fda63ec182910a1d045cd9a32d8e49b0e5323266db6da12a2fe50171147927b3186815a56f12c16ee7
   languageName: node
   linkType: hard
 
-"@typescript-eslint/parser@npm:^6.18.1, @typescript-eslint/parser@npm:^6.7.3":
-  version: 6.18.1
-  resolution: "@typescript-eslint/parser@npm:6.18.1"
+"@typescript-eslint/parser@npm:^6.18.0, @typescript-eslint/parser@npm:^6.7.3":
+  version: 6.18.0
+  resolution: "@typescript-eslint/parser@npm:6.18.0"
   dependencies:
-    "@typescript-eslint/scope-manager": "npm:6.18.1"
-    "@typescript-eslint/types": "npm:6.18.1"
-    "@typescript-eslint/typescript-estree": "npm:6.18.1"
-    "@typescript-eslint/visitor-keys": "npm:6.18.1"
+    "@typescript-eslint/scope-manager": "npm:6.18.0"
+    "@typescript-eslint/types": "npm:6.18.0"
+    "@typescript-eslint/typescript-estree": "npm:6.18.0"
+    "@typescript-eslint/visitor-keys": "npm:6.18.0"
     debug: "npm:^4.3.4"
   peerDependencies:
     eslint: ^7.0.0 || ^8.0.0
   peerDependenciesMeta:
     typescript:
       optional: true
-  checksum: b853d39dcf886668f9aa9ea12094e722d35be20855dc7f01c80ee847bf4f7e27aa74693c3a33d4d813705214bda28be1d6c7ca29e590233f894f556203171d29
+  checksum: 6798332819f839454a8405e31cfaa0fe908d5966be929bef55e78ac51a0ff3868feb42b38e7772cedf88277f6b2841b3d91f6c573eacb945e2741ecae94047c7
   languageName: node
   linkType: hard
 
@@ -3884,22 +3885,22 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@typescript-eslint/scope-manager@npm:6.18.1":
-  version: 6.18.1
-  resolution: "@typescript-eslint/scope-manager@npm:6.18.1"
+"@typescript-eslint/scope-manager@npm:6.18.0":
+  version: 6.18.0
+  resolution: "@typescript-eslint/scope-manager@npm:6.18.0"
   dependencies:
-    "@typescript-eslint/types": "npm:6.18.1"
-    "@typescript-eslint/visitor-keys": "npm:6.18.1"
-  checksum: ab75663cda67a2c95267f240f2e062a0aafab0df6d625043a134c8e1d61e193c0d3cfa49c802bd554b3fd80f4b7df5ea3f86ef2eb6994ba8b5e0790cc9868c84
+    "@typescript-eslint/types": "npm:6.18.0"
+    "@typescript-eslint/visitor-keys": "npm:6.18.0"
+  checksum: c2c465e6803f78d3300142167a8a79dd2613c64cf464a40a9cf6b13a2c10c3d82ca30bb9c2d26aba7f054b8740b38e1d25f377fcdf917aba489d5b5ea2550858
   languageName: node
   linkType: hard
 
-"@typescript-eslint/type-utils@npm:6.18.1":
-  version: 6.18.1
-  resolution: "@typescript-eslint/type-utils@npm:6.18.1"
+"@typescript-eslint/type-utils@npm:6.18.0":
+  version: 6.18.0
+  resolution: "@typescript-eslint/type-utils@npm:6.18.0"
   dependencies:
-    "@typescript-eslint/typescript-estree": "npm:6.18.1"
-    "@typescript-eslint/utils": "npm:6.18.1"
+    "@typescript-eslint/typescript-estree": "npm:6.18.0"
+    "@typescript-eslint/utils": "npm:6.18.0"
     debug: "npm:^4.3.4"
     ts-api-utils: "npm:^1.0.1"
   peerDependencies:
@@ -3907,7 +3908,7 @@ __metadata:
   peerDependenciesMeta:
     typescript:
       optional: true
-  checksum: f775011c35aef10b11e7cffde698fca8b06b313785cd44422c60a40b6643d85a38cbd9a3cbdef011d963680d65383c40988d983e9dfd01258ae50a2f37f6d293
+  checksum: 9f4a392fe7b7f6b1fb02acbdaa331e764775f6404c29ca66774419e39552523d878227020526ca9c438d997555a99600f8d711496e9a435fb14a779e25ed094e
   languageName: node
   linkType: hard
 
@@ -3918,10 +3919,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@typescript-eslint/types@npm:6.18.1":
-  version: 6.18.1
-  resolution: "@typescript-eslint/types@npm:6.18.1"
-  checksum: e304620953257a5af3b323697845d3fb41ffbb7944df2f84559675ef8ad71cc33011be30149efd8d34c5dedcbe92c6abee67cb1b95cb3dd56f15b9393b3435a6
+"@typescript-eslint/types@npm:6.18.0":
+  version: 6.18.0
+  resolution: "@typescript-eslint/types@npm:6.18.0"
+  checksum: fc507ca7a1bfec04467087165ff6722f7b9aa9a089ecf0c17656824a951b92ca014766e480122de850057c63a3066628985eb0681c5bbb80ab41d94e7bb52288
   languageName: node
   linkType: hard
 
@@ -3943,12 +3944,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@typescript-eslint/typescript-estree@npm:6.18.1, @typescript-eslint/typescript-estree@npm:^6.7.3":
-  version: 6.18.1
-  resolution: "@typescript-eslint/typescript-estree@npm:6.18.1"
+"@typescript-eslint/typescript-estree@npm:6.18.0, @typescript-eslint/typescript-estree@npm:^6.7.3":
+  version: 6.18.0
+  resolution: "@typescript-eslint/typescript-estree@npm:6.18.0"
   dependencies:
-    "@typescript-eslint/types": "npm:6.18.1"
-    "@typescript-eslint/visitor-keys": "npm:6.18.1"
+    "@typescript-eslint/types": "npm:6.18.0"
+    "@typescript-eslint/visitor-keys": "npm:6.18.0"
     debug: "npm:^4.3.4"
     globby: "npm:^11.1.0"
     is-glob: "npm:^4.0.3"
@@ -3958,24 +3959,24 @@ __metadata:
   peerDependenciesMeta:
     typescript:
       optional: true
-  checksum: 33307bc87c3270f84f149545da79a15afaafacc5671e42f4aa827947f09c35ed114705f108ffa59d6ab175f8b838aa08cf10f9efe5b7793aca0792f879bbc7ca
+  checksum: b65392e944baba97ed98e99a1e7122b7b05fa0d9a082b48d0190b377ae9e2ae4ed72d505a2f0f05f2ca78908f0e4b0f1acd44d345c7f4f4415fcec6bb2c57791
   languageName: node
   linkType: hard
 
-"@typescript-eslint/utils@npm:6.18.1":
-  version: 6.18.1
-  resolution: "@typescript-eslint/utils@npm:6.18.1"
+"@typescript-eslint/utils@npm:6.18.0":
+  version: 6.18.0
+  resolution: "@typescript-eslint/utils@npm:6.18.0"
   dependencies:
     "@eslint-community/eslint-utils": "npm:^4.4.0"
     "@types/json-schema": "npm:^7.0.12"
     "@types/semver": "npm:^7.5.0"
-    "@typescript-eslint/scope-manager": "npm:6.18.1"
-    "@typescript-eslint/types": "npm:6.18.1"
-    "@typescript-eslint/typescript-estree": "npm:6.18.1"
+    "@typescript-eslint/scope-manager": "npm:6.18.0"
+    "@typescript-eslint/types": "npm:6.18.0"
+    "@typescript-eslint/typescript-estree": "npm:6.18.0"
     semver: "npm:^7.5.4"
   peerDependencies:
     eslint: ^7.0.0 || ^8.0.0
-  checksum: 967728f062ec7ef434b9d05af6b45ae59e0f8f314e4aa7cefc8e2c30a277cf269b7913f3e9f8d26a86c3faf8b2c9385ba1221cfcd856e9b228421fc6508b41bc
+  checksum: f91798069e337ed42b7e415aabded833d540dab9adf66ae88183003428892584946f20a5496aae2dc65fc0f65b66496baed785a3470ee782b6e2dd25b9200c6c
   languageName: node
   linkType: hard
 
@@ -4007,13 +4008,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@typescript-eslint/visitor-keys@npm:6.18.1":
-  version: 6.18.1
-  resolution: "@typescript-eslint/visitor-keys@npm:6.18.1"
+"@typescript-eslint/visitor-keys@npm:6.18.0":
+  version: 6.18.0
+  resolution: "@typescript-eslint/visitor-keys@npm:6.18.0"
   dependencies:
-    "@typescript-eslint/types": "npm:6.18.1"
+    "@typescript-eslint/types": "npm:6.18.0"
     eslint-visitor-keys: "npm:^3.4.1"
-  checksum: 2d6c5ffa52e89caec0b4958d7079fb4ca9f17c19b5f5d5b8446b4eef0079fd59cd9959f469cc13c4e7d72ef4c47849e561fe323f3cebafa01bc916d43082e57b
+  checksum: bf34a357549d515607c761f385b7c7c82aaa07795cada0be2e1e3860c5103fbf731408ac07eaeb0517b51426d77ef9b194dfb94f205c776107a46e0d0027b452
   languageName: node
   linkType: hard
 
@@ -9880,7 +9881,7 @@ __metadata:
   resolution: "opossum-ui@workspace:."
   dependencies:
     "@babel/core": "npm:^7.23.7"
-    "@babel/preset-env": "npm:^7.23.8"
+    "@babel/preset-env": "npm:^7.23.7"
     "@babel/preset-react": "npm:^7.23.3"
     "@babel/preset-typescript": "npm:^7.23.3"
     "@commitlint/cli": "npm:^18.4.4"
@@ -9896,7 +9897,7 @@ __metadata:
     "@reduxjs/toolkit": "npm:^2.0.1"
     "@tanstack/eslint-plugin-query": "npm:^5.17.7"
     "@tanstack/react-query": "npm:^5.17.9"
-    "@testing-library/dom": "npm:^9.3.4"
+    "@testing-library/dom": "npm:^9.3.3"
     "@testing-library/jest-dom": "npm:^6.2.0"
     "@testing-library/react": "npm:^14.1.2"
     "@testing-library/user-event": "npm:^14.5.2"
@@ -9914,8 +9915,8 @@ __metadata:
     "@types/recharts": "npm:^1.8.29"
     "@types/stream-json": "npm:^1.7.7"
     "@types/uuid": "npm:^9.0.7"
-    "@typescript-eslint/eslint-plugin": "npm:^6.18.1"
-    "@typescript-eslint/parser": "npm:^6.18.1"
+    "@typescript-eslint/eslint-plugin": "npm:^6.18.0"
+    "@typescript-eslint/parser": "npm:^6.18.0"
     "@vitejs/plugin-react": "npm:^4.2.1"
     babel-jest: "npm:^29.7.0"
     babel-plugin-transform-import-meta: "npm:^2.2.1"


Test patch:
diff --git a/src/Frontend/Components/AggregatedAttributionsPanel/__tests__/AccordionPanel.util.test.ts b/src/Frontend/Components/AggregatedAttributionsPanel/__tests__/AccordionPanel.util.test.ts
index d5e47bac..07d30146 100644
--- a/src/Frontend/Components/AggregatedAttributionsPanel/__tests__/AccordionPanel.util.test.ts
+++ b/src/Frontend/Components/AggregatedAttributionsPanel/__tests__/AccordionPanel.util.test.ts
@@ -5,6 +5,7 @@
 import {
   Attributions,
   AttributionsToHashes,
+  Criticality,
   ResourcesToAttributions,
   ResourcesWithAttributedChildren,
 } from '../../../../shared/shared-types';
@@ -17,7 +18,7 @@ import { PanelAttributionData } from '../../../util/get-contained-packages';
 import {
   getContainedManualDisplayPackageInfosWithCount,
   getExternalDisplayPackageInfosWithCount,
-  sortDisplayPackageInfosWithCountByCountAndPackageName,
+  sortDisplayPackageInfosWithCountByCriticalityAndCountAndPackageName,
 } from '../AccordionPanel.util';
 
 describe('getExternalDisplayPackageInfosWithCount', () => {
@@ -54,6 +55,7 @@ describe('getExternalDisplayPackageInfosWithCount', () => {
         testAttributions,
         testExternalAttributionsToHashes,
         testPackagePanelTitle,
+        false,
       ),
     ).toEqual([expectedPackageCardIds, expectedDisplayPackageInfosWithCount]);
   });
@@ -92,6 +94,7 @@ describe('getExternalDisplayPackageInfosWithCount', () => {
         testAttributions,
         testExternalAttributionsToHashes,
         testPackagePanelTitle,
+        false,
       ),
     ).toEqual([expectedPackageCardIds, expectedDisplayPackageInfosWithCount]);
   });
@@ -122,6 +125,7 @@ describe('getExternalDisplayPackageInfosWithCount', () => {
         testAttributions,
         testExternalAttributionsToHashes,
         testPackagePanelTitle,
+        false,
       ),
     ).toEqual([expectedPackageCardIds, expectedDisplayPackageInfosWithCount]);
   });
@@ -162,6 +166,7 @@ describe('getExternalDisplayPackageInfosWithCount', () => {
         testAttributions,
         testExternalAttributionsToHashes,
         testPackagePanelTitle,
+        false,
       ),
     ).toEqual([expectedPackageCardIds, expectedDisplayPackageInfosWithCount]);
   });
@@ -192,6 +197,7 @@ describe('getExternalDisplayPackageInfosWithCount', () => {
         testAttributions,
         testExternalAttributionsToHashes,
         testPackagePanelTitle,
+        false,
       ),
     ).toEqual([expectedPackageCardIds, expectedDisplayPackageInfosWithCount]);
   });
@@ -240,6 +246,7 @@ describe('getExternalDisplayPackageInfosWithCount', () => {
         testAttributions,
         testExternalAttributionsToHashes,
         testPackagePanelTitle,
+        false,
       ),
     ).toEqual([expectedPackageCardIds, expectedDisplayPackageInfosWithCount]);
   });
@@ -311,13 +318,14 @@ describe('getContainedManualDisplayPackageInfosWithCount', () => {
         selectedResourceId,
         manualData,
         panelTitle: testPackagePanelTitle,
+        sortByCriticality: false,
       }),
     ).toEqual([expectedPackageCardIds, expectedDisplayPackageInfosWithCount]);
   });
 });
 
-describe('sortDisplayPackageInfosWithCountByCountAndPackageName', () => {
-  it('sorts items correctly', () => {
+describe('sortDisplayPackageInfosWithCountByCriticalityAndCountAndPackageName', () => {
+  it('sorts items correctly, ignoring criticality', () => {
     const initialPackageCardIds: Array<string> = [
       'pcid1',
       'pcid2',
@@ -362,8 +370,102 @@ describe('sortDisplayPackageInfosWithCountByCountAndPackageName', () => {
     ];
 
     const result = initialPackageCardIds.sort(
-      sortDisplayPackageInfosWithCountByCountAndPackageName(
+      sortDisplayPackageInfosWithCountByCriticalityAndCountAndPackageName(
         testDisplayPackageInfosWithCount,
+        true,
+      ),
+    );
+    expect(result).toEqual(expectedPackageCardIds);
+  });
+
+  it('sorts items correctly by criticality, package name and version', () => {
+    const initialPackageCardIds: Array<string> = [
+      'pcid1',
+      'pcid2',
+      'pcid3',
+      'pcid4',
+      'pcid5',
+      'pcid6',
+      'pcid7',
+      'pcid8',
+      'pcid9',
+      'pcid10',
+    ];
+    const testDisplayPackageInfosWithCount: DisplayPackageInfosWithCount = {
+      pcid1: {
+        displayPackageInfo: { attributionIds: ['uuid1'] },
+        count: 10,
+      },
+      pcid2: {
+        displayPackageInfo: { attributionIds: ['uuid2'], packageName: 'c' },
+        count: 11,
+      },
+      pcid3: {
+        displayPackageInfo: { attributionIds: ['uuid3'], packageName: 'b' },
+        count: 10,
+      },
+      pcid4: {
+        displayPackageInfo: { attributionIds: ['uuid4'], packageName: 'e' },
+        count: 1,
+      },
+      pcid5: {
+        displayPackageInfo: { attributionIds: ['uuid5'], packageName: 'z' },
+        count: 10,
+      },
+      pcid6: {
+        displayPackageInfo: { attributionIds: ['uuid6'], packageName: 'd' },
+        count: 1,
+      },
+      pcid7: {
+        displayPackageInfo: {
+          attributionIds: ['uuid7'],
+          packageName: 'a',
+          criticality: Criticality.Medium,
+        },
+        count: 10,
+      },
+      pcid8: {
+        displayPackageInfo: {
+          attributionIds: ['uuid7'],
+          packageName: 'a',
+          criticality: Criticality.Medium,
+        },
+        count: 11,
+      },
+      pcid9: {
+        displayPackageInfo: {
+          attributionIds: ['uuid7'],
+          packageName: 'b',
+          criticality: Criticality.Medium,
+        },
+        count: 10,
+      },
+      pcid10: {
+        displayPackageInfo: {
+          attributionIds: ['uuid7'],
+          packageName: 'a',
+          criticality: Criticality.High,
+        },
+        count: 10,
+      },
+    };
+    const expectedPackageCardIds: Array<string> = [
+      'pcid10',
+      'pcid8',
+      'pcid7',
+      'pcid9',
+      'pcid2',
+      'pcid3',
+      'pcid5',
+      'pcid1',
+      'pcid6',
+      'pcid4',
+    ];
+
+    const result = initialPackageCardIds.sort(
+      sortDisplayPackageInfosWithCountByCriticalityAndCountAndPackageName(
+        testDisplayPackageInfosWithCount,
+        true,
       ),
     );
     expect(result).toEqual(expectedPackageCardIds);
diff --git a/src/Frontend/Components/AttributionColumn/__tests__/AttributionColumn.test.tsx b/src/Frontend/Components/AttributionColumn/__tests__/AttributionColumn.test.tsx
index ded4f7e4..3bc18c3f 100644
--- a/src/Frontend/Components/AttributionColumn/__tests__/AttributionColumn.test.tsx
+++ b/src/Frontend/Components/AttributionColumn/__tests__/AttributionColumn.test.tsx
@@ -21,9 +21,7 @@ import { text } from '../../../../shared/text';
 import { faker } from '../../../../testing/Faker';
 import { AttributionType } from '../../../enums/enums';
 import {
-  setExternalData,
   setFrequentLicenses,
-  setManualData,
   setTemporaryDisplayPackageInfo,
 } from '../../../state/actions/resource-actions/all-views-simple-actions';
 import { setSelectedResourceId } from '../../../state/actions/resource-actions/audit-view-simple-actions';
@@ -401,60 +399,6 @@ describe('The AttributionColumn', () => {
     );
   });
 
-  it('renders a chip for modified preferred and reverts to original state', async () => {
-    const originId = faker.string.uuid();
-    const manualAttributionId = faker.opossum.attributionId();
-    const temporaryDisplayPackageInfo = faker.opossum.displayPackageInfo({
-      originIds: [originId],
-      wasPreferred: false,
-    });
-    const [attributionId, packageInfo] = faker.opossum.externalAttribution({
-      originIds: [originId],
-      wasPreferred: true,
-    });
-    renderComponent(
-      <AttributionColumn
-        isEditable={true}
-        onSaveButtonClick={noop}
-        onSaveGloballyButtonClick={noop}
-        saveFileRequestListener={noop}
-        onDeleteButtonClick={noop}
-        onDeleteGloballyButtonClick={noop}
-      />,
-      {
-        actions: [
-          setTemporaryDisplayPackageInfo(temporaryDisplayPackageInfo),
-          setManualData(
-            faker.opossum.manualAttributions({
-              [manualAttributionId]: temporaryDisplayPackageInfo,
-            }),
-            faker.opossum.resourcesToAttributions({
-              [faker.opossum.filePath()]: [manualAttributionId],
-            }),
-          ),
-          setExternalData(
-            faker.opossum.externalAttributions({
-              [attributionId]: packageInfo,
-            }),
-            faker.opossum.resourcesToAttributions({
-              [faker.opossum.filePath()]: [attributionId],
-            }),
-          ),
-        ],
-      },
-    );
-
-    expect(
-      screen.getByText(text.auditingOptions.modifiedPreferred),
-    ).toBeInTheDocument();
-
-    await userEvent.click(screen.getByLabelText('undo modified preferred'));
-
-    expect(
-      screen.queryByText(text.auditingOptions.modifiedPreferred),
-    ).not.toBeInTheDocument();
-  });
-
   it('renders a URL icon and opens a link in browser', () => {
     const temporaryDisplayPackageInfo: DisplayPackageInfo = {
       url: 'https://www.testurl.com/',
diff --git a/src/Frontend/Components/AttributionList/__tests__/AttributionList.test.tsx b/src/Frontend/Components/AttributionList/__tests__/AttributionList.test.tsx
index 87c5bac9..c07bdd4a 100644
--- a/src/Frontend/Components/AttributionList/__tests__/AttributionList.test.tsx
+++ b/src/Frontend/Components/AttributionList/__tests__/AttributionList.test.tsx
@@ -5,8 +5,11 @@
 import { screen } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 
+import { Criticality } from '../../../../shared/shared-types';
+import { text } from '../../../../shared/text';
 import { faker } from '../../../../testing/Faker';
 import { loadFromFile } from '../../../state/actions/resource-actions/load-actions';
+import { setVariable } from '../../../state/actions/variables-actions/variables-actions';
 import { getSelectedAttributionIdInAttributionView } from '../../../state/selectors/attribution-view-resource-selectors';
 import {
   getParsedInputFileEnrichedWithTestData,
@@ -50,6 +53,50 @@ describe('AttributionList', () => {
     ).toBe(2);
   });
 
+  it('sorts attributions by criticality', () => {
+    const [attributionId1, packageInfo1] = faker.opossum.manualAttribution({
+      packageName: 'A',
+    });
+    const [attributionId2, packageInfo2] = faker.opossum.manualAttribution({
+      packageName: 'B',
+      criticality: Criticality.High,
+    });
+    const [attributionId3, packageInfo3] = faker.opossum.manualAttribution({
+      packageName: 'C',
+      criticality: Criticality.Medium,
+    });
+    renderComponent(<AttributionList />, {
+      actions: [
+        loadFromFile(
+          getParsedInputFileEnrichedWithTestData({
+            manualAttributions: faker.opossum.manualAttributions({
+              [attributionId1]: packageInfo1,
+              [attributionId2]: packageInfo2,
+              [attributionId3]: packageInfo3,
+            }),
+          }),
+        ),
+        setVariable(
+          'active-sorting-attribution-view',
+          text.attributionViewSorting.byCriticality,
+        ),
+      ],
+    });
+
+    const packageDisplay1 = screen.getByText(
+      `${packageInfo1.packageName}, ${packageInfo1.packageVersion}`,
+    );
+    const packageDisplay2 = screen.getByText(
+      `${packageInfo2.packageName}, ${packageInfo2.packageVersion}`,
+    );
+    const packageDisplay3 = screen.getByText(
+      `${packageInfo3.packageName}, ${packageInfo3.packageVersion}`,
+    );
+
+    expect(packageDisplay2.compareDocumentPosition(packageDisplay3)).toBe(4);
+    expect(packageDisplay3.compareDocumentPosition(packageDisplay1)).toBe(4);
+  });
+
   it('sets selected attribution ID on card click', async () => {
     const [attributionId, packageInfo] = faker.opossum.manualAttribution();
     const { store } = renderComponent(<AttributionList />, {
@@ -93,7 +140,7 @@ describe('AttributionList', () => {
       ],
     });
 
-    await userEvent.click(screen.getByLabelText('Filters'));
+    await userEvent.click(screen.getByLabelText('Filter'));
     await selectFilter(screen, 'Needs Follow-Up');
 
     expect(
@@ -125,7 +172,7 @@ describe('AttributionList', () => {
       ],
     });
 
-    await userEvent.click(screen.getByLabelText('Filters'));
+    await userEvent.click(screen.getByLabelText('Filter'));
     await selectFilter(screen, 'Needs Review by QA');
 
     expect(
@@ -157,7 +204,7 @@ describe('AttributionList', () => {
       ],
     });
 
-    await userEvent.click(screen.getByLabelText('Filters'));
+    await userEvent.click(screen.getByLabelText('Filter'));
     await selectFilter(screen, 'Pre-selected');
 
     expect(
@@ -189,7 +236,7 @@ describe('AttributionList', () => {
       ],
     });
 
-    await userEvent.click(screen.getByLabelText('Filters'));
+    await userEvent.click(screen.getByLabelText('Filter'));
     await selectFilter(screen, 'First Party');
 
     expect(
@@ -221,7 +268,7 @@ describe('AttributionList', () => {
       ],
     });
 
-    await userEvent.click(screen.getByLabelText('Filters'));
+    await userEvent.click(screen.getByLabelText('Filter'));
     await selectFilter(screen, 'Third Party');
 
     expect(
@@ -253,7 +300,7 @@ describe('AttributionList', () => {
       ],
     });
 
-    await userEvent.click(screen.getByLabelText('Filters'));
+    await userEvent.click(screen.getByLabelText('Filter'));
     await selectFilter(screen, 'Currently Preferred');
 
     expect(
@@ -285,7 +332,7 @@ describe('AttributionList', () => {
       ],
     });
 
-    await userEvent.click(screen.getByLabelText('Filters'));
+    await userEvent.click(screen.getByLabelText('Filter'));
     await selectFilter(screen, 'Previously Preferred');
 
     expect(
@@ -319,7 +366,7 @@ describe('AttributionList', () => {
       ],
     });
 
-    await userEvent.click(screen.getByLabelText('Filters'));
+    await userEvent.click(screen.getByLabelText('Filter'));
     await selectFilter(screen, 'Low Confidence');
 
     expect(
@@ -351,7 +398,7 @@ describe('AttributionList', () => {
       ],
     });
 
-    await userEvent.click(screen.getByLabelText('Filters'));
+    await userEvent.click(screen.getByLabelText('Filter'));
     await selectFilter(screen, 'Excluded from Notice');
 
     expect(
diff --git a/src/Frontend/Components/Filter/__tests__/FilterMultiSelect.util.test.ts b/src/Frontend/Components/Filter/__tests__/FilterMultiSelect.util.test.ts
index fd4ada23..c22109be 100644
--- a/src/Frontend/Components/Filter/__tests__/FilterMultiSelect.util.test.ts
+++ b/src/Frontend/Components/Filter/__tests__/FilterMultiSelect.util.test.ts
@@ -2,17 +2,38 @@
 // SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
 //
 // SPDX-License-Identifier: Apache-2.0
-import { Attributions } from '../../../../shared/shared-types';
+import { Attributions, FollowUp } from '../../../../shared/shared-types';
 import { faker } from '../../../../testing/Faker';
-import {
-  setExternalData,
-  setManualData,
-} from '../../../state/actions/resource-actions/all-views-simple-actions';
+import { setManualData } from '../../../state/actions/resource-actions/all-views-simple-actions';
 import { setVariable } from '../../../state/actions/variables-actions/variables-actions';
 import { renderHook } from '../../../test-helpers/render';
 import { Filter, useFilteredAttributions } from '../FilterMultiSelect.util';
 
 describe('useFilteredAttributions', () => {
+  const testManualUuid = 'a32f2f96-f40e-11ea-adc1-0242ac120002';
+  const testOtherManualUuid = 'a32f2f96-f40e-11ea-adc1-0242ac120003';
+  const testManualAttributions: Attributions = {};
+  testManualAttributions[testManualUuid] = {
+    attributionConfidence: 0,
+    comment: 'Some comment',
+    packageName: 'Test package',
+    packageVersion: '1.0',
+    copyright: 'Copyright John Doe',
+    licenseText: 'Some license text',
+    firstParty: true,
+  };
+  testManualAttributions[testOtherManualUuid] = {
+    attributionConfidence: 0,
+    comment: 'Some other comment',
+    packageName: 'Test other package',
+    packageVersion: '2.0',
+    copyright: 'other Copyright John Doe',
+    licenseText: 'Some other license text',
+    followUp: FollowUp,
+    needsReview: true,
+    preferred: true,
+  };
+
   it('returns filtered attributions without filter', () => {
     const [attributionId1, packageInfo1] = faker.opossum.manualAttribution({
       followUp: 'FOLLOW_UP',
@@ -162,54 +183,4 @@ describe('useFilteredAttributions', () => {
       [attributionId1]: packageInfo1,
     });
   });
-
-  it('returns filtered attributions with modified preferred filter', () => {
-    const originId1 = faker.string.uuid();
-    const originId2 = faker.string.uuid();
-    const [attributionId1, packageInfo1] = faker.opossum.manualAttribution({
-      originIds: [originId1],
-      wasPreferred: false,
-    });
-    const [attributionId2, packageInfo2] = faker.opossum.manualAttribution({
-      originIds: [originId2],
-      wasPreferred: false,
-    });
-    const [attributionId3, packageInfo3] = faker.opossum.externalAttribution({
-      originIds: [originId1],
-      wasPreferred: true,
-    });
-    const [attributionId4, packageInfo4] = faker.opossum.externalAttribution({
-      originIds: [originId2],
-      wasPreferred: false,
-    });
-    const { result } = renderHook(() => useFilteredAttributions(), {
-      actions: [
-        setManualData(
-          faker.opossum.manualAttributions({
-            [attributionId1]: packageInfo1,
-            [attributionId2]: packageInfo2,
-          }),
-          faker.opossum.resourcesToAttributions({
-            [faker.opossum.filePath()]: [attributionId1, attributionId2],
-          }),
-        ),
-        setExternalData(
-          faker.opossum.externalAttributions({
-            [attributionId3]: packageInfo3,
-            [attributionId4]: packageInfo4,
-          }),
-          faker.opossum.resourcesToAttributions({
-            [faker.opossum.filePath()]: [attributionId3, attributionId4],
-          }),
-        ),
-        setVariable<Array<Filter>>('active-filters', [
-          'Modified Previously Preferred',
-        ]),
-      ],
-    });
-
-    expect(result.current.attributions).toEqual<Attributions>({
-      [attributionId1]: packageInfo1,
-    });
-  });
 });
diff --git a/src/Frontend/Components/SelectMenu/__tests__/SelectMenu.test.tsx b/src/Frontend/Components/SelectMenu/__tests__/SelectMenu.test.tsx
deleted file mode 100644
index 9962ad1b..00000000
--- a/src/Frontend/Components/SelectMenu/__tests__/SelectMenu.test.tsx
+++ /dev/null
@@ -1,209 +0,0 @@
-// SPDX-FileCopyrightText: Meta Platforms, Inc. and its affiliates
-// SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
-//
-// SPDX-License-Identifier: Apache-2.0
-import { screen, within } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-
-import { faker } from '../../../../testing/Faker';
-import { renderComponent } from '../../../test-helpers/render';
-import { SelectMenu, SelectMenuOption } from '../SelectMenu';
-
-function createOption(props: Partial<SelectMenuOption> = {}): SelectMenuOption {
-  return {
-    id: faker.string.uuid(),
-    label: faker.company.name(),
-    selected: false,
-    ...props,
-  };
-}
-
-describe('SelectMenu', () => {
-  it('displays selected option in single mode', () => {
-    const anchorEl = document.createElement('div');
-    const setAnchorEl = jest.fn();
-    const option1 = createOption();
-    const option2 = createOption({ selected: true });
-    renderComponent(
-      <SelectMenu
-        anchorEl={anchorEl}
-        setAnchorEl={setAnchorEl}
-        options={[option1, option2]}
-      />,
-    );
-
-    expect(
-      within(
-        screen.getByRole('menuitem', { name: option1.label!.toString() }),
-      ).getByTestId('CheckIcon'),
-    ).not.toBeVisible();
-    expect(
-      within(
-        screen.getByRole('menuitem', { name: option2.label!.toString() }),
-      ).getByTestId('CheckIcon'),
-    ).toBeVisible();
-  });
-
-  it('selects new option in single mode', async () => {
-    const anchorEl = document.createElement('div');
-    const setAnchorEl = jest.fn();
-    const onAdd = jest.fn();
-    const onDelete = jest.fn();
-    const option1 = createOption({ onAdd });
-    const option2 = createOption({ selected: true, onDelete });
-    renderComponent(
-      <SelectMenu
-        anchorEl={anchorEl}
-        setAnchorEl={setAnchorEl}
-        options={[option1, option2]}
-      />,
-    );
-
-    await userEvent.click(
-      screen.getByRole('menuitem', { name: option1.label!.toString() }),
-    );
-
-    expect(
-      within(
-        screen.getByRole('menuitem', { name: option1.label!.toString() }),
-      ).getByTestId('CheckIcon'),
-    ).toBeVisible();
-    expect(
-      within(
-        screen.getByRole('menuitem', { name: option2.label!.toString() }),
-      ).getByTestId('CheckIcon'),
-    ).not.toBeVisible();
-    expect(setAnchorEl).toHaveBeenCalledTimes(1);
-    expect(setAnchorEl).toHaveBeenCalledWith(undefined);
-    expect(onAdd).toHaveBeenCalledTimes(1);
-    expect(onDelete).toHaveBeenCalledTimes(1);
-  });
-
-  it('displays selected options in multiple mode', () => {
-    const anchorEl = document.createElement('div');
-    const setAnchorEl = jest.fn();
-    const option1 = createOption();
-    const option2 = createOption({ selected: true });
-    renderComponent(
-      <SelectMenu
-        anchorEl={anchorEl}
-        setAnchorEl={setAnchorEl}
-        options={[option1, option2]}
-        multiple
-      />,
-    );
-
-    expect(
-      within(
-        screen.getByRole('menuitem', { name: option1.label!.toString() }),
-      ).getByTestId('CheckIcon'),
-    ).not.toBeVisible();
-    expect(
-      within(
-        screen.getByRole('menuitem', { name: option2.label!.toString() }),
-      ).getByTestId('CheckIcon'),
-    ).toBeVisible();
-  });
-
-  it('selects new option in multiple mode', async () => {
-    const anchorEl = document.createElement('div');
-    const setAnchorEl = jest.fn();
-    const onAdd = jest.fn();
-    const onDelete = jest.fn();
-    const option1 = createOption({ onAdd });
-    const option2 = createOption({ selected: true, onDelete });
-    renderComponent(
-      <SelectMenu
-        anchorEl={anchorEl}
-        setAnchorEl={setAnchorEl}
-        options={[option1, option2]}
-        multiple
-      />,
-    );
-
-    await userEvent.click(
-      screen.getByRole('menuitem', { name: option1.label!.toString() }),
-    );
-    await userEvent.click(
-      screen.getByRole('menuitem', { name: option2.label!.toString() }),
-    );
-
-    expect(
-      within(
-        screen.getByRole('menuitem', { name: option1.label!.toString() }),
-      ).getByTestId('CheckIcon'),
-    ).toBeVisible();
-    expect(
-      within(
-        screen.getByRole('menuitem', { name: option2.label!.toString() }),
-      ).getByTestId('CheckIcon'),
-    ).not.toBeVisible();
-    expect(setAnchorEl).not.toHaveBeenCalled();
-
-    await userEvent.keyboard('{Escape}');
-
-    expect(setAnchorEl).toHaveBeenCalledWith(undefined);
-    expect(onAdd).toHaveBeenCalledTimes(1);
-    expect(onDelete).toHaveBeenCalledTimes(1);
-  });
-
-  it('does not display selected options in multiple mode when selected are hidden', () => {
-    const anchorEl = document.createElement('div');
-    const setAnchorEl = jest.fn();
-    const option1 = createOption();
-    const option2 = createOption({ selected: true });
-    renderComponent(
-      <SelectMenu
-        anchorEl={anchorEl}
-        setAnchorEl={setAnchorEl}
-        options={[option1, option2]}
-        multiple
-        hideSelected
-      />,
-    );
-
-    expect(
-      within(
-        screen.getByRole('menuitem', { name: option1.label!.toString() }),
-      ).getByTestId('CheckIcon'),
-    ).not.toBeVisible();
-    expect(
-      screen.queryByRole('menuitem', { name: option2.label!.toString() }),
-    ).not.toBeInTheDocument();
-  });
-
-  it('selects new option in multiple mode when selected are hidden', async () => {
-    const anchorEl = document.createElement('div');
-    const setAnchorEl = jest.fn();
-    const onAdd = jest.fn();
-    const onDelete = jest.fn();
-    const option1 = createOption({ onAdd });
-    const option2 = createOption({ selected: true, onDelete });
-    renderComponent(
-      <SelectMenu
-        anchorEl={anchorEl}
-        setAnchorEl={setAnchorEl}
-        options={[option1, option2]}
-        multiple
-        hideSelected
-      />,
-    );
-
-    await userEvent.click(
-      screen.getByRole('menuitem', { name: option1.label!.toString() }),
-    );
-
-    expect(
-      within(
-        screen.getByRole('menuitem', { name: option1.label!.toString() }),
-      ).getByTestId('CheckIcon'),
-    ).toBeVisible();
-    expect(setAnchorEl).not.toHaveBeenCalled();
-
-    await userEvent.keyboard('{Escape}');
-
-    expect(setAnchorEl).toHaveBeenCalledWith(undefined);
-    expect(onAdd).toHaveBeenCalledTimes(1);
-    expect(onDelete).not.toHaveBeenCalled();
-  });
-});
diff --git a/src/Frontend/util/__tests__/get-alphabetical-comparer.test.tsx b/src/Frontend/util/__tests__/get-alphabetical-comparer.test.tsx
index 9da51e89..9e184fe0 100644
--- a/src/Frontend/util/__tests__/get-alphabetical-comparer.test.tsx
+++ b/src/Frontend/util/__tests__/get-alphabetical-comparer.test.tsx
@@ -2,7 +2,8 @@
 // SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
 //
 // SPDX-License-Identifier: Apache-2.0
-import { Attributions } from '../../../shared/shared-types';
+import { Attributions, Criticality } from '../../../shared/shared-types';
+import { faker } from '../../../testing/Faker';
 import {
   compareAlphabeticalStrings,
   getAlphabeticalComparerForAttributions,
@@ -36,7 +37,7 @@ describe('getAlphabeticalComparerForAttributions', () => {
       },
     };
     const sortedAttributionIds = Object.keys(testAttributions).sort(
-      getAlphabeticalComparerForAttributions(testAttributions),
+      getAlphabeticalComparerForAttributions(testAttributions, false),
     );
 
     expect(sortedAttributionIds).toEqual(['3', '5', '4', '2', '1']);
@@ -56,7 +57,7 @@ describe('getAlphabeticalComparerForAttributions', () => {
       },
     };
     const sortedAttributionIds = Object.keys(testAttributions).sort(
-      getAlphabeticalComparerForAttributions(testAttributions),
+      getAlphabeticalComparerForAttributions(testAttributions, false),
     );
 
     expect(sortedAttributionIds).toEqual(['2', '3', '1']);
@@ -79,11 +80,34 @@ describe('getAlphabeticalComparerForAttributions', () => {
       },
     };
     const sortedAttributionIds = Object.keys(testAttributions).sort(
-      getAlphabeticalComparerForAttributions(testAttributions),
+      getAlphabeticalComparerForAttributions(testAttributions, false),
     );
 
     expect(sortedAttributionIds).toEqual(['2', '1', '3']);
   });
+
+  it('sorts by criticality', () => {
+    const testAttributions: Attributions = {
+      '1': faker.opossum.manualPackageInfo({
+        packageName: 'Test package 1',
+        packageVersion: '1.0',
+      }),
+      '2': faker.opossum.manualPackageInfo({
+        criticality: Criticality.Medium,
+      }),
+      '3': faker.opossum.manualPackageInfo({
+        criticality: Criticality.High,
+      }),
+      '4': faker.opossum.manualPackageInfo({
+        packageName: 'Test package 2',
+      }),
+    };
+    const sortedAttributionIds = Object.keys(testAttributions).sort(
+      getAlphabeticalComparerForAttributions(testAttributions, true),
+    );
+
+    expect(sortedAttributionIds).toEqual(['3', '2', '1', '4']);
+  });
 });
 
 describe('compareAlphabeticalStrings', () => {
diff --git a/src/Frontend/web-workers/__tests__/signals-worker.test.ts b/src/Frontend/web-workers/__tests__/signals-worker.test.ts
index 4e47f3bf..723d08b1 100644
--- a/src/Frontend/web-workers/__tests__/signals-worker.test.ts
+++ b/src/Frontend/web-workers/__tests__/signals-worker.test.ts
@@ -45,6 +45,7 @@ describe('SignalsWorker', () => {
     const dispatch = jest.fn();
     const worker = new SignalsWorker(dispatch, {
       manualData: faker.opossum.manualAttributionData(),
+      sortByCriticality: false,
     });
 
     worker.processInput({
@@ -76,6 +77,7 @@ describe('SignalsWorker', () => {
       externalData: faker.opossum.externalAttributionData(),
       resolvedExternalAttributions: new Set<string>(),
       attributionsToHashes: {},
+      sortByCriticality: false,
     });
 
     worker.processInput({
diff --git a/src/e2e-tests/page-objects/AttributionList.ts b/src/e2e-tests/page-objects/AttributionList.ts
index 70d3daaa..d2d34639 100644
--- a/src/e2e-tests/page-objects/AttributionList.ts
+++ b/src/e2e-tests/page-objects/AttributionList.ts
@@ -14,7 +14,7 @@ export class AttributionList {
   constructor(window: Page) {
     this.node = window.getByLabel('attribution list');
     this.attributionCard = new PackageCard(window, this.node);
-    this.filterButton = this.node.getByLabel('Filters', { exact: true });
+    this.filterButton = this.node.getByLabel('Filter', { exact: true });
   }
 
   public assert = {