Statistics: Support for Public Shares, various fixes (#1889)
This commit is contained in:
parent
017170c2e2
commit
2dfac887cd
@ -3,11 +3,14 @@ import type {
|
|||||||
ApiChannelStatistics,
|
ApiChannelStatistics,
|
||||||
ApiGroupStatistics,
|
ApiGroupStatistics,
|
||||||
ApiMessageStatistics,
|
ApiMessageStatistics,
|
||||||
|
ApiMessagePublicForward,
|
||||||
StatisticsGraph,
|
StatisticsGraph,
|
||||||
StatisticsOverviewItem,
|
StatisticsOverviewItem,
|
||||||
StatisticsOverviewPercentage,
|
StatisticsOverviewPercentage,
|
||||||
StatisticsOverviewPeriod,
|
StatisticsOverviewPeriod,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
import { buildAvatarHash } from './chats';
|
||||||
|
import { buildApiPeerId } from './peers';
|
||||||
|
|
||||||
export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiChannelStatistics {
|
export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiChannelStatistics {
|
||||||
return {
|
return {
|
||||||
@ -61,6 +64,31 @@ export function buildMessageStatistics(stats: GramJs.stats.MessageStats): ApiMes
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildMessagePublicForwards(
|
||||||
|
result: GramJs.messages.TypeMessages,
|
||||||
|
): ApiMessagePublicForward[] | undefined {
|
||||||
|
if (!result || !('messages' in result)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.messages.map((message) => {
|
||||||
|
const peerId = buildApiPeerId((message.peerId as GramJs.PeerChannel).channelId, 'channel');
|
||||||
|
const channel = result.chats.find((p) => buildApiPeerId(p.id, 'channel') === peerId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
messageId: message.id,
|
||||||
|
views: (message as GramJs.Message).views,
|
||||||
|
title: (channel as GramJs.Channel).title,
|
||||||
|
chat: {
|
||||||
|
id: peerId,
|
||||||
|
type: 'chatTypeChannel',
|
||||||
|
username: (channel as GramJs.Channel).username,
|
||||||
|
avatarHash: buildAvatarHash((channel as GramJs.Channel).photo),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function buildGraph(
|
export function buildGraph(
|
||||||
result: GramJs.TypeStatsGraph, isPercentage?: boolean,
|
result: GramJs.TypeStatsGraph, isPercentage?: boolean,
|
||||||
): StatisticsGraph | undefined {
|
): StatisticsGraph | undefined {
|
||||||
|
|||||||
@ -85,7 +85,8 @@ export {
|
|||||||
} from './reactions';
|
} from './reactions';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
fetchChannelStatistics, fetchGroupStatistics, fetchMessageStatistics, fetchStatisticsAsyncGraph,
|
fetchChannelStatistics, fetchGroupStatistics, fetchMessageStatistics,
|
||||||
|
fetchMessagePublicForwards, fetchStatisticsAsyncGraph,
|
||||||
} from './statistics';
|
} from './statistics';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -2,13 +2,14 @@ import BigInt from 'big-integer';
|
|||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat, ApiChannelStatistics, ApiGroupStatistics, ApiMessageStatistics, StatisticsGraph,
|
ApiChat, ApiChannelStatistics, ApiGroupStatistics, ApiMessageStatistics, ApiMessagePublicForward, StatisticsGraph,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { invokeRequest } from './client';
|
import { invokeRequest } from './client';
|
||||||
|
import { addEntitiesWithPhotosToLocalDb } from '../helpers';
|
||||||
import { buildInputEntity } from '../gramjsBuilders';
|
import { buildInputEntity } from '../gramjsBuilders';
|
||||||
import {
|
import {
|
||||||
buildChannelStatistics, buildGroupStatistics, buildMessageStatistics, buildGraph,
|
buildChannelStatistics, buildGroupStatistics, buildMessageStatistics, buildMessagePublicForwards, buildGraph,
|
||||||
} from '../apiBuilders/statistics';
|
} from '../apiBuilders/statistics';
|
||||||
|
|
||||||
export async function fetchChannelStatistics({
|
export async function fetchChannelStatistics({
|
||||||
@ -58,6 +59,32 @@ export async function fetchMessageStatistics({
|
|||||||
return buildMessageStatistics(result);
|
return buildMessageStatistics(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchMessagePublicForwards({
|
||||||
|
chat,
|
||||||
|
messageId,
|
||||||
|
dcId,
|
||||||
|
}: {
|
||||||
|
chat: ApiChat;
|
||||||
|
messageId: number;
|
||||||
|
dcId?: number;
|
||||||
|
}): Promise<ApiMessagePublicForward[] | undefined> {
|
||||||
|
const result = await invokeRequest(new GramJs.stats.GetMessagePublicForwards({
|
||||||
|
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||||
|
msgId: messageId,
|
||||||
|
offsetPeer: new GramJs.InputPeerEmpty(),
|
||||||
|
}), undefined, undefined, undefined, dcId);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('chats' in result) {
|
||||||
|
addEntitiesWithPhotosToLocalDb(result.chats);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildMessagePublicForwards(result);
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchStatisticsAsyncGraph({
|
export async function fetchStatisticsAsyncGraph({
|
||||||
token,
|
token,
|
||||||
x,
|
x,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { ApiMessage } from './messages';
|
import type { ApiChat } from './chats';
|
||||||
|
import type { ApiMessage, ApiPhoto } from './messages';
|
||||||
|
|
||||||
export interface ApiChannelStatistics {
|
export interface ApiChannelStatistics {
|
||||||
growthGraph?: StatisticsGraph | string;
|
growthGraph?: StatisticsGraph | string;
|
||||||
@ -34,6 +35,15 @@ export interface ApiMessageStatistics {
|
|||||||
viewsGraph?: StatisticsGraph | string;
|
viewsGraph?: StatisticsGraph | string;
|
||||||
forwards?: number;
|
forwards?: number;
|
||||||
views?: number;
|
views?: number;
|
||||||
|
publicForwards?: number;
|
||||||
|
publicForwardsData?: ApiMessagePublicForward[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiMessagePublicForward {
|
||||||
|
messageId: number;
|
||||||
|
views?: number;
|
||||||
|
title?: string;
|
||||||
|
chat: ApiChat;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StatisticsGraph {
|
export interface StatisticsGraph {
|
||||||
|
|||||||
@ -439,6 +439,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
|| contentKey === HeaderContent.SharedMedia
|
|| contentKey === HeaderContent.SharedMedia
|
||||||
|| contentKey === HeaderContent.MemberList
|
|| contentKey === HeaderContent.MemberList
|
||||||
|| contentKey === HeaderContent.AddingMembers
|
|| contentKey === HeaderContent.AddingMembers
|
||||||
|
|| contentKey === HeaderContent.MessageStatistics
|
||||||
|| isManagement
|
|| isManagement
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import type { ApiMessageStatistics, StatisticsGraph } from '../../../api/types';
|
import type { ApiMessageStatistics, ApiMessagePublicForward, StatisticsGraph } from '../../../api/types';
|
||||||
import { selectChat } from '../../../global/selectors';
|
import { selectChat } from '../../../global/selectors';
|
||||||
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
@ -14,6 +14,7 @@ import useForceUpdate from '../../../hooks/useForceUpdate';
|
|||||||
|
|
||||||
import Loading from '../../ui/Loading';
|
import Loading from '../../ui/Loading';
|
||||||
import StatisticsOverview from './StatisticsOverview';
|
import StatisticsOverview from './StatisticsOverview';
|
||||||
|
import StatisticsPublicForward from './StatisticsPublicForward';
|
||||||
|
|
||||||
import './Statistics.scss';
|
import './Statistics.scss';
|
||||||
|
|
||||||
@ -156,6 +157,16 @@ const Statistics: FC<OwnProps & StateProps> = ({
|
|||||||
<div className={buildClassName('Statistics__graph', !loadedCharts.current.includes(graph) && 'hidden')} />
|
<div className={buildClassName('Statistics__graph', !loadedCharts.current.includes(graph) && 'hidden')} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{Boolean(statistics.publicForwards) && (
|
||||||
|
<div className="Statistics__public-forwards">
|
||||||
|
<h2 className="Statistics__public-forwards-title">{lang('Stats.Message.PublicShares')}</h2>
|
||||||
|
|
||||||
|
{statistics.publicForwardsData!.map((item: ApiMessagePublicForward) => (
|
||||||
|
<StatisticsPublicForward data={item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
|
||||||
&__messages {
|
&__messages, &__public-forwards {
|
||||||
padding: 1rem 0.25rem;
|
padding: 1rem 0;
|
||||||
border-top: 1px solid var(--color-borders);
|
border-top: 1px solid var(--color-borders);
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
@ -38,6 +38,7 @@
|
|||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +48,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.lovely-chart--header {
|
.lovely-chart--header {
|
||||||
margin: 0 1rem;
|
margin: 0 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lovely-chart--header,
|
.lovely-chart--header,
|
||||||
|
|||||||
@ -160,6 +160,8 @@ const Statistics: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
loadedCharts.current.push(name);
|
loadedCharts.current.push(name);
|
||||||
|
|
||||||
|
containerRef.current!.children[index].classList.remove('hidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
@ -180,7 +182,7 @@ const Statistics: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
{graphs.map((graph) => (
|
{graphs.map((graph) => (
|
||||||
<div className={buildClassName('Statistics__graph', !loadedCharts.current.includes(graph) && 'hidden')} />
|
<div key={graph} className="Statistics__graph hidden" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
margin-right: 2em;
|
margin-right: 2em;
|
||||||
padding-left: 0.25rem;
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type {
|
|||||||
ApiChannelStatistics, ApiGroupStatistics, ApiMessageStatistics, StatisticsOverviewItem,
|
ApiChannelStatistics, ApiGroupStatistics, ApiMessageStatistics, StatisticsOverviewItem,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
|
||||||
import { formatIntegerCompact } from '../../../util/textFormat';
|
import { formatInteger, formatIntegerCompact } from '../../../util/textFormat';
|
||||||
import { formatFullDate } from '../../../util/dateFormat';
|
import { formatFullDate } from '../../../util/dateFormat';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -44,11 +44,14 @@ const GROUP_OVERVIEW: OverviewCell[][] = [
|
|||||||
|
|
||||||
const MESSAGE_OVERVIEW: OverviewCell[][] = [
|
const MESSAGE_OVERVIEW: OverviewCell[][] = [
|
||||||
[
|
[
|
||||||
{ name: 'views', title: 'StatisticViews', isPlain: true },
|
{ name: 'views', title: 'Stats.Message.Views', isPlain: true },
|
||||||
{
|
{
|
||||||
name: 'forwards', title: 'PrivateShares', isPlain: true, isApproximate: true,
|
name: 'forwards', title: 'Stats.Message.PrivateShares', isPlain: true, isApproximate: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
{ name: 'publicForwards', title: 'Stats.Message.PublicShares', isPlain: true },
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
@ -103,7 +106,9 @@ const StatisticsOverview: FC<OwnProps> = ({ isGroup, isMessage, statistics }) =>
|
|||||||
if (cell.isPlain) {
|
if (cell.isPlain) {
|
||||||
return (
|
return (
|
||||||
<td className="StatisticsOverview__table-cell">
|
<td className="StatisticsOverview__table-cell">
|
||||||
<b className="StatisticsOverview__table-value">{cell.isApproximate ? `≈${field}` : field}</b>
|
<b className="StatisticsOverview__table-value">
|
||||||
|
{cell.isApproximate ? `≈${formatInteger(field)}` : formatInteger(field)}
|
||||||
|
</b>
|
||||||
<h3 className="StatisticsOverview__table-heading">{lang(cell.title)}</h3>
|
<h3 className="StatisticsOverview__table-heading">{lang(cell.title)}</h3>
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
|
|||||||
25
src/components/right/statistics/StatisticsPublicForward.scss
Normal file
25
src/components/right/statistics/StatisticsPublicForward.scss
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
.StatisticsPublicForward {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:hover, &:active {
|
||||||
|
background-color: var(--color-chat-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__views {
|
||||||
|
color: var(--color-text-meta);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/components/right/statistics/StatisticsPublicForward.tsx
Normal file
41
src/components/right/statistics/StatisticsPublicForward.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React, { memo, useCallback } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import { getActions } from '../../../global';
|
||||||
|
import type { ApiMessagePublicForward } from '../../../api/types';
|
||||||
|
|
||||||
|
import Avatar from '../../common/Avatar';
|
||||||
|
|
||||||
|
import './StatisticsPublicForward.scss';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
data: ApiMessagePublicForward;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StatisticsPublicForward: FC<OwnProps> = ({ data }) => {
|
||||||
|
const lang = useLang();
|
||||||
|
const { openChatByUsername } = getActions();
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
openChatByUsername({ username: data.chat.username, messageId: data.messageId });
|
||||||
|
}, [data, openChatByUsername]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="StatisticsPublicForward" onClick={handleClick}>
|
||||||
|
<Avatar size="medium" chat={data.chat} />
|
||||||
|
|
||||||
|
<div className="StatisticsPublicForward__info">
|
||||||
|
<div className="StatisticsPublicForward__title">
|
||||||
|
{data.title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="StatisticsPublicForward__views">
|
||||||
|
{lang('ChannelStats.ViewsCount', data.views, 'i')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(StatisticsPublicForward);
|
||||||
@ -1,8 +1,7 @@
|
|||||||
.StatisticsRecentMessage {
|
.StatisticsRecentMessage {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem 0.75rem;
|
||||||
border-radius: var(--border-radius-default-small);
|
|
||||||
|
|
||||||
&:hover, &:active {
|
&:hover, &:active {
|
||||||
background-color: var(--color-chat-hover);
|
background-color: var(--color-chat-hover);
|
||||||
|
|||||||
@ -49,7 +49,7 @@ const StatisticsRecentMessage: FC<OwnProps> = ({ message }) => {
|
|||||||
{renderSummary(lang, message, mediaBlobUrl || mediaThumbnail, isRoundVideo)}
|
{renderSummary(lang, message, mediaBlobUrl || mediaThumbnail, isRoundVideo)}
|
||||||
</div>
|
</div>
|
||||||
<div className="StatisticsRecentMessage__meta">
|
<div className="StatisticsRecentMessage__meta">
|
||||||
{lang('ChannelStats.ViewsCount', message.views)}
|
{lang('ChannelStats.ViewsCount', message.views, 'i')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -44,10 +44,16 @@ addActionHandler('loadMessageStatistics', async (global, actions, payload) => {
|
|||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
|
||||||
const { views, forwards } = selectChatMessages(global, chatId)[messageId];
|
const { views, forwards } = selectChatMessages(global, chatId)[messageId];
|
||||||
|
|
||||||
result.views = views;
|
result.views = views;
|
||||||
result.forwards = forwards;
|
result.forwards = forwards;
|
||||||
|
|
||||||
|
const dcId = chat.fullInfo!.statisticsDcId;
|
||||||
|
const publicForwards = await callApi('fetchMessagePublicForwards', { chat, messageId, dcId });
|
||||||
|
result.publicForwards = publicForwards?.length;
|
||||||
|
result.publicForwardsData = publicForwards;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
|
||||||
setGlobal(updateMessageStatistics(global, result));
|
setGlobal(updateMessageStatistics(global, result));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1226,4 +1226,5 @@ folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates;
|
|||||||
stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats;
|
stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats;
|
||||||
stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph;
|
stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph;
|
||||||
stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats;
|
stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats;
|
||||||
|
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||||
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;`;
|
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;`;
|
||||||
@ -227,6 +227,7 @@
|
|||||||
"stats.getBroadcastStats",
|
"stats.getBroadcastStats",
|
||||||
"stats.getMegagroupStats",
|
"stats.getMegagroupStats",
|
||||||
"stats.getMessageStats",
|
"stats.getMessageStats",
|
||||||
|
"stats.getMessagePublicForwards",
|
||||||
"stats.loadAsyncGraph",
|
"stats.loadAsyncGraph",
|
||||||
"messages.getAttachMenuBots",
|
"messages.getAttachMenuBots",
|
||||||
"messages.getAttachMenuBot",
|
"messages.getAttachMenuBot",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user