From 7aec1bc30862b81de8dcb43b61f8fdd13c935ecd Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 14 Oct 2021 20:44:59 +0200 Subject: [Glitch] Add graphs and retention metrics to admin dashboard (#16829) Port 07341e7aa60fe7c7d4f298136af99276820940e7 to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/components/admin/Counter.js | 115 +++++++++++++++++ .../flavours/glitch/components/admin/Dimension.js | 92 ++++++++++++++ .../flavours/glitch/components/admin/Retention.js | 141 +++++++++++++++++++++ .../flavours/glitch/components/admin/Trends.js | 73 +++++++++++ 4 files changed, 421 insertions(+) create mode 100644 app/javascript/flavours/glitch/components/admin/Counter.js create mode 100644 app/javascript/flavours/glitch/components/admin/Dimension.js create mode 100644 app/javascript/flavours/glitch/components/admin/Retention.js create mode 100644 app/javascript/flavours/glitch/components/admin/Trends.js (limited to 'app/javascript/flavours/glitch/components/admin') diff --git a/app/javascript/flavours/glitch/components/admin/Counter.js b/app/javascript/flavours/glitch/components/admin/Counter.js new file mode 100644 index 000000000..39ef216bd --- /dev/null +++ b/app/javascript/flavours/glitch/components/admin/Counter.js @@ -0,0 +1,115 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import api from 'flavours/glitch/util/api'; +import { FormattedNumber } from 'react-intl'; +import { Sparklines, SparklinesCurve } from 'react-sparklines'; +import classNames from 'classnames'; +import Skeleton from 'flavours/glitch/components/skeleton'; + +const percIncrease = (a, b) => { + let percent; + + if (b !== 0) { + if (a !== 0) { + percent = (b - a) / a; + } else { + percent = 1; + } + } else if (b === 0 && a === 0) { + percent = 0; + } else { + percent = - 1; + } + + return percent; +}; + +export default class Counter extends React.PureComponent { + + static propTypes = { + measure: PropTypes.string.isRequired, + start_at: PropTypes.string.isRequired, + end_at: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + href: PropTypes.string, + }; + + state = { + loading: true, + data: null, + }; + + componentDidMount () { + const { measure, start_at, end_at } = this.props; + + api().post('/api/v1/admin/measures', { keys: [measure], start_at, end_at }).then(res => { + this.setState({ + loading: false, + data: res.data, + }); + }).catch(err => { + console.error(err); + }); + } + + render () { + const { label, href } = this.props; + const { loading, data } = this.state; + + let content; + + if (loading) { + content = ( + + + + + ); + } else { + const measure = data[0]; + const percentChange = percIncrease(measure.previous_total * 1, measure.total * 1); + + content = ( + + + 0, negative: percentChange < 0 })}>{percentChange > 0 && '+'} + + ); + } + + const inner = ( + +
+ {content} +
+ +
+ {label} +
+ +
+ {!loading && ( + x.value * 1)}> + + + )} +
+
+ ); + + if (href) { + return ( + + {inner} + + ); + } else { + return ( +
+ {inner} +
+ ); + } + } + +} diff --git a/app/javascript/flavours/glitch/components/admin/Dimension.js b/app/javascript/flavours/glitch/components/admin/Dimension.js new file mode 100644 index 000000000..b4fbf86c8 --- /dev/null +++ b/app/javascript/flavours/glitch/components/admin/Dimension.js @@ -0,0 +1,92 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import api from 'flavours/glitch/util/api'; +import { FormattedNumber } from 'react-intl'; +import { roundTo10 } from 'flavours/glitch/util/numbers'; +import Skeleton from 'flavours/glitch/components/skeleton'; + +export default class Dimension extends React.PureComponent { + + static propTypes = { + dimension: PropTypes.string.isRequired, + start_at: PropTypes.string.isRequired, + end_at: PropTypes.string.isRequired, + limit: PropTypes.number.isRequired, + label: PropTypes.string.isRequired, + }; + + state = { + loading: true, + data: null, + }; + + componentDidMount () { + const { start_at, end_at, dimension, limit } = this.props; + + api().post('/api/v1/admin/dimensions', { keys: [dimension], start_at, end_at, limit }).then(res => { + this.setState({ + loading: false, + data: res.data, + }); + }).catch(err => { + console.error(err); + }); + } + + render () { + const { label, limit } = this.props; + const { loading, data } = this.state; + + let content; + + if (loading) { + content = ( + + + {Array.from(Array(limit)).map((_, i) => ( + + + + + + ))} + +
+ + + +
+ ); + } else { + const sum = data[0].data.reduce((sum, cur) => sum + (cur.value * 1), 0); + + content = ( + + + {data[0].data.map(item => ( + + + + + + ))} + +
+ + {item.human_key} + + {typeof item.human_value !== 'undefined' ? item.human_value : } +
+ ); + } + + return ( +
+

{label}

+ + {content} +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/components/admin/Retention.js b/app/javascript/flavours/glitch/components/admin/Retention.js new file mode 100644 index 000000000..8295362a4 --- /dev/null +++ b/app/javascript/flavours/glitch/components/admin/Retention.js @@ -0,0 +1,141 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import api from 'flavours/glitch/util/api'; +import { FormattedMessage, FormattedNumber, FormattedDate } from 'react-intl'; +import classNames from 'classnames'; +import { roundTo10 } from 'flavours/glitch/util/numbers'; + +const dateForCohort = cohort => { + switch(cohort.frequency) { + case 'day': + return ; + default: + return ; + } +}; + +export default class Retention extends React.PureComponent { + + static propTypes = { + start_at: PropTypes.string, + end_at: PropTypes.string, + frequency: PropTypes.string, + }; + + state = { + loading: true, + data: null, + }; + + componentDidMount () { + const { start_at, end_at, frequency } = this.props; + + api().post('/api/v1/admin/retention', { start_at, end_at, frequency }).then(res => { + this.setState({ + loading: false, + data: res.data, + }); + }).catch(err => { + console.error(err); + }); + } + + render () { + const { loading, data } = this.state; + + let content; + + if (loading) { + content = ; + } else { + content = ( + + + + + + + + {data[0].data.slice(1).map((retention, i) => ( + + ))} + + + + + + + + {data[0].data.slice(1).map((retention, i) => { + const average = data.reduce((sum, cohort, k) => cohort.data[i + 1] ? sum + (cohort.data[i + 1].percent - sum)/(k + 1) : sum, 0); + + return ( + + ); + })} + + + + + {data.slice(0, -1).map(cohort => ( + + + + + + {cohort.data.slice(1).map(retention => ( + + ))} + + ))} + +
+
+ +
+
+
+ +
+
+
+ {i + 1} +
+
+
+ +
+
+
+ sum + ((cohort.data[0].value * 1) - sum) / (i + 1), 0)} maximumFractionDigits={0} /> +
+
+
+ +
+
+
+ {dateForCohort(cohort)} +
+
+
+ +
+
+
+ +
+
+ ); + } + + return ( +
+

+ + {content} +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/components/admin/Trends.js b/app/javascript/flavours/glitch/components/admin/Trends.js new file mode 100644 index 000000000..d7c4eb72c --- /dev/null +++ b/app/javascript/flavours/glitch/components/admin/Trends.js @@ -0,0 +1,73 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import api from 'flavours/glitch/util/api'; +import { FormattedMessage } from 'react-intl'; +import classNames from 'classnames'; +import Hashtag from 'flavours/glitch/components/hashtag'; + +export default class Trends extends React.PureComponent { + + static propTypes = { + limit: PropTypes.number.isRequired, + }; + + state = { + loading: true, + data: null, + }; + + componentDidMount () { + const { limit } = this.props; + + api().get('/api/v1/admin/trends', { params: { limit } }).then(res => { + this.setState({ + loading: false, + data: res.data, + }); + }).catch(err => { + console.error(err); + }); + } + + render () { + const { limit } = this.props; + const { loading, data } = this.state; + + let content; + + if (loading) { + content = ( +
+ {Array.from(Array(limit)).map((_, i) => ( + + ))} +
+ ); + } else { + content = ( +
+ {data.map(hashtag => ( + day.uses)} + className={classNames(hashtag.requires_review && 'trends__item--requires-review', !hashtag.trendable && !hashtag.requires_review && 'trends__item--disabled')} + /> + ))} +
+ ); + } + + return ( +
+

+ + {content} +
+ ); + } + +} -- cgit From 97151840b02499a0cec1360907a6b86a1df02b3b Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Fri, 26 Nov 2021 15:01:04 +0900 Subject: [Glitch] Port upstream changes about trending links --- .../flavours/glitch/components/admin/Counter.js | 5 +++-- .../flavours/glitch/components/admin/Dimension.js | 5 +++-- .../flavours/glitch/components/admin/Trends.js | 2 +- app/javascript/flavours/glitch/styles/accounts.scss | 16 ++++++++++++++++ app/javascript/flavours/glitch/styles/dashboard.scss | 10 ++++++++++ 5 files changed, 33 insertions(+), 5 deletions(-) (limited to 'app/javascript/flavours/glitch/components/admin') diff --git a/app/javascript/flavours/glitch/components/admin/Counter.js b/app/javascript/flavours/glitch/components/admin/Counter.js index 39ef216bd..2bc9ce482 100644 --- a/app/javascript/flavours/glitch/components/admin/Counter.js +++ b/app/javascript/flavours/glitch/components/admin/Counter.js @@ -32,6 +32,7 @@ export default class Counter extends React.PureComponent { end_at: PropTypes.string.isRequired, label: PropTypes.string.isRequired, href: PropTypes.string, + params: PropTypes.object, }; state = { @@ -40,9 +41,9 @@ export default class Counter extends React.PureComponent { }; componentDidMount () { - const { measure, start_at, end_at } = this.props; + const { measure, start_at, end_at, params } = this.props; - api().post('/api/v1/admin/measures', { keys: [measure], start_at, end_at }).then(res => { + api().post('/api/v1/admin/measures', { keys: [measure], start_at, end_at, [measure]: params }).then(res => { this.setState({ loading: false, data: res.data, diff --git a/app/javascript/flavours/glitch/components/admin/Dimension.js b/app/javascript/flavours/glitch/components/admin/Dimension.js index b4fbf86c8..a924d093c 100644 --- a/app/javascript/flavours/glitch/components/admin/Dimension.js +++ b/app/javascript/flavours/glitch/components/admin/Dimension.js @@ -13,6 +13,7 @@ export default class Dimension extends React.PureComponent { end_at: PropTypes.string.isRequired, limit: PropTypes.number.isRequired, label: PropTypes.string.isRequired, + params: PropTypes.object, }; state = { @@ -21,9 +22,9 @@ export default class Dimension extends React.PureComponent { }; componentDidMount () { - const { start_at, end_at, dimension, limit } = this.props; + const { start_at, end_at, dimension, limit, params } = this.props; - api().post('/api/v1/admin/dimensions', { keys: [dimension], start_at, end_at, limit }).then(res => { + api().post('/api/v1/admin/dimensions', { keys: [dimension], start_at, end_at, limit, [dimension]: params }).then(res => { this.setState({ loading: false, data: res.data, diff --git a/app/javascript/flavours/glitch/components/admin/Trends.js b/app/javascript/flavours/glitch/components/admin/Trends.js index d7c4eb72c..60e367f00 100644 --- a/app/javascript/flavours/glitch/components/admin/Trends.js +++ b/app/javascript/flavours/glitch/components/admin/Trends.js @@ -19,7 +19,7 @@ export default class Trends extends React.PureComponent { componentDidMount () { const { limit } = this.props; - api().get('/api/v1/admin/trends', { params: { limit } }).then(res => { + api().get('/api/v1/admin/trends/tags', { params: { limit } }).then(res => { this.setState({ loading: false, data: res.data, diff --git a/app/javascript/flavours/glitch/styles/accounts.scss b/app/javascript/flavours/glitch/styles/accounts.scss index a5ddde937..fe7dfc20f 100644 --- a/app/javascript/flavours/glitch/styles/accounts.scss +++ b/app/javascript/flavours/glitch/styles/accounts.scss @@ -327,3 +327,19 @@ margin-top: 10px; } } + +.batch-table__row--muted .pending-account__header { + &, + a, + strong { + color: lighten($ui-base-color, 26%); + } +} + +.batch-table__row--attention .pending-account__header { + &, + a, + strong { + color: $gold-star; + } +} diff --git a/app/javascript/flavours/glitch/styles/dashboard.scss b/app/javascript/flavours/glitch/styles/dashboard.scss index 5e900e8c5..0a881bc10 100644 --- a/app/javascript/flavours/glitch/styles/dashboard.scss +++ b/app/javascript/flavours/glitch/styles/dashboard.scss @@ -100,6 +100,16 @@ transition: all 200ms ease-out; } + &.positive { + background: lighten($ui-base-color, 4%); + color: $valid-value-color; + } + + &.negative { + background: lighten($ui-base-color, 4%); + color: $error-value-color; + } + span { flex: 1 1 auto; } -- cgit From e061ea5f71109edbbf7869265f443448cace4f60 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 17 Dec 2021 23:00:41 +0100 Subject: [Glitch] Change title of retention chart Port 0c17fd91091fd2f230224d5fce218688d480502c to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/components/admin/Retention.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch/components/admin') diff --git a/app/javascript/flavours/glitch/components/admin/Retention.js b/app/javascript/flavours/glitch/components/admin/Retention.js index 8295362a4..9127839f6 100644 --- a/app/javascript/flavours/glitch/components/admin/Retention.js +++ b/app/javascript/flavours/glitch/components/admin/Retention.js @@ -42,6 +42,7 @@ export default class Retention extends React.PureComponent { render () { const { loading, data } = this.state; + const { frequency } = this.props; let content; @@ -129,9 +130,18 @@ export default class Retention extends React.PureComponent { ); } + let title = null; + switch(frequency) { + case 'day': + title = ; + break; + default: + title = ; + }; + return (
-

+

{title}

{content}
-- cgit From 69208ef6ff16860a885eb389b3d14afd93531252 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 17 Jan 2022 09:41:33 +0100 Subject: [Glitch] Add notifications for statuses deleted by moderators Port front-end changes from 14f436c457560862fafabd753eb314c8b8a8e674 to glitch-soc Signed-off-by: Claire --- .../components/admin/ReportReasonSelector.js | 159 ++++++++++ app/javascript/flavours/glitch/styles/admin.scss | 328 ++++++++++++++++++++- app/javascript/flavours/glitch/styles/polls.scss | 15 + .../flavours/glitch/util/backend_links.js | 2 +- 4 files changed, 488 insertions(+), 16 deletions(-) create mode 100644 app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js (limited to 'app/javascript/flavours/glitch/components/admin') diff --git a/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js b/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js new file mode 100644 index 000000000..0f2a4fe36 --- /dev/null +++ b/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js @@ -0,0 +1,159 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import api from 'flavours/glitch/util/api'; +import { injectIntl, defineMessages } from 'react-intl'; +import classNames from 'classnames'; + +const messages = defineMessages({ + other: { id: 'report.categories.other', defaultMessage: 'Other' }, + spam: { id: 'report.categories.spam', defaultMessage: 'Spam' }, + violation: { id: 'report.categories.violation', defaultMessage: 'Content violates one or more server rules' }, +}); + +class Category extends React.PureComponent { + + static propTypes = { + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + selected: PropTypes.bool, + disabled: PropTypes.bool, + onSelect: PropTypes.func, + children: PropTypes.node, + }; + + handleClick = () => { + const { id, disabled, onSelect } = this.props; + + if (!disabled) { + onSelect(id); + } + }; + + render () { + const { id, text, disabled, selected, children } = this.props; + + return ( +
+ {selected && } + +
+ + {text} +
+ + {(selected && children) && ( +
+ {children} +
+ )} +
+ ); + } + +} + +class Rule extends React.PureComponent { + + static propTypes = { + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + selected: PropTypes.bool, + disabled: PropTypes.bool, + onToggle: PropTypes.func, + }; + + handleClick = () => { + const { id, disabled, onToggle } = this.props; + + if (!disabled) { + onToggle(id); + } + }; + + render () { + const { id, text, disabled, selected } = this.props; + + return ( +
+ + {selected && } + {text} +
+ ); + } + +} + +export default @injectIntl +class ReportReasonSelector extends React.PureComponent { + + static propTypes = { + id: PropTypes.string.isRequired, + category: PropTypes.string.isRequired, + rule_ids: PropTypes.arrayOf(PropTypes.string), + disabled: PropTypes.bool, + intl: PropTypes.object.isRequired, + }; + + state = { + category: this.props.category, + rule_ids: this.props.rule_ids || [], + rules: [], + }; + + componentDidMount() { + api().get('/api/v1/instance').then(res => { + this.setState({ + rules: res.data.rules, + }); + }).catch(err => { + console.error(err); + }); + } + + _save = () => { + const { id, disabled } = this.props; + const { category, rule_ids } = this.state; + + if (disabled) { + return; + } + + api().put(`/api/v1/admin/reports/${id}`, { + category, + rule_ids, + }).catch(err => { + console.error(err); + }); + }; + + handleSelect = id => { + this.setState({ category: id }, () => this._save()); + }; + + handleToggle = id => { + const { rule_ids } = this.state; + + if (rule_ids.includes(id)) { + this.setState({ rule_ids: rule_ids.filter(x => x !== id ) }, () => this._save()); + } else { + this.setState({ rule_ids: [...rule_ids, id] }, () => this._save()); + } + }; + + render () { + const { disabled, intl } = this.props; + const { rules, category, rule_ids } = this.state; + + return ( +
+ + + + {rules.map(rule => )} + +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss index 8fd556c73..92061585a 100644 --- a/app/javascript/flavours/glitch/styles/admin.scss +++ b/app/javascript/flavours/glitch/styles/admin.scss @@ -595,39 +595,44 @@ body, .log-entry { line-height: 20px; - padding: 15px 0; + padding: 15px; + padding-left: 15px * 2 + 40px; background: $ui-base-color; - border-bottom: 1px solid lighten($ui-base-color, 4%); + border-bottom: 1px solid darken($ui-base-color, 8%); + position: relative; + + &:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } &:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; border-bottom: 0; } + &:hover { + background: lighten($ui-base-color, 4%); + } + &__header { - display: flex; - justify-content: flex-start; - align-items: center; color: $darker-text-color; font-size: 14px; - padding: 0 10px; } &__avatar { - margin-right: 10px; + position: absolute; + left: 15px; + top: 15px; .avatar { - display: block; - margin: 0; - border-radius: 50%; + border-radius: 4px; width: 40px; height: 40px; } } - &__content { - max-width: calc(100% - 90px); - } - &__title { word-wrap: break-word; } @@ -643,6 +648,14 @@ body, text-decoration: none; font-weight: 500; } + + a { + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } } a.name-tag, @@ -671,8 +684,9 @@ a.inline-name-tag, a.name-tag, .name-tag { - display: flex; + display: inline-flex; align-items: center; + vertical-align: top; .avatar { display: block; @@ -1130,3 +1144,287 @@ a.sparkline { } } } + +.report-reason-selector { + border-radius: 4px; + background: $ui-base-color; + margin-bottom: 20px; + + &__category { + cursor: pointer; + border-bottom: 1px solid darken($ui-base-color, 8%); + + &:last-child { + border-bottom: 0; + } + + &__label { + padding: 15px; + } + + &__rules { + margin-left: 30px; + } + } + + &__rule { + cursor: pointer; + padding: 15px; + } +} + +.report-header { + display: grid; + grid-gap: 15px; + grid-template-columns: minmax(0, 1fr) 300px; + + &__details { + &__item { + border-bottom: 1px solid lighten($ui-base-color, 8%); + padding: 15px 0; + + &:last-child { + border-bottom: 0; + } + + &__header { + font-weight: 600; + padding: 4px 0; + } + } + + &--horizontal { + display: grid; + grid-auto-columns: minmax(0, 1fr); + grid-auto-flow: column; + + .report-header__details__item { + border-bottom: 0; + } + } + } +} + +.account-card { + background: $ui-base-color; + border-radius: 4px; + + &__header { + padding: 4px; + border-radius: 4px; + height: 128px; + + img { + display: block; + margin: 0; + width: 100%; + height: 100%; + object-fit: cover; + background: darken($ui-base-color, 8%); + } + } + + &__title { + margin-top: -25px; + display: flex; + align-items: flex-end; + + &__avatar { + padding: 15px; + + img { + display: block; + margin: 0; + width: 56px; + height: 56px; + background: darken($ui-base-color, 8%); + border-radius: 8px; + } + } + + .display-name { + color: $darker-text-color; + padding-bottom: 15px; + font-size: 15px; + + bdi { + display: block; + color: $primary-text-color; + font-weight: 500; + } + } + } + + &__bio { + padding: 0 15px; + overflow: hidden; + text-overflow: ellipsis; + word-wrap: break-word; + max-height: 18px * 2; + position: relative; + + &::after { + display: block; + content: ""; + width: 50px; + height: 18px; + position: absolute; + bottom: 0; + right: 15px; + background: linear-gradient(to left, $ui-base-color, transparent); + pointer-events: none; + } + } + + &__actions { + display: flex; + align-items: center; + padding-top: 10px; + + &__button { + flex: 0 0 auto; + padding: 0 15px; + } + } + + &__counters { + flex: 1 1 auto; + display: grid; + grid-auto-columns: minmax(0, 1fr); + grid-auto-flow: column; + + &__item { + padding: 15px; + text-align: center; + color: $primary-text-color; + font-weight: 600; + font-size: 15px; + + small { + display: block; + color: $darker-text-color; + font-weight: 400; + font-size: 13px; + } + } + } +} + +.report-notes { + margin-bottom: 20px; + + &__item { + background: $ui-base-color; + position: relative; + padding: 15px; + padding-left: 15px * 2 + 40px; + border-bottom: 1px solid darken($ui-base-color, 8%); + + &:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } + + &:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: 0; + } + + &:hover { + background-color: lighten($ui-base-color, 4%); + } + + &__avatar { + position: absolute; + left: 15px; + top: 15px; + border-radius: 4px; + width: 40px; + height: 40px; + } + + &__header { + color: $darker-text-color; + font-size: 15px; + line-height: 20px; + margin-bottom: 4px; + + .username a { + color: $primary-text-color; + font-weight: 500; + text-decoration: none; + margin-right: 5px; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + + time { + margin-left: 5px; + vertical-align: baseline; + } + } + + &__content { + font-size: 15px; + line-height: 20px; + word-wrap: break-word; + font-weight: 400; + color: $primary-text-color; + + p { + margin-bottom: 20px; + white-space: pre-wrap; + unicode-bidi: plaintext; + + &:last-child { + margin-bottom: 0; + } + } + } + + &__actions { + position: absolute; + top: 15px; + right: 15px; + text-align: right; + } + } +} + +.report-actions { + border: 1px solid darken($ui-base-color, 8%); + + &__item { + display: flex; + align-items: center; + line-height: 18px; + border-bottom: 1px solid darken($ui-base-color, 8%); + + &:last-child { + border-bottom: 0; + } + + &__button { + flex: 0 0 auto; + width: 100px; + padding: 15px; + padding-right: 0; + + .button { + display: block; + width: 100%; + } + } + + &__description { + padding: 15px; + font-size: 14px; + color: $dark-text-color; + } + } +} diff --git a/app/javascript/flavours/glitch/styles/polls.scss b/app/javascript/flavours/glitch/styles/polls.scss index 5fc41ed9e..a2cdecf06 100644 --- a/app/javascript/flavours/glitch/styles/polls.scss +++ b/app/javascript/flavours/glitch/styles/polls.scss @@ -150,6 +150,21 @@ &:active { outline: 0 !important; } + + &.disabled { + border-color: $dark-text-color; + + &.active { + background: $dark-text-color; + } + + &:active, + &:focus, + &:hover { + border-color: $dark-text-color; + border-width: 1px; + } + } } &__number { diff --git a/app/javascript/flavours/glitch/util/backend_links.js b/app/javascript/flavours/glitch/util/backend_links.js index 0fb378cc1..2e5111a7f 100644 --- a/app/javascript/flavours/glitch/util/backend_links.js +++ b/app/javascript/flavours/glitch/util/backend_links.js @@ -3,7 +3,7 @@ export const profileLink = '/settings/profile'; export const signOutLink = '/auth/sign_out'; export const termsLink = '/terms'; export const accountAdminLink = (id) => `/admin/accounts/${id}`; -export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses/${status_id}`; +export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses?id=${status_id}`; export const filterEditLink = (id) => `/filters/${id}/edit`; export const relationshipsLink = '/relationships'; export const securityLink = '/auth/edit'; -- cgit From 9483d0c6b2b3eaffca8e02e8be4b9a21c5f9e767 Mon Sep 17 00:00:00 2001 From: Claire Date: Sun, 23 Jan 2022 16:01:25 +0100 Subject: [Glitch] Change `percent` to `rate` in retention metrics API Port a63495230a3a28e022504f36356cd75b17b635ba to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/components/admin/Retention.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'app/javascript/flavours/glitch/components/admin') diff --git a/app/javascript/flavours/glitch/components/admin/Retention.js b/app/javascript/flavours/glitch/components/admin/Retention.js index 9127839f6..6d7e4b279 100644 --- a/app/javascript/flavours/glitch/components/admin/Retention.js +++ b/app/javascript/flavours/glitch/components/admin/Retention.js @@ -88,7 +88,7 @@ export default class Retention extends React.PureComponent { {data[0].data.slice(1).map((retention, i) => { - const average = data.reduce((sum, cohort, k) => cohort.data[i + 1] ? sum + (cohort.data[i + 1].percent - sum)/(k + 1) : sum, 0); + const average = data.reduce((sum, cohort, k) => cohort.data[i + 1] ? sum + (cohort.data[i + 1].rate - sum)/(k + 1) : sum, 0); return ( @@ -118,8 +118,8 @@ export default class Retention extends React.PureComponent { {cohort.data.slice(1).map(retention => ( -
- +
+
))} -- cgit