Skip to content

Thrive rss #5912

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Dec 7, 2021
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -356,14 +356,14 @@ workflows:
filters:
branches:
only:
- free
- thrive-rss
# This is beta env for production soft releases
- "build-prod-beta":
context : org-global
filters:
branches:
only:
- free
- mm-leaderboard-theme
# This is stage env for production QA releases
- "build-prod-staging":
context : org-global
Expand Down
1 change: 1 addition & 0 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ module.exports = {
HOME: '/home',
BLOG: 'https://www.topcoder-dev.com/blog',
BLOG_FEED: 'https://www.topcoder.com/blog/feed/',
THRIVE_FEED: 'https://topcoder-dev.com/api/feeds/thrive',
COMMUNITY: 'https://community.topcoder-dev.com',
FORUMS: 'https://apps.topcoder-dev.com/forums',
FORUMS_VANILLA: 'https://vanilla.topcoder-dev.com',
Expand Down
1 change: 1 addition & 0 deletions config/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ module.exports = {
CS: 'https://cs.topcoder.com',
},
EMAIL_VERIFY_URL: 'http://www.topcoder.com/settings/account/changeEmail',
THRIVE_FEED: 'https://topcoder.com/api/feeds/thrive',
},
/* Filestack configuration for uploading Submissions
* These are for the production back end */
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
"redux-promise": "^0.6.0",
"request-ip": "^2.0.2",
"require-context": "^1.1.0",
"rss": "^1.2.2",
"rss-parser": "^3.12.0",
"serialize-javascript": "^2.1.1",
"serve-favicon": "^2.5.0",
Expand Down
2 changes: 2 additions & 0 deletions src/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import mmLeaderboardRouter from './routes/mmLeaderboard';
import growsurfRouter from './routes/growsurf';
import gSheetsRouter from './routes/gSheet';
import blogRouter from './routes/blog';
import feedsRouter from './routes/feeds';

