Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Topics tab refactoring #4826

Merged
merged 11 commits into from
Jan 3, 2025
13 changes: 11 additions & 2 deletions server/routes/dev_place/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ def place_charts(place_dcid: str):
full_chart_config = copy.deepcopy(current_app.config['CHART_CONFIG'])

# Filter chart config by category
overview_chart_config = [c for c in full_chart_config if c.get("isOverview")]
if place_category == OVERVIEW_CATEGORY:
chart_config = [c for c in full_chart_config if c.get("isOverview")]
chart_config = overview_chart_config
else:
chart_config = [
c for c in full_chart_config if c["category"] == place_category
Expand All @@ -103,6 +104,14 @@ def place_charts(place_dcid: str):
place_dcid=place_dcid,
child_place_type=child_place_type)

# Always execute the call for the overview category to fetch the valid categories.
filtered_chart_config_for_category = (
filtered_chart_config if place_category == OVERVIEW_CATEGORY else
place_utils.filter_chart_config_by_place_dcid(
chart_config=overview_chart_config,
place_dcid=place_dcid,
child_place_type=child_place_type))

# Translate chart config titles
translated_chart_config = place_utils.translate_chart_config(
filtered_chart_config)
Expand All @@ -113,7 +122,7 @@ def place_charts(place_dcid: str):

# Translate category strings
translated_category_strings = place_utils.get_translated_category_strings(
filtered_chart_config)
filtered_chart_config_for_category)

