Skip to content

Commit

Permalink
fix: support big images
Browse files Browse the repository at this point in the history
Twipics has a limit of 36M pixels for an image.

So for image bigger than that we directly use S3 to display it.
  • Loading branch information
gregberge committed Nov 20, 2024
1 parent 6afe41b commit 0803963
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 14 deletions.
15 changes: 12 additions & 3 deletions apps/backend/src/graphql/definitions/Screenshot.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { invariant } from "@argos/util/invariant";
import gqlTag from "graphql-tag";

import { getPublicImageUrl, getPublicUrl } from "@/storage/index.js";
import {
getPublicImageFileUrl,
getPublicUrl,
getTwicPicsUrl,
} from "@/storage/index.js";

import type { IResolvers } from "../__generated__/resolver-types.js";

Expand Down Expand Up @@ -77,8 +81,13 @@ export const typeDefs = gql`

export const resolvers: IResolvers = {
Screenshot: {
url: async (screenshot) => {
return getPublicImageUrl(screenshot.s3Id);
url: async (screenshot, _args, ctx) => {
if (!screenshot.fileId) {
return getTwicPicsUrl(screenshot.s3Id);
}
const file = await ctx.loaders.File.load(screenshot.fileId);
invariant(file, "File not found");
return getPublicImageFileUrl(file);
},
width: async (screenshot, _args, ctx) => {
if (!screenshot.fileId) {
Expand Down
15 changes: 10 additions & 5 deletions apps/backend/src/graphql/definitions/ScreenshotDiff.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { invariant } from "@argos/util/invariant";
import gqlTag from "graphql-tag";

import { getPublicImageUrl } from "@/storage/index.js";
import { getPublicImageFileUrl, getTwicPicsUrl } from "@/storage/index.js";

import type {
IResolvers,
Expand Down Expand Up @@ -56,11 +56,16 @@ export const resolvers: IResolvers = {
}
return ctx.loaders.Screenshot.load(screenshotDiff.compareScreenshotId);
},
url: async (screenshotDiff) => {
if (!screenshotDiff.s3Id) {
return null;
url: async (screenshotDiff, _args, ctx) => {
if (!screenshotDiff.fileId) {
if (!screenshotDiff.s3Id) {
return null;
}
return getTwicPicsUrl(screenshotDiff.s3Id);
}
return getPublicImageUrl(screenshotDiff.s3Id);
const file = await ctx.loaders.File.load(screenshotDiff.fileId);
invariant(file, "File not found");
return getPublicImageFileUrl(file);
},
name: async (screenshotDiff, _args, ctx) => {
const [baseScreenshot, compareScreenshot] = await Promise.all([
Expand Down
14 changes: 11 additions & 3 deletions apps/backend/src/slack/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
ScreenshotDiff,
SlackInstallation,
} from "@/database/models";
import { getPublicImageUrl } from "@/storage";
import { getPublicImageFileUrl, getTwicPicsUrl } from "@/storage";

/**
* Set the accountId in the cookies.
Expand Down Expand Up @@ -246,7 +246,7 @@ async function unfurlBuild(
? ScreenshotDiff.query()
.findById(params.diffId)
.where("buildId", build.id)
.withGraphFetched("[baseScreenshot, compareScreenshot]")
.withGraphFetched("[baseScreenshot.file, compareScreenshot.file]")
: null,
]);
invariant(status, "Status should be loaded");
Expand All @@ -255,7 +255,15 @@ async function unfurlBuild(

const screenshot =
screenshotDiff?.compareScreenshot || screenshotDiff?.baseScreenshot;
const imageUrl = screenshot ? await getPublicImageUrl(screenshot.s3Id) : null;
const imageUrl = await (() => {
if (!screenshot) {
return null;
}
if (!screenshot.file) {
return getTwicPicsUrl(screenshot.s3Id);
}
return getPublicImageFileUrl(screenshot.file);
})();

const attachment: Bolt.types.MessageAttachment = {
title: `Build ${build.number}${build.name}${build.project.account.name || build.project.account.slug}/${build.project.name}`,
Expand Down
33 changes: 30 additions & 3 deletions apps/backend/src/storage/public-url.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import config from "@/config/index.js";
import { File as FileModel } from "@/database/models/File.js";

import { getS3Client } from "./s3.js";
import { getSignedGetObjectUrl } from "./signed-url.js";
Expand All @@ -14,10 +15,36 @@ export async function getPublicUrl(key: string) {
return url;
}

export async function getPublicImageUrl(key: string) {
const TWIC_PICS_PIXELS_LIMIT = 36_000_000;

function checkIsImageFile(file: FileModel) {
return file.type === "screenshot" || file.type === "screenshotDiff";
}

function checkIsSizedFile(
file: FileModel,
): file is FileModel & { width: number; height: number } {
return Boolean(file.width && file.height);
}

function getPixelsInFile(file: FileModel) {
if (checkIsImageFile(file) && checkIsSizedFile(file)) {
return file.width * file.height;
}
return null;
}

export function getTwicPicsUrl(key: string) {
return new URL(key, config.get("s3.publicImageBaseUrl")).href;
}

export async function getPublicImageFileUrl(file: FileModel) {
if (config.get("s3.publicImageBaseUrl")) {
return new URL(key, config.get("s3.publicImageBaseUrl")).href;
const pixels = getPixelsInFile(file);
if (pixels && pixels < TWIC_PICS_PIXELS_LIMIT) {
return getTwicPicsUrl(file.key);
}
}

return getPublicUrl(key);
return getPublicUrl(file.key);
}
11 changes: 11 additions & 0 deletions apps/frontend/src/ui/TwicPicture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,22 @@ export type TwicPictureProps = React.ImgHTMLAttributes<HTMLImageElement> & {
original?: boolean;
};

const TWIC_PICS_DOMAIN = "argos.twic.pics";

function checkIsTwicPicsUrl(url: string) {
return new URL(url).hostname === TWIC_PICS_DOMAIN;
}

export const TwicPicture = forwardRef(function TwicPicture(
props: TwicPictureProps,
ref: React.ForwardedRef<HTMLImageElement>,
) {
const { src, transforms = [], original, ...rest } = props;

if (!checkIsTwicPicsUrl(src)) {
return <img ref={ref} src={src} {...rest} />;
}

if (original) {
return (
<img
Expand Down

0 comments on commit 0803963

Please sign in to comment.