Statistics: Support for Public Shares, various fixes (#1889)

This commit is contained in:
Alexander Zinchuk 2022-05-31 20:58:39 +04:00
parent 017170c2e2
commit 2dfac887cd
17 changed files with 176 additions and 18 deletions

View File

@ -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 {

View File

@ -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 {

View File

@ -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,

View File

@ -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 {

View File

@ -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
); );

View File

@ -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>
); );
}; };

View File

@ -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,

View File

@ -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>

View File

@ -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;

View File

@ -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>
); );

View 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;
}
}

View 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);

View File

@ -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);

View File

@ -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>

View File

@ -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));
}); });

View File

@ -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;`;

View File

@ -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",