Skip to content

Commit

Permalink
Dashboard: support filtering stories by taxonomy (#11625)
Browse files Browse the repository at this point in the history
Co-authored-by: Sam Blinde <samantha.blinde@formidable.com>
Co-authored-by: Jonny Harris <spacedmonkey@users.noreply.github.com>
Co-authored-by: Pascal Birchler <pascalb@google.com>
  • Loading branch information
4 people authored Jun 13, 2022
1 parent d8f8529 commit 8f8319b
Show file tree
Hide file tree
Showing 27 changed files with 1,163 additions and 24 deletions.
8 changes: 8 additions & 0 deletions includes/Admin/Dashboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,13 @@ public function load_stories_dashboard(): void {
'/web-stories/v1/settings/',
'/web-stories/v1/publisher-logos/',
'/web-stories/v1/users/me/',
'/web-stories/v1/taxonomies/?' . build_query(
[
'type' => $this->story_post_type->get_slug(),
'context' => 'edit',
'hierarchical' => 'true',
]
),
$rest_url . '?' . build_query(
[
'_embed' => rawurlencode(
Expand Down Expand Up @@ -456,6 +463,7 @@ public function get_dashboard_settings(): array {
'settings' => '/web-stories/v1/settings/',
'pages' => '/wp/v2/pages/',
'publisherLogos' => '/web-stories/v1/publisher-logos/',
'taxonomies' => '/web-stories/v1/taxonomies/',
'products' => '/web-stories/v1/products/',
],
'vendors' => $vendors,
Expand Down
3 changes: 3 additions & 0 deletions packages/dashboard/src/app/api/apiProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ import { createContext } from '@googleforcreators/react';
import useStoryApi from './useStoryApi';
import useTemplateApi from './useTemplateApi';
import useUsersApi from './useUsersApi';
import useTaxonomyApi from './useTaxonomyApi';

export const ApiContext = createContext({ state: {}, actions: {} });

export default function ApiProvider({ children }) {
const { api: usersApi } = useUsersApi();
const { api: taxonomyApi } = useTaxonomyApi();
const { templates, api: templateApi } = useTemplateApi();
const { stories, api: storyApi } = useStoryApi();

Expand All @@ -43,6 +45,7 @@ export default function ApiProvider({ children }) {
storyApi,
templateApi,
usersApi,
taxonomyApi,
},
};

Expand Down
34 changes: 34 additions & 0 deletions packages/dashboard/src/app/api/useTaxonomyApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Internal dependencies
*/
import { useConfig } from '../config';

function useTaxonomyApi() {
const {
apiCallbacks: { getTaxonomies, getTaxonomyTerms },
} = useConfig();

return {
api: {
getTaxonomies,
getTaxonomyTerms,
},
};
}

export default useTaxonomyApi;
134 changes: 134 additions & 0 deletions packages/dashboard/src/app/views/myStories/filters/provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* External dependencies
*/
import {
createContext,
useReducer,
useEffect,
useCallback,
useMemo,
} from '@googleforcreators/react';
import PropTypes from 'prop-types';

/** @typedef {import('react')} Node */

/**
* Internal dependencies
*/
import reducer from './reducer';
import useTaxonomyFilters from './taxonomy/useTaxonomyFilters';
import * as types from './types';

export const filterContext = createContext({
state: {},
actions: {},
});

/**
* Keeps track of the current filters state.
*
* Each filter will have its own logic associated with
* initilization and how to query terms.
*
*
* @param {Object} root0 props for the provider
* @param {Node} root0.children the children to be rendered
* @return {Node} React node
*/

export default function FiltersProvider({ children }) {
// each filter type will have its own logic for initilizing and querying
const { initializeTaxonomyFilters } = useTaxonomyFilters();

const [state, dispatch] = useReducer(reducer, {
filtersLoading: true,
filters: [],
});

/**
* Dispatch UPDATE_FILTER with new data for a given filter
*
* @param {string} key key property on one of the filter objects
* @param {Object} value the properties with updated values
* @return {void}
*/
const updateFilter = useCallback((key, value) => {
dispatch({ type: types.UPDATE_FILTER, payload: { key, value } });
}, []);

/**
* Dispatch REGISTER_FILTERS with all the filters data
*
* @param {Array} payload array of filters data
* @return {void}
*/
const registerFilters = useCallback((value) => {
dispatch({ type: types.REGISTER_FILTERS, payload: { value } });
}, []);

/**
* Sets up the shape of the filters data
* and calls registerFilters with all filters.
*
* @return {void}
*/
const initializeFilters = useCallback(() => {
const filters = initializeTaxonomyFilters();

registerFilters(filters);
}, [registerFilters, initializeTaxonomyFilters]);

/**
* Returns a object where the keys are the filter keys
* and the values are the filterId
*
* @return {Object}
*/
const getFiltersObject = useCallback(() => {
const filterObj = {};
for (const filter of state.filters) {
const { key, filterId } = filter;
if (filterId) {
filterObj[key] = filterId;
}
}
return filterObj;
}, [state.filters]);

const contextValue = useMemo(() => {
return {
state,
actions: { updateFilter, registerFilters, getFiltersObject },
};
}, [state, updateFilter, registerFilters, getFiltersObject]);

useEffect(() => {
initializeFilters();
}, [initializeFilters]);

return (
<filterContext.Provider value={contextValue}>
{children}
</filterContext.Provider>
);
}

FiltersProvider.propTypes = {
children: PropTypes.node,
};
73 changes: 73 additions & 0 deletions packages/dashboard/src/app/views/myStories/filters/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Internal dependencies
*/
import * as types from './types';

/**
* Update the filters state
*
* TODO: May need updating to handle all filter types within the dashboard.
*
* @param {Object} state Current state
* @param {Object} payload Action payload
* @param {string} payload.key Key for associated filter.
* @param {Object} payload.value Value to set on the filter.
* @return {Object} New state
*/

const reducer = (state, { type, payload = {} }) => {
switch (type) {
case types.UPDATE_FILTER: {
const { key, value } = payload;
const filter = state.filters.find((f) => f.key === key);

if (!filter) {
return state;
}

// remove 'filter-by' value
if (value.filterId && filter?.filterId === value.filterId) {
value.filterId = null;
}

// replace old filter
const idx = state.filters.indexOf(filter);
const filters = [...state.filters];
filters[idx] = { ...filter, ...value };

return {
...state,
filters,
};
}

case types.REGISTER_FILTERS: {
const { value } = payload;
return {
...state,
filtersLoading: false,
filters: value,
};
}
default:
return state;
}
};

export default reducer;
Loading

0 comments on commit 8f8319b

Please sign in to comment.