/* Dome API for topcoder communities */
import tcCommunitiesDemoApi from './tc-communities';
Expand Down Expand Up @@ -143,6 +144,7 @@ async function onExpressJsSetup(server) {
server.use('/api/growsurf', growsurfRouter);
server.use('/api/gsheets', gSheetsRouter);
server.use('/api/blog', blogRouter);
server.use('/api/feeds', feedsRouter);

// serve demo api
server.use(
Expand Down
62 changes: 62 additions & 0 deletions src/server/routes/feeds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* The routes that expose assets and content from Contentful CMS to the CDN.
*/

import express from 'express';
import RSS from 'rss';
import ReactDOMServer from 'react-dom/server';
import md from 'utils/markdown';
import {
getService,
} from '../services/contentful';

const cors = require('cors');

const routes = express.Router();

// Enables CORS on those routes according config above
// ToDo configure CORS for set of our trusted domains
routes.use(cors());
routes.options('*', cors());

routes.get('/thrive', async (req, res, next) => {
try {
const data = await getService('EDU', 'master', true).queryEntries({
content_type: 'article',
limit: 20,
order: '-sys.createdAt',
include: 2,
});
const feed = new RSS({
title: 'Topcoder Thrive',
description: 'Tutorials And Workshops That Matter | Thrive | Topcoder',
feed_url: 'https://topcoder.com/api/feeds/thrive',
site_url: 'https://topcoder.com/thrive',
image_url: 'https://www.topcoder.com/wp-content/uploads/2020/05/cropped-TC-Icon-32x32.png',
docs: 'https://www.topcoder.com/thrive/tracks?track=Topcoder',
webMaster: '<[email protected]> Kiril Kartunov',
copyright: '2021 - today, Topcoder',
language: 'en',
categories: ['Competitive Programming', 'Data Science', 'Design', 'Development', 'QA', 'Gig work', 'Topcoder'],
ttl: '60',
});
if (data && data.total) {
data.items.forEach((entry) => {
feed.item({
title: entry.fields.title,
description: ReactDOMServer.renderToString(md(entry.fields.content)),
url: `https://topcoder.com/thrive/articles/${entry.fields.slug || encodeURIComponent(entry.fields.title)}?utm_source=community&utm_campaign=thrive-feed&utm_medium=promotion`,
date: entry.fields.creationDate,
categories: entry.fields.tags,
author: entry.fields.contentAuthor[0].fields.name,
});
});
}
res.set('Content-Type', 'application/rss+xml');
res.send(feed.xml({ indent: true }));
} catch (e) {
next(e);
}
});

export default routes;
16 changes: 14 additions & 2 deletions src/shared/actions/mmLeaderboard.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { redux } from 'topcoder-react-utils';
import { redux, config } from 'topcoder-react-utils';
import Service from 'services/mmLeaderboard';
import _ from 'lodash';


/**
* Fetch init
*/
Expand Down Expand Up @@ -34,11 +35,22 @@ async function getMMLeaderboardDone(id) {
score: scores && scores.length ? scores[0].score : '...',
});
});
data = _.orderBy(data, [d => (Number(d.score) ? Number(d.score) : 0)], ['desc']).map((r, i) => ({
data = _.orderBy(data, [d => (Number(d.score) ? Number(d.score) : 0), d => new Date(d.updated) - new Date()], ['desc']).map((r, i) => ({
...r,
rank: i + 1,
score: r.score % 1 ? Number(r.score).toFixed(5) : r.score,
}));
// Fetch member photos and rating for top 10
const results = await Promise.all(
_.take(data, 10).map(d => fetch(`${config.API.V5}/members/${d.createdBy}`)),
);
const memberData = await Promise.all(results.map(r => r.json()));
// merge with data
// eslint-disable-next-line array-callback-return
memberData.map((member, indx) => {
data[indx].photoUrl = member.photoURL;
data[indx].rating = member.maxRating && member.maxRating.rating;
});
}
return {
id,
Expand Down
100 changes: 89 additions & 11 deletions src/shared/components/MMatchLeaderboard/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ import PT from 'prop-types';
import _ from 'lodash';
import React, { Component } from 'react';
import { fixStyle } from 'utils/contentful';
import { getRatingColor } from 'utils/tc';
import cn from 'classnames';
import { Scrollbars } from 'react-custom-scrollbars';
import './style.scss';
import { config } from 'topcoder-react-utils';
import { PrimaryButton } from 'topcoder-react-ui-kit';
import tc from 'components/buttons/themed/tc.scss';
import defaultStyles from './style.scss';

const DEFAULT_AVATAR_URL = 'https://images.ctfassets.net/b5f1djy59z3a/4PTwZVSf3W7qgs9WssqbVa/4c51312671a4b9acbdfd7f5e22320b62/default_avatar.svg';

export default class MMLeaderboard extends Component {
constructor(props) {
Expand All @@ -46,6 +52,8 @@ export default class MMLeaderboard extends Component {
tableHeight,
tableWidth,
headerIndexCol,
theme,
challengeId,
} = this.props;

let {
Expand Down Expand Up @@ -76,6 +84,73 @@ export default class MMLeaderboard extends Component {
}

const renderData = () => {
if (data.length && theme && theme === 'Podium') {
return (
<div className={defaultStyles.podiumTheme}>
<div className={defaultStyles.top3}>
<div className={defaultStyles.swapper}>
{_.take(data, 2).map((member, indx) => (
<div className={defaultStyles[`topMember${indx + 1}`]}>
<div className={defaultStyles.topMemberPhotoAndPlace}>
<img src={member.photoUrl || DEFAULT_AVATAR_URL} className={defaultStyles.topMemberPhoto} alt={`Avatar of ${member.createdBy}`} />
<div className={defaultStyles[`topMemberRank${indx + 1}`]}>{member.rank}</div>
</div>
<div className={defaultStyles.topMemberLink}>
<a href={`${config.URL.BASE}/members/${member.createdBy}`} target="_blank" rel="noreferrer" style={{ color: getRatingColor(member.rating) }}>{member.createdBy}</a>
<p className={defaultStyles.topMemberScore}>{member.score}</p>
</div>
</div>
))}
</div>
<div className={defaultStyles.topMember3}>
<div className={defaultStyles.topMemberPhotoAndPlace}>
<img src={data[2].photoUrl || DEFAULT_AVATAR_URL} className={defaultStyles.topMemberPhoto} alt={`Avatar of ${data[2].createdBy}`} />
<div className={defaultStyles.topMemberRank3}>{data[2].rank}</div>
</div>
<div className={defaultStyles.topMemberLink}>
<a href={`${config.URL.BASE}/members/${data[2].createdBy}`} target="_blank" rel="noreferrer" style={{ color: getRatingColor(data[2].rating) }}>{data[2].createdBy}</a>
<p className={defaultStyles.topMemberScore}>{data[2].score}</p>
</div>
</div>
</div>
<div className={defaultStyles.followers}>
<div className={defaultStyles.followersLeft}>
{_.slice(data, 3, 7).map(member => (
<div className={defaultStyles.follower}>
<span>{member.rank}.&nbsp;</span>
<a href={`${config.URL.BASE}/members/${member.createdBy}`} target="_blank" rel="noreferrer" style={{ color: getRatingColor(member.rating) }}>{member.createdBy}</a>
<span className={defaultStyles.followerScore}>{member.score}</span>
</div>
))}
</div>
{
data.length > 7 && (
<div className={defaultStyles.followersRight}>
{_.slice(data, 7, 10).map(member => (
<div className={defaultStyles.follower}>
<span>{member.rank}.&nbsp;</span>
<a href={`${config.URL.BASE}/members/${member.createdBy}`} target="_blank" rel="noreferrer" style={{ color: getRatingColor(member.rating) }}>{member.createdBy}</a>
<span className={defaultStyles.followerScore}>{member.score}</span>
</div>
))}
{
data.length > 10 && (
<PrimaryButton
to={`${config.URL.BASE}/challenges/${challengeId}?tab=submissions`}
openNewTab
theme={{
button: tc['primary-green-sm'],
}}
>
See Full Leaderbord
</PrimaryButton>
)}
</div>
)}
</div>
</div>
);
}
if (property) {
if (data.length > 0 && data[0][property]) {
if (typeof data[0][property] === 'string') {
Expand Down Expand Up @@ -107,17 +182,17 @@ export default class MMLeaderboard extends Component {

const header = cols => (
<tr>
{ countRows && (<th styleName="header-cell"><span>{headerIndexCol}</span></th>) }
{ countRows && (<th className={defaultStyles['header-cell']}><span>{headerIndexCol}</span></th>) }
{
cols.map((c) => {
const name = c.headerName;
const { styles } = c;
return name ? (
<th key={name} style={fixStyle(styles)} styleName="header-cell">
<div styleName="header-table-content">
<th key={name} style={fixStyle(styles)} className={defaultStyles['header-cell']}>
<div className={defaultStyles['header-table-content']}>
<span>{ name }</span>
<button
styleName="sort-container"
className={defaultStyles['sort-container']}
onClick={() => {
if (!sortParam.field || sortParam.field !== c.property) {
sortParam.field = c.property;
Expand Down Expand Up @@ -147,7 +222,7 @@ export default class MMLeaderboard extends Component {
);
const bodyRow = (record, cols, i) => (
<tr key={Object.values(record)}>
{ (countRows && (limit <= 0 || i < limit)) ? <td styleName="body-row"> {i + 1} </td> : ' ' }
{ (countRows && (limit <= 0 || i < limit)) ? <td className={defaultStyles['body-row']}> {i + 1} </td> : ' ' }
{
cols.map((c) => {
const prop = c.property;
Expand All @@ -169,8 +244,8 @@ export default class MMLeaderboard extends Component {
}
}
return value ? (
<td key={record[prop]} style={fixStyle(styles)} title={value} styleName="body-row">
{memberLinks ? (<a styleName="handle-link" href={`${window.origin}/members/${value}`} target={`${_.includes(window.origin, 'www') ? '_self' : '_blank'}`}>{value}</a>) : value}
<td key={record[prop]} style={fixStyle(styles)} title={value} className={defaultStyles['body-row']}>
{memberLinks ? (<a className={defaultStyles['handle-link']} href={`${window.origin}/members/${value}`} target={`${_.includes(window.origin, 'www') ? '_self' : '_blank'}`}>{value}</a>) : value}
</td>
) : null;
})
Expand All @@ -179,10 +254,10 @@ export default class MMLeaderboard extends Component {
);
return (
<Scrollbars
styleName="component-container"
className={defaultStyles['component-container']}
style={{ height: tableHeight, width: tableWidth }}
>
<table styleName="table-container">
<table className={defaultStyles['table-container']}>
<thead>
{ header(columns) }
</thead>
Expand Down Expand Up @@ -221,7 +296,7 @@ export default class MMLeaderboard extends Component {
};
return (
<React.Fragment>
{ data.length ? renderData() : <h4 styleName="no-data-title">No data available yet.</h4> }
{ data.length ? renderData() : <h4 className={defaultStyles['no-data-title']}>No data available yet.</h4> }
</React.Fragment>
);
}
Expand All @@ -236,6 +311,7 @@ MMLeaderboard.defaultProps = {
tableHeight: '100%',
tableWidth: '100%',
headerIndexCol: '',
theme: null,
};

MMLeaderboard.propTypes = {
Expand All @@ -257,4 +333,6 @@ MMLeaderboard.propTypes = {
PT.func,
]),
headerIndexCol: PT.string,
theme: PT.string,
challengeId: PT.string.isRequired,
};
Loading