Skip to content

Commit

Permalink
Use home page data from backend (#1134)
Browse files Browse the repository at this point in the history
* Use home page data from backend

* update URL for API

* update response data

* rename to plugin_types

* fix missing rename for plugin_types

* fix missing rename for plugin_types in AppLoader
  • Loading branch information
codemonkey800 committed Jul 11, 2023
1 parent 457fda9 commit 7eb1456
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 101 deletions.
93 changes: 92 additions & 1 deletion frontend/mock-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ if (process.env.MOCK_SERVER === 'false') {

const cors = require('cors');
const express = require('express');
const { set, pick, get } = require('lodash');
const { set, pick, get, sample, shuffle } = require('lodash');

const napariPlugin = require('./src/fixtures/plugin.json');
const pluginIndex = require('./src/fixtures/index.json');
Expand All @@ -31,6 +31,97 @@ app.get('/plugins/index', async (_, res) => {
res.json(pluginIndex);
});

function getSectionPlugins({ limit, pluginMap, filter = () => true, sort }) {
const result = [];

while (result.length < limit && pluginMap.size > 0) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
let plugins = Array.from(pluginMap.values()).filter(filter);

if (plugins.length === 0) {
break;
}

if (sort) {
plugins.sort(sort);
} else {
plugins = shuffle(plugins);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-non-null-assertion
const pluginKey = plugins[0].name;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const plugin = pluginMap.get(pluginKey);
pluginMap.delete(pluginKey);
result.push(plugin);
}

return result;
}

function compareDates(dateA, dateB) {
// time in ms makes newer dates have higher values
return new Date(dateB).getTime() - new Date(dateA).getTime();
}

app.get('/plugin/home/sections/:sections', (req, res) => {
const limit = +req.query.limit || 3;
const sections = req.params.sections.split(',');
const pluginMap = new Map(pluginIndex.map((plugin) => [plugin.name, plugin]));

const pluginDataType =
sample(
Array.from(
new Set(
pluginIndex
.flatMap((plugin) => plugin.plugin_types || [])
.filter((pluginType) => pluginType !== 'theme'),
),
),
) || 'sample_data';

const data = {
plugin_types: {
type: pluginDataType,
plugins: getSectionPlugins({
pluginMap,
limit,
filter: (plugin) => !!plugin.plugin_types.includes(pluginDataType),
}),
},

newest: {
plugins: getSectionPlugins({
pluginMap,
limit,
sort: (a, b) => compareDates(a.first_released, b.first_released),
}),
},

recently_updated: {
plugins: getSectionPlugins({
pluginMap,
limit,
sort: (a, b) => compareDates(a.release_date, b.release_date),
}),
},

top_installed: {
plugins: getSectionPlugins({
pluginMap,
limit,
sort: (a, b) => b.total_installs - a.total_installs,
}),
},
};

const response = {};
for (const section of sections) {
set(response, section, data[section]);
}

res.json(response);
});

app.get('/plugins/:name', async (req, res) => {
const plugin = pluginIndex.find(({ name }) => name === req.params.name);

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/AppLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ export function AppLoader({ nextUrl }: Props) {
<LoadingStateProvider loading key="/">
<HomePageProvider
pluginSections={{
plugin_type: {
plugin_types: {
plugins,
type: PluginType.SampleData,
},
newest: { plugins },
recently_updated: { plugins },
top_installs: { plugins },
top_installed: { plugins },
}}
>
<HomePage />
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/HomePage/FeaturedPlugins.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ export function FeaturedPlugins() {
const isLoading = useLoadingState();

const {
plugin_type: pluginTypeSection,
plugin_types: pluginTypeSection,
newest: newestSection,
recently_updated: recentlyUpdatedSection,
top_installs: topInstallsSection,
top_installed: topInstallsSection,
} = pluginSections;

return (
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/types/homePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ export type PluginSection<T = Record<string, unknown>> = T & {
};

export enum PluginSectionType {
pluginType = 'plugin_type',
pluginType = 'plugin_types',
newest = 'newest',
recentlyUpdated = 'recently_updated',
topInstalls = 'top_installs',
topInstalls = 'top_installed',
}

export interface PluginSectionsResponse {
Expand Down
99 changes: 5 additions & 94 deletions frontend/src/utils/HubAPIClient.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
/* eslint-disable max-classes-per-file */

import axios, { AxiosRequestConfig } from 'axios';
import { sample, set, shuffle } from 'lodash';
import { snapshot } from 'valtio';

import { BROWSER, PROD, SERVER, STAGING } from '@/constants/env';
import { featureFlagsStore } from '@/store/featureFlags';
import { compareDates } from '@/store/search/sorters';
import {
PluginData,
PluginIndexData,
PluginMetrics,
PluginSectionsResponse,
PluginSectionType,
PluginType,
} from '@/types';
import { CollectionData, CollectionIndexData } from '@/types/collections';

Expand Down Expand Up @@ -194,102 +191,16 @@ class HubAPIClient {
return validateMetricsData(data);
}

private getSectionPlugins({
count,
pluginMap,
filter = () => true,
sort,
}: {
count: number;
pluginMap: Map<string, PluginIndexData>;
filter?: (plugin: PluginIndexData) => boolean;
sort?: (a: PluginIndexData, b: PluginIndexData) => number;
}) {
const result: PluginIndexData[] = [];

while (result.length < count && pluginMap.size > 0) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
let plugins = Array.from(pluginMap.values()).filter(filter)!;

if (sort) {
plugins.sort(sort);
} else {
plugins = shuffle(plugins);
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-non-null-assertion
const pluginKey = plugins[0].name;

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const plugin = pluginMap.get(pluginKey)!;
pluginMap.delete(pluginKey);

result.push(plugin);
}

return result;
}

// TODO Replace with actual API calls, for now create data client-side
// TODO Add data validation
async getPluginSections(
sections: PluginSectionType[],
): Promise<PluginSectionsResponse> {
const index = await this.getPluginIndex();
const pluginMap = new Map(index.map((plugin) => [plugin.name, plugin]));

const pluginDataType =
sample(
Array.from(
new Set(
index
.flatMap((plugin) => plugin.plugin_types ?? [])
.filter((pluginType) => pluginType !== PluginType.Theme),
),
),
) ?? PluginType.SampleData;

const data: PluginSectionsResponse = {
plugin_type: {
type: pluginDataType,
plugins: this.getSectionPlugins({
pluginMap,
count: 3,
filter: (plugin) => !!plugin.plugin_types?.includes(pluginDataType),
}),
},

newest: {
plugins: this.getSectionPlugins({
pluginMap,
count: 3,
sort: (a, b) => compareDates(a.first_released, b.first_released),
}),
},

recently_updated: {
plugins: this.getSectionPlugins({
pluginMap,
count: 3,
sort: (a, b) => compareDates(a.release_date, b.release_date),
}),
},

top_installs: {
plugins: this.getSectionPlugins({
pluginMap,
count: 3,
sort: (a, b) => b.total_installs - a.total_installs,
}),
},
};

const response: PluginSectionsResponse = {};
for (const section of sections) {
set(response, section, data[section]);
if (sections.length === 0) {
return {};
}

return response;
return this.sendRequest<PluginSectionsResponse>(
`/plugin/home/sections/${sections.join(',')}`,
);
}
}

Expand Down

0 comments on commit 7eb1456

Please sign in to comment.