Skip to content

Commit

Permalink
Merge pull request #3 from Eyevinn/source-management-sorting
Browse files Browse the repository at this point in the history
Source management sorting
  • Loading branch information
Saelmala authored Sep 2, 2024
2 parents d1bce4e + 2347751 commit bb5c1a2
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 46 deletions.
89 changes: 50 additions & 39 deletions src/api/manager/job/syncInventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { upsertSource } from '../sources';
import { getDatabase } from '../../mongoClient/dbClient';
import { WithId } from 'mongodb';

type SourceWithoutLastConnected = Omit<Source, 'lastConnected'>;

// TODO: getSourcesFromAPI should return ResourcesSourceResponse and changed to our model later
async function getSourcesFromAPI() {
const ingests = await getIngests();
const resolvedIngests = (
Expand All @@ -15,32 +18,35 @@ async function getSourcesFromAPI() {
result.status === 'fulfilled'
)
.map((result) => result.value);
const sources: Source[] = resolvedIngests.flatMap((ingest) => {
return ingest.sources.map(
(source) =>
({
status: source.active ? 'new' : 'gone',
name: source.name,
type: 'camera',
tags: {
location: 'Unknown'
},
ingest_name: ingest.name,
ingest_source_name: source.name,
video_stream: {
width: source?.video_stream?.width,
height: source?.video_stream?.height,
frame_rate:
source?.video_stream?.frame_rate_n /
source?.video_stream?.frame_rate_d
},
audio_stream: {
number_of_channels: source?.audio_stream?.number_of_channels,
sample_rate: source?.audio_stream?.sample_rate
}
} satisfies Source)
);
});

const sources: SourceWithoutLastConnected[] = resolvedIngests.flatMap(
(ingest) => {
return ingest.sources.map(
(source) =>
({
status: source.active ? 'new' : 'gone',
name: source.name,
type: 'camera',
tags: {
location: 'Unknown'
},
ingest_name: ingest.name,
ingest_source_name: source.name,
video_stream: {
width: source?.video_stream?.width,
height: source?.video_stream?.height,
frame_rate:
source?.video_stream?.frame_rate_n /
source?.video_stream?.frame_rate_d
},
audio_stream: {
number_of_channels: source?.audio_stream?.number_of_channels,
sample_rate: source?.audio_stream?.sample_rate
}
} satisfies SourceWithoutLastConnected)
);
}
);
return sources;
}

Expand All @@ -67,26 +73,31 @@ export async function runSyncInventory() {
// If source was not found in response from API, always mark it as gone
return { ...inventorySource, status: 'gone' } satisfies WithId<Source>;
}
// Keep all old fields from the inventory source (name, tags, id, audio_stream etc), but update the status
// Keep all old fields from the inventory source (name, tags, id, audio_stream etc), but update the status and set the lastConnected to the current date
return {
...inventorySource,
status: apiSource.status
status: apiSource.status,
lastConnected:
apiSource.status !== 'gone' ? new Date() : inventorySource.lastConnected
} satisfies WithId<Source>;
});

// Look for new sources that doesn't already exist in the inventory,
// these should all be added to the inventory, status of these are set in getSourcesFromAPI.
const newSourcesToUpsert = apiSources.filter((source) => {
const existingSource = dbInventoryWithCorrectStatus.find(
(inventorySource) => {
return (
source.ingest_name === inventorySource.ingest_name &&
source.ingest_source_name === inventorySource.ingest_source_name
);
}
);
return !existingSource;
});

const newSourcesToUpsert = apiSources
.filter((source) => {
const existingSource = dbInventoryWithCorrectStatus.find(
(inventorySource) => {
return (
source.ingest_name === inventorySource.ingest_name &&
source.ingest_source_name === inventorySource.ingest_source_name
);
}
);
return !existingSource;
})
.map((source) => ({ ...source, lastConnected: new Date() }));

