Skip to content
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

Add political bias labels no algolia update #24

Open
wants to merge 28 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ac5bef3
Add stub of migration to add political bias labels
lmcnulty Mar 2, 2023
b759b1f
Add db integration to migration
lmcnulty Mar 2, 2023
3fcc723
Make bias icons display on cite page
lmcnulty Mar 3, 2023
74efd8f
index on add-political-bias-labels: 3fcc7236 Make bias icons display …
lmcnulty Mar 3, 2023
1859f04
WIP on add-political-bias-labels: 3fcc7236 Make bias icons display on…
lmcnulty Mar 3, 2023
f4ab8b0
Add bias labels to Algolia, add icons to Discover
lmcnulty Mar 6, 2023
e680080
index on (no branch): f4ab8b08 Add bias labels to Algolia, add icons …
lmcnulty Mar 6, 2023
7577c91
WIP on (no branch): f4ab8b08 Add bias labels to Algolia, add icons to…
lmcnulty Mar 6, 2023
ac1ffb6
Move bias explanation to modal
lmcnulty Mar 7, 2023
c29c058
Add missing key to taxonomyform
lmcnulty Mar 9, 2023
165cf97
Disable modal during SSR
lmcnulty Mar 9, 2023
8df57fe
Check type explicitly
lmcnulty Mar 9, 2023
822c3c4
Add missing negator
lmcnulty Mar 9, 2023
06a1ad7
Merge staging
lmcnulty Mar 14, 2023
d6b1791
Merge branch 'staging' into add-political-bias-labels
lmcnulty Mar 17, 2023
78bbef0
Render modal conditionally, deduplicate labels, remove green badge
lmcnulty Mar 17, 2023
e0a315d
Merge staging
lmcnulty Mar 21, 2023
4f20a66
Merge branch 'staging' into add-political-bias-labels
lmcnulty Mar 22, 2023
d13df96
Add check for reports
lmcnulty Mar 23, 2023
bffa62e
Add subsetting for Algolia
lmcnulty Mar 27, 2023
9d94b5f
Resolve conflicts
lmcnulty Apr 6, 2023
a06cde0
Fix merge conflicts
lmcnulty Apr 26, 2023
afb48ab
Fix alignment
lmcnulty Apr 26, 2023
519d927
Merge staging
lmcnulty May 12, 2023
0d11c77
Remove algolia updates
lmcnulty May 12, 2023
80b4fc5
Add missing publications arguments
lmcnulty May 13, 2023
6ea7d26
Merge staging
lmcnulty May 23, 2023
5b2a30c
Merge staging
lmcnulty Jun 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions site/gatsby-site/gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const plugins = [
'classifications',
'reports',
'entities',
'publications',
],
connectionString: config.mongodb.connectionString,
extraParams: {
Expand Down
11 changes: 11 additions & 0 deletions site/gatsby-site/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,17 @@ exports.createSchemaCustomization = ({ actions }) => {
public: Boolean
complete_from: completeFrom
}


type mongodbAiidprodPublicationsHarm_labels {
label: String
labeler: String
}
type mongodbAiidprodPublications implements Node {
domain: String
title: String
harm_labels: [mongodbAiidprodPublicationsHarm_labels]
}
`;

createTypes(typeDefs);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
const config = require('../config');

const axios = require('axios');

const jsdom = require('jsdom');

exports.up = async ({ context: { client } }) => {
const { JSDOM } = jsdom;

const publications = [];

const addBiasLabel = (publicationTitle, publicationDomain, labeler, label) => {
const existingPublication = publications.find((p) => p.domain == publicationDomain);

if (existingPublication) {
existingPublication.bias_labels.push({ label, labeler });
} else {
publications.push({
title: publicationTitle,
domain: publicationDomain,
bias_labels: [{ label, labeler }],
});
}
};

for (const alignment of [
'left',
'leftcenter',
'center',
'right-center',
'right',
'fake-news',
'conspiracy',
]) {
const mbfcResponse = await axios.get('https://mediabiasfactcheck.com/' + alignment);

if (mbfcResponse.status == 200) {
const mbfcPage = new JSDOM(mbfcResponse.data);

for (const tr of [...mbfcPage.window.document.querySelectorAll('#mbfc-table tr')]) {
const deepestChild = getDeepestChild(tr);

let tokens = deepestChild.textContent.split(' ');

let lastToken = tokens.pop();

let domain;

if (lastToken[0] == '(') {
domain = new URL(
'http://' +
lastToken
.slice(1, lastToken.length - 1) // Remove parentheses
.replace(/^(www|m)\./, '')
).hostname;
} else {
tokens.push(lastToken);
}
const title = tokens.join(' ');

if (domain) {
addBiasLabel(
title,
domain,
'mediabiasfactcheck.com',
{
left: 'left',
right: 'right',

// These occur in most sources, and it's best to normalize them
// to a specific spelling / wording across sources
// so we can check if different sources agree.
leftcenter: 'center-left',
'right-center': 'center-right',

// They use "center" in the url but "least biased" in the site text.
// There's a difference between being unbiased
// and being biased towards the center.
// They seem to be trying to capture the former:
//
// > These sources have minimal bias and use very few loaded words
// > (wording that attempts to influence an audience
// > by using appeal to emotion or stereotypes).
// > The reporting is factual and usually sourced.
// > These are the most credible media sources.
//
// Same with "fake-news" and "conspiracy".
center: 'least biased',
'fake-news': 'questionable',
conspiracy: 'conspiracy/pseudoscience',
}[alignment]
);
}
}
}
}
await client.connect();

const publicationsCollection = await client
.db(config.realm.production_db.db_name)
.createCollection('publications');

for (const publication of publications) {
publicationsCollection.insertOne(publication);
}
};

exports.down = async ({ context: { client } }) => {
await client.connect();

await client.db(config.realm.production_db.db_name).dropCollection('publications');
};

var getDeepestChild = (htmlNode) => {
if (htmlNode.children.length == 0) {
return htmlNode;
} else {
return getDeepestChild(htmlNode.children[0]);
}
};
22 changes: 20 additions & 2 deletions site/gatsby-site/page-creators/createCitationPages.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const { switchLocalizedPath } = require('../i18n');
const createCitationPages = async (graphql, createPage, { languages }) => {
const result = await graphql(
`
query IncidentIDs {
query ContextData {
allMongodbAiidprodIncidents {
nodes {
incident_id
Expand All @@ -32,13 +32,26 @@ const createCitationPages = async (graphql, createPage, { languages }) => {
language
image_url
cloudinary_id
source_domain
}
}

allMongodbAiidprodPublications {
nodes {
title
domain
bias_labels {
label
labeler
}
}
}
}
`
);

const { allMongodbAiidprodIncidents, allMongodbAiidprodReports } = result.data;
const { allMongodbAiidprodIncidents, allMongodbAiidprodReports, allMongodbAiidprodPublications } =
result.data;

// Incident reports list
const incidentReportsMap = {};
Expand Down Expand Up @@ -80,6 +93,10 @@ const createCitationPages = async (graphql, createPage, { languages }) => {
reports: incidentReportsMap[incident_id],
}));

const publications = allMongodbAiidprodPublications.nodes.filter((publication) =>
incidentReportsMap[incident_id].some((report) => report.source_domain == publication.domain)
);

pageContexts.push({
incident,
incident_id,
Expand All @@ -89,6 +106,7 @@ const createCitationPages = async (graphql, createPage, { languages }) => {
nlp_similar_incidents,
editor_similar_incidents,
editor_dissimilar_incidents,
publications,
});
}

Expand Down
85 changes: 85 additions & 0 deletions site/gatsby-site/src/components/BiasLabels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useState } from 'react';
import { Modal } from 'flowbite-react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
faArrowCircleLeft,
faArrowCircleRight,
faChevronCircleLeft,
faChevronCircleRight,
faCheckCircle,
faExclamationCircle,
} from '@fortawesome/free-solid-svg-icons';

export function BiasIcon({ bias_labels, publicationName, className, style }) {
bias_labels ||= [];

// The modal causes server-side-rendering problems,
// so we need to disable rendering it
// until an interaction has occurred on the client-side.
const [modalRendered, setModalRendered] = useState(false);

const [modalVisible, setModalVisible] = useState(false);

const hasLabelName = (labelName) => bias_labels.some((biasLabel) => biasLabel.label == labelName);

const possibleLabels = [
'least biased',
'questionable',
'left',
'right',
'center-left',
'center-right',
];

return (
<>
<button
className={`${className || ''} bg-none border-0 p-0 m-0`}
style={style}
onClick={() => {
setModalRendered(true);
setModalVisible(true);
}}
onMouseEnter={() => setModalRendered(true)}
>
{possibleLabels.map(
(labelName) =>
hasLabelName(labelName) && (
<FontAwesomeIcon
key={labelName}
title={labelName}
icon={
{
'least biased': faCheckCircle,
questionable: faExclamationCircle,
left: faArrowCircleLeft,
right: faArrowCircleRight,
'center-left': faChevronCircleLeft,
'center-right': faChevronCircleRight,
}[labelName]
}
className={`mr-1 -mb-px ${
{ questionable: 'text-red-500' }[labelName] || 'text-inherit'
}`}
/>
)
)}
</button>
{modalRendered && (
<Modal show={modalVisible} onClose={() => setModalVisible(false)}>
<Modal.Header>{publicationName}</Modal.Header>
<Modal.Body>
<p className="mt-0">The bias of this source was assessed as follows:</p>
<ul className="list-disc pl-4">
{bias_labels.map((biasLabel) => (
<li key={biasLabel.label}>
“{biasLabel.label}” by {biasLabel.labeler}
</li>
))}
</ul>
</Modal.Body>
</Modal>
)}
</>
);
}
19 changes: 15 additions & 4 deletions site/gatsby-site/src/components/reports/ReportCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import { RESPONSE_TAG } from 'utils/entities';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
import { hasVariantData } from 'utils/variants';
import { BiasIcon } from 'components/BiasLabels';
import { format, fromUnixTime } from 'date-fns';

const ReportCard = ({
item,
className = '',
alwaysExpanded = false,
actions = null,
alwaysExpanded = false,
className = '',
item,
publications,
reportTitle = null,
}) => {
const { t } = useTranslation();
Expand All @@ -42,6 +44,8 @@ const ReportCard = ({
}
};

const publication = (publications || []).find((p) => p.domain == item.source_domain);

const toggleReadMoreKeyDown = (e) => {
if (e.key === 'Enter') {
toggleReadMore();
Expand Down Expand Up @@ -157,7 +161,13 @@ const ReportCard = ({
</button>
)}
</div>
<div className="flex justify-between flex-wrap">
<div className="flex gap-1">
<BiasIcon
bias_labels={Array.from(
new Set(publication?.bias_labels?.map((biasLabel) => JSON.stringify(biasLabel)))
).map((json) => JSON.parse(json))}
publicationName={publication?.title}
/>
<WebArchiveLink url={item.url} className="text-dark-gray">
{item.source_domain} &middot;{' '}
{item.date_published
Expand All @@ -166,6 +176,7 @@ const ReportCard = ({
? format(fromUnixTime(item.epoch_date_published), 'yyyy')
: 'Needs publish date'}
</WebArchiveLink>
<div className="mx-auto" />
{actions && <>{actions}</>}
</div>
<div className="mt-1 flex w-fit">
Expand Down
4 changes: 3 additions & 1 deletion site/gatsby-site/src/templates/cite.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function CitePage(props) {
nlp_similar_incidents,
editor_similar_incidents,
editor_dissimilar_incidents,
publications,
},
data: {
allMongodbAiidprodTaxa,
Expand Down Expand Up @@ -94,7 +95,6 @@ function CitePage(props) {
<AiidHelmet {...{ metaTitle, metaDescription, path: props.location.pathname, metaImage }}>
<meta property="og:type" content="website" />
</AiidHelmet>

{isLiveData ? (
<CiteDynamicTemplate
allMongodbAiidprodTaxa={allMongodbAiidprodTaxa}
Expand All @@ -106,6 +106,7 @@ function CitePage(props) {
editor_dissimilar_incidents={editor_dissimilar_incidents}
locationPathName={props.location.pathname}
setIsLiveData={setIsLiveData}
publications={publications}
/>
) : (
<CiteTemplate
Expand All @@ -124,6 +125,7 @@ function CitePage(props) {
editor_similar_incidents={editor_similar_incidents}
editor_dissimilar_incidents={editor_dissimilar_incidents}
setIsLiveData={setIsLiveData}
publications={publications}
/>
)}
</div>
Expand Down
2 changes: 2 additions & 0 deletions site/gatsby-site/src/templates/citeDynamicTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function CiteDynamicTemplate({
editor_dissimilar_incidents,
locationPathName,
setIsLiveData,
publications,
}) {
const { locale } = useLocalization();

Expand Down Expand Up @@ -154,6 +155,7 @@ function CiteDynamicTemplate({
editor_dissimilar_incidents={editor_dissimilar_incidents}
liveVersion={true}
setIsLiveData={setIsLiveData}
publications={publications}
/>
)
)}
Expand Down
Loading