diff --git a/src/api/settings.ts b/src/api/settings.ts index 57e5cd019..fb8168c8d 100644 --- a/src/api/settings.ts +++ b/src/api/settings.ts @@ -28,12 +28,18 @@ export const enum SettingsType { awsCategoryKeys = 'awsCategoryKeys', awsCategoryKeysEnable = 'awsCategoryKeysEnable', awsCategoryKeysDisable = 'awsCategoryKeysDisable', + tags = 'tags', + tagsEnable = 'tagsEnable', + tagsDisable = 'tagsDisable', } export const SettingsTypePaths: Partial> = { [SettingsType.awsCategoryKeys]: 'settings/aws_category_keys/', [SettingsType.awsCategoryKeysEnable]: 'settings/aws_category_keys/enable/', [SettingsType.awsCategoryKeysDisable]: 'settings/aws_category_keys/disable/', + [SettingsType.tags]: 'settings/tags', + [SettingsType.tagsEnable]: 'settings/tags/enable/', + [SettingsType.tagsDisable]: 'settings/tags/disable/', }; export function fetchSettings(settingsType: SettingsType, query: string) { diff --git a/src/routes/settings/costCategory/costCategory.tsx b/src/routes/settings/costCategory/costCategory.tsx index bb05fcebb..b31e2fe91 100644 --- a/src/routes/settings/costCategory/costCategory.tsx +++ b/src/routes/settings/costCategory/costCategory.tsx @@ -66,8 +66,8 @@ const CostCategory: React.FC = ({ canWrite }) => { const getPagination = (isDisabled = false, isBottom = false) => { const count = settings?.meta ? settings.meta.count : 0; - const limit = settings?.meta?.limit ? settings.meta.limit : baseQuery.limit; // Todo: API doesn't provide limit - const offset = settings?.meta?.offset ? settings.meta.offset : baseQuery.offset; // Todo: API doesn't provide offset + const limit = settings?.meta ? settings.meta.limit : baseQuery.limit; + const offset = settings?.meta ? settings.meta.offset : baseQuery.offset; const page = Math.trunc(offset / limit + 1); return ( @@ -180,7 +180,7 @@ const CostCategory: React.FC = ({ canWrite }) => { }; const handleOnSetPage = pageNumber => { - const newQuery = queryUtils.handleOnSetPage(query, settings, pageNumber); + const newQuery = queryUtils.handleOnSetPage(query, settings, pageNumber, true); setQuery(newQuery); }; @@ -206,7 +206,6 @@ const CostCategory: React.FC = ({ canWrite }) => { const categories = getCategories(); const isDisabled = categories.length === 0; - // Note: Providers are fetched via the AccountSettings component used by all routes if (settingsError) { return ; } diff --git a/src/routes/settings/costCategory/costCategoryTable.tsx b/src/routes/settings/costCategory/costCategoryTable.tsx index ba93df7ae..f3f377fae 100644 --- a/src/routes/settings/costCategory/costCategoryTable.tsx +++ b/src/routes/settings/costCategory/costCategoryTable.tsx @@ -7,6 +7,7 @@ import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { DataTable } from 'routes/components/dataTable'; +import { styles } from 'routes/components/dataTable/dataTable.styles'; import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; @@ -28,12 +29,6 @@ interface CostCategoryState { type CostCategoryProps = CostCategoryOwnProps; -export const CostCategoryColumnIds = { - infrastructure: 'infrastructure', - monthOverMonth: 'monthOverMonth', - supplementary: 'supplementary', -}; - class CostCategoryBase extends React.Component { public state: CostCategoryState = { columns: [], @@ -68,7 +63,7 @@ class CostCategoryBase extends React.Component{intl.formatMessage(messages.disabled)} ), + style: styles.lastItem, }, ], item, diff --git a/src/routes/settings/tagDetails/tagDetails.tsx b/src/routes/settings/tagDetails/tagDetails.tsx index 19f7a782d..890bef599 100644 --- a/src/routes/settings/tagDetails/tagDetails.tsx +++ b/src/routes/settings/tagDetails/tagDetails.tsx @@ -1,8 +1,8 @@ import { PageSection, Pagination, PaginationVariant } from '@patternfly/react-core'; import type { Query } from 'api/queries/query'; import { getQuery } from 'api/queries/query'; -import type { Report } from 'api/reports/report'; -import { ReportPathsType, ReportType } from 'api/reports/report'; +import type { Settings, SettingsData } from 'api/settings'; +import { SettingsType } from 'api/settings'; import type { AxiosError } from 'axios'; import messages from 'locales/messages'; import React, { useEffect, useState } from 'react'; @@ -12,12 +12,11 @@ import type { AnyAction } from 'redux'; import type { ThunkDispatch } from 'redux-thunk'; import { Loading } from 'routes/components/page/loading'; import { NotAvailable } from 'routes/components/page/notAvailable'; -import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; -import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; import * as queryUtils from 'routes/utils/query'; import type { RootState } from 'store'; import { FetchStatus } from 'store/common'; -import { reportActions, reportSelectors } from 'store/reports'; +import { settingsActions, settingsSelectors } from 'store/settings'; +import { useStateCallback } from 'utils/hooks'; import { styles } from './tagDetails.styles'; import { TagTable } from './tagTable'; @@ -32,56 +31,42 @@ export interface TagDetailsMapProps { } export interface TagDetailsStateProps { - report?: Report; - reportError?: AxiosError; - reportFetchStatus?: FetchStatus; - reportQueryString?: string; + settings?: Settings; + settingsError?: AxiosError; + settingsStatus?: FetchStatus; + settingsQueryString?: string; } type TagDetailsProps = TagDetailsOwnProps; const baseQuery: Query = { - filter: { - resolution: 'monthly', - time_scope_units: 'month', - time_scope_value: -1, - limit: 10, - offset: 0, - }, + limit: 10, + offset: 0, filter_by: {}, - group_by: { - project: '*', - }, order_by: { - cost: 'desc', + key: 'asc', }, }; const TagDetails: React.FC = ({ canWrite }) => { - const [isAllSelected, setIsAllSelected] = useState(false); const [query, setQuery] = useState({ ...baseQuery }); - const [selectedItems, setSelectedItems] = useState([]); + const [selectedItems, setSelectedItems] = useStateCallback([]); + const dispatch: ThunkDispatch = useDispatch(); const intl = useIntl(); - const { report, reportError, reportFetchStatus, reportQueryString } = useMapToProps({ query }); + const { settings, settingsError, settingsStatus } = useMapToProps({ query }); - const getComputedItems = () => { - return getUnsortedComputedReportItems({ - report, - idKey: 'project' as any, - }); + const getTags = () => { + if (settings) { + return settings.data as any; + } + return []; }; const getPagination = (isDisabled = false, isBottom = false) => { - const count = report && report.meta ? report.meta.count : 0; - const limit = - report && report.meta && report.meta.filter && report.meta.filter.limit - ? report.meta.filter.limit - : baseQuery.filter.limit; - const offset = - report && report.meta && report.meta.filter && report.meta.filter.offset - ? report.meta.filter.offset - : baseQuery.filter.offset; + const count = settings?.meta ? settings.meta.count : 0; + const limit = settings?.meta ? settings.meta.limit : baseQuery.limit; + const offset = settings?.meta ? settings.meta.offset : baseQuery.offset; const page = Math.trunc(offset / limit + 1); return ( @@ -110,28 +95,28 @@ const TagDetails: React.FC = ({ canWrite }) => { handleOnSort(sortType, isSortAscending)} - report={report} - reportQueryString={reportQueryString} + settings={settings} selectedItems={selectedItems} /> ); }; - const getToolbar = (computedItems: ComputedReportItem[]) => { - const isDisabled = computedItems.length === 0; - const itemsTotal = report && report.meta ? report.meta.count : 0; + const getToolbar = (tags: SettingsData[]) => { + const hasEnabledItem = selectedItems.find(item => item.enabled); + const hasDisabledItem = selectedItems.find(item => !item.enabled); + const itemsTotal = settings?.meta ? settings.meta.count : 0; return ( = ({ canWrite }) => { pagination={getPagination(isDisabled)} query={query} selectedItems={selectedItems} + showBulkSelectAll={false} /> ); }; const handleOnBulkSelected = (action: string) => { if (action === 'none') { - setIsAllSelected(false); - setSelectedItems([]); - } else if (action === 'page') { - setIsAllSelected(false); - setSelectedItems(getComputedItems()); - } else if (action === 'all') { - setIsAllSelected(!isAllSelected); setSelectedItems([]); + } else if (action === 'all' || action === 'page') { + setSelectedItems(getTags()); } }; - const handleOnDisableTags = () => {}; + const handleOnDisableTags = () => { + if (selectedItems.length > 0) { + setSelectedItems([], () => { + dispatch( + settingsActions.updateSettings(SettingsType.tagsDisable, { + ids: selectedItems.map(item => item.uuid), + }) + ); + }); + } + }; - const handleOnEnableTags = () => {}; + const handleOnEnableTags = () => { + if (selectedItems.length > 0) { + setSelectedItems([], () => { + dispatch( + settingsActions.updateSettings(SettingsType.tagsEnable, { + ids: selectedItems.map(item => item.uuid), + }) + ); + }); + } + }; const handleOnFilterAdded = filter => { const newQuery = queryUtils.handleOnFilterAdded(query, filter); @@ -178,22 +179,21 @@ const TagDetails: React.FC = ({ canWrite }) => { }; const handleOnSetPage = pageNumber => { - const newQuery = queryUtils.handleOnSetPage(query, report, pageNumber); + const newQuery = queryUtils.handleOnSetPage(query, settings, pageNumber, true); setQuery(newQuery); }; - const handleOnSelected = (items: ComputedReportItem[], isSelected: boolean = false) => { - let newItems = [...(isAllSelected ? getComputedItems() : selectedItems)]; + const handleOnSelected = (items: SettingsData[], isSelected: boolean = false) => { + let newItems = [...selectedItems]; if (items && items.length > 0) { if (isSelected) { items.map(item => newItems.push(item)); } else { items.map(item => { - newItems = newItems.filter(val => val.id !== item.id); + newItems = newItems.filter(val => val.uuid !== item.uuid); }); } } - setIsAllSelected(false); setSelectedItems(newItems); }; @@ -202,11 +202,10 @@ const TagDetails: React.FC = ({ canWrite }) => { setQuery(newQuery); }; - const computedItems = getComputedItems(); - const isDisabled = computedItems.length === 0; + const tags = getTags(); + const isDisabled = tags.length === 0; - // Note: Providers are fetched via the AccountSettings component used by all routes - if (reportError) { + if (settingsError) { return ; } return ( @@ -220,8 +219,8 @@ const TagDetails: React.FC = ({ canWrite }) => { ), })} - {getToolbar(computedItems)} - {reportFetchStatus === FetchStatus.inProgress ? ( + {getToolbar(tags)} + {settingsStatus === FetchStatus.inProgress ? ( ) : ( <> @@ -235,32 +234,48 @@ const TagDetails: React.FC = ({ canWrite }) => { // eslint-disable-next-line no-empty-pattern const useMapToProps = ({ query }: TagDetailsMapProps): TagDetailsStateProps => { - const reportType = ReportType.cost; - const reportPathsType = ReportPathsType.ocp; const dispatch: ThunkDispatch = useDispatch(); - const reportQueryString = getQuery(query); - const report = useSelector((state: RootState) => - reportSelectors.selectReport(state, reportPathsType, reportType, reportQueryString) + const settingsQuery = { + filter_by: query.filter_by, + limit: query.limit, + offset: query.offset, + order_by: query.order_by, + }; + const settingsQueryString = getQuery(settingsQuery); + const settings = useSelector((state: RootState) => + settingsSelectors.selectSettings(state, SettingsType.tags, settingsQueryString) ); - const reportFetchStatus = useSelector((state: RootState) => - reportSelectors.selectReportFetchStatus(state, reportPathsType, reportType, reportQueryString) + const settingsStatus = useSelector((state: RootState) => + settingsSelectors.selectSettingsStatus(state, SettingsType.tags, settingsQueryString) + ); + const settingsError = useSelector((state: RootState) => + settingsSelectors.selectSettingsError(state, SettingsType.tags, settingsQueryString) + ); + + const settingsUpdateDisableStatus = useSelector((state: RootState) => + settingsSelectors.selectSettingsUpdateStatus(state, SettingsType.tagsDisable) ); - const reportError = useSelector((state: RootState) => - reportSelectors.selectReportError(state, reportPathsType, reportType, reportQueryString) + const settingsUpdateEnableStatus = useSelector((state: RootState) => + settingsSelectors.selectSettingsUpdateStatus(state, SettingsType.tagsEnable) ); useEffect(() => { - if (!reportError && reportFetchStatus !== FetchStatus.inProgress) { - dispatch(reportActions.fetchReport(reportPathsType, reportType, reportQueryString)); + if ( + !settingsError && + settingsStatus !== FetchStatus.inProgress && + settingsUpdateDisableStatus !== FetchStatus.inProgress && + settingsUpdateEnableStatus !== FetchStatus.inProgress + ) { + dispatch(settingsActions.fetchSettings(SettingsType.tags, settingsQueryString)); } - }, [query]); + }, [query, settingsUpdateDisableStatus, settingsUpdateEnableStatus]); return { - report, - reportError, - reportFetchStatus, - reportQueryString, + settings, + settingsError, + settingsStatus, + settingsQueryString, }; }; diff --git a/src/routes/settings/tagDetails/tagTable.tsx b/src/routes/settings/tagDetails/tagTable.tsx index 74cae8ef6..ca602c780 100644 --- a/src/routes/settings/tagDetails/tagTable.tsx +++ b/src/routes/settings/tagDetails/tagTable.tsx @@ -1,7 +1,7 @@ import 'routes/components/dataTable/dataTable.scss'; import { Label } from '@patternfly/react-core'; -import type { Report, ReportItem } from 'api/reports/report'; +import type { Settings, SettingsData } from 'api/settings'; import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; @@ -9,21 +9,19 @@ import { injectIntl } from 'react-intl'; import { DataTable } from 'routes/components/dataTable'; import { styles } from 'routes/components/dataTable/dataTable.styles'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; -import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; interface TagTableOwnProps extends RouterComponentProps, WrappedComponentProps { + canWrite?: boolean; filterBy?: any; isAllSelected?: boolean; isLoading?: boolean; - isReadOnly?: boolean; onSelected(items: ComputedReportItem[], isSelected: boolean); onSort(value: string, isSortAscending: boolean); orderBy?: any; - report: Report; - reportQueryString: string; - selectedItems?: ComputedReportItem[]; + selectedItems?: SettingsData[]; + settings: Settings; } interface TagTableState { @@ -33,12 +31,6 @@ interface TagTableState { type TagTableProps = TagTableOwnProps; -export const TagTableColumnIds = { - infrastructure: 'infrastructure', - monthOverMonth: 'monthOverMonth', - supplementary: 'supplementary', -}; - class TagTableBase extends React.Component { public state: TagTableState = { columns: [], @@ -50,9 +42,9 @@ class TagTableBase extends React.Component { } public componentDidUpdate(prevProps: TagTableProps) { - const { report, selectedItems } = this.props; - const currentReport = report && report.data ? JSON.stringify(report.data) : ''; - const previousReport = prevProps.report && prevProps.report.data ? JSON.stringify(prevProps.report.data) : ''; + const { selectedItems, settings } = this.props; + const currentReport = settings?.data ? JSON.stringify(settings.data) : ''; + const previousReport = prevProps?.settings.data ? JSON.stringify(prevProps.settings.data) : ''; if (previousReport !== currentReport || prevProps.selectedItems !== selectedItems) { this.initDatum(); @@ -60,60 +52,55 @@ class TagTableBase extends React.Component { } private initDatum = () => { - const { intl, isAllSelected, isReadOnly, report, selectedItems } = this.props; - if (!report) { + const { canWrite, intl, selectedItems, settings } = this.props; + if (!settings) { return; } const rows = []; - const computedItems = getUnsortedComputedReportItems({ - report, - idKey: 'project' as any, - }); + const tags = settings?.data ? (settings.data as any) : []; const columns = [ { name: '', // Selection column }, { - orderBy: 'project', // Todo: update filter name + orderBy: 'key', name: intl.formatMessage(messages.detailsResourceNames, { value: 'name' }), - ...(computedItems.length && { isSortable: true }), + ...(tags.length && { isSortable: true }), }, { - orderBy: 'status', + orderBy: 'enabled', name: intl.formatMessage(messages.detailsResourceNames, { value: 'status' }), - ...(computedItems.length && { isSortable: true }), + ...(tags.length && { isSortable: true }), }, { - orderBy: 'source_type', + orderBy: 'provider_type', name: intl.formatMessage(messages.sourceType), - ...(computedItems.length && { isSortable: true }), + ...(tags.length && { isSortable: true }), style: styles.lastItemColumn, }, ]; - computedItems.map(item => { - const label = item && item.label !== null ? item.label : ''; - + tags.map(item => { rows.push({ cells: [ {}, // Empty cell for row selection { - value: label, + value: item.key ? item.key : '', }, { - value: ( - + value: item.enabled ? ( + + ) : ( + ), }, - { value: 'source type', style: styles.lastItem }, + { value: item.provider_type ? item.provider_type : '', style: styles.lastItem }, ], item, - selected: isAllSelected || (selectedItems && selectedItems.find(val => val.id === item.id) !== undefined), - selectionDisabled: isReadOnly, + selected: selectedItems && selectedItems.find(val => val.uuid === item.uuid) !== undefined, + selectionDisabled: !canWrite, }); }); diff --git a/src/routes/settings/tagDetails/tagToolbar.tsx b/src/routes/settings/tagDetails/tagToolbar.tsx index 89aef2e27..7015b8a55 100644 --- a/src/routes/settings/tagDetails/tagToolbar.tsx +++ b/src/routes/settings/tagDetails/tagToolbar.tsx @@ -18,6 +18,8 @@ interface TagToolbarOwnProps { canWrite?: boolean; isAllSelected?: boolean; isDisabled?: boolean; + isPrimaryActionDisabled?: boolean; + isSecondaryActionDisabled?: boolean; itemsPerPage?: number; itemsTotal?: number; onBulkSelected(action: string); @@ -28,6 +30,7 @@ interface TagToolbarOwnProps { pagination?: React.ReactNode; query?: OcpQuery; selectedItems?: ComputedReportItem[]; + showBulkSelectAll?: boolean; } interface TagToolbarStateProps { @@ -55,7 +58,15 @@ export class TagToolbarBase extends React.Component { - const { canWrite, intl, onDisableTags, onEnableTags, selectedItems } = this.props; + const { + canWrite, + intl, + isPrimaryActionDisabled, + isSecondaryActionDisabled, + onDisableTags, + onEnableTags, + selectedItems, + } = this.props; const isDisabled = !canWrite || selectedItems.length === 0; const tooltip = intl.formatMessage(!canWrite ? messages.readOnlyPermissions : messages.selectCategories); @@ -63,13 +74,18 @@ export class TagToolbarBase extends React.Component -