Create new deployment on changing preview deployment to production (#61)
* Create new deployment when changing to production * Remove unnecessary todos * Move deployment id and url creation in database method * Display correct details in deployment dialog box * Rename relativeTime function to relativeTimeISO * Refactor resolver methods to service class * Refactor to move github app to service class --------- Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
parent
e0001466e0
commit
bd6a6b330c
@ -2,6 +2,8 @@ import { DataSource, DeepPartial, FindManyOptions, FindOneOptions, FindOptionsWh
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
import { customAlphabet } from 'nanoid';
|
||||||
|
import { lowercase, numbers } from 'nanoid-dictionary';
|
||||||
|
|
||||||
import { DatabaseConfig } from './config';
|
import { DatabaseConfig } from './config';
|
||||||
import { User } from './entity/User';
|
import { User } from './entity/User';
|
||||||
@ -15,6 +17,8 @@ import { PROJECT_DOMAIN } from './constants';
|
|||||||
|
|
||||||
const log = debug('snowball:database');
|
const log = debug('snowball:database');
|
||||||
|
|
||||||
|
const nanoid = customAlphabet(lowercase + numbers, 8);
|
||||||
|
|
||||||
// TODO: Fix order of methods
|
// TODO: Fix order of methods
|
||||||
export class Database {
|
export class Database {
|
||||||
private dataSource: DataSource;
|
private dataSource: DataSource;
|
||||||
@ -171,7 +175,16 @@ export class Database {
|
|||||||
|
|
||||||
async addDeployement (data: DeepPartial<Deployment>): Promise<Deployment> {
|
async addDeployement (data: DeepPartial<Deployment>): Promise<Deployment> {
|
||||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||||
const deployment = await deploymentRepository.save(data);
|
|
||||||
|
const id = nanoid();
|
||||||
|
const url = `${data.project!.name}-${id}.${PROJECT_DOMAIN}`;
|
||||||
|
|
||||||
|
const updatedData = {
|
||||||
|
...data,
|
||||||
|
id,
|
||||||
|
url
|
||||||
|
};
|
||||||
|
const deployment = await deploymentRepository.save(updatedData);
|
||||||
|
|
||||||
return deployment;
|
return deployment;
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,6 @@ export const main = async (): Promise<void> => {
|
|||||||
// TODO: get config path using cli
|
// TODO: get config path using cli
|
||||||
const { server, database, githubOauth } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
const { server, database, githubOauth } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
||||||
|
|
||||||
const db = new Database(database);
|
|
||||||
await db.init();
|
|
||||||
const service = new Service(db);
|
|
||||||
|
|
||||||
// TODO: Move to Service class
|
// TODO: Move to Service class
|
||||||
const app = new OAuthApp({
|
const app = new OAuthApp({
|
||||||
clientType: 'oauth-app',
|
clientType: 'oauth-app',
|
||||||
@ -30,8 +26,12 @@ export const main = async (): Promise<void> => {
|
|||||||
clientSecret: githubOauth.clientSecret
|
clientSecret: githubOauth.clientSecret
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const db = new Database(database);
|
||||||
|
await db.init();
|
||||||
|
const service = new Service(db, app);
|
||||||
|
|
||||||
const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.gql')).toString();
|
const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.gql')).toString();
|
||||||
const resolvers = await createResolvers(db, app, service);
|
const resolvers = await createResolvers(service);
|
||||||
|
|
||||||
await createAndStartServer(typeDefs, resolvers, server);
|
await createAndStartServer(typeDefs, resolvers, server);
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { DeepPartial, FindOptionsWhere } from 'typeorm';
|
import { DeepPartial, FindOptionsWhere } from 'typeorm';
|
||||||
|
|
||||||
import { OAuthApp } from '@octokit/oauth-app';
|
|
||||||
|
|
||||||
import { Service } from './service';
|
import { Service } from './service';
|
||||||
import { Database } from './database';
|
|
||||||
import { Permission } from './entity/ProjectMember';
|
import { Permission } from './entity/ProjectMember';
|
||||||
import { Domain } from './entity/Domain';
|
import { Domain } from './entity/Domain';
|
||||||
import { Project } from './entity/Project';
|
import { Project } from './entity/Project';
|
||||||
@ -13,7 +10,7 @@ import { EnvironmentVariable } from './entity/EnvironmentVariable';
|
|||||||
const log = debug('snowball:database');
|
const log = debug('snowball:database');
|
||||||
|
|
||||||
// TODO: Remove Database argument and refactor code to Service
|
// TODO: Remove Database argument and refactor code to Service
|
||||||
export const createResolvers = async (db: Database, app: OAuthApp, service: Service): Promise<any> => {
|
export const createResolvers = async (service: Service): Promise<any> => {
|
||||||
return {
|
return {
|
||||||
Query: {
|
Query: {
|
||||||
// TODO: add custom type for context
|
// TODO: add custom type for context
|
||||||
@ -121,9 +118,9 @@ export const createResolvers = async (db: Database, app: OAuthApp, service: Serv
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateDeploymentToProd: async (_: any, { deploymentId }: { deploymentId: string }) => {
|
updateDeploymentToProd: async (_: any, { deploymentId }: { deploymentId: string }, context: any) => {
|
||||||
try {
|
try {
|
||||||
return await service.updateDeploymentToProd(deploymentId);
|
return Boolean(await service.updateDeploymentToProd(context.userId, deploymentId));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(err);
|
log(err);
|
||||||
return false;
|
return false;
|
||||||
@ -201,19 +198,17 @@ export const createResolvers = async (db: Database, app: OAuthApp, service: Serv
|
|||||||
},
|
},
|
||||||
|
|
||||||
authenticateGitHub: async (_: any, { code }: { code: string }, context: any) => {
|
authenticateGitHub: async (_: any, { code }: { code: string }, context: any) => {
|
||||||
// TOO: Move to Service class
|
try {
|
||||||
const { authentication: { token } } = await app.createToken({
|
return await service.authenticateGitHub(code, context.userId);
|
||||||
code
|
} catch (err) {
|
||||||
});
|
log(err);
|
||||||
|
return false;
|
||||||
await db.updateUser(context.userId, { gitHubToken: token });
|
}
|
||||||
|
|
||||||
return { token };
|
|
||||||
},
|
},
|
||||||
|
|
||||||
unauthenticateGitHub: async (_: any, __: object, context: any) => {
|
unauthenticateGitHub: async (_: any, __: object, context: any) => {
|
||||||
try {
|
try {
|
||||||
return db.updateUser(context.userId, { gitHubToken: null });
|
return service.unauthenticateGitHub(context.userId, { gitHubToken: null });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(err);
|
log(err);
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import { customAlphabet } from 'nanoid';
|
|
||||||
import { lowercase, numbers } from 'nanoid-dictionary';
|
|
||||||
import { DeepPartial, FindOptionsWhere } from 'typeorm';
|
import { DeepPartial, FindOptionsWhere } from 'typeorm';
|
||||||
|
|
||||||
|
import { OAuthApp } from '@octokit/oauth-app';
|
||||||
|
|
||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
import { Deployment, Environment } from './entity/Deployment';
|
import { Deployment, Environment } from './entity/Deployment';
|
||||||
import { Domain } from './entity/Domain';
|
import { Domain } from './entity/Domain';
|
||||||
@ -11,15 +11,13 @@ import { Organization } from './entity/Organization';
|
|||||||
import { Project } from './entity/Project';
|
import { Project } from './entity/Project';
|
||||||
import { Permission, ProjectMember } from './entity/ProjectMember';
|
import { Permission, ProjectMember } from './entity/ProjectMember';
|
||||||
import { User } from './entity/User';
|
import { User } from './entity/User';
|
||||||
import { PROJECT_DOMAIN } from './constants';
|
|
||||||
|
|
||||||
const nanoid = customAlphabet(lowercase + numbers, 8);
|
|
||||||
|
|
||||||
export class Service {
|
export class Service {
|
||||||
private db: Database;
|
private db: Database;
|
||||||
|
private app: OAuthApp;
|
||||||
|
|
||||||
constructor (db: Database) {
|
constructor (db: Database, app: OAuthApp) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
this.app = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUser (userId: string): Promise<User | null> {
|
async getUser (userId: string): Promise<User | null> {
|
||||||
@ -149,7 +147,7 @@ export class Service {
|
|||||||
return this.db.deleteEnvironmentVariable(environmentVariableId);
|
return this.db.deleteEnvironmentVariable(environmentVariableId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateDeploymentToProd (deploymentId: string): Promise<boolean> {
|
async updateDeploymentToProd (userId: string, deploymentId: string): Promise<Deployment> {
|
||||||
const deployment = await this.db.getDeployment({ where: { id: deploymentId }, relations: { project: true } });
|
const deployment = await this.db.getDeployment({ where: { id: deploymentId }, relations: { project: true } });
|
||||||
|
|
||||||
if (!deployment) {
|
if (!deployment) {
|
||||||
@ -173,13 +171,18 @@ export class Service {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateResult = await this.db.updateDeploymentById(deploymentId, {
|
const { createdAt, updatedAt, ...updatedDeployment } = deployment;
|
||||||
environment: Environment.Production,
|
|
||||||
domain: prodBranchDomains[0],
|
updatedDeployment.isCurrent = true;
|
||||||
isCurrent: true
|
updatedDeployment.environment = Environment.Production;
|
||||||
|
updatedDeployment.domain = prodBranchDomains[0];
|
||||||
|
updatedDeployment.createdBy = Object.assign(new User(), {
|
||||||
|
id: userId
|
||||||
});
|
});
|
||||||
|
|
||||||
return updateResult;
|
const newDeployement = await this.db.addDeployement(updatedDeployment);
|
||||||
|
|
||||||
|
return newDeployement;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addProject (userId: string, organizationSlug: string, data: DeepPartial<Project>): Promise<Project | undefined> {
|
async addProject (userId: string, organizationSlug: string, data: DeepPartial<Project>): Promise<Project | undefined> {
|
||||||
@ -243,9 +246,6 @@ export class Service {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedDeployment.id = nanoid();
|
|
||||||
updatedDeployment.url = `${updatedDeployment.project.name}-${updatedDeployment.id}.${PROJECT_DOMAIN}`;
|
|
||||||
|
|
||||||
const oldDeployment = await this.db.updateDeploymentById(deploymentId, { domain: null, isCurrent: false });
|
const oldDeployment = await this.db.updateDeploymentById(deploymentId, { domain: null, isCurrent: false });
|
||||||
const newDeployement = await this.db.addDeployement(updatedDeployment);
|
const newDeployement = await this.db.addDeployement(updatedDeployment);
|
||||||
|
|
||||||
@ -360,4 +360,18 @@ export class Service {
|
|||||||
|
|
||||||
return updateResult;
|
return updateResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async authenticateGitHub (code:string, userId: string): Promise<{token: string}> {
|
||||||
|
const { authentication: { token } } = await this.app.createToken({
|
||||||
|
code
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.db.updateUser(userId, { gitHubToken: token });
|
||||||
|
|
||||||
|
return { token };
|
||||||
|
}
|
||||||
|
|
||||||
|
async unauthenticateGitHub (userId: string, data: DeepPartial<User>): Promise<boolean> {
|
||||||
|
return this.db.updateUser(userId, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
{
|
{
|
||||||
"id": "2379cf1f-a232-4ad2-ae14-4d881131cc26",
|
"id": "2379cf1f-a232-4ad2-ae14-4d881131cc26",
|
||||||
"name": "Snowball Tools",
|
"name": "Snowball Tools",
|
||||||
"slug": "snowball-tools"
|
"slug": "snowball-tools-1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "7eb9b3eb-eb74-4b53-b59a-69884c82a7fb",
|
"id": "7eb9b3eb-eb74-4b53-b59a-69884c82a7fb",
|
||||||
"name": "AirFoil",
|
"name": "AirFoil",
|
||||||
"slug": "airfoil"
|
"slug": "airfoil-2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from '@material-tailwind/react';
|
} from '@material-tailwind/react';
|
||||||
|
|
||||||
import { relativeTime } from '../../utils/time';
|
import { relativeTimeISO } from '../../utils/time';
|
||||||
import { ProjectDetails } from '../../types/project';
|
import { ProjectDetails } from '../../types/project';
|
||||||
|
|
||||||
interface ProjectCardProps {
|
interface ProjectCardProps {
|
||||||
@ -45,7 +45,7 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ project }) => {
|
|||||||
{project.latestCommit.message}
|
{project.latestCommit.message}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="small" color="gray">
|
<Typography variant="small" color="gray">
|
||||||
{relativeTime(project.latestCommit.createdAt)} on{' '}
|
{relativeTimeISO(project.latestCommit.createdAt)} on{' '}
|
||||||
{project.latestCommit.branch}
|
{project.latestCommit.branch}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ import { useNavigate, useParams } from 'react-router-dom';
|
|||||||
|
|
||||||
import { Chip, IconButton } from '@material-tailwind/react';
|
import { Chip, IconButton } from '@material-tailwind/react';
|
||||||
|
|
||||||
import { relativeTime } from '../../../utils/time';
|
import { relativeTimeISO } from '../../../utils/time';
|
||||||
import { GitRepositoryDetails } from '../../../types/project';
|
import { GitRepositoryDetails } from '../../../types/project';
|
||||||
import { useGQLClient } from '../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../context/GQLClientContext';
|
||||||
|
|
||||||
@ -24,7 +24,6 @@ const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ repository }) => {
|
|||||||
|
|
||||||
const { addProject } = await client.addProject(orgSlug!, {
|
const { addProject } = await client.addProject(orgSlug!, {
|
||||||
name: `${repository.owner!.login}-${repository.name}`,
|
name: `${repository.owner!.login}-${repository.name}`,
|
||||||
// TODO: Get organization id from context or URL
|
|
||||||
prodBranch: repository.default_branch!,
|
prodBranch: repository.default_branch!,
|
||||||
repository: repository.full_name,
|
repository: repository.full_name,
|
||||||
});
|
});
|
||||||
@ -50,7 +49,7 @@ const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ repository }) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p>{repository.updated_at && relativeTime(repository.updated_at)}</p>
|
<p>{repository.updated_at && relativeTimeISO(repository.updated_at)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden group-hover:block">
|
<div className="hidden group-hover:block">
|
||||||
<IconButton size="sm">{'>'}</IconButton>
|
<IconButton size="sm">{'>'}</IconButton>
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import { Typography, IconButton } from '@material-tailwind/react';
|
import { Typography, IconButton } from '@material-tailwind/react';
|
||||||
|
|
||||||
import { relativeTime } from '../../../utils/time';
|
import { relativeTimeISO } from '../../../utils/time';
|
||||||
import { GitCommitDetails } from '../../../types/project';
|
import { GitCommitDetails } from '../../../types/project';
|
||||||
|
|
||||||
interface ActivityCardProps {
|
interface ActivityCardProps {
|
||||||
@ -17,7 +17,8 @@ const ActivityCard = ({ activity }: ActivityCardProps) => {
|
|||||||
<div className="grow">
|
<div className="grow">
|
||||||
<Typography>{activity.commit.author?.name}</Typography>
|
<Typography>{activity.commit.author?.name}</Typography>
|
||||||
<Typography variant="small" color="gray">
|
<Typography variant="small" color="gray">
|
||||||
{relativeTime(activity.commit.author!.date!)} ^ {activity.branch.name}
|
{relativeTimeISO(activity.commit.author!.date!)} ^{' '}
|
||||||
|
{activity.branch.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="small" color="gray">
|
<Typography variant="small" color="gray">
|
||||||
{activity.commit.message}
|
{activity.commit.message}
|
||||||
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
import { Typography, Chip, Card } from '@material-tailwind/react';
|
import { Typography, Chip, Card } from '@material-tailwind/react';
|
||||||
import { color } from '@material-tailwind/react/types/components/chip';
|
import { color } from '@material-tailwind/react/types/components/chip';
|
||||||
import { DeploymentDetails } from '../../../../types/project';
|
import { DeploymentDetails } from '../../../../types/project';
|
||||||
import { relativeTime } from '../../../../utils/time';
|
import { relativeTimeMs } from '../../../../utils/time';
|
||||||
|
|
||||||
interface DeploymentDialogBodyCardProps {
|
interface DeploymentDialogBodyCardProps {
|
||||||
deployment: DeploymentDetails;
|
deployment: DeploymentDetails;
|
||||||
@ -28,14 +28,14 @@ const DeploymentDialogBodyCard = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Typography variant="small" className="text-black">
|
<Typography variant="small" className="text-black">
|
||||||
{deployment.title}
|
{deployment.url}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="small">
|
<Typography variant="small">
|
||||||
^ {deployment.branch} ^ {deployment.commitHash}{' '}
|
^ {deployment.branch} ^ {deployment.commitHash}{' '}
|
||||||
{deployment.commit.message}
|
{deployment.commit.message}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="small">
|
<Typography variant="small">
|
||||||
^ {relativeTime(deployment.updatedAt)} ^ {deployment.author}
|
^ {relativeTimeMs(deployment.createdAt)} ^ {deployment.createdBy.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
@ -5,3 +5,6 @@ export const COMMIT_DETAILS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ORGANIZATION_ID = '2379cf1f-a232-4ad2-ae14-4d881131cc26';
|
export const ORGANIZATION_ID = '2379cf1f-a232-4ad2-ae14-4d881131cc26';
|
||||||
|
|
||||||
|
export const GIT_TEMPLATE_LINK =
|
||||||
|
'https://git.vdb.to/cerc-io/test-progressive-web-app';
|
||||||
|
@ -3,6 +3,7 @@ import { Outlet, useLocation, useSearchParams } from 'react-router-dom';
|
|||||||
|
|
||||||
import Stepper from '../../../../components/Stepper';
|
import Stepper from '../../../../components/Stepper';
|
||||||
import templateDetails from '../../../../assets/templates.json';
|
import templateDetails from '../../../../assets/templates.json';
|
||||||
|
import { GIT_TEMPLATE_LINK } from '../../../../constants';
|
||||||
|
|
||||||
const STEPPER_VALUES = [
|
const STEPPER_VALUES = [
|
||||||
{ step: 1, route: '/projects/create/template', label: 'Create repository' },
|
{ step: 1, route: '/projects/create/template', label: 'Create repository' },
|
||||||
@ -31,8 +32,11 @@ const CreateWithTemplate = () => {
|
|||||||
<div className="flex justify-between w-5/6 my-4 bg-gray-200 rounded-xl p-6">
|
<div className="flex justify-between w-5/6 my-4 bg-gray-200 rounded-xl p-6">
|
||||||
<div>^</div>
|
<div>^</div>
|
||||||
<div className="grow">{template?.name}</div>
|
<div className="grow">{template?.name}</div>
|
||||||
{/* TODO: Get template Git link from DB */}
|
<div>
|
||||||
<div>^snowball-tools/react-native-starter</div>
|
<a href={GIT_TEMPLATE_LINK} target="_blank" rel="noreferrer">
|
||||||
|
cerc-io/test-progressive-web-app
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-3 w-5/6 p-6">
|
<div className="grid grid-cols-3 w-5/6 p-6">
|
||||||
<div>
|
<div>
|
||||||
|
@ -50,7 +50,6 @@ const CreateRepo = () => {
|
|||||||
|
|
||||||
const { addProject } = await client.addProject(orgSlug!, {
|
const { addProject } = await client.addProject(orgSlug!, {
|
||||||
name: `${gitRepo.data.owner!.login}-${gitRepo.data.name}`,
|
name: `${gitRepo.data.owner!.login}-${gitRepo.data.name}`,
|
||||||
// TODO: Get organization id from context or URL
|
|
||||||
prodBranch: gitRepo.data.default_branch ?? 'main',
|
prodBranch: gitRepo.data.default_branch ?? 'main',
|
||||||
repository: gitRepo.data.full_name,
|
repository: gitRepo.data.full_name,
|
||||||
});
|
});
|
||||||
@ -95,8 +94,6 @@ const CreateRepo = () => {
|
|||||||
}
|
}
|
||||||
}, [gitAccounts]);
|
}, [gitAccounts]);
|
||||||
|
|
||||||
// TODO: Get users and orgs from GitHub
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(submitRepoHandler)}>
|
<form onSubmit={handleSubmit(submitRepoHandler)}>
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
@ -135,10 +132,7 @@ const CreateRepo = () => {
|
|||||||
name="account"
|
name="account"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<AsyncSelect
|
<AsyncSelect {...field}>
|
||||||
{...field}
|
|
||||||
label={!field.value ? 'Select an account / Organization' : ''}
|
|
||||||
>
|
|
||||||
{gitAccounts.map((account, key) => (
|
{gitAccounts.map((account, key) => (
|
||||||
<Option key={key} value={account}>
|
<Option key={key} value={account}>
|
||||||
^ {account}
|
^ {account}
|
||||||
|
@ -6,7 +6,7 @@ import { DateTime } from 'luxon';
|
|||||||
* @param {string} time - The input time in ISO 8601 format.
|
* @param {string} time - The input time in ISO 8601 format.
|
||||||
* @returns {string} - A human-readable relative time string.
|
* @returns {string} - A human-readable relative time string.
|
||||||
*/
|
*/
|
||||||
export const relativeTime = (time: string) => {
|
export const relativeTimeISO = (time: string) => {
|
||||||
return DateTime.fromISO(time).toRelative();
|
return DateTime.fromISO(time).toRelative();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -17,5 +17,5 @@ export const relativeTime = (time: string) => {
|
|||||||
* @returns {string} - A human-readable relative time string.
|
* @returns {string} - A human-readable relative time string.
|
||||||
*/
|
*/
|
||||||
export const relativeTimeMs = (time: string) => {
|
export const relativeTimeMs = (time: string) => {
|
||||||
return relativeTime(new Date(Number(time)).toISOString());
|
return relativeTimeISO(new Date(Number(time)).toISOString());
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user