diff --git a/.github/workflows/PROD-studios-selected.yaml b/.github/workflows/PROD-studios-selected.yaml new file mode 100644 index 000000000..c6b49dd85 --- /dev/null +++ b/.github/workflows/PROD-studios-selected.yaml @@ -0,0 +1,123 @@ +name: PROD - Deploy selected satellite studio +on: + workflow_dispatch: + inputs: + site: + description: 'Name of studio to deploy' + required: true + type: choice + options: + - brazil + - germany + - argentina + - poland + - japan + - storage + - equinorfunds + - southkorea + - celticsea +permissions: + id-token: write +jobs: + update-radix-config: + runs-on: ubuntu-latest + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + steps: + - name: Checkout internal 🛎️ + id: checkout-internal + uses: actions/checkout@v3 + with: + repository: 'equinor/energyvision-internal' + ref: main + token: ${{ secrets.PADMS_PAT }} # Replace with ssh as per https://stackoverflow.com/questions/60222741/github-actions-and-git-clone-issue + - name: Promote preprod tag to prod 🗒️ + if: github.ref == 'refs/heads/main' + shell: bash + id: modify-radix + run: | + # Install pre-requisite + python -m pip install --user ruamel.yaml + python ci/promoteToProduction.py studio ${{ github.event.inputs.site }} + git config --global user.name 'github' + git config --global user.email 'padms@users.noreply.github.com' + git remote set-url origin https://x-access-token:${{ secrets.PADMS_PAT }}@github.com/equinor/energyvision-internal + if [[ `git status --porcelain` ]] + then + git commit -am "🚀 Deploy ${{ github.event.inputs.site }} to production" + git pull --rebase origin main + git push origin HEAD:main + fi + - uses: act10ns/slack@v2 + with: + status: ${{ job.status }} + steps: ${{ toJson(steps) }} + if: failure() + + deploy-satellite-studios: + needs: update-radix-config + runs-on: ubuntu-latest + environment: + name: production + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + DOCKER_BUILDKIT: 1 + steps: + - name: Checkout 🛎️ + uses: actions/checkout@v3 + + - name: Get token 🔑 + id: get-token + uses: ./.github/workflows/get-auth-token/ + + # PRODUCTION CLUSTERS + - name: Deploy to Radix 🚀 + id: deploy + uses: equinor/radix-github-actions@master + with: + args: > + create job + deploy + --token-environment + --context platform2 + -a equinor-web-sites + -e ${{ secrets.ENV }} + -f + + - uses: act10ns/slack@v2 + with: + status: ${{ job.status }} + steps: ${{ toJson(steps) }} + if: failure() + + store-image-tags: + needs: deploy-satellite-studios + runs-on: ubuntu-latest + steps: + - name: Checkout internal 🛎️ + id: checkout-internal + uses: actions/checkout@v3 + with: + repository: 'equinor/energyvision-internal' + ref: main + token: ${{ secrets.PADMS_PAT }} + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: '14' # Or any version you prefer + + - name: Install dependencies + run: | + npm install js-yaml + + - name: Run script for 'studio' + run: node ci/storeImageTags.js studio + + - name: Commit and push changes + run: | + git config --global user.name 'github' + git config --global user.email 'padms@users.noreply.github.com' + git remote set-url origin https://x-access-token:${{ secrets.PADMS_PAT }}@github.com/equinor/energyvision-internal + git add latest-prod-tags.json + git commit -m "Update latest-prod-tags.json with new tags" || echo "No changes to commit" + git push diff --git a/sanityv3/actions/CustomPublishAction.ts b/sanityv3/actions/CustomPublishAction.ts index cb56c43bb..0ece930df 100644 --- a/sanityv3/actions/CustomPublishAction.ts +++ b/sanityv3/actions/CustomPublishAction.ts @@ -7,52 +7,23 @@ import { DocumentActionsContext, SanityClient, } from 'sanity' -import { dataset, apiVersion } from '../sanity.client' +import { apiVersion } from '../sanity.client' import { useToast } from '@sanity/ui' -const projectId = import.meta.env.SANITY_STUDIO_API_PROJECT_ID || 'h61q9gi9' -/** Secret site already exposes the mutation token. So we can reuse it instead. */ -const token = import.meta.env.SANITY_STUDIO_HISTORY_API_TOKEN || import.meta.env.SANITY_STUDIO_MUTATION_TOKEN - const FIRST_PUBLISHED_AT_FIELD_NAME = 'firstPublishedAt' +const LAST_MODIFIED_AT_FIELD_NAME = 'lastModifiedAt' const requiresConfirm = ['news', 'localNews'] const requiresFirstPublished = ['news', 'localNews', 'magazine'] -const shouldAddFirstPublishedAt = async (props: DocumentActionProps) => { - if (!requiresFirstPublished.includes(props.type)) return false - let error = false - // https://github.com/sanity-io/sanity/issues/2179 - const revisions = await fetch( - `https://${projectId}.api.sanity.io/${apiVersion}/data/history/${dataset}/transactions/${props.id}?excludeContent=true`, - { - method: 'GET', - headers: new Headers({ - Authorization: `Bearer ${token}`, - }), - }, - ) - .then((res) => res.text()) - .catch((err: Error) => { - console.error(err) - error = true - }) - - if (error) throw 'Failed retrieving history of document.' - - const hasBeenPublished = !!revisions - - return !hasBeenPublished || !props.published?.[FIRST_PUBLISHED_AT_FIELD_NAME] -} +const updateCustomPublishFields = async (id: string, client: SanityClient, setFirstPublish: boolean) => { + const currentTimeStamp = new Date().toISOString() + const patch = client.patch(id).set({ [LAST_MODIFIED_AT_FIELD_NAME]: currentTimeStamp }) + if (setFirstPublish) patch.set({ [FIRST_PUBLISHED_AT_FIELD_NAME]: currentTimeStamp }) -const addFirstPublishedAtField = async (id: string, client: SanityClient) => { - await client - .patch(id) - .set({ [FIRST_PUBLISHED_AT_FIELD_NAME]: new Date().toISOString() }) - .commit() - .catch((e) => { - throw e - }) + await patch.commit().catch((e) => { + throw e + }) } export function createCustomPublishAction(originalAction: DocumentActionComponent, context: DocumentActionsContext) { @@ -64,8 +35,12 @@ export function createCustomPublishAction(originalAction: DocumentActionComponen const handlePublish = async () => { try { - if (await shouldAddFirstPublishedAt(props)) { - await addFirstPublishedAtField(props.draft?._id || props.id, client) + if (requiresFirstPublished.includes(props.type)) { + await updateCustomPublishFields( + props.draft?._id || props.id, + client, + !props.published?.[FIRST_PUBLISHED_AT_FIELD_NAME], + ) } originalResult.onHandle && originalResult.onHandle() } catch (e) { diff --git a/sanityv3/schemas/documents/magazine.ts b/sanityv3/schemas/documents/magazine.ts index e11a5da26..94c3b5196 100644 --- a/sanityv3/schemas/documents/magazine.ts +++ b/sanityv3/schemas/documents/magazine.ts @@ -71,6 +71,14 @@ export default { readOnly: true, hidden: true, }, + { + // Set automatically in the custom action "ConfirmPublishWithi18n" + title: 'Date and time of when the document was last updated at', + name: 'lastModifiedAt', + type: 'datetime', + readOnly: true, + hidden: true, + }, { title: 'Meta information', name: 'seo', diff --git a/sanityv3/schemas/documents/news/sharedNewsFields.ts b/sanityv3/schemas/documents/news/sharedNewsFields.ts index f70a16c78..e51639c61 100644 --- a/sanityv3/schemas/documents/news/sharedNewsFields.ts +++ b/sanityv3/schemas/documents/news/sharedNewsFields.ts @@ -96,6 +96,14 @@ export const publishDateTime = [ readOnly: true, hidden: true, }, + { + // Set automatically in the custom action "ConfirmPublishWithi18n" + title: 'Date and time of when the document was last updated at', + name: 'lastModifiedAt', + type: 'datetime', + readOnly: true, + hidden: true, + }, ] export const tags = { diff --git a/sanityv3/schemas/index.ts b/sanityv3/schemas/index.ts index f56ff6a17..94de9a56a 100644 --- a/sanityv3/schemas/index.ts +++ b/sanityv3/schemas/index.ts @@ -109,6 +109,34 @@ const { routeHomepage, } = documentSchemaTypes +const { + pageNotFound, + internalServerError, + assetFile, + assetTag, + countryTag, + event, + eventTag, + landingPage, + footer, + externalRedirect, + localNews, + localNewsTag, + magazine, + magazineTag, + magazineIndex, + route, + redirect, + newsroom, + news, + page, + tag, + subMenu, + siteMenu, + simpleMenu, + routeHomepage, +} = documentSchemaTypes + const routeSchemas = languages.map(({ name, title }) => { return route(name, title) }) diff --git a/sanityv3/scripts/issue-1868/createMetadataN.mjs b/sanityv3/scripts/issue-1868/createMetadataN.mjs new file mode 100644 index 000000000..64962ab41 --- /dev/null +++ b/sanityv3/scripts/issue-1868/createMetadataN.mjs @@ -0,0 +1,127 @@ +import { sanityClients } from './getSanityClients.mjs' +import { testDocs, SCHEMA_TYPE } from './testDocument.mjs' + +/** + * This migration script creates new `translation.metadata` documents for all + * documents that have a `__i18n_refs` field that is an array of references. + * + * This migration is necessary for the new version of the plugin to work. + * + * 1. Take a backup of your dataset with: + * `npx sanity@latest dataset export` + * + * 2. Copy this file to the root of your Sanity Studio project + * + * 3. Update the `UNSET_REFS_FIELD`, `UNSET_BASE_FIELD`, + * and `SCHEMA_TYPE` constants to match your use + * + * 4. Run the script (replace with the name of your schema type): + * npx sanity@latest exec ./createMetadata.ts --with-user-token + * + * 5. Repeat for every schema type and dataset using the updated plugin + */ + +const client = sanityClients[0] +// Values in this field will be used to create meta documents +const UNSET_REFS_FIELD = `_langRefs` + +// This field will NOT be modified in this script +// Run the `renameLanguageField.ts` script after if you want to change this +const LANGUAGE_FIELD = `_lang` +// eslint-disable-next-line no-console +console.log( + `Finding "${SCHEMA_TYPE}" documents with translation references in a "${UNSET_REFS_FIELD}" field to create "translation.metadata" documents.`, +) + +const fetchDocuments = () => + client.fetch( + `*[ + _type in $type + && (defined(${UNSET_REFS_FIELD}) ) + && defined(${LANGUAGE_FIELD}) + + ][0...100] { + _id, + _rev, + ${LANGUAGE_FIELD}, + ${UNSET_REFS_FIELD}, + }`, + { type: SCHEMA_TYPE, testDocs: testDocs }, + ) + +const buildMetadata = (docs) => { + return docs + .filter((doc) => doc?.[UNSET_REFS_FIELD]?.length) + .map((doc) => { + return { + create: { + _type: 'translation.metadata', + translations: [ + { + _key: doc[LANGUAGE_FIELD], + value: { + _type: 'reference', + _ref: doc._id.replace(`drafts.`, ``), + ...(doc[UNSET_REFS_FIELD].some((ref) => typeof ref._weak !== 'undefined') + ? { _weak: doc[UNSET_REFS_FIELD].find((ref) => ref._weak)?._weak } + : {}), + }, + }, + ...doc[UNSET_REFS_FIELD].map(({ _ref, _key, _weak }) => ({ + _key, + value: { + _type: 'reference', + _ref, + ...(typeof _weak === 'undefined' ? {} : { _weak }), + }, + })), + ], + }, + patch: { + id: doc._id, + patch: { + unset: [UNSET_REFS_FIELD], + // this will cause the migration to fail if any of the documents has been + // modified since it was fetched. + ifRevisionID: doc._rev, + }, + }, + } + }) +} + +const commitTransaction = (tx) => tx.commit() + +const migrateNextBatch = async () => { + // Get all docs that match query + const documents = await fetchDocuments() + console.log('Found ' + documents.length + ' docs') + + // Create new metadata documents before unsetting + const metadatas = buildMetadata(documents) + + if (metadatas.length) { + const tx = client.transaction() + metadatas.forEach((metadata) => { + console.log(JSON.stringify(metadata.patch) + '\n') + return tx.create(metadata.create).patch(metadata.patch.id, metadata.patch.patch) + }) + await commitTransaction(tx) + } + if (documents.length === 0) { + // eslint-disable-next-line no-console + console.debug('No more documents to create or patch!') + // eslint-disable-next-line no-console + console.debug( + 'Be sure to migrate your "language" field using the "renameLanguageField.ts" script or update your plugin configuration\'s "Langage Field" setting', + ) + return null + } + return migrateNextBatch() +} + +migrateNextBatch().catch((err) => { + console.error(err) + // eslint-disable-next-line no-process-exit + process.exit(1) +}) diff --git a/sanityv3/scripts/issue-1868/renameLangField.mjs b/sanityv3/scripts/issue-1868/renameLangField.mjs index 7fc48d1f8..ddfb017ca 100644 --- a/sanityv3/scripts/issue-1868/renameLangField.mjs +++ b/sanityv3/scripts/issue-1868/renameLangField.mjs @@ -25,14 +25,14 @@ import { testDocs, SCHEMA_TYPE } from './testDocument.mjs' const UNSET_FIELD_NAME = `_lang` const NEW_FIELD_NAME = `lang` -//const SCHEMA_TYPE = [`magazine`, `localNews`] +// This field will be unset from all documents that contain it +const UNSET_BASE_FIELD = `__i18n_base` // This will use the client configured in ./sanity.cli.ts const client = sanityClients[0] //&& _id in $testDocs -const query = `*[_type in $type && defined(${UNSET_FIELD_NAME}) - +const query = `*[_type in $type && defined(${UNSET_FIELD_NAME}) || defined(${UNSET_BASE_FIELD}) ][0...100] { _id, _rev, @@ -49,7 +49,7 @@ const buildPatches = (docs) => id: doc._id, patch: { set: { [NEW_FIELD_NAME]: doc[UNSET_FIELD_NAME] }, - unset: [UNSET_FIELD_NAME], + unset: [UNSET_FIELD_NAME, UNSET_BASE_FIELD], // this will cause the migration to fail if any of the // documents have been modified since the original fetch. ifRevisionID: doc._rev, diff --git a/sanityv3/scripts/issue-1868/retainLastModified.mjs b/sanityv3/scripts/issue-1868/retainLastModified.mjs new file mode 100644 index 000000000..3c99bb3f9 --- /dev/null +++ b/sanityv3/scripts/issue-1868/retainLastModified.mjs @@ -0,0 +1,66 @@ +import { sanityClients } from './getSanityClients.mjs' + +/** + * This migration script adds `lastModifiedAt` and sets it to the current _updatedAt field. + * Applicable to news, localNews and magazine. + */ + +const UPDATED_AT = `_updatedAt` +const LAST_MODIFIED_AT = `lastModifiedAt` +const SCHEMA_TYPE = [`news`, `localNews`, 'magazine'] + +// This will use the client configured in ./sanity.cli.ts +const client = sanityClients[0] + +//&& _id in $testDocs +const query = `*[_type in $type && !defined(${LAST_MODIFIED_AT}) ][0...100] { + _id, + _rev, + ${UPDATED_AT} + }` +const fetchDocuments = () => + client.fetch(query, { + type: SCHEMA_TYPE, + }) + +const buildPatches = (docs) => + docs.map((doc) => ({ + id: doc._id, + patch: { + setIfMissing: { [LAST_MODIFIED_AT]: doc[UPDATED_AT] }, + // this will cause the migration to fail if any of the + // documents have been modified since the original fetch. + ifRevisionID: doc._rev, + }, + })) + +const createTransaction = (patches) => + patches.reduce((tx, patch) => tx.patch(patch.id, patch.patch), client.transaction()) + +const commitTransaction = (tx) => tx.commit() + +const migrateNextBatch = async () => { + const documents = await fetchDocuments() + console.log(`Found ${documents.length} documents to migrate\n`) + + const patches = buildPatches(documents) + if (patches.length === 0) { + // eslint-disable-next-line no-console + console.debug('No more documents to migrate!') + return null + } + // eslint-disable-next-line no-console + console.debug( + `Migrating batch:\n %s`, + patches.map((patch) => `${patch.id} => ${JSON.stringify(patch.patch)}`).join('\n'), + ) + const transaction = createTransaction(patches) + await commitTransaction(transaction) + return migrateNextBatch() +} + +migrateNextBatch().catch((err) => { + console.error(err) + // eslint-disable-next-line no-process-exit + process.exit(1) +}) diff --git a/web/lib/queries/common/pageContentFields.ts b/web/lib/queries/common/pageContentFields.ts index a5ea8e53f..f43793a3d 100644 --- a/web/lib/queries/common/pageContentFields.ts +++ b/web/lib/queries/common/pageContentFields.ts @@ -13,7 +13,7 @@ import { imageCarouselFields } from './imageCarouselFields' import { keyNumbersFields } from './keyNumbersFields' import { noDrafts, sameLang } from './langAndDrafts' import promoteMagazine from './promotions/promoteMagazine' -import { publishDateTimeQuery } from './publishDateTime' +import { lastUpdatedTimeQuery, publishDateTimeQuery } from './publishDateTime' const pageContentFields = /* groq */ ` _type == "keyNumbers" =>{ @@ -274,7 +274,7 @@ _type == "keyNumbers" =>{ ] | order(${publishDateTimeQuery} desc)[0...3]{ "type": _type, "id": _id, - "updatedAt": _updatedAt, + "updatedAt": ${lastUpdatedTimeQuery}, title, heroImage, "publishDateTime": ${publishDateTimeQuery}, @@ -449,7 +449,7 @@ _type == "keyNumbers" =>{ ] | order(${publishDateTimeQuery} desc){ "type": _type, "id": _id, - "updatedAt": _updatedAt, + "updatedAt": ${lastUpdatedTimeQuery}, title, heroImage, "publishDateTime": ${publishDateTimeQuery}, diff --git a/web/lib/queries/common/publishDateTime.ts b/web/lib/queries/common/publishDateTime.ts index 7c0b024c1..7a3133cdc 100644 --- a/web/lib/queries/common/publishDateTime.ts +++ b/web/lib/queries/common/publishDateTime.ts @@ -5,3 +5,7 @@ export const publishDateTimeQuery = /* groq */ ` coalesce(firstPublishedAt, _createdAt) ) ` + +export const lastUpdatedTimeQuery = /* groq */ ` + coalesce(lastModifiedAt,_updatedAt) +` diff --git a/web/lib/queries/localNews.ts b/web/lib/queries/localNews.ts index 3015a62d2..de9715b86 100644 --- a/web/lib/queries/localNews.ts +++ b/web/lib/queries/localNews.ts @@ -5,12 +5,12 @@ import { ingressForNewsQuery, relatedLinksForNewsQuery, } from './common/newsSubqueries' -import { publishDateTimeQuery } from './common/publishDateTime' +import { publishDateTimeQuery, lastUpdatedTimeQuery } from './common/publishDateTime' import { fixPreviewForDrafts } from './common/langAndDrafts' const localNewsFields = /* groq */ ` "id": _id, - "updatedAt": _updatedAt, + "updatedAt": ${lastUpdatedTimeQuery}, title, heroImage, ${slugsForNewsAndMagazine}, diff --git a/web/lib/queries/news.ts b/web/lib/queries/news.ts index 191f70210..51d75876f 100644 --- a/web/lib/queries/news.ts +++ b/web/lib/queries/news.ts @@ -7,14 +7,14 @@ import { ingressForNewsQuery, relatedLinksForNewsQuery, } from './common/newsSubqueries' -import { publishDateTimeQuery } from './common/publishDateTime' +import { lastUpdatedTimeQuery, publishDateTimeQuery } from './common/publishDateTime' export const excludeCrudeOilAssays = Flags.IS_DEV || Flags.IS_GLOBAL_PROD ? /* groq */ `!('crude-oil-assays' in tags[]->key.current) &&` : '' const latestNewsFields = /* groq */ ` "id": _id, - "updatedAt": _updatedAt, + "updatedAt": ${lastUpdatedTimeQuery}, title, heroImage, ${slugsForNewsAndMagazine}, @@ -26,7 +26,7 @@ const latestNewsFields = /* groq */ ` const newsFields = /* groq */ ` "id": _id, - "updatedAt": _updatedAt, + "updatedAt": ${lastUpdatedTimeQuery}, title, heroImage, "publishDateTime": ${publishDateTimeQuery}, @@ -70,7 +70,7 @@ export const newsPromotionQuery = /* groq */ ` ] | order(${publishDateTimeQuery} desc)[0...3]{ "type": _type, "id": _id, - "updatedAt": _updatedAt, + "updatedAt": ${lastUpdatedTimeQuery}, title, heroImage, "publishDateTime": ${publishDateTimeQuery}, diff --git a/web/twMerge/index.ts b/web/twMerge/index.ts index 443ec1030..c033ab00f 100644 --- a/web/twMerge/index.ts +++ b/web/twMerge/index.ts @@ -12,6 +12,7 @@ const envisTwMerge = extendTailwindMerge({ minWidth: ['viewport'], lineHeight: ['text', 'earthy', 'misty', 'cloudy', 'planetary', 'inherit'], size: ['arrow-right'], + lineHeight: ['text', 'earthy', 'misty', 'cloudy', 'planetary', 'inherit'], }, }, })