}) => {
const { t } = useTranslation("profile")
- const { user } = useAuth()
const functions = getFunctions()
const followBillFunction = httpsCallable(functions, "followOrg")
const unfollowBillFunction = httpsCallable(functions, "unfollowOrg")
@@ -25,7 +25,7 @@ export const FollowButton = ({
const topicName = `org-${profileId}`
const subscriptionRef = collection(
firestore,
- `/users/${uid}/activeTopicSubscriptions/`
+ `/users/${user?.uid}/activeTopicSubscriptions/`
)
const [queryResult, setQueryResult] = useState("")
@@ -42,8 +42,8 @@ export const FollowButton = ({
}, [subscriptionRef, profileId, setQueryResult]) // dependencies of orgQuery
useEffect(() => {
- uid ? orgQuery() : null
- }, [uid, orgQuery]) // dependencies of useEffect
+ user?.uid ? orgQuery() : null
+ }, [user?.uid, orgQuery]) // dependencies of useEffect
const handleFollowClick = async () => {
// ensure user is not null
@@ -52,9 +52,6 @@ export const FollowButton = ({
}
try {
- if (!uid) {
- throw new Error("User not found")
- }
const orgLookup = {
profileId: profileId,
type: "org"
@@ -76,9 +73,6 @@ export const FollowButton = ({
}
try {
- if (!uid) {
- throw new Error("User not found")
- }
const orgLookup = {
profileId: profileId,
type: "org"
diff --git a/components/ProfilePage/ProfileButtons.tsx b/components/ProfilePage/ProfileButtons.tsx
index cd16f7d48..f95bc46c6 100644
--- a/components/ProfilePage/ProfileButtons.tsx
+++ b/components/ProfilePage/ProfileButtons.tsx
@@ -3,12 +3,11 @@ import { useTranslation } from "next-i18next"
import { Button } from "../bootstrap"
import styled from "styled-components"
import { FillButton, GearButton, ToggleButton } from "components/buttons"
-import { Col } from "react-bootstrap"
-import { Story } from "stories/atoms/BaseButton.stories"
import { Internal } from "components/links"
import { useProfile, ProfileHook } from "components/db"
import { FollowButton } from "./FollowButton"
import { useFlags } from "components/featureFlags"
+import { useAuth } from "../auth"
export const StyledButton = styled(Button).attrs(props => ({
className: `col-12 d-flex align-items-center justify-content-center py-3 text-nowrap`,
@@ -90,9 +89,22 @@ export function ProfileButtonsUser({
)
}
-export function ProfileButtonsOrg({ isUser }: { isUser: boolean }) {
+export function ProfileButtonsOrg({
+ profileId,
+ isUser
+}: {
+ profileId: string
+ isUser: boolean
+}) {
const { followOrg } = useFlags()
+ const { user } = useAuth()
return (
- <>{isUser ? : followOrg ? : null}>
+ <>
+ {isUser ? (
+
+ ) : followOrg && user ? (
+
+ ) : null}
+ >
)
}
diff --git a/components/ProfilePage/ProfileHeader.tsx b/components/ProfilePage/ProfileHeader.tsx
index 800ed72e3..1526d21dd 100644
--- a/components/ProfilePage/ProfileHeader.tsx
+++ b/components/ProfilePage/ProfileHeader.tsx
@@ -34,7 +34,9 @@ export const ProfileHeader = ({
{profile.fullName}
- {isOrg ? : null}
+ {isOrg ? (
+
+ ) : null}
diff --git a/components/about/SupportMapleCardContent/SupportMapleCardContent.tsx b/components/about/SupportMapleCardContent/SupportMapleCardContent.tsx
index b4a858b7e..2139cf9aa 100644
--- a/components/about/SupportMapleCardContent/SupportMapleCardContent.tsx
+++ b/components/about/SupportMapleCardContent/SupportMapleCardContent.tsx
@@ -7,13 +7,12 @@ const DonateCardContent = () => {
{`${t("donate.bodytextOne")} `}
{t("donate.donorsLink")}
- {` ${t("donate.bodytextTwo")}`}
>
)
@@ -33,7 +32,9 @@ const VolunteerCardContent = () => {
{t("volunteer.githubLink")}
{`, ${t("volunteer.bodytextTwo")} `}
-
info@mapletestimony.org.
+
+ {"admin@mapletestimony.org."}
+
>
)
diff --git a/components/search/testimony/TestimonyHit.tsx b/components/search/testimony/TestimonyHit.tsx
index 1793ae006..d4ce1f06e 100644
--- a/components/search/testimony/TestimonyHit.tsx
+++ b/components/search/testimony/TestimonyHit.tsx
@@ -8,6 +8,7 @@ import { useBill } from "components/db/bills"
import { FollowOrgButton } from "components/shared/FollowButton"
import { Image } from "react-bootstrap"
import { useFlags } from "components/featureFlags"
+import { useAuth } from "components/auth"
export const TestimonyHit = ({ hit }: { hit: Hit
}) => {
const url = maple.testimony({ publishedId: hit.id })
@@ -37,6 +38,7 @@ const TestimonyResult = ({ hit }: { hit: Hit }) => {
) : (
hit.fullName
)
+ const { user } = useAuth()
const { followOrg } = useFlags()
return (
@@ -65,7 +67,9 @@ const TestimonyResult = ({ hit }: { hit: Hit }) => {
Written by {writtenBy}
- {followOrg && isOrg && }
+ {followOrg && isOrg && user && (
+
+ )}
diff --git a/functions/src/index.ts b/functions/src/index.ts
index 3ed4db170..a351822bc 100644
--- a/functions/src/index.ts
+++ b/functions/src/index.ts
@@ -32,7 +32,8 @@ export {
} from "./testimony"
export {
publishNotifications,
- populateNotificationEvents,
+ populateBillNotificationEvents,
+ populateOrgNotificationEvents,
cleanupNotifications,
deliverNotifications,
httpsPublishNotifications,
diff --git a/functions/src/notifications/index.ts b/functions/src/notifications/index.ts
index dda66127a..bd66dec64 100644
--- a/functions/src/notifications/index.ts
+++ b/functions/src/notifications/index.ts
@@ -1,6 +1,7 @@
// Import the functions
import { publishNotifications } from "./publishNotifications"
-import { populateNotificationEvents } from "./populateNotificationEvents"
+import { populateBillNotificationEvents } from "./populateBillNotificationEvents"
+import { populateOrgNotificationEvents } from "./populateOrgNotificationEvents"
import { cleanupNotifications } from "./cleanupNotifications"
import { deliverNotifications } from "./deliverNotifications"
import { httpsPublishNotifications } from "./httpsPublishNotifications"
@@ -12,7 +13,8 @@ import { updateNextDigestAt } from "./updateNextDigestAt"
// Export the functions
export {
publishNotifications,
- populateNotificationEvents,
+ populateBillNotificationEvents,
+ populateOrgNotificationEvents,
cleanupNotifications,
deliverNotifications,
httpsPublishNotifications,
diff --git a/functions/src/notifications/populateNotificationEvents.ts b/functions/src/notifications/populateBillNotificationEvents.ts
similarity index 76%
rename from functions/src/notifications/populateNotificationEvents.ts
rename to functions/src/notifications/populateBillNotificationEvents.ts
index 6602ae3b4..da6bff519 100644
--- a/functions/src/notifications/populateNotificationEvents.ts
+++ b/functions/src/notifications/populateBillNotificationEvents.ts
@@ -7,22 +7,13 @@
import * as functions from "firebase-functions"
import * as admin from "firebase-admin"
import { Timestamp } from "../firebase"
-import { BillHistory } from "../bills/types"
+import { BillNotification } from "./types"
// Get a reference to the Firestore database
const db = admin.firestore()
-type Notification = {
- type: string
- court: string
- id: string
- name: string
- history: BillHistory
- historyUpdateTime: Timestamp
-}
-
-// Define the populateNotificationEvents function
-export const populateNotificationEvents = functions.firestore
+// Define the populateBillNotificationEvents function
+export const populateBillNotificationEvents = functions.firestore
.document("/generalCourts/{court}/bills/{billId}")
.onWrite(async (snapshot, context) => {
if (!snapshot.after.exists) {
@@ -41,13 +32,16 @@ export const populateNotificationEvents = functions.firestore
if (documentCreated) {
console.log("New document created")
- const newNotificationEvent: Notification = {
+ const newNotificationEvent: BillNotification = {
type: "bill",
- court: court,
- id: newData?.id,
- name: newData?.id,
- history: newData?.history,
- historyUpdateTime: Timestamp.now()
+
+ billCourt: court,
+ billId: newData?.id,
+ billName: newData?.content.Title,
+
+ billHistory: newData?.history,
+
+ updateTime: Timestamp.now()
}
await db.collection("/notificationEvents").add(newNotificationEvent)
@@ -63,7 +57,9 @@ export const populateNotificationEvents = functions.firestore
const notificationEventSnapshot = await db
.collection("/notificationEvents")
- .where("name", "==", newData?.id)
+ .where("type", "==", "bill")
+ .where("billCourt", "==", court)
+ .where("billId", "==", newData?.id)
.get()
console.log(
@@ -81,8 +77,8 @@ export const populateNotificationEvents = functions.firestore
.collection("/notificationEvents")
.doc(notificationEventId)
.update({
- history: newData?.history,
- historyUpdateTime: Timestamp.now()
+ billHistory: newData?.history,
+ updateTime: Timestamp.now()
})
}
}
diff --git a/functions/src/notifications/populateOrgNotificationEvents.ts b/functions/src/notifications/populateOrgNotificationEvents.ts
new file mode 100644
index 000000000..ee3e5465b
--- /dev/null
+++ b/functions/src/notifications/populateOrgNotificationEvents.ts
@@ -0,0 +1,90 @@
+// Sets up a document trigger for /events and queries the activeTopicSubscriptions collection group in Firestore
+// for all subscriptions for the given topic event, then creates a notification document in the user's notification feed.
+// This function runs every time a new topic event is created in the /events collection.
+// Creates a notification document in the user's notification feed for each active subscription.
+
+// Import necessary Firebase modules
+import * as functions from "firebase-functions"
+import * as admin from "firebase-admin"
+import { Timestamp } from "../firebase"
+import { OrgNotification } from "./types"
+
+// Get a reference to the Firestore database
+const db = admin.firestore()
+
+// Define the populateOrgNotificationEvents function
+export const populateOrgNotificationEvents = functions.firestore
+ .document("/users/{userId}/publishedTestimony/{testimonyId}")
+ .onWrite(async (snapshot, context) => {
+ if (!snapshot.after.exists) {
+ console.error("New snapshot does not exist")
+ return
+ }
+
+ const documentCreated = !snapshot.before.exists
+
+ const oldData = snapshot.before.data()
+ const newData = snapshot.after.data()
+
+ // New testimony added
+ if (documentCreated) {
+ console.log("New document created")
+
+ const newNotificationEvent: OrgNotification = {
+ type: "org",
+
+ billCourt: newData?.court.toString(),
+ billId: newData?.billId,
+ billName: newData?.billTitle,
+
+ orgId: newData?.authorUid,
+ testimonyUser: newData?.fullName,
+ testimonyPosition: newData?.position,
+ testimonyContent: newData?.content,
+ testimonyVersion: newData?.version,
+
+ updateTime: Timestamp.now()
+ }
+
+ await db.collection("/notificationEvents").add(newNotificationEvent)
+
+ return
+ }
+
+ const oldVersion = oldData?.version
+ const newVersion = newData?.version
+
+ const testimonyChanged = oldVersion !== newVersion
+ console.log(`oldVersion: ${oldVersion}, newVersion: ${newVersion}`)
+
+ const notificationEventSnapshot = await db
+ .collection("/notificationEvents")
+ .where("type", "==", "org")
+ .where("billCourt", "==", newData?.court.toString())
+ .where("billId", "==", newData?.billId)
+ .where("authorUid", "==", newData?.authorUid)
+ .get()
+
+ console.log(
+ `${notificationEventSnapshot.docs} ${notificationEventSnapshot.docs.length}`
+ )
+
+ if (!notificationEventSnapshot.empty) {
+ const notificationEventId = notificationEventSnapshot.docs[0].id
+
+ if (testimonyChanged) {
+ console.log("Testimony changed")
+
+ // Update the existing notification event
+ await db
+ .collection("/notificationEvents")
+ .doc(notificationEventId)
+ .update({
+ testimonyPosition: newData?.position,
+ testimonyContent: newData?.content,
+ testimonyVersion: newData?.version,
+ updateTime: Timestamp.now()
+ })
+ }
+ }
+ })
diff --git a/functions/src/notifications/publishNotifications.ts b/functions/src/notifications/publishNotifications.ts
index 43663255a..3afc12812 100644
--- a/functions/src/notifications/publishNotifications.ts
+++ b/functions/src/notifications/publishNotifications.ts
@@ -7,49 +7,57 @@
import * as functions from "firebase-functions"
import * as admin from "firebase-admin"
import { Timestamp } from "../firebase"
+import { BillNotification, OrgNotification } from "./types"
// Get a reference to the Firestore database
const db = admin.firestore()
const createNotificationFields = (
- entity: {
- court: any
- id: string
- name: string
- history: string
- lastUpdatedTime: any
- }, // history is an array, it needs to be concatenated
- type: string
+ entity: BillNotification | OrgNotification
) => {
- let topicName = ""
- let header = ""
- let court = null
- switch (type) {
+ let topicName: string
+ let header: string
+ let court: string | null = null
+ let bodyText: string
+ let subheader: string
+
+ switch (entity.type) {
case "bill":
- topicName = `bill-${entity.court}-${entity.id}` // looks for fields in event document
- header = entity.name
- court = entity.court
+ topicName = `bill-${entity.billCourt}-${entity.billId}`
+ header = entity.billId
+ court = entity.billCourt
+ if (entity.billHistory.length < 1) {
+ console.log(`Invalid history length: ${entity.billHistory.length}`)
+ throw new Error(`Invalid history length: ${entity.billHistory.length}`)
+ }
+ let lastHistoryAction = entity.billHistory[entity.billHistory.length - 1]
+ bodyText = `${lastHistoryAction.Action}`
+ subheader = `${lastHistoryAction.Branch}`
break
+
case "org":
- topicName = `org-${entity.id}`
- header = entity.name
+ topicName = `org-${entity.orgId}`
+ header = entity.billName
+ court = entity.billCourt
+ bodyText = entity.testimonyContent
+ subheader = entity.testimonyUser
break
+
default:
- // handle exception for entities that don't fit schema
- console.log(`Invalid entity type: ${type}`)
- throw new Error(`Invalid entity type: ${type}`)
+ console.log(`Invalid entity: ${entity}`)
+ throw new Error(`Invalid entity: ${entity}`)
}
+
return {
- // set up notification document fields
topicName,
- uid: "", // user id will be populated in the publishNotifications function
+ uid: "",
notification: {
- bodyText: entity.history, // may change depending on event type
+ bodyText: bodyText,
header,
- id: entity.id,
- subheader: "Do we need a sub heading", // may change depending on event type
- timestamp: entity.lastUpdatedTime, // could also be fullDate ; might need to remove this all together
- type,
+ id: entity.billId,
+ subheader: subheader,
+ timestamp: entity.updateTime,
+ type: entity.type,
court,
delivered: false
},
@@ -62,7 +70,10 @@ export const publishNotifications = functions.firestore
.document("/notificationEvents/{topicEventId}")
.onWrite(async (snapshot, context) => {
// Get the newly created topic event data
- const topic = snapshot?.after.data()
+ const topic = snapshot?.after.data() as
+ | BillNotification
+ | OrgNotification
+ | undefined
if (!topic) {
console.error("Invalid topic data:", topic)
@@ -70,58 +81,73 @@ export const publishNotifications = functions.firestore
}
// Extract related Bill or Org data from the topic event
-
const notificationPromises: any[] = []
+
+ // Create a batch
+ const batch = db.batch()
console.log(`topic type: ${topic.type}`)
- if (topic.type == "bill") {
- console.log("bill")
+ const handleNotifications = async (
+ topic: BillNotification | OrgNotification
+ ) => {
+ const notificationFields = createNotificationFields(topic)
- const handleBillNotifications = async (topic: {
- court: any
- id: string
- name: string
- history: string
- lastUpdatedTime: any
- }) => {
- const notificationFields = createNotificationFields(topic, "bill")
+ console.log(JSON.stringify(notificationFields))
- console.log(JSON.stringify(notificationFields))
+ const topicNameSnapshot = await db
+ .collectionGroup("activeTopicSubscriptions")
+ .where("topicName", "==", notificationFields.topicName)
+ .get()
- const subscriptionsSnapshot = await db
+ // Send a testimony notification to all users subscribed to the Bill
+ let billSnapshot
+ if (notificationFields.notification.type !== "bill") {
+ billSnapshot = await db
.collectionGroup("activeTopicSubscriptions")
- .where("topicName", "==", notificationFields.topicName)
+ .where(
+ "topicName",
+ "==",
+ `bill-${notificationFields.notification.court}-${notificationFields.notification.id}`
+ )
.get()
+ }
- subscriptionsSnapshot.docs.forEach(doc => {
- const subscription = doc.data()
- const { uid } = subscription
-
- // Add the uid to the notification document
- notificationFields.uid = uid
+ const uniqueDocs = new Map()
- console.log(
- `Pushing notifications to users/${uid}/userNotificationFeed`
- )
+ // Add documents from topicNameSnapshot to the Map
+ topicNameSnapshot.docs.forEach(doc => {
+ uniqueDocs.set(doc.data().uid, doc.data())
+ })
- // Create a notification document in the user's notification feed
- notificationPromises.push(
- db
- .collection(`users/${uid}/userNotificationFeed`)
- .add(notificationFields)
- )
+ // If billSnapshot exists, add its documents to the Map
+ if (billSnapshot) {
+ billSnapshot.docs.forEach(doc => {
+ uniqueDocs.set(doc.data().uid, doc.data())
})
}
- await handleBillNotifications({
- court: topic.court,
- id: topic.id,
- name: topic.name,
- history: JSON.stringify(topic.history),
- lastUpdatedTime: topic.historyUpdateTime
+ // Convert the Map values to an array to get the unique documents
+ const subscriptionsSnapshot = Array.from(uniqueDocs.values())
+
+ subscriptionsSnapshot.forEach(subscription => {
+ const { uid } = subscription
+
+ // Add the uid to the notification document
+ notificationFields.uid = uid
+
+ console.log(
+ `Pushing notifications to users/${uid}/userNotificationFeed`
+ )
+
+ // Get a reference to the new notification document
+ const docRef = db.collection(`users/${uid}/userNotificationFeed`).doc()
+
+ // Add the write operation to the batch
+ batch.set(docRef, notificationFields)
})
}
- // Wait for all notification documents to be created
- await Promise.all(notificationPromises)
+ await handleNotifications(topic)
+
+ notificationPromises.push(batch.commit())
})
diff --git a/functions/src/notifications/types.ts b/functions/src/notifications/types.ts
new file mode 100644
index 000000000..35144f003
--- /dev/null
+++ b/functions/src/notifications/types.ts
@@ -0,0 +1,24 @@
+import { BillHistory } from "../bills/types"
+import { Timestamp } from "../firebase"
+
+export interface Notification {
+ type: string
+ updateTime: Timestamp
+ billCourt: string
+ billId: string
+ billName: string
+}
+
+export type BillNotification = Notification & {
+ type: "bill"
+ billHistory: BillHistory
+}
+
+export type OrgNotification = Notification & {
+ type: "org"
+ orgId: string
+ testimonyUser: string
+ testimonyPosition: string
+ testimonyContent: string
+ testimonyVersion: number
+}
diff --git a/pages/bills/index.tsx b/pages/bills/index.tsx
index b82b64d99..45507492a 100644
--- a/pages/bills/index.tsx
+++ b/pages/bills/index.tsx
@@ -8,7 +8,7 @@ export default createPage({
Page: () => {
return (
- All Bills
+ Browse Bills
)
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 16aa31a58..c138f9c7f 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -45,6 +45,16 @@
"newcomer" : "New to MAPLE? For extra support, check",
"notInCommittee": "Bill not currently in committee",
"orgs": "Organizations",
+ "partners": {
+ "header": "Our Partners",
+ "desc1": "The project is developed in partnership between the",
+ "desc2": "NuLawLab",
+ "desc3": "and scholars at",
+ "desc4": "Boston College Law School",
+ "desc5": "and",
+ "desc6": "Harvard University's Berkman Klein Center for Internet & Society",
+ "pid": "MAPLE is an initiative of Partners in Democracy - Education, a 501(c)(3) non profit organization. Partners In Democracy is building nationwide partnerships and exploring how we can drive democracy renovation efforts forward in states around the country — starting by focusing our efforts here at home in Massachusetts."
+ },
"pending_upgrade_warning": {
"header": "Organization Request In Progress",
"content": "Your request to be upgraded to an organization is currently in progress. You will be notified by email when your request has been reviewed."
diff --git a/public/locales/en/footer.json b/public/locales/en/footer.json
index 0070a42f3..58b211286 100644
--- a/public/locales/en/footer.json
+++ b/public/locales/en/footer.json
@@ -6,9 +6,9 @@
"resources": "Resources"
},
"links": {
- "learnWriting": "To Writing Effective Testimony",
+ "learnWriting": "To Write Effective Testimony",
"learnProcess": "About the Legislative Process",
- "learnWhy": "Why use MAPLE",
+ "learnWhy": "Why Use MAPLE",
"supportMaple": "Support MAPLE",
"ourMission": "Mission & Goals",
"team": "Team",
diff --git a/public/locales/en/supportmaple.json b/public/locales/en/supportmaple.json
index c37e84764..c8fd6d5fa 100644
--- a/public/locales/en/supportmaple.json
+++ b/public/locales/en/supportmaple.json
@@ -2,9 +2,8 @@
"title": "How to Support MAPLE",
"donate": {
"header": "Donate",
- "bodytextOne": "MAPLE is a fiscally sponsored initiative of the 501(c)(3), the Open Collective Foundation (OCF).",
- "donorsLink": "You can see a full list of our donors and expenditures on our Open Collective webpage.",
- "bodytextTwo": "This is where you can view the details of every donation we've received and every dollar we've spent - we are 100% transparent. We would be grateful for your financial support. "
+ "bodytextOne": "MAPLE is an initiative of Partners in Democracy - Education, a 501(c)(3) non profit organization. You can donate directly to the development of MAPLE through this ",
+ "donorsLink": "link"
},
"volunteer": {
"header": "Volunteer",
diff --git a/public/pid.png b/public/pid.png
new file mode 100644
index 000000000..7187bcab4
Binary files /dev/null and b/public/pid.png differ
diff --git a/scripts/firebase-admin/backfillBillNotificationEvents.ts b/scripts/firebase-admin/backfillBillNotificationEvents.ts
new file mode 100644
index 000000000..db44e498d
--- /dev/null
+++ b/scripts/firebase-admin/backfillBillNotificationEvents.ts
@@ -0,0 +1,53 @@
+import { Timestamp } from "functions/src/firebase"
+import { Script } from "./types"
+import { BillNotification } from "functions/src/notifications/types"
+import { Record, Number } from "runtypes"
+
+const Args = Record({
+ court: Number
+})
+
+export const script: Script = async ({ db, args }) => {
+ console.log(args)
+
+ const a = Args.check(args)
+ const court = a.court.toString()
+
+ const snapshot = await db.collection(`/generalCourts/${court}/bills`).get()
+
+ const batchLimit = 500
+ let batch = db.batch()
+ let operationCount = 0
+
+ for (const doc of snapshot.docs) {
+ const data = doc.data()
+
+ if (data) {
+ const notificationEvent: BillNotification = {
+ type: "bill",
+
+ billCourt: court,
+ billId: data.id,
+ billName: data?.content.Title,
+
+ billHistory: data.history,
+
+ updateTime: Timestamp.now()
+ }
+
+ const ref = db.collection("/notificationEvents").doc()
+ batch.set(ref, notificationEvent)
+ operationCount++
+
+ if (operationCount === batchLimit) {
+ await batch.commit()
+ batch = db.batch()
+ operationCount = 0
+ }
+ }
+ }
+
+ if (operationCount > 0) {
+ await batch.commit()
+ }
+}
diff --git a/scripts/firebase-admin/generateBillHistory.ts b/scripts/firebase-admin/generateBillHistory.ts
new file mode 100644
index 000000000..6bc4ffaea
--- /dev/null
+++ b/scripts/firebase-admin/generateBillHistory.ts
@@ -0,0 +1,40 @@
+import { Timestamp, FieldValue } from "../../functions/src/firebase"
+import { Record, String, Number } from "runtypes"
+import { Script } from "./types"
+import { BillHistoryAction } from "../../functions/src/bills/types"
+
+const Args = Record({
+ court: Number,
+ bills: String
+})
+
+export const script: Script = async ({ db, args }) => {
+ const a = Args.check(args)
+ const bills = a.bills.split(" ")
+ const court = a.court
+ let batch = db.batch()
+ let opsCounter = 0
+
+ for (const id of bills) {
+ const billHistoryAction: BillHistoryAction = {
+ Date: Timestamp.now().valueOf(),
+ Branch: Math.random() < 0.5 ? "Senate" : "House",
+ Action: (Math.random() + 1).toString(36).substring(2)
+ }
+
+ const billRef = db.collection(`/generalCourts/${court}/bills`).doc(`${id}`)
+ batch.update(billRef, { history: FieldValue.arrayUnion(billHistoryAction) })
+ opsCounter++
+
+ if (opsCounter % 500 === 0) {
+ await batch.commit()
+ batch = db.batch()
+ }
+ }
+
+ if (opsCounter % 500 !== 0) {
+ await batch.commit()
+ }
+
+ console.log(`Batch of ${opsCounter} bills updated.`)
+}