response = PlaceChartsApiResponse(
charts=charts,
Expand Down
3 changes: 3 additions & 0 deletions server/routes/dev_place/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
from flask_babel import gettext

from server.lib import fetch
from server.lib.cache import cache
from server.lib.i18n import DEFAULT_LOCALE
from server.routes import TIMEOUT
from server.routes.dev_place.types import Chart
from server.routes.dev_place.types import Place
import server.routes.shared_api.place as place_api
Expand Down Expand Up @@ -128,6 +130,7 @@ def get_place_type_with_parent_places_links(dcid: str) -> str:
return ''


@cache.cached(timeout=TIMEOUT, query_string=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be @cache.memoize() to include the arguments in the cache key: https://flask-caching.readthedocs.io/en/latest/#memoization

Also-you can enable caching locally for testing by setting USE_MEMCACHE = True here

Copy link
Contributor Author

@gmechali gmechali Jan 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done and tested cache locally, confirming it's very fast now!

def filter_chart_config_by_place_dcid(chart_config: List[Dict],
place_dcid: str,
child_place_type=str):
Expand Down
174 changes: 88 additions & 86 deletions static/js/place/dev_place_main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,61 @@ const PlaceHeader = (props: {
);
};

/**
* Component that renders the individual topic navigation buttons.
* Shows buttons for the topics created and highlights the currently selected category.
*
* @param props.category The category for the current button
* @param props.selectedCategory The currently selected category
* @param props.forceDevPlaces Whether the flag to force dev places should be propagated.
* @param props.place The place object containing the DCID for generating URLs
* @returns Button component for the current topic
*/
const TopicItem = (props: {
category: string;
selectedCategory: string;
forceDevPlaces: boolean;
place: NamedTypedPlace;
}): React.JSX.Element => {
const { category, selectedCategory, forceDevPlaces, place } = props;

const createHref = (
category: string,
forceDevPlaces: boolean,
place: NamedTypedPlace
): string => {
const isOverview = category === "Overview";

let href = `/place/${place.dcid}`;
Copy link
Contributor

@dwnoble dwnoble Jan 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using URLSearchParams might make this more clear. Something like:

const href = `/place/${place.dcid}`;
const params = new URLSearchParams();

if (!isOverview) {
  params.set("category", category);
}
if (forceDevPlaces) {
  params.set("force_dev_places", "true");
}

const queryString = params.toString();
if (queryString) {
  return `${href}?${queryString}`;
} else {
  return href;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - definitely cleans it up a bit, thanks!

if (!isOverview || forceDevPlaces) {
href += "?";
}
if (!isOverview) {
href += `category=${category}`;
}
if (forceDevPlaces && !isOverview) {
href += "&";
}
if (forceDevPlaces) {
href += "force_dev_places=true";
}
return href;
};

return (
<div className="item-list-item">
<a
href={createHref(category, forceDevPlaces, place)}
className={`item-list-text + ${
selectedCategory === category ? " selected" : ""
}`}
>
{category}
</a>
</div>
);
};

/**
* Component that renders the topic navigation tabs.
* Shows tabs for Overview and different categories like Economics, Health, etc.
Expand All @@ -194,103 +249,34 @@ const PlaceHeader = (props: {
* @returns Navigation component with topic tabs
*/
const PlaceTopicTabs = ({
topics,
forceDevPlaces,
category,
place,
}: {
topics: string[];
forceDevPlaces: boolean;
category: string;
place: NamedTypedPlace;
}): React.JSX.Element => {
if (!topics || topics.length == 0) {
return <></>;
}

return (
<div className="explore-topics-box">
<span className="explore-relevant-topics">Relevant topics</span>
<div className="item-list-container">
<div className="item-list-inner">
<div className="item-list-item">
<a
className={`item-list-text ${
category === "Overview" ? "selected" : ""
}`}
href={`/place/${place.dcid}${
forceDevPlaces ? "?force_dev_places=true" : ""
}`}
>
Overview
</a>
</div>
<div className="item-list-item">
<a
className={`item-list-text ${
category === "Economics" ? "selected" : ""
}`}
href={`/place/${place.dcid}?category=Economics${
forceDevPlaces ? "&force_dev_places=true" : ""
}`}
>
Economics
</a>
</div>
<div className="item-list-item">
<a
className={`item-list-text ${
category === "Health" ? "selected" : ""
}`}
href={`/place/${place.dcid}?category=Health${
forceDevPlaces ? "&force_dev_places=true" : ""
}`}
>
Health
</a>
</div>
<div className="item-list-item">
<a
className={`item-list-text ${
category === "Equity" ? "selected" : ""
}`}
href={`/place/${place.dcid}?category=Equity${
forceDevPlaces ? "&force_dev_places=true" : ""
}`}
>
Equity
</a>
</div>
<div className="item-list-item">
<a
className={`item-list-text ${
category === "Demographics" ? "selected" : ""
}`}
href={`/place/${place.dcid}?category=Demographics${
forceDevPlaces ? "&force_dev_places=true" : ""
}`}
>
Demographics
</a>
</div>
<div className="item-list-item">
<a
className={`item-list-text ${
category === "Environment" ? "selected" : ""
}`}
href={`/place/${place.dcid}?category=Environment${
forceDevPlaces ? "&force_dev_places=true" : ""
}`}
>
Environment
</a>
</div>
<div className="item-list-item">
<a
className={`item-list-text ${
category === "Energy" ? "selected" : ""
}`}
href={`/place/${place.dcid}?category=Energy${
forceDevPlaces ? "&force_dev_places=true" : ""
}`}
>
Energy
</a>
</div>
{topics.map((topic) => (
<TopicItem
key={topic}
category={topic}
selectedCategory={category}
forceDevPlaces={forceDevPlaces}
place={place}
/>
))}
</div>
</div>
</div>
Expand Down Expand Up @@ -540,6 +526,7 @@ export const DevPlaceMain = (): React.JSX.Element => {
const [childPlaces, setChildPlaces] = useState<NamedTypedPlace[]>([]);
const [parentPlaces, setParentPlaces] = useState<NamedTypedPlace[]>([]);
const [pageConfig, setPageConfig] = useState<SubjectPageConfig>();
const [categories, setCategories] = useState<string[]>();

const urlParams = new URLSearchParams(window.location.search);
const category = urlParams.get("category") || "Overview";
Expand Down Expand Up @@ -586,16 +573,30 @@ export const DevPlaceMain = (): React.JSX.Element => {

setPlaceChartsApiResponse(placeChartsApiResponse);
setRelatedPlacesApiResponse(relatedPlacesApiResponse);
const pageConfig = placeChartsApiResponsesToPageConfig(
const config = placeChartsApiResponsesToPageConfig(
placeChartsApiResponse
);
setChildPlaceType(relatedPlacesApiResponse.childPlaceType);
setChildPlaces(relatedPlacesApiResponse.childPlaces);
setParentPlaces(relatedPlacesApiResponse.parentPlaces);
setPageConfig(pageConfig);
setPageConfig(config);
})();
}, [place]);

useEffect(() => {
if (placeChartsApiResponse && placeChartsApiResponse.charts) {
const categories = new Set<string>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this categories set used somewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh i was using that before but then switched to using the translatedCategories directly, so I think we're good now. Removed.

placeChartsApiResponse.charts.forEach((chart) => {
categories.add(chart.category);
});
setCategories(
["Overview"].concat(
Object.values(placeChartsApiResponse.translatedCategoryStrings)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this categories list should store both the english & translated values (one to use on the page text, and one to use in the URL param for the place page)

Copy link
Contributor

@dwnoble dwnoble Jan 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also- we should add "Overview": "_translated overview value_" to the translatedCategoryStrings entry in the API response.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I see. Are the translation implemented yet? I tried using it with different HL params and it didnt do anything. I assume we'll need a bit more refactoring here, for example the overview topic will need a translation too, so it should come from the backend probably.
I added a todo to figure this part out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translations are implemented, but i think there's some extra wiring we need to do to get the frontend to pass the language to the API.

FR example in autopush: https://autopush.datacommons.org/api/dev-place/charts/country/LVA?category=Overview&hl=fr

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack will see what's up with those in follow up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good!

)
);
}
}, [placeChartsApiResponse, setPlaceChartsApiResponse, setCategories]);

if (!place) {
return <div>Loading...</div>;
}
Expand All @@ -607,6 +608,7 @@ export const DevPlaceMain = (): React.JSX.Element => {
placeSubheader={placeSubheader}
/>
<PlaceTopicTabs
topics={categories}
category={category}
place={place}
forceDevPlaces={forceDevPlaces}
Expand Down
Loading