const sourcesToUpsert = [
...newSourcesToUpsert,
Expand Down
58 changes: 56 additions & 2 deletions src/components/filter/FilterDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { ChangeEvent, useEffect, useState } from 'react';
import { useTranslate } from '../../i18n/useTranslate';
import { SortSelect } from './SortSelect';
import { IconArrowsSort } from '@tabler/icons-react';

function FilterDropdown({
close,
Expand All @@ -13,7 +15,8 @@ function FilterDropdown({
setIsTypeHidden,
setIsLocationHidden,
setSelectedTags,
setOnlyShowActiveSources: setOnlyShowConfigSources
setOnlyShowActiveSources: setOnlyShowConfigSources,
handleSorting
}: {
close: () => void;
types: string[];
Expand All @@ -27,15 +30,31 @@ function FilterDropdown({
setIsLocationHidden: React.Dispatch<React.SetStateAction<boolean>>;
setOnlyShowActiveSources: React.Dispatch<React.SetStateAction<boolean>>;
setSelectedTags: React.Dispatch<React.SetStateAction<Set<string>>>;
handleSorting: (reversedOrder: boolean) => void;
}) {
const t = useTranslate();

const [searchedTypes, setSearchedTypes] = useState<string[]>([]);
const [searchedLocations, setSearchedLocations] = useState<string[]>([]);
const [selectedValue, setSelectedValue] = useState<string>(
t('inventory_list.no_sorting_applied')
);
const [reverseSortOrder, setReverseSortOrder] = useState<boolean>(false);

useEffect(() => {
setSearchedTypes(types);
setSearchedLocations(locations);
}, [types, locations]);

useEffect(() => {
if (
selectedValue === t('inventory_list.no_sorting_applied') &&
reverseSortOrder
) {
setReverseSortOrder(false);
}
}, [selectedValue, reverseSortOrder]);

const hideLocationDiv = () => {
setIsLocationHidden(true);
};
Expand All @@ -54,6 +73,14 @@ function FilterDropdown({
setSelectedTags(new Set<string>(temp));
};

useEffect(() => {
if (
reverseSortOrder ||
selectedValue === t('inventory_list.most_recent_connection')
)
handleSorting(reverseSortOrder);
}, [reverseSortOrder, selectedValue]);

function addFilterComponent(type: string, component: string, index: number) {
const id = `${type}-${component}-id`;
const key = `${type}-${index}`;
Expand Down Expand Up @@ -122,7 +149,6 @@ function FilterDropdown({
setSearchedLocations(temp);
};

const t = useTranslate();
return (
<div
id="dropdownDefaultCheckbox"
Expand Down Expand Up @@ -215,6 +241,34 @@ function FilterDropdown({
})}
</ul>
</div>
<div className="flex items-center mt-2 text-p">
<span className="flex min-w-[20%] mr-5">
{t('inventory_list.sort_by')}
</span>
<SortSelect
value={selectedValue}
onChange={(e) => {
setSelectedValue(e.target.value);
}}
options={[
t('inventory_list.no_sorting_applied'),
t('inventory_list.most_recent_connection')
]}
/>
<button
className={`ml-2 p-1 rounded-md ${
selectedValue === t('inventory_list.no_sorting_applied')
? 'text-white/50'
: 'text-white'
} ${reverseSortOrder ? 'bg-zinc-800' : 'bg-zinc-600'}`}
onClick={() => setReverseSortOrder(!reverseSortOrder)}
disabled={
selectedValue === t('inventory_list.no_sorting_applied')
}
>
<IconArrowsSort />
</button>
</div>
</li>
<li className="relative rounded w-full px-3">
<div className="flex flex-row justify-between mt-4">
Expand Down
13 changes: 13 additions & 0 deletions src/components/filter/FilterOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ function FilterOptions({
}
};

const handleSorting = (reversedOrder: boolean) => {
const sortedSourcesArray = Array.from(tempSet.values()).sort((a, b) => {
const dateA = new Date(a.lastConnected).getTime();
const dateB = new Date(b.lastConnected).getTime();
return reversedOrder ? dateA - dateB : dateB - dateA;
});
tempSet = new Map(
sortedSourcesArray.map((source) => [source._id.toString(), source])
);
onFilteredSources(tempSet);
};

return (
<ClickAwayListener
onClickAway={() => {
Expand Down Expand Up @@ -127,6 +139,7 @@ function FilterOptions({
setIsLocationHidden={setIsLocationHidden}
setSelectedTags={setSelectedTags}
setOnlyShowActiveSources={setOnlyShowActiveSources}
handleSorting={handleSorting}
/>
</div>
</ClickAwayListener>
Expand Down
21 changes: 21 additions & 0 deletions src/components/filter/SortSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
type SortSelectProps = {
value: string;
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
options: readonly string[];
};

export const SortSelect = ({ value, onChange, options }: SortSelectProps) => {
return (
<select
className="border justify-center text-sm rounded-lg w-1/2 pl-2 py-1.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-p"
value={value}
onChange={onChange}
>
{options.map((value) => (
<option value={value} key={value}>
{value}
</option>
))}
</select>
);
};
8 changes: 7 additions & 1 deletion src/components/inventory/EditViewContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface IInput {
name: string;
location: string;
type: SourceType | '';
lastConnected: Date | '';
audioMapping?: Numbers[];
}

Expand All @@ -41,7 +42,10 @@ interface IContext {
}

export const EditViewContext = createContext<IContext>({
input: [{ name: '', location: '', type: '', audioMapping: [] }, () => null],
input: [
{ name: '', location: '', type: '', lastConnected: '', audioMapping: [] },
() => null
],
saved: [undefined, () => null],
loading: false,
isSame: true,
Expand All @@ -66,6 +70,7 @@ export default function Context({
name: source.name,
location: source.tags.location,
type: source.type,
lastConnected: source.lastConnected,
// audioMapping: source?.stream_settings?.audio_mapping || []
audioMapping: source?.audio_stream.audio_mapping || []
});
Expand All @@ -81,6 +86,7 @@ export default function Context({
name: source.name,
location: source.tags.location,
type: source.type,
lastConnected: source.lastConnected,
// audioMapping: source?.stream_settings?.audio_mapping || []
audioMapping: source?.audio_stream.audio_mapping || []
}));
Expand Down
9 changes: 9 additions & 0 deletions src/components/inventory/editView/GeneralSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ export default function GeneralSettings() {
</div>
</div>

<div className="flex mb-5">
<h2 className="flex w-[100px] items-center">
{t('source.last_connected')}
</h2>
<div className="flex-col">
<p>{new Date(input.lastConnected).toLocaleString()}</p>
</div>
</div>

{height && width && (
<div className="flex mb-5">
<h2 className="flex w-[100px] items-center">{t('video')}</h2>
Expand Down
4 changes: 4 additions & 0 deletions src/components/sourceListItem/SourceListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ function InventoryListItem({
: capitalize(source.tags.location)
})}
</h2>
<h2 className="text-sm">
{t('source.last_connected')}:{' '}
{new Date(source.lastConnected).toLocaleString()}
</h2>
<h2 className="text-xs">
{t('source.ingest', {
ingest: source.ingest_name
Expand Down
8 changes: 6 additions & 2 deletions src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export const en = {
audio: 'Audio: {{audio}}',
orig: 'Original Name: {{name}}',
metadata: 'Source Metadata',
location_unknown: 'Unknown'
location_unknown: 'Unknown',
last_connected: 'Last connection'
},
delete_source_status: {
delete_stream: 'Delete stream',
Expand Down Expand Up @@ -510,7 +511,10 @@ export const en = {
locations: 'Location',
active_sources: 'Active Sources',
add: 'Add',
edit: 'Edit'
edit: 'Edit',
sort_by: 'Sort by',
no_sorting_applied: 'No sorting selected',
most_recent_connection: 'Most recent connection'
},
clear: 'Clear',
apply: 'Apply',
Expand Down
8 changes: 6 additions & 2 deletions src/i18n/locales/sv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export const sv = {
audio: 'Ljud: {{audio}}',
orig: 'Enhetsnamn: {{name}}',
metadata: 'Käll-metadata',
location_unknown: 'Okänd'
location_unknown: 'Okänd',
last_connected: 'Senast uppkoppling'
},
delete_source_status: {
delete_stream: 'Radera ström',
Expand Down Expand Up @@ -512,7 +513,10 @@ export const sv = {
locations: 'Plats',
active_sources: 'Aktiva källor',
add: 'Lägg till',
edit: 'Redigera'
edit: 'Redigera',
sort_by: 'Sortera på',
no_sorting_applied: 'Ingen sortering vald',
most_recent_connection: 'Senast anslutning'
},
clear: 'Rensa',
apply: 'Applicera',
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/Source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface Source {
ingest_source_name: string;
video_stream: VideoStream;
audio_stream: AudioStream;
lastConnected: Date;
}

export interface SourceReference {
Expand Down

0 comments on commit bb5c1a2

Please sign in to comment.