feat: channels
This commit is contained in:
parent
977c9ef3c8
commit
34e8fd2c3f
|
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)`,
|
||||
|
|
|
|||
Loading…
Reference in New Issue