feat: channels

This commit is contained in:
Nevo David 2024-07-14 17:24:16 +07:00
parent 977c9ef3c8
commit 34e8fd2c3f
6 changed files with 64 additions and 24 deletions

View File

@ -16,6 +16,7 @@ import { ApiTags } from '@nestjs/swagger';
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';
import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';
import { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';
@ApiTags('Analytics')
@Controller('/analytics')
@ -104,22 +105,29 @@ export class AnalyticsController {
}
if (integrationProvider.analytics) {
const loadAnalytics = await integrationProvider.analytics(
getIntegration.internalId,
getIntegration.token,
+date
);
await ioRedis.set(
`integration:${org.id}:${integration}:${date}`,
JSON.stringify(loadAnalytics),
'EX',
!process.env.NODE_ENV || process.env.NODE_ENV === 'development'
? 1
: 3600
);
return loadAnalytics;
try {
const loadAnalytics = await integrationProvider.analytics(
getIntegration.internalId,
getIntegration.token,
+date
);
await ioRedis.set(
`integration:${org.id}:${integration}:${date}`,
JSON.stringify(loadAnalytics),
'EX',
!process.env.NODE_ENV || process.env.NODE_ENV === 'development'
? 1
: 3600
);
return loadAnalytics;
} catch (e) {
if (e instanceof RefreshToken) {
await this._integrationService.disconnectChannel(org.id, getIntegration);
return [];
}
}
}
return {};
return [];
}
}

View File

@ -11,6 +11,7 @@ import { RenderAnalytics } from '@gitroom/frontend/components/platform-analytics
import { Select } from '@gitroom/react/form/select';
import { Button } from '@gitroom/react/form/button';
import { useRouter } from 'next/navigation';
import { useToaster } from '@gitroom/react/toaster/toaster';
const allowedIntegrations = [
'facebook',
@ -19,7 +20,7 @@ const allowedIntegrations = [
// 'tiktok',
'youtube',
'pinterest',
'threads'
'threads',
];
export const PlatformAnalytics = () => {
@ -27,7 +28,8 @@ export const PlatformAnalytics = () => {
const router = useRouter();
const [current, setCurrent] = useState(0);
const [key, setKey] = useState(7);
const [refresh, setRefresh] = useState(false);
const toaster = useToaster();
const load = useCallback(async () => {
const int = (await (await fetch('/integrations/list')).json()).integrations;
return int.filter((f: any) => allowedIntegrations.includes(f.identifier));
@ -127,7 +129,7 @@ export const PlatformAnalytics = () => {
You have to add Social Media channels
</div>
<div className="text-[20px]">
Supported: {allowedIntegrations.map(p => capitalize(p)).join(', ')}
Supported: {allowedIntegrations.map((p) => capitalize(p)).join(', ')}
</div>
<Button onClick={() => router.push('/launches')}>
Go to the calendar to add channels
@ -144,7 +146,17 @@ export const PlatformAnalytics = () => {
{sortedIntegrations.map((integration, index) => (
<div
key={integration.id}
onClick={() => setCurrent(index)}
onClick={() => {
if (integration.refreshNeeded) {
toaster.show('Please refresh the integration from the calendar', 'warning');
return ;
}
setRefresh(true);
setTimeout(() => {
setRefresh(false);
}, 10);
setCurrent(index);
}}
className={clsx(
'flex gap-[8px] items-center',
currentIntegration.id !== integration.id &&
@ -159,7 +171,7 @@ export const PlatformAnalytics = () => {
>
{(integration.inBetweenSteps || integration.refreshNeeded) && (
<div className="absolute left-0 top-0 w-[39px] h-[46px] cursor-pointer">
<div className="bg-red-500 w-[15px] h-[15px] rounded-full -left-[5px] -top-[5px] absolute z-[200] text-[10px] flex justify-center items-center">
<div className="bg-red-500 w-[15px] h-[15px] rounded-full left-0 -top-[5px] absolute z-[200] text-[10px] flex justify-center items-center">
!
</div>
<div className="bg-black/60 w-[39px] h-[46px] left-0 top-0 absolute rounded-full z-[199]" />
@ -212,7 +224,7 @@ export const PlatformAnalytics = () => {
</Select>
</div>
<div className="flex-1">
{!!keys && !!currentIntegration && (
{!!keys && !!currentIntegration && !refresh && (
<RenderAnalytics integration={currentIntegration} date={keys} />
)}
</div>

View File

@ -57,6 +57,9 @@ export const RenderAnalytics: FC<{ integration: Integration; date: number }> = (
return (
<div className="grid grid-cols-3 gap-[20px]">
{data?.length === 0 && (
<div>This channel needs to be refreshed</div>
)}
{data?.map((p: any, index: number) => (
<div key={`pl-${index}`} className="flex">
<div className="flex-1 bg-secondary py-[10px] px-[16px] gap-[10px] flex flex-col">

View File

@ -23,6 +23,18 @@ export class IntegrationRepository {
});
}
disconnectChannel(org: string, id: string) {
return this._integration.model.integration.update({
where: {
id,
organizationId: org,
},
data: {
refreshNeeded: true,
},
});
}
createOrUpdateIntegration(
org: string,
name: string,

View File

@ -77,6 +77,11 @@ export class IntegrationService {
}
}
async disconnectChannel(orgId: string, integration: Integration) {
await this._integrationRepository.disconnectChannel(orgId, integration.id);
await this.informAboutRefreshError(orgId, integration);
}
async informAboutRefreshError(orgId: string, integration: Integration) {
await this._notificationService.inAppNotification(
orgId,

View File

@ -218,7 +218,7 @@ export class LinkedinPageProvider
const startDate = dayjs().subtract(date, 'days').unix() * 1000;
const { elements }: { elements: Root[]; paging: any } = await (
await fetch(
await this.fetch(
`https://api.linkedin.com/rest/organizationPageStatistics?q=organization&organization=${encodeURIComponent(
`urn:li:organization:${id}`
)}&timeIntervals=(timeRange:(start:${startDate},end:${endDate}),timeGranularityType:DAY)`,
@ -233,7 +233,7 @@ export class LinkedinPageProvider
).json();
const { elements: elements2 }: { elements: Root[]; paging: any } = await (
await fetch(
await this.fetch(
`https://api.linkedin.com/rest/organizationalEntityFollowerStatistics?q=organizationalEntity&organizationalEntity=${encodeURIComponent(
`urn:li:organization:${id}`
)}&timeIntervals=(timeRange:(start:${startDate},end:${endDate}),timeGranularityType:DAY)`,
@ -248,7 +248,7 @@ export class LinkedinPageProvider
).json();
const { elements: elements3 }: { elements: Root[]; paging: any } = await (
await fetch(
await this.fetch(
`https://api.linkedin.com/rest/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=${encodeURIComponent(
`urn:li:organization:${id}`
)}&timeIntervals=(timeRange:(start:${startDate},end:${endDate}),timeGranularityType:DAY)`,