diff --git a/docs/feature_flags.md b/docs/feature_flags.md
index 84a4b7614b..50c24aa34b 100644
--- a/docs/feature_flags.md
+++ b/docs/feature_flags.md
@@ -90,9 +90,9 @@ For example, to enable the "autocomplete" feature:
* Enable: https://datacommons.org/explore?enable_feature=autocomplete
-To enable both the autocomplete and metadata_modal features:
+To enable both the autocomplete and page_overview_ga features:
-* Enable: https://datacommons.org/explore?enable_feature=autocomplete&enable_feature=metadata_modal
+* Enable: https://datacommons.org/explore?enable_feature=autocomplete&enable_feature=page_overview_ga
To manually disable a feature flag, use the `disable_feature` URL parameter.
diff --git a/static/js/components/subject_page/block.tsx b/static/js/components/subject_page/block.tsx
index 892e3e8d7b..f1d4cad014 100644
--- a/static/js/components/subject_page/block.tsx
+++ b/static/js/components/subject_page/block.tsx
@@ -57,10 +57,6 @@ import {
FacetSelector,
FacetSelectorFacetInfo,
} from "../../shared/facet_selector/facet_selector";
-import {
- isFeatureEnabled,
- METADATA_FEATURE_FLAG,
-} from "../../shared/feature_flags/util";
import { usePromiseResolver } from "../../shared/hooks/promise_resolver";
import { NamedPlace, NamedTypedPlace, StatVarSpec } from "../../shared/types";
import {
@@ -246,7 +242,7 @@ function blockEligibleForFacetSelector(
columns: ColumnConfig[],
isWebComponentBlock: boolean
): boolean {
- if (isWebComponentBlock || !isFeatureEnabled(METADATA_FEATURE_FLAG)) {
+ if (isWebComponentBlock) {
return false;
}
diff --git a/static/js/components/tiles/chart_footer.tsx b/static/js/components/tiles/chart_footer.tsx
index 1d7e345074..d2436bcc94 100644
--- a/static/js/components/tiles/chart_footer.tsx
+++ b/static/js/components/tiles/chart_footer.tsx
@@ -22,10 +22,6 @@ import React, { RefObject, useState } from "react";
import { intl } from "../../i18n/i18n";
import { messages } from "../../i18n/i18n_messages";
-import {
- isFeatureEnabled,
- METADATA_FEATURE_FLAG,
-} from "../../shared/feature_flags/util";
import {
GA_EVENT_TILE_DOWNLOAD,
GA_EVENT_TILE_EXPLORE_MORE,
@@ -99,17 +95,16 @@ export function ChartFooter(props: ChartFooterPropType): JSX.Element {
)}
- {props.getObservationSpecs &&
- isFeatureEnabled(METADATA_FEATURE_FLAG) && (
-
-
-
- )}
+ {props.getObservationSpecs && (
+
+
+
+ )}
{props.exploreLink && (
diff --git a/static/js/shared/facet_selector/facet_option_content.tsx b/static/js/shared/facet_selector/facet_option_content.tsx
index 80bc2c2ccc..ff5e27b3ac 100644
--- a/static/js/shared/facet_selector/facet_option_content.tsx
+++ b/static/js/shared/facet_selector/facet_option_content.tsx
@@ -31,7 +31,7 @@ import { facetSelectionComponentMessages } from "../../i18n/i18n_facet_selection
import { metadataComponentMessages } from "../../i18n/i18n_metadata_messages";
import { humanizeIsoDuration } from "../periodicity";
import { StatMetadata } from "../stat_types";
-import { SELECTOR_PREFIX } from "./facet_selector_rich";
+import { SELECTOR_PREFIX } from "./facet_selector";
interface FacetOptionContentProps {
// The metadata for the facet.
diff --git a/static/js/shared/facet_selector/facet_selector.tsx b/static/js/shared/facet_selector/facet_selector.tsx
index 6196fedde5..b42de7c8e3 100644
--- a/static/js/shared/facet_selector/facet_selector.tsx
+++ b/static/js/shared/facet_selector/facet_selector.tsx
@@ -15,22 +15,53 @@
*/
/**
- * Renders either the Rich or Simple facet selector component based on a
- * feature flag. This component acts as a switchboard.
+ * Component to allow the facet selection for a chart. It will render
+ * an interface where the user can select either:
+ *
+ * 1. Standard mode: a facet for each stat var, where each stat var may
+ * be given its own stat var.
+ * 2. Grouped mode: a single facet list, where only one facet is chosen
+ * (and this facet is applied to all stat vars).
*/
-/* TODO (nick-next): When the feature flag is to be removed, remove this
- component and rename FacetSelectorRich to FacetSelector
- */
+/** @jsxImportSource @emotion/react */
-import React, { ReactElement } from "react";
+import { css, useTheme } from "@emotion/react";
+import _, { isEqual } from "lodash";
+import React, { ReactElement, useEffect, useMemo, useState } from "react";
+import { Button } from "../../components/elements/button/button";
+import { DebugFlag } from "../../components/elements/debug_flag";
+import {
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+} from "../../components/elements/dialog/dialog";
+import { intl } from "../../i18n/i18n";
+import { facetSelectionComponentMessages } from "../../i18n/i18n_facet_selection_messages";
+import { messages } from "../../i18n/i18n_messages";
import { FacetSelectionCriteria } from "../../types/facet_selection_criteria";
-import { isFeatureEnabled, METADATA_FEATURE_FLAG } from "../feature_flags/util";
+import { findMatchingFacets } from "../../utils/data_fetch_utils";
import { StatMetadata } from "../stat_types";
-import { FacetSelectorRich } from "./facet_selector_rich";
-import { FacetSelectorSimple } from "./facet_selector_simple";
+import { FacetSelectorGroupedContent } from "./facet_selector_grouped_content";
+import { FacetSelectorStandardContent } from "./facet_selector_standard_content";
+
+export const SELECTOR_PREFIX = "source-selector";
+/**
+ * If true, the facet selector will show all available facet options, even if they
+ * are inconsistent across different statistical variables in grouped mode. It will
+ * also display a debug flag in debug mode to indicate the issue.
+ * If false, it will filter the list to only show facet options that are common to
+ * all statistical variables.
+ *
+ * In non-grouped mode, this flag will have no effect.
+ */
+const SHOW_INCONSISTENT_FACETS = false;
+
+// The information needed in SourceSelector component for a single stat var to
+// get the list of available facets
export interface FacetSelectorFacetInfo {
// dcid of the stat var
dcid: string;
@@ -43,7 +74,7 @@ export interface FacetSelectorFacetInfo {
displayNames?: Record;
}
-interface FacetSelectorPropType {
+interface FacetSelectorProps {
// the variant with small used for the old tools, inline as an inline
// text button and standard elsewhere
variant?: "standard" | "small" | "inline";
@@ -62,19 +93,363 @@ interface FacetSelectorPropType {
svFacetId: Record,
metadataMap: Record
) => void;
- // If set, when a facet is selected for one stat var, the corresponding
- // facet is selected for all other stat vars. This only applies if all
- // stat vars have the same facet choices.
+ // If set, when a facet is selected for one stat var, the corresponding facet
+ // is selected for all other stat vars. Only facets in common with all stat
+ // vars will be selectable. This is currently used only for bar charts.
allowSelectionGrouping?: boolean;
// Facet Selector
facetSelector?: FacetSelectionCriteria;
+ // useInjectedFacet
+ useInjectedFacet?: boolean;
+ setUseInjectedFacet?: (useInjectedFacet: boolean) => void;
}
-export function FacetSelector(props: FacetSelectorPropType): ReactElement {
- const useRichSelector = isFeatureEnabled(METADATA_FEATURE_FLAG);
+/**
+ * This function builds the final list of stat vars and their respective facets that
+ * will be used by the selector.
+ *
+ * In standard mode (not grouping selections into a single list), it returns the list unchanged.
+ * In grouped mode, it removes facets that are not common to all stat vars,
+ * unless showInconsistentFacets is true.
+ *
+ * @param originalFacetList {FacetSelectorFacetInfo[] | null} List of stat vars with facets passed into the component
+ * @param allowSelectionGrouping {boolean} If true, the component plans to display a single list of facet.
+ * @param showInconsistentFacets {boolean} If true, in grouped mode the selector will not drop inconsistent facets.
+ */
+const buildFinalFacetList = (
+ originalFacetList: FacetSelectorFacetInfo[] | null,
+ allowSelectionGrouping: boolean,
+ showInconsistentFacets: boolean
+): FacetSelectorFacetInfo[] | null => {
+ if (showInconsistentFacets || !allowSelectionGrouping || !originalFacetList) {
+ return originalFacetList;
+ }
+
+ const totalStatVars = originalFacetList.length;
+ if (totalStatVars <= 1) {
+ return originalFacetList;
+ }
+
+ // 1. We determine the facet ids in common with all stat vars
+ let commonFacetIds = new Set(Object.keys(originalFacetList[0].metadataMap));
+
+ for (let i = 1; i < totalStatVars; i++) {
+ if (commonFacetIds.size === 0) {
+ break;
+ }
+ const currentFacetIds = Object.keys(originalFacetList[i].metadataMap);
+ const intersection = new Set();
+ for (const facetId of currentFacetIds) {
+ if (commonFacetIds.has(facetId)) {
+ intersection.add(facetId);
+ }
+ }
+ commonFacetIds = intersection;
+ }
- if (useRichSelector) {
- return ;
+ // 2. We rebuild the facetList, only including those consistent facets.
+ const filteredList: FacetSelectorFacetInfo[] = [];
+ for (const originalFacetInfo of originalFacetList) {
+ const newMetadataMap: Record = {};
+ for (const facetId of commonFacetIds) {
+ if (originalFacetInfo.metadataMap[facetId]) {
+ newMetadataMap[facetId] = originalFacetInfo.metadataMap[facetId];
+ }
+ }
+ filteredList.push({
+ ...originalFacetInfo,
+ metadataMap: newMetadataMap,
+ });
}
- return ;
+
+ return filteredList;
+};
+
+export function FacetSelector(props: FacetSelectorProps): ReactElement {
+ const {
+ variant = "standard",
+ mode,
+ facetList,
+ loading,
+ error,
+ allowSelectionGrouping = false,
+ } = props;
+ const theme = useTheme();
+ const [modalOpen, setModalOpen] = useState(false);
+ const [useInjectedFacet, setUseInjectedFacet] = useState(true);
+
+ const finalFacetList = useMemo(() => {
+ return buildFinalFacetList(
+ facetList,
+ allowSelectionGrouping,
+ SHOW_INCONSISTENT_FACETS
+ );
+ }, [facetList, allowSelectionGrouping]);
+
+ const totalFacetOptionCount = useMemo(() => {
+ if (!finalFacetList) {
+ return 0;
+ }
+ const uniqueFacetIds = new Set();
+ finalFacetList.forEach((facetInfo) => {
+ Object.keys(facetInfo.metadataMap).forEach((facetId) => {
+ if (facetId !== "") {
+ uniqueFacetIds.add(facetId);
+ }
+ });
+ });
+ return uniqueFacetIds.size;
+ }, [finalFacetList]);
+
+ const hasAlternativeSources = useMemo(() => {
+ if (loading || !finalFacetList) {
+ return false;
+ }
+ return finalFacetList.some(
+ (facetInfo) => Object.keys(facetInfo.metadataMap).length > 1
+ );
+ }, [finalFacetList, loading]);
+
+ function areFacetsConsistent(
+ facetList: FacetSelectorFacetInfo[] | null
+ ): boolean {
+ if (!facetList || facetList.length <= 1) {
+ return true;
+ }
+ const firstFacetIds = Object.keys(facetList[0].metadataMap).sort();
+ for (let i = 1; i < facetList.length; i++) {
+ const currentFacetIds = Object.keys(facetList[i].metadataMap).sort();
+ if (!isEqual(firstFacetIds, currentFacetIds)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if (!hasAlternativeSources) {
+ if (mode === "download") {
+ return null;
+ }
+ return ;
+ }
+
+ const showInconsistentFacetFlag =
+ SHOW_INCONSISTENT_FACETS &&
+ allowSelectionGrouping &&
+ !loading &&
+ !error &&
+ !areFacetsConsistent(facetList);
+
+ /*
+ This is true if more than one choice can be made in the dialog (i.e.,
+ we are not in grouped mode, and we have more than one stat var).
+ */
+ const multipleChoicesAvailable =
+ !allowSelectionGrouping && finalFacetList && finalFacetList.length > 1;
+
+ return (
+ <>
+
+ {showInconsistentFacetFlag && (
+