Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/content-insights/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const Dashboard = () => {

const metricsCalculator = new MetricsCalculator(entries, scheduledActions, {
needsUpdateMonths: installation.needsUpdateMonths,
needsUpdateContentTypes: installation.needsUpdateContentTypes,
recentlyPublishedDays: installation.recentlyPublishedDays,
timeToPublishDays: installation.timeToPublishDays,
});
Expand Down
9 changes: 8 additions & 1 deletion apps/content-insights/src/components/NeedsUpdateTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ import { getEnvironmentId } from '../utils/sdkUtils';
export const NeedsUpdateTable = ({
entries,
contentTypes,
selectedContentTypeIds,
}: {
entries: EntryProps[];
contentTypes: Map<string, ContentTypeProps>;
selectedContentTypeIds?: string[];
}) => {
const sdk = useSDK<HomeAppSDK | PageAppSDK>();
const [currentPage, setCurrentPage] = useState(0);
const { items, total, isFetching, error } = useNeedsUpdate(entries, currentPage, contentTypes);
const { items, total, isFetching, error } = useNeedsUpdate(
entries,
currentPage,
contentTypes,
selectedContentTypeIds
);

const columns = useMemo<TableColumn<NeedsUpdateItem>[]>(
() => [
Expand Down
40 changes: 34 additions & 6 deletions apps/content-insights/src/components/ScheduledContentTabs.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useState } from 'react';
import { Box, Flex, Tabs, Text } from '@contentful/f36-components';
import { Box, Flex, FormControl, Tabs, Text } from '@contentful/f36-components';
import { ScheduledContentTable } from './ScheduledContentTable';
import { RecentlyPublishedTable } from './RecentlyPublishedTable';
import { NeedsUpdateTable } from './NeedsUpdateTable';
import { styles } from './Dashboard.styles';
import { useSDK } from '@contentful/react-apps-toolkit';
import { HomeAppSDK, PageAppSDK } from '@contentful/app-sdk';
import { HomeAppSDK, PageAppSDK, ConfigAppSDK } from '@contentful/app-sdk';
import { ReactNode } from 'react';
import { EntryProps, ScheduledActionProps, ContentTypeProps } from 'contentful-management';
import ContentTypeMultiSelect, { ContentType } from './ContentTypeMultiSelect';
import type { AppInstallationParameters } from '../locations/ConfigScreen';

interface TabPanelContentProps {
description: string;
Expand All @@ -34,10 +36,15 @@ export const ScheduledContentTabs = ({
entries: EntryProps[];
contentTypes: Map<string, ContentTypeProps>;
}) => {
const { parameters } = useSDK<HomeAppSDK | PageAppSDK>();
const recentlyPublishedDays = parameters?.installation?.recentlyPublishedDays;
const needsUpdateMonths = parameters?.installation?.needsUpdateMonths;
const sdk = useSDK<HomeAppSDK | PageAppSDK>();
const { parameters } = sdk;
const installation = (parameters?.installation ?? {}) as AppInstallationParameters;
const recentlyPublishedDays = installation.recentlyPublishedDays;
const needsUpdateMonths = installation.needsUpdateMonths;
const [currentTab, setCurrentTab] = useState('scheduled');
const [selectedNeedsUpdateContentTypes, setSelectedNeedsUpdateContentTypes] = useState<
ContentType[]
>([]);

return (
<Box marginTop="spacingXl">
Expand Down Expand Up @@ -70,7 +77,28 @@ export const ScheduledContentTabs = ({
description={`Content older than ${needsUpdateMonths} ${
needsUpdateMonths === 1 ? 'month' : 'months'
Comment thread
harikakondur marked this conversation as resolved.
} will appear here.`}>
<NeedsUpdateTable entries={entries} contentTypes={contentTypes} />
<FormControl marginBottom="spacingM">
<FormControl.Label>Filter by content type</FormControl.Label>
<ContentTypeMultiSelect
selectedContentTypes={selectedNeedsUpdateContentTypes}
setSelectedContentTypes={setSelectedNeedsUpdateContentTypes}
sdk={sdk as unknown as ConfigAppSDK}
initialSelectedIds={installation.needsUpdateContentTypes}
disablePills={false}
/>
<FormControl.HelpText>
Leave empty to use the configured default.
</FormControl.HelpText>
</FormControl>
<NeedsUpdateTable
entries={entries}
contentTypes={contentTypes}
selectedContentTypeIds={
selectedNeedsUpdateContentTypes.length > 0
? selectedNeedsUpdateContentTypes.map((ct) => ct.id)
: undefined
}
/>
</TabPanelContent>
</Tabs.Panel>
</Tabs>
Expand Down
24 changes: 19 additions & 5 deletions apps/content-insights/src/hooks/useNeedsUpdateContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,40 @@ function calculateAgeInDays(date: Date): number {
return Math.floor(diffTime / msPerDay);
}

const EMPTY_CONTENT_TYPES: string[] = [];

export function useNeedsUpdate(
entries: EntryProps[],
page: number = 0,
contentTypes: Map<string, ContentTypeProps>
contentTypes: Map<string, ContentTypeProps>,
overrideContentTypeIds?: string[]
): UseNeedsUpdateResult {
const sdk = useSDK<HomeAppSDK | PageAppSDK>();
const needsUpdateMonths =
((sdk.parameters.installation ?? {}) as AppInstallationParameters).needsUpdateMonths ?? 6;
const installation = (sdk.parameters.installation ?? {}) as AppInstallationParameters;
const needsUpdateMonths = installation.needsUpdateMonths ?? 6;
const installationContentTypes = installation.needsUpdateContentTypes ?? EMPTY_CONTENT_TYPES;
const activeContentTypeIds = overrideContentTypeIds ?? installationContentTypes;
const defaultLocale = sdk.locales.default;

Comment thread
harikakondur marked this conversation as resolved.
const activeContentTypeSet = useMemo(
() => (activeContentTypeIds.length > 0 ? new Set(activeContentTypeIds) : null),
[activeContentTypeIds]
);

const filteredEntries = useMemo(
() =>
entries.filter((entry) => {
if (activeContentTypeSet !== null) {
const contentTypeId = entry.sys.contentType?.sys?.id;
if (!contentTypeId || !activeContentTypeSet.has(contentTypeId)) return false;
}

const updatedAt = parseDate(entry?.sys?.updatedAt);
if (!updatedAt) return false;
const thresholdDate = subMonths(new Date(), needsUpdateMonths);

return updatedAt.getTime() < thresholdDate.getTime();
}),
[entries, needsUpdateMonths]
[entries, needsUpdateMonths, activeContentTypeSet]
);

const userIds = getUniqueUserIdsFromEntries(filteredEntries);
Expand Down
21 changes: 20 additions & 1 deletion apps/content-insights/src/locations/ConfigScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import TextInputInteger from '../components/TextInputInteger';

export interface AppInstallationParameters {
defaultContentTypes?: string[];
needsUpdateContentTypes?: string[];
needsUpdateMonths?: number;
recentlyPublishedDays?: number;
showUpcomingReleases?: boolean;
Expand All @@ -40,6 +41,7 @@ export interface AppInstallationParameters {
const ConfigScreen = () => {
const [parameters, setParameters] = useState<AppInstallationParameters>({});
const [selectedContentTypes, setSelectedContentTypes] = useState<ContentType[]>([]);
const [needsUpdateContentTypes, setNeedsUpdateContentTypes] = useState<ContentType[]>([]);
const sdk = useSDK<ConfigAppSDK>();

const [errors, setErrors] = useState<Record<string, string>>({});
Expand Down Expand Up @@ -86,10 +88,11 @@ const ConfigScreen = () => {
parameters: {
...parameters,
defaultContentTypes: selectedContentTypes.map((ct) => ct.id),
needsUpdateContentTypes: needsUpdateContentTypes.map((ct) => ct.id),
},
targetState: currentState,
};
}, [parameters, selectedContentTypes, sdk]);
}, [parameters, selectedContentTypes, needsUpdateContentTypes, sdk]);

useEffect(() => {
sdk.app.onConfigure(() => onConfigure());
Expand Down Expand Up @@ -181,6 +184,22 @@ const ConfigScreen = () => {
</FormControl.HelpText>
</FormControl>

<FormControl marginBottom="spacingL">
<FormControl.Label>
Content types included in &quot;Needs update&quot;
</FormControl.Label>
<ContentTypeMultiSelect
selectedContentTypes={needsUpdateContentTypes}
setSelectedContentTypes={setNeedsUpdateContentTypes}
sdk={sdk}
initialSelectedIds={parameters.needsUpdateContentTypes}
/>
<FormControl.HelpText>
Only entries of the selected content types will count toward the &quot;Needs
update&quot; metric. Leave empty to include all content types.
</FormControl.HelpText>
</FormControl>

<FormControl
marginBottom="spacingL"
isRequired
Expand Down
12 changes: 10 additions & 2 deletions apps/content-insights/src/metrics/MetricsCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class MetricsCalculator {
private readonly scheduledActions: ReadonlyArray<ScheduledActionProps>;
private readonly now: Date; // to maintain all the metrics consistent at the same current time
private readonly needsUpdateMonths: number;
private readonly needsUpdateContentTypeSet: ReadonlySet<string>;
private readonly recentlyPublishedDays: number;
private readonly timeToPublishDays: number;

Expand All @@ -20,6 +21,7 @@ export class MetricsCalculator {
scheduledActions: ReadonlyArray<ScheduledActionProps>,
options?: {
needsUpdateMonths?: number;
needsUpdateContentTypes?: string[];
recentlyPublishedDays?: number;
timeToPublishDays?: number;
}
Expand All @@ -28,6 +30,7 @@ export class MetricsCalculator {
this.scheduledActions = scheduledActions;
this.now = new Date();
this.needsUpdateMonths = options?.needsUpdateMonths ?? NEEDS_UPDATE_MONTHS_RANGE.min;
this.needsUpdateContentTypeSet = new Set(options?.needsUpdateContentTypes ?? []);
this.recentlyPublishedDays =
options?.recentlyPublishedDays ?? RECENTLY_PUBLISHED_DAYS_RANGE.min;
this.timeToPublishDays = options?.timeToPublishDays ?? TIME_TO_PUBLISH_DAYS_RANGE.min;
Expand Down Expand Up @@ -167,9 +170,14 @@ export class MetricsCalculator {
for (const entry of this.entries) {
const updatedAt = parseDate(entry?.sys?.updatedAt);
if (!updatedAt) continue;
if (updatedAt.getTime() < cutoff.getTime()) {
count += 1;
if (updatedAt.getTime() >= cutoff.getTime()) continue;

if (this.needsUpdateContentTypeSet.size > 0) {
const contentTypeId = entry.sys.contentType?.sys?.id;
if (!contentTypeId || !this.needsUpdateContentTypeSet.has(contentTypeId)) continue;
}

count += 1;
}

return {
Expand Down
2 changes: 1 addition & 1 deletion apps/content-insights/src/utils/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CreatorViewSetting } from './types';
export const NEEDS_UPDATE_MONTHS_RANGE = { min: 1, max: 24 };
export const RECENTLY_PUBLISHED_DAYS_RANGE = { min: 1, max: 30 };
export const TIME_TO_PUBLISH_DAYS_RANGE = { min: 7, max: 90 };
export const ITEMS_PER_PAGE = 5;
export const ITEMS_PER_PAGE = 10;

// Creator view options
export const CREATOR_VIEW_OPTIONS: { value: CreatorViewSetting; label: string }[] = [
Expand Down
Loading
Loading