import { useState, useEffect } from 'react' import { siteConfig } from 'site/site.config.mjs' import { nsMerge } from 'shared/utils.mjs' import { UserIcon, FlagIcon, ChatIcon, BoolYesIcon, WarningIcon, DownIcon, } from 'shared/components/icons.mjs' import { TimeAgo, ns as timeAgoNs } from 'shared/components/timeago/index.mjs' import { Mdx } from 'shared/components/mdx/dynamic.mjs' import { WebLink } from 'shared/components/link.mjs' import { useTranslation } from 'next-i18next' import { Spinner } from 'shared/components/spinner.mjs' export const ns = nsMerge('support', timeAgoNs) /* * GitHub GraphQL queries must be properly quoted and can't handle newlines */ const query = { open: 'query { ' + 'repository(owner: "freesewing", name: "freesewing") { ' + ' issues(states: OPEN, labels: ["statusReported", "statusConfirmed"], first: 20) { ' + ' nodes { ' + ' title body createdAt url number updatedAt ' + ' author { login url } ' + ' labels (first: 5) { edges { node { name } } } ' + ' comments(last: 3) { edges { node { body createdAt url author { login url } } } } ' + ' timelineItems(last: 15, itemTypes:[ ISSUE_COMMENT, CLOSED_EVENT,ASSIGNED_EVENT,REOPENED_EVENT, REFERENCED_EVENT]) { edges { node { ' + ' __typename ' + ' ... on ClosedEvent { createdAt actor { url login } } ' + ' ... on ReopenedEvent { createdAt actor { url login } } ' + ' ... on ReferencedEvent { createdAt actor { url login } commit { url oid message } } ' + ' ... on IssueComment { createdAt body url author { url login } } ' + ' ... on AssignedEvent { createdAt actor { url login } assignee { ... on User { login url } } } ' + ' } } } ' + ' } } } } ', closed: 'query { ' + 'repository(owner: "freesewing", name: "freesewing") { ' + ' issues(states: CLOSED, labels: ["statusResolved"], first: 20) { ' + ' nodes { ' + ' title body url number createdAt closedAt ' + ' author { login url } ' + ' comments(last: 3) { edges { node { body createdAt url author { login url } } } } ' + ' timelineItems(last: 15, itemTypes:[ ISSUE_COMMENT, CLOSED_EVENT,ASSIGNED_EVENT,REOPENED_EVENT, REFERENCED_EVENT]) { edges { node { ' + ' __typename ' + ' ... on ClosedEvent { createdAt actor { url login } } ' + ' ... on ReopenedEvent { createdAt actor { url login } } ' + ' ... on ReferencedEvent { createdAt actor { url login } commit { url oid message } } ' + ' ... on IssueComment { createdAt body url author { url login } } ' + ' ... on AssignedEvent { createdAt actor { url login } assignee { ... on User { login url } } } ' + ' } } } ' + ' } } } } ', } /* * Helper method * Runs a GraphQL query and returns the result as JSON */ const runQuery = async (query) => { let result try { result = await fetch('https://api.github.com/graphql', { method: 'POST', headers: { Authorization: `Bearer ${siteConfig.issueToken}`, }, body: JSON.stringify({ query }), }) } catch (err) { console.log(err) return false } const data = await result.json() return data } /* * Helper method to filter out GraphQL nodes based on a label set on them */ const filterOnLabel = (nodes, label) => nodes.filter((node) => node.labels.edges.filter((edge) => edge.node.name === label).length > 0 ? true : false ) /* * Method that load status issues from GitHub and * sets the result with the setter method passed to it. * * If issues are found, this will create and object * with reported, confirmed, and resolved as keys and * the list of issues as value of those keys. */ const loadStatusIssues = async (setIssues) => { const open = await runQuery(query.open) const closed = await runQuery(query.closed) const now = Date.now() setIssues({ reported: filterOnLabel(open.data.repository.issues.nodes, 'statusReported'), confirmed: filterOnLabel(open.data.repository.issues.nodes, 'statusConfirmed'), resolved: closed.data.repository.issues.nodes.filter((node) => { const closed = new Date(node.closedAt).valueOf() // Only show what was closed in the last 36 hours return now - closed < 36 * 60 * 60 * 1000 }), }) } const AssignedEvent = ({ evt, t }) => (
{t('support:issueAssigned')} {t('support:to')} ({t('support:by')} )
) const ClosedEvent = ({ evt, t }) => (
{t('support:issueClosed')} {t('support:by')}
) const ReopenedEvent = ({ evt, t }) => (
{t('support:issueReopened')} {t('support:by')}
) const IssueComment = ({ evt, t }) => (
{t('support:commentAdded')} } /> {t('support:by')}
) const ReferencedEvent = ({ evt, t }) => (
{t('support:issueReferenced')} } /> {t('support:by')} {t('support:in')}
) const events = { IssueComment, AssignedEvent, ReferencedEvent, ReopenedEvent, ClosedEvent, } const Null = () => null const Event = (props) => { if (!props.evt.node) return null const Component = events[props.evt.node.__typename] || Null return } const Issue = ({ issue, t }) => { const [detail, setDetail] = useState(false) const btnClasses = 'w-full my-1 rounded hover:bg-opacity-10 hover:bg-secondary text-left text-base-content p-1 px-2 flex flex-row items-center justify-between' if (!detail) return ( ) return (
{t('support:reported')}{' '} } /> {t('support:by')}
{issue.timelineItems.edges.length > 0 ? ( <>
{t('support:updates')}
{issue.timelineItems.edges.length}
{issue.timelineItems.edges.map((evt, i) => ( ))} ) : null}
) } const Issues = ({ issues, t }) => (
{issues.map((issue) => ( ))}
) export const Status = () => { const { t } = useTranslation(ns) /* * null: We are (still) loading issues * Object: Object with 'reported', 'confirmed' and 'resolved' keys each holding an array of issues */ const [issues, setIssues] = useState(null) useEffect(() => { if (issues === null) loadStatusIssues(setIssues) }, [issues]) return ( <> {issues === null ? ( ) : [...issues.reported, ...issues.confirmed].length < 1 ? ( <> {t('support:status')}
{t('support:allOk')}
) : ( <>

{t('support:status')}

{issues.reported.length > 0 ? ( <>
{t('support:reportedIssues')}
) : (
{t('support:noReportedIssues')}
)} {issues.confirmed.length > 0 ? ( <>
{t('support:confirmedIssues')}
) : (
{t('support:noConfirmedIssues')}
)} )} {issues && issues.resolved.length > 0 ? ( <>
{t('support:recentlyResolvedIssues')}
) : null} ) }