Merge pull request #1895 from omnivore-app/add-top-percentage-to-read-position

Add top percentage to the reading position in API
This commit is contained in:
Hongbo Wu
2023-03-10 13:11:10 +08:00
committed by GitHub
8 changed files with 120 additions and 59 deletions

View File

@ -256,7 +256,7 @@ export const updatePage = async (
ctx: PageContext
): Promise<boolean> => {
try {
const { body } = await client.update({
await client.update({
index: INDEX_ALIAS,
id,
body: {
@ -269,8 +269,6 @@ export const updatePage = async (
retry_on_conflict: 3,
})
if (body.result !== 'updated') return false
if (page.state === ArticleSavingRequestStatus.Deleted) {
await ctx.pubsub.entityDeleted(EntityType.PAGE, id, ctx.uid)
return true

View File

@ -242,6 +242,7 @@ export interface Page {
originalHtml?: string | null
slug: string
labels?: Label[]
readingProgressTopPercent?: number
readingProgressPercent: number
readingProgressAnchorIndex: number
createdAt: Date
@ -283,6 +284,7 @@ export interface SearchItem {
uploadFileId?: string | null
url: string
archivedAt?: Date | null
readingProgressTopPercent?: number
readingProgressPercent: number
readingProgressAnchorIndex: number
userId: string

View File

@ -110,6 +110,7 @@ export type Article = {
readAt?: Maybe<Scalars['Date']>;
readingProgressAnchorIndex: Scalars['Int'];
readingProgressPercent: Scalars['Float'];
readingProgressTopPercent?: Maybe<Scalars['Float']>;
recommendations?: Maybe<Array<Recommendation>>;
savedAt: Scalars['Date'];
savedByViewer?: Maybe<Scalars['Boolean']>;
@ -2125,6 +2126,7 @@ export type SaveArticleReadingProgressInput = {
id: Scalars['ID'];
readingProgressAnchorIndex: Scalars['Int'];
readingProgressPercent: Scalars['Float'];
readingProgressTopPercent?: InputMaybe<Scalars['Float']>;
};
export type SaveArticleReadingProgressResult = SaveArticleReadingProgressError | SaveArticleReadingProgressSuccess;
@ -2233,6 +2235,7 @@ export type SearchItem = {
readAt?: Maybe<Scalars['Date']>;
readingProgressAnchorIndex: Scalars['Int'];
readingProgressPercent: Scalars['Float'];
readingProgressTopPercent?: Maybe<Scalars['Float']>;
recommendations?: Maybe<Array<Recommendation>>;
savedAt: Scalars['Date'];
shortId?: Maybe<Scalars['String']>;
@ -4142,6 +4145,7 @@ export type ArticleResolvers<ContextType = ResolverContext, ParentType extends R
readAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
readingProgressAnchorIndex?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
readingProgressPercent?: Resolver<ResolversTypes['Float'], ParentType, ContextType>;
readingProgressTopPercent?: Resolver<Maybe<ResolversTypes['Float']>, ParentType, ContextType>;
recommendations?: Resolver<Maybe<Array<ResolversTypes['Recommendation']>>, ParentType, ContextType>;
savedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
savedByViewer?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
@ -5365,6 +5369,7 @@ export type SearchItemResolvers<ContextType = ResolverContext, ParentType extend
readAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
readingProgressAnchorIndex?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
readingProgressPercent?: Resolver<ResolversTypes['Float'], ParentType, ContextType>;
readingProgressTopPercent?: Resolver<Maybe<ResolversTypes['Float']>, ParentType, ContextType>;
recommendations?: Resolver<Maybe<Array<ResolversTypes['Recommendation']>>, ParentType, ContextType>;
savedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
shortId?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;

View File

@ -86,6 +86,7 @@ type Article {
readAt: Date
readingProgressAnchorIndex: Int!
readingProgressPercent: Float!
readingProgressTopPercent: Float
recommendations: [Recommendation!]
savedAt: Date!
savedByViewer: Boolean
@ -1532,6 +1533,7 @@ input SaveArticleReadingProgressInput {
id: ID!
readingProgressAnchorIndex: Int!
readingProgressPercent: Float!
readingProgressTopPercent: Float
}
union SaveArticleReadingProgressResult = SaveArticleReadingProgressError | SaveArticleReadingProgressSuccess
@ -1633,6 +1635,7 @@ type SearchItem {
readAt: Date
readingProgressAnchorIndex: Int!
readingProgressPercent: Float!
readingProgressTopPercent: Float
recommendations: [Recommendation!]
savedAt: Date!
shortId: String

View File

@ -764,7 +764,14 @@ export const saveArticleReadingProgressResolver = authorized<
>(
async (
_,
{ input: { id, readingProgressPercent, readingProgressAnchorIndex } },
{
input: {
id,
readingProgressPercent,
readingProgressAnchorIndex,
readingProgressTopPercent,
},
},
{ claims: { uid }, pubsub }
) => {
const page = await getPageByParam({ userId: uid, _id: id })
@ -774,31 +781,43 @@ export const saveArticleReadingProgressResolver = authorized<
}
if (
(!readingProgressPercent && readingProgressPercent !== 0) ||
readingProgressPercent < 0 ||
readingProgressPercent > 100
readingProgressPercent > 100 ||
(readingProgressTopPercent &&
(readingProgressTopPercent < 0 ||
readingProgressTopPercent > readingProgressPercent))
) {
return { errorCodes: [SaveArticleReadingProgressErrorCode.BadData] }
}
// If we have a top percent, we only save it if it's greater than the current top percent
// or set to zero if the top percent is zero.
const readingProgressTopPercentToSave = readingProgressTopPercent
? Math.max(readingProgressTopPercent, page.readingProgressTopPercent || 0)
: readingProgressTopPercent === 0
? 0
: undefined
console.log(
'readingProgressTopPercentToSave',
readingProgressTopPercentToSave
)
// If setting to zero we accept the update, otherwise we require it
// be greater than the current reading progress.
const shouldUpdate =
readingProgressPercent === 0 ||
page.readingProgressPercent < readingProgressPercent ||
page.readingProgressAnchorIndex < readingProgressAnchorIndex
const updatedPart = {
readingProgressPercent: shouldUpdate
? readingProgressPercent
: page.readingProgressPercent,
readingProgressAnchorIndex: shouldUpdate
? readingProgressAnchorIndex
: page.readingProgressAnchorIndex,
readingProgressPercent:
readingProgressPercent === 0
? 0
: Math.max(readingProgressPercent, page.readingProgressPercent),
readingProgressAnchorIndex: Math.max(
readingProgressAnchorIndex,
page.readingProgressAnchorIndex
),
readingProgressTopPercent: readingProgressTopPercentToSave,
readAt: new Date(),
}
await updatePage(id, updatedPart, { pubsub, uid })
const updated = await updatePage(id, updatedPart, { pubsub, uid })
if (!updated) {
return { errorCodes: [SaveArticleReadingProgressErrorCode.NotFound] }
}
return {
updatedArticle: {

View File

@ -363,6 +363,7 @@ const schema = gql`
savedAt: Date!
updatedAt: Date!
publishedAt: Date
readingProgressTopPercent: Float
readingProgressPercent: Float!
readingProgressAnchorIndex: Int!
sharedComment: String
@ -609,6 +610,7 @@ const schema = gql`
| SaveArticleReadingProgressError
input SaveArticleReadingProgressInput {
id: ID!
readingProgressTopPercent: Float
readingProgressPercent: Float!
readingProgressAnchorIndex: Int!
}
@ -1526,6 +1528,7 @@ const schema = gql`
createdAt: Date!
updatedAt: Date
isArchived: Boolean!
readingProgressTopPercent: Float
readingProgressPercent: Float!
readingProgressAnchorIndex: Int!
author: String

View File

@ -298,7 +298,8 @@ const setBookmarkQuery = (articleId: string, bookmark: boolean) => {
const saveArticleReadingProgressQuery = (
articleId: string,
progress: number
progress: number,
topPercent: number | null = null
) => {
return `
mutation {
@ -307,6 +308,7 @@ const saveArticleReadingProgressQuery = (
id: "${articleId}",
readingProgressPercent: ${progress}
readingProgressAnchorIndex: 0
readingProgressTopPercent: ${topPercent}
}
) {
... on SaveArticleReadingProgressSuccess {
@ -314,6 +316,7 @@ const saveArticleReadingProgressQuery = (
id
readingProgressPercent
readAt
readingProgressTopPercent
}
}
... on SaveArticleReadingProgressError {
@ -695,9 +698,9 @@ describe('Article API', () => {
describe('saveArticleReadingProgressResolver', () => {
let query = ''
let articleId = ''
let progress = 0.5
let pageId = ''
let progress = 0.5
let topPercent: number | null = null
before(async () => {
pageId = (await createTestElasticPage(user.id)).id!
@ -707,46 +710,71 @@ describe('Article API', () => {
await deletePage(pageId, ctx)
})
beforeEach(() => {
query = saveArticleReadingProgressQuery(articleId, progress)
it('saves a reading progress on an article', async () => {
query = saveArticleReadingProgressQuery(pageId, progress, topPercent)
const res = await graphqlRequest(query, authToken).expect(200)
expect(
res.body.data.saveArticleReadingProgress.updatedArticle
.readingProgressPercent
).to.eq(progress)
expect(res.body.data.saveArticleReadingProgress.updatedArticle.readAt).not
.null
})
context('when we save a reading progress on an article', () => {
before(async () => {
articleId = pageId
progress = 0.5
})
it('should not allow setting the reading progress lower than current progress', async () => {
const firstQuery = saveArticleReadingProgressQuery(pageId, 75)
const firstRes = await graphqlRequest(firstQuery, authToken).expect(200)
expect(
firstRes.body.data.saveArticleReadingProgress.updatedArticle
.readingProgressPercent
).to.eq(75)
await refreshIndex()
it('should save a reading progress on an article', async () => {
const res = await graphqlRequest(query, authToken).expect(200)
expect(
res.body.data.saveArticleReadingProgress.updatedArticle
.readingProgressPercent
).to.eq(progress)
expect(res.body.data.saveArticleReadingProgress.updatedArticle.readAt)
.not.null
})
// Now try to set to a lower value (50), value should not be updated
// refresh index to ensure the reading progress is updated
const secondQuery = saveArticleReadingProgressQuery(pageId, 50)
const secondRes = await graphqlRequest(secondQuery, authToken).expect(200)
expect(
secondRes.body.data.saveArticleReadingProgress.updatedArticle
.readingProgressPercent
).to.eq(75)
})
it('should not allow setting the reading progress lower than current progress', async () => {
const firstQuery = saveArticleReadingProgressQuery(articleId, 75)
const firstRes = await graphqlRequest(firstQuery, authToken).expect(200)
expect(
firstRes.body.data.saveArticleReadingProgress.updatedArticle
.readingProgressPercent
).to.eq(75)
await refreshIndex()
it('does not save topPercent if not undefined', async () => {
query = saveArticleReadingProgressQuery(pageId, progress, null)
const res = await graphqlRequest(query, authToken).expect(200)
expect(
res.body.data.saveArticleReadingProgress.updatedArticle
.readingProgressTopPercent
).to.be.null
})
// Now try to set to a lower value (50), value should not be updated
// refresh index to ensure the reading progress is updated
const secondQuery = saveArticleReadingProgressQuery(articleId, 50)
const secondRes = await graphqlRequest(secondQuery, authToken).expect(
200
)
expect(
secondRes.body.data.saveArticleReadingProgress.updatedArticle
.readingProgressPercent
).to.eq(75)
})
it('saves topPercent if defined', async () => {
const topPercent = 0.2
query = saveArticleReadingProgressQuery(pageId, progress, topPercent)
const res = await graphqlRequest(query, authToken).expect(200)
expect(
res.body.data.saveArticleReadingProgress.updatedArticle
.readingProgressTopPercent
).to.eql(topPercent)
})
it('saves topPercent as 0 if defined as 0', async () => {
const topPercent = 0
query = saveArticleReadingProgressQuery(pageId, progress, topPercent)
const res = await graphqlRequest(query, authToken).expect(200)
expect(
res.body.data.saveArticleReadingProgress.updatedArticle
.readingProgressTopPercent
).to.eql(topPercent)
})
it('returns BAD_DATA error if top position is greater than bottom position', async () => {
query = saveArticleReadingProgressQuery(pageId, 0.5, 0.8)
const res = await graphqlRequest(query, authToken).expect(200)
expect(res.body.data.saveArticleReadingProgress.errorCodes).to.eql([
'BAD_DATA',
])
})
})

View File

@ -126,6 +126,9 @@
}
}
},
"readingProgressTopPercent": {
"type": "float"
},
"readingProgressPercent": {
"type": "float"
},
@ -206,4 +209,4 @@
}
}
}
}
}