cofacts / rumors-api

GraphQL API server for clients like rumors-site and rumors-line-bot
https://api.cofacts.tw
MIT License
109 stars 26 forks source link

Record feedback target author and allow filtering by feedback authors #285

Closed MrOrz closed 2 years ago

MrOrz commented 2 years ago

Fixes #280

coveralls commented 2 years ago

Coverage Status

Coverage increased (+0.07%) to 87.422% when pulling 8b5981141f81fae7ddace91c6efed553efe3abc5 on record-feedback-target-author into d806fc6f179aa68caf13f0b9cc54ebfc5f52cb37 on master.

MrOrz commented 2 years ago

Migration script (not checked in repo)

// src/scripts/migrations/fillFeedbackAuthors.js
/**
 * How to use this script to migrate elasticsearch schema
 *  1. First of all, ensure latest API has been deployed; new feedbacks have `replyUserId` and `articleReplyUserId` filled.
 *  2. On target machine, update schema and reindex the following indexes using `npm run reload -- <index name>`:
 *     analytics (optional), articlereplyfeedbacks
 *  1. On local machine, put script to under rumors-api project directory's src/scripts
 *  2. Start ssh port forwarding `ssh -L 62222:staging.server.path.to:62222 root@staging.server.path.to`
 *  3. Add `ELASTICSEARCH_URL=http://localhost:62222` to .env
 *  4. Run `npx babel-node src/scripts/migrations/fillFeedbackAuthors.js`
 */
import 'dotenv/config';
import { SingleBar } from 'cli-progress';

import getAllDocs from 'util/getAllDocs';
import client from 'util/client';
import DataLoaders from 'graphql/dataLoaders';

// query to list out article reply feedbacks without replyUserId
const QUERY_TO_PROCESS = {
  bool: {
    must_not: {
      exists: { field: 'replyUserId' },
    },
  },
};

async function processBatch(feedbackMap) {
  let operations = [];
  for (const [_id, doc] of feedbackMap) {
    operations.push(
      { update: { _index: 'articlereplyfeedbacks', _type: 'doc', _id } },
      { doc }
    );
  }
  await client.bulk({ body: operations });
}

async function main() {
  const loader = new DataLoaders();
  const bar = new SingleBar({ stopOnComplete: true });

  const {
    body: { count },
  } = await client.count({
    index: 'articlereplyfeedbacks',
    type: 'doc',
    body: {
      query: QUERY_TO_PROCESS,
    },
  });

  bar.start(count, 0);

  // Feedback ID to doc
  let feedbackMap = new Map();
  let promises = [];

  for await (const { _id: id, _source: feedback } of getAllDocs(
    'articlereplyfeedbacks',
    QUERY_TO_PROCESS,
    { size: 100 }
  )) {
    bar.increment();
    promises.push(
      loader.docLoader
        .loadMany([
          {
            index: 'replies',
            id: feedback.replyId,
          },
          {
            index: 'articles',
            id: feedback.articleId,
          },
        ])
        .then(([{ userId: replyUserId }, article]) => {
          const articleReply = article.articleReplies.find(
            ar => ar.replyId === feedback.replyId
          );
          if (!articleReply) return;

          const { userId: articleReplyUserId } = articleReply;
          feedbackMap.set(id, { replyUserId, articleReplyUserId });
        })
    );

    if (promises.length === 100) {
      await Promise.all(promises);
      promises = [];

      await processBatch(feedbackMap);
      feedbackMap = new Map();
    }
  }

  await Promise.all(promises);
  await processBatch(feedbackMap);

  bar.stop();
}

if (require.main === module) {
  main();
}

Test target:

Run result:


Staging run result

MrOrz commented 2 years ago

Migration report on production