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 { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
||||||
import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
||||||
import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';
|
import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';
|
||||||
|
import { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||||
|
|
||||||
@ApiTags('Analytics')
|
@ApiTags('Analytics')
|
||||||
@Controller('/analytics')
|
@Controller('/analytics')
|
||||||
|
|
@ -104,22 +105,29 @@ export class AnalyticsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (integrationProvider.analytics) {
|
if (integrationProvider.analytics) {
|
||||||
const loadAnalytics = await integrationProvider.analytics(
|
try {
|
||||||
getIntegration.internalId,
|
const loadAnalytics = await integrationProvider.analytics(
|
||||||
getIntegration.token,
|
getIntegration.internalId,
|
||||||
+date
|
getIntegration.token,
|
||||||
);
|
+date
|
||||||
await ioRedis.set(
|
);
|
||||||
`integration:${org.id}:${integration}:${date}`,
|
await ioRedis.set(
|
||||||
JSON.stringify(loadAnalytics),
|
`integration:${org.id}:${integration}:${date}`,
|
||||||
'EX',
|
JSON.stringify(loadAnalytics),
|
||||||
!process.env.NODE_ENV || process.env.NODE_ENV === 'development'
|
'EX',
|
||||||
? 1
|
!process.env.NODE_ENV || process.env.NODE_ENV === 'development'
|
||||||
: 3600
|
? 1
|
||||||
);
|
: 3600
|
||||||
return loadAnalytics;
|
);
|
||||||
|
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 { Select } from '@gitroom/react/form/select';
|
||||||
import { Button } from '@gitroom/react/form/button';
|
import { Button } from '@gitroom/react/form/button';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||||
|
|
||||||
const allowedIntegrations = [
|
const allowedIntegrations = [
|
||||||
'facebook',
|
'facebook',
|
||||||
|
|
@ -19,7 +20,7 @@ const allowedIntegrations = [
|
||||||
// 'tiktok',
|
// 'tiktok',
|
||||||
'youtube',
|
'youtube',
|
||||||
'pinterest',
|
'pinterest',
|
||||||
'threads'
|
'threads',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PlatformAnalytics = () => {
|
export const PlatformAnalytics = () => {
|
||||||
|
|
@ -27,7 +28,8 @@ export const PlatformAnalytics = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [current, setCurrent] = useState(0);
|
const [current, setCurrent] = useState(0);
|
||||||
const [key, setKey] = useState(7);
|
const [key, setKey] = useState(7);
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
const toaster = useToaster();
|
||||||
const load = useCallback(async () => {
|
const load = useCallback(async () => {
|
||||||
const int = (await (await fetch('/integrations/list')).json()).integrations;
|
const int = (await (await fetch('/integrations/list')).json()).integrations;
|
||||||
return int.filter((f: any) => allowedIntegrations.includes(f.identifier));
|
return int.filter((f: any) => allowedIntegrations.includes(f.identifier));
|
||||||
|
|
@ -127,7 +129,7 @@ export const PlatformAnalytics = () => {
|
||||||
You have to add Social Media channels
|
You have to add Social Media channels
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[20px]">
|
<div className="text-[20px]">
|
||||||
Supported: {allowedIntegrations.map(p => capitalize(p)).join(', ')}
|
Supported: {allowedIntegrations.map((p) => capitalize(p)).join(', ')}
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={() => router.push('/launches')}>
|
<Button onClick={() => router.push('/launches')}>
|
||||||
Go to the calendar to add channels
|
Go to the calendar to add channels
|
||||||
|
|
@ -144,7 +146,17 @@ export const PlatformAnalytics = () => {
|
||||||
{sortedIntegrations.map((integration, index) => (
|
{sortedIntegrations.map((integration, index) => (
|
||||||
<div
|
<div
|
||||||
key={integration.id}
|
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(
|
className={clsx(
|
||||||
'flex gap-[8px] items-center',
|
'flex gap-[8px] items-center',
|
||||||
currentIntegration.id !== integration.id &&
|
currentIntegration.id !== integration.id &&
|
||||||
|
|
@ -159,7 +171,7 @@ export const PlatformAnalytics = () => {
|
||||||
>
|
>
|
||||||
{(integration.inBetweenSteps || integration.refreshNeeded) && (
|
{(integration.inBetweenSteps || integration.refreshNeeded) && (
|
||||||
<div className="absolute left-0 top-0 w-[39px] h-[46px] cursor-pointer">
|
<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>
|
||||||
<div className="bg-black/60 w-[39px] h-[46px] left-0 top-0 absolute rounded-full z-[199]" />
|
<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>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{!!keys && !!currentIntegration && (
|
{!!keys && !!currentIntegration && !refresh && (
|
||||||
<RenderAnalytics integration={currentIntegration} date={keys} />
|
<RenderAnalytics integration={currentIntegration} date={keys} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,9 @@ export const RenderAnalytics: FC<{ integration: Integration; date: number }> = (
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-3 gap-[20px]">
|
<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) => (
|
{data?.map((p: any, index: number) => (
|
||||||
<div key={`pl-${index}`} className="flex">
|
<div key={`pl-${index}`} className="flex">
|
||||||
<div className="flex-1 bg-secondary py-[10px] px-[16px] gap-[10px] flex flex-col">
|
<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(
|
createOrUpdateIntegration(
|
||||||
org: string,
|
org: string,
|
||||||
name: 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) {
|
async informAboutRefreshError(orgId: string, integration: Integration) {
|
||||||
await this._notificationService.inAppNotification(
|
await this._notificationService.inAppNotification(
|
||||||
orgId,
|
orgId,
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,7 @@ export class LinkedinPageProvider
|
||||||
const startDate = dayjs().subtract(date, 'days').unix() * 1000;
|
const startDate = dayjs().subtract(date, 'days').unix() * 1000;
|
||||||
|
|
||||||
const { elements }: { elements: Root[]; paging: any } = await (
|
const { elements }: { elements: Root[]; paging: any } = await (
|
||||||
await fetch(
|
await this.fetch(
|
||||||
`https://api.linkedin.com/rest/organizationPageStatistics?q=organization&organization=${encodeURIComponent(
|
`https://api.linkedin.com/rest/organizationPageStatistics?q=organization&organization=${encodeURIComponent(
|
||||||
`urn:li:organization:${id}`
|
`urn:li:organization:${id}`
|
||||||
)}&timeIntervals=(timeRange:(start:${startDate},end:${endDate}),timeGranularityType:DAY)`,
|
)}&timeIntervals=(timeRange:(start:${startDate},end:${endDate}),timeGranularityType:DAY)`,
|
||||||
|
|
@ -233,7 +233,7 @@ export class LinkedinPageProvider
|
||||||
).json();
|
).json();
|
||||||
|
|
||||||
const { elements: elements2 }: { elements: Root[]; paging: any } = await (
|
const { elements: elements2 }: { elements: Root[]; paging: any } = await (
|
||||||
await fetch(
|
await this.fetch(
|
||||||
`https://api.linkedin.com/rest/organizationalEntityFollowerStatistics?q=organizationalEntity&organizationalEntity=${encodeURIComponent(
|
`https://api.linkedin.com/rest/organizationalEntityFollowerStatistics?q=organizationalEntity&organizationalEntity=${encodeURIComponent(
|
||||||
`urn:li:organization:${id}`
|
`urn:li:organization:${id}`
|
||||||
)}&timeIntervals=(timeRange:(start:${startDate},end:${endDate}),timeGranularityType:DAY)`,
|
)}&timeIntervals=(timeRange:(start:${startDate},end:${endDate}),timeGranularityType:DAY)`,
|
||||||
|
|
@ -248,7 +248,7 @@ export class LinkedinPageProvider
|
||||||
).json();
|
).json();
|
||||||
|
|
||||||
const { elements: elements3 }: { elements: Root[]; paging: any } = await (
|
const { elements: elements3 }: { elements: Root[]; paging: any } = await (
|
||||||
await fetch(
|
await this.fetch(
|
||||||
`https://api.linkedin.com/rest/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=${encodeURIComponent(
|
`https://api.linkedin.com/rest/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=${encodeURIComponent(
|
||||||
`urn:li:organization:${id}`
|
`urn:li:organization:${id}`
|
||||||
)}&timeIntervals=(timeRange:(start:${startDate},end:${endDate}),timeGranularityType:DAY)`,
|
)}&timeIntervals=(timeRange:(start:${startDate},end:${endDate}),timeGranularityType:DAY)`,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue