creator analytics improvements

This commit is contained in:
Sean Yesmunt 2020-04-22 15:07:38 -04:00
parent a391f35542
commit 837158218e
6 changed files with 205 additions and 72 deletions

View file

@ -17,7 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed
- Fixed the title of the subscribe button getting out of sync on fast hover movements _community pr!_ ([#4054](https://github.com/lbryio/lbry-desktop/pull/4054))
- Handle errors better on the creator analytics page and add a new card for stats on users most recent content ([#4043](https://github.com/lbryio/lbry-desktop/pull/4043))
- Add fallback when images fail to load _community pr!_ ([#4019](https://github.com/lbryio/lbry-desktop/pull/4019))
- Fetch new content when clicking LBRY logo while on homepage _community pr!_ ([#4031](https://github.com/lbryio/lbry-desktop/pull/4031))
- Aligns text across browsers and desktop _community pr!_ ([#4050](https://github.com/lbryio/lbry-desktop/pull/4050))

View file

@ -1166,5 +1166,30 @@
"Enter Your lbry.tv Password": "Enter Your lbry.tv Password",
"Signing in as %email%": "Signing in as %email%",
"Forgot Password?": "Forgot Password?",
"Use Magic Link": "Use Magic Link"
"Use Magic Link": "Use Magic Link",
"No recent publishes": "No recent publishes",
"Error Fetching Stats": "Error Fetching Stats",
"Something something something. Make sure you are signed in with the correct email and have data sharing on.": "Something something something. Make sure you are signed in with the correct email and have data sharing on.",
"%follower_count_weekly_change% this week": "%follower_count_weekly_change% this week",
"%all_content_views% views": "%all_content_views% views",
"+ %all_content_views_weekly_change% this week": "+ %all_content_views_weekly_change% this week",
"%lbc_received_changed% this week": "%lbc_received_changed% this week",
" Earnings may also include any LBC you've sent yourself or added as support. We are working on making this more accurate. Check your wallet page for the correct total balance.": " Earnings may also include any LBC you've sent yourself or added as support. We are working on making this more accurate. Check your wallet page for the correct total balance.",
"Your Recent Content": "Your Recent Content",
"No recent publishes found for this channel. Publish something new and track how it's performing here.": "No recent publishes found for this channel. Publish something new and track how it's performing here.",
"Most Viewed Content": "Most Viewed Content",
"%all_time_top_views% views - %all_time_views_weekly_change% this week": "%all_time_top_views% views - %all_time_views_weekly_change% this week",
"Successfully abandoned your support.": "Successfully abandoned your support.",
"/wallet": "/wallet",
"Send a tip to %url%": "Send a tip to %url%",
"You sent %amount% LBC as a tip, Mahalo!": "You sent %amount% LBC as a tip, Mahalo!",
"No Stats Found": "No Stats Found",
"Sorry about that. Try refreshing or something else.": "Sorry about that. Try refreshing or something else.",
"You are not able to see those stats right now. Make sure you are signed in with the correct email and have data sharing turned on.": "You are not able to see those stats right now. Make sure you are signed in with the correct email and have data sharing turned on.",
"You are not able to see this channel's stats right now. Make sure you are signed in with the correct email and have data sharing turned on.": "You are not able to see this channel's stats right now. Make sure you are signed in with the correct email and have data sharing turned on.",
"You are not able to see this channel's stats. Make sure you are signed in with the correct email and have data sharing turned on.": "You are not able to see this channel's stats. Make sure you are signed in with the correct email and have data sharing turned on.",
"There are no stats for this channel. Make sure you are signed in with the correct email and have data sharing turned on.": "There are no stats for this channel. Make sure you are signed in with the correct email and have data sharing turned on.",
"%follower_count% followers": "%follower_count% followers",
"%lbc_received% LBC Earned": "%lbc_received% LBC Earned",
"Earnings may also include any LBC you've sent yourself or added as support. We are working on making this more accurate. Check your wallet page for the correct total balance.": "Earnings may also include any LBC you've sent yourself or added as support. We are working on making this more accurate. Check your wallet page for the correct total balance."
}

View file

@ -1,10 +1,9 @@
import { connect } from 'react-redux';
import { selectMyChannelClaims, selectFetchingMyChannels, doPrepareEdit } from 'lbry-redux';
import { makeSelectClaimForUri, doPrepareEdit } from 'lbry-redux';
import CreatorAnalytics from './view';
const select = state => ({
channels: selectMyChannelClaims(state),
fetchingChannels: selectFetchingMyChannels(state),
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
});
const perform = dispatch => ({

View file

@ -3,107 +3,116 @@ import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import React from 'react';
import { Lbryio } from 'lbryinc';
import ChannelSelector from 'component/channelSelector';
import ClaimPreview from 'component/claimPreview';
import Card from 'component/common/card';
import Spinner from 'component/spinner';
import Icon from 'component/common/icon';
import usePersistedState from 'effects/use-persisted-state';
import Button from 'component/button';
import Yrbl from 'component/yrbl';
import { useHistory } from 'react-router-dom';
import analytics from 'analytics';
type Props = {
channels: Array<ChannelClaim>,
claim: ?ChannelClaim,
fetchingChannels: boolean,
prepareEdit: string => void,
};
const UNAUTHENTICATED_ERROR = 'unauthenticated';
const GENERIC_ERROR = 'error';
export default function CreatorAnalytics(props: Props) {
const { channels, prepareEdit } = props;
const { prepareEdit, claim } = props;
const history = useHistory();
const [stats, setStats] = React.useState();
const [selectedChannelUrl, setSelectedChannelUrl] = usePersistedState('analytics-selected-channel');
const [error, setError] = React.useState();
const [fetchingStats, setFetchingStats] = React.useState(false);
const hasChannels = channels && channels.length > 0;
const firstChannel = hasChannels && channels[0];
const firstChannelUrl = firstChannel && (firstChannel.canonical_url || firstChannel.permanent_url); // permanent_url is needed for pending publishes
const selectedChannelClaim =
channels &&
channels.find(claim => claim.canonical_url === selectedChannelUrl || claim.permanent_url === selectedChannelUrl);
const selectedChannelClaimId = selectedChannelClaim && selectedChannelClaim.claim_id;
const channelFoundForSelectedChannelUrl =
channels &&
channels.find(channel => {
return selectedChannelUrl === channel.canonical_url || selectedChannelUrl === channel.permanent_url;
});
const claimId = claim && claim.claim_id;
const channelHasClaims = claim && claim.meta && claim.meta.claims_in_channel && claim.meta.claims_in_channel > 0;
React.useEffect(() => {
// set default channel
if ((!selectedChannelUrl || !channelFoundForSelectedChannelUrl) && firstChannelUrl) {
setSelectedChannelUrl(firstChannelUrl);
}
}, [selectedChannelUrl, firstChannelUrl, channelFoundForSelectedChannelUrl]);
setStats(null);
}, [claimId]);
const channelForEffect = JSON.stringify(selectedChannelClaim);
const channelForEffect = JSON.stringify(claim);
React.useEffect(() => {
if (selectedChannelClaimId && channelForEffect) {
if (claimId && channelForEffect) {
setFetchingStats(true);
Lbryio.call('reports', 'content', { claim_id: selectedChannelClaimId })
Lbryio.call('reports', 'content', { claim_id: claimId })
.then(res => {
setFetchingStats(false);
setStats(res);
})
.catch(() => {
const channelToSend = JSON.parse(channelForEffect);
analytics.apiLogPublish(channelToSend);
.catch(error => {
if (error.response.status === 401) {
setError(UNAUTHENTICATED_ERROR);
const channelToSend = JSON.parse(channelForEffect);
analytics.apiLogPublish(channelToSend);
} else {
setError(GENERIC_ERROR);
}
setFetchingStats(false);
});
}
}, [selectedChannelClaimId, channelForEffect, setFetchingStats, setStats]);
}, [claimId, channelForEffect, setFetchingStats, setStats]);
return (
<React.Fragment>
<div className="section">
<ChannelSelector
selectedChannelUrl={selectedChannelUrl}
onChannelSelect={newChannelUrl => {
setStats(null);
setSelectedChannelUrl(newChannelUrl);
}}
/>
</div>
{fetchingStats && !stats && (
{!stats && (
<div className="main--empty">
<Spinner delayed />
{fetchingStats ? (
<Spinner delayed />
) : (
<div>
{error && (
<Yrbl
type="sad"
title={error === GENERIC_ERROR ? __('No Stats Found') : __('Error Fetching Stats')}
subtitle={
error === GENERIC_ERROR
? __(
'There are no stats for this channel. Make sure you are signed in with the correct email and have data sharing turned on.'
)
: __(
"You are not able to see this channel's stats. Make sure you are signed in with the correct email and have data sharing turned on."
)
}
/>
)}
{!error && (
<Yrbl
title={
channelHasClaims
? __('No recent publishes')
: __("You haven't published anything with this channel yet!")
}
subtitle={
<Button
button="primary"
label={__('Publish Something')}
onClick={() => {
if (claim) {
prepareEdit(claim.name);
history.push(`/$/${PAGES.PUBLISH}`);
}
}}
/>
}
/>
)}
</div>
)}
</div>
)}
{!fetchingStats && !stats && (
<section className="main--empty">
<Yrbl
title={__("You haven't published anything with this channel yet!")}
subtitle={
<Button
button="primary"
label={__('Publish Something')}
onClick={() => {
if (selectedChannelClaim) {
prepareEdit(selectedChannelClaim.name);
history.push(`/$/${PAGES.PUBLISH}`);
}
}}
/>
}
/>
</section>
)}
{stats && (
<div className="section">
<div className="columns">
<Card
iconColor
title={<span>{stats.ChannelSubs} followers</span>}
title={<span>{__('%follower_count% followers', { follower_count: stats.ChannelSubs })}</span>}
icon={ICONS.SUBSCRIBE}
subtitle={
<div className="card__data-subtitle">
@ -134,11 +143,81 @@ export default function CreatorAnalytics(props: Props) {
</div>
<Card
title={
<div className="card__data-subtitle">
<span>{__('Most Viewed Content')}</span>
</div>
iconColor
className="section"
title={<span>{__('%lbc_received% LBC Earned', { lbc_received: stats.AllLBCReceived })}</span>}
icon={ICONS.SUBSCRIBE}
subtitle={
<React.Fragment>
<div className="card__data-subtitle">
<span>
{'+'}{' '}
{__('%lbc_received_changed% this week', {
lbc_received_changed: stats.LBCReceivedChange || 0,
})}
</span>
{stats.LBCReceivedChange > 0 && <Icon icon={ICONS.SUPPORT} iconColor="green" size={18} />}
</div>
<p className="help">
{__(
"Earnings may also include any LBC you've sent yourself or added as support. We are working on making this more accurate. Check your wallet page for the correct total balance."
)}
</p>
</React.Fragment>
}
/>
{stats.VideoURITopNew ? (
<Card
className="section"
title={__('Your Recent Content')}
body={
<React.Fragment>
<div className="card--inline">
<ClaimPreview uri={stats.VideoViewsTopNew} />
</div>
<div className="section__subtitle card__data-subtitle">
<span>
{__('%view_count% %views%', {
view_count: stats.VideoViewsTopNew,
views: stats.VideoViewsTopNew === 1 ? 'view' : 'views',
})}
</span>
{stats.VideoViewsTopNew > 0 && <Icon icon={ICONS.SUPPORT} iconColor="green" size={18} />}
</div>
</React.Fragment>
}
/>
) : (
<Card
className="section"
title={__('Your Recent Content')}
subtitle={
!stats.VideoURITopNew &&
__(
"No recent publishes found for this channel. Publish something new and track how it's performing here."
)
}
actions={
<div className="section__actions">
<Button
button="primary"
label={__('New Publish')}
onClick={() => {
if (claim) {
prepareEdit(claim.name);
history.push(`/$/${PAGES.PUBLISH}`);
}
}}
/>
</div>
}
/>
)}
<Card
className="section"
title={__('Most Viewed Content')}
body={
<React.Fragment>
<div className="card--inline">

View file

@ -4,6 +4,8 @@ import Page from 'component/page';
import Spinner from 'component/spinner';
import Button from 'component/button';
import CreatorAnalytics from 'component/creatorAnalytics';
import ChannelSelector from 'component/channelSelector';
import usePersistedState from 'effects/use-persisted-state';
type Props = {
channels: Array<ChannelClaim>,
@ -13,6 +15,22 @@ type Props = {
export default function CreatorDashboardPage(props: Props) {
const { channels, fetchingChannels, openChannelCreateModal } = props;
const [selectedChannelUrl, setSelectedChannelUrl] = usePersistedState('analytics-selected-channel');
const hasChannels = channels && channels.length > 0;
const firstChannel = hasChannels && channels[0];
const firstChannelUrl = firstChannel && (firstChannel.canonical_url || firstChannel.permanent_url); // permanent_url is needed for pending publishes
const channelFoundForSelectedChannelUrl =
channels &&
channels.find(channel => {
return selectedChannelUrl === channel.canonical_url || selectedChannelUrl === channel.permanent_url;
});
React.useEffect(() => {
// set default channel
if ((!selectedChannelUrl || !channelFoundForSelectedChannelUrl) && firstChannelUrl) {
setSelectedChannelUrl(firstChannelUrl);
}
}, [setSelectedChannelUrl, selectedChannelUrl, firstChannelUrl, channelFoundForSelectedChannelUrl]);
return (
<Page>
@ -33,7 +51,19 @@ export default function CreatorDashboardPage(props: Props) {
</section>
)}
{!fetchingChannels && channels && channels.length && <CreatorAnalytics />}
{!fetchingChannels && channels && channels.length && (
<React.Fragment>
<div className="section">
<ChannelSelector
selectedChannelUrl={selectedChannelUrl}
onChannelSelect={newChannelUrl => {
setSelectedChannelUrl(newChannelUrl);
}}
/>
</div>
<CreatorAnalytics uri={selectedChannelUrl} />
</React.Fragment>
)}
</Page>
);
}

View file

@ -16,7 +16,7 @@
}
.yrbl__content {
max-width: 500px;
max-width: 400px;
}
.yrbl--first-run {