• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

EcrituresNumeriques / stylo / 18501738723

14 Oct 2025 03:29PM UTC coverage: 39.609% (+0.1%) from 39.489%
18501738723

push

github

web-flow
fix(metadata): Nettoie les valeurs vides pour ne pas les faire apparaitre dans le YAML (#1710)

610 of 860 branches covered (70.93%)

Branch coverage included in aggregate %.

27 of 30 new or added lines in 3 files covered. (90.0%)

5981 of 15780 relevant lines covered (37.9%)

2.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.43
/front/src/hooks/article.js
1
import { merge } from 'allof-merge'
1✔
2
import { useSelector } from 'react-redux'
1✔
3

4
import { toYaml } from '../components/Write/metadata/yaml.js'
1✔
5
import { toEntries } from '../helpers/bibtex.js'
1✔
6
import { executeQuery } from '../helpers/graphQL.js'
1✔
7
import { clean } from '../schemas/schemas.js'
1✔
8
import useFetchData, {
1✔
9
  useConditionalFetchData,
10
  useMutateData,
11
} from './graphql.js'
12

13
import {
1✔
14
  addTags,
15
  deleteArticle,
16
  duplicateArticle,
17
  getArticleMetadata,
18
  getArticleTags,
19
  getEditableArticle,
20
  removeTags,
21
  renameArticle,
22
  updateWorkingVersion,
23
  updateZoteroLinkMutation,
24
} from '../components/Article.graphql'
25
import {
1✔
26
  createVersion,
27
  getArticleVersion,
28
  getArticleVersions,
29
  renameVersion,
30
} from './Versions.graphql'
31

32
export function useArticleTagActions({ articleId }) {
1✔
33
  const sessionToken = useSelector((state) => state.sessionToken)
6✔
34
  const { data, mutate, error, isLoading } = useFetchData(
6✔
35
    { query: getArticleTags, variables: { articleId } },
6✔
36
    {
6✔
37
      revalidateOnFocus: false,
6✔
38
      revalidateOnReconnect: false,
6✔
39
    }
6✔
40
  )
6✔
41
  const add = async (tagId) => {
6✔
42
    const result = await executeQuery({
1✔
43
      query: addTags,
1✔
44
      variables: {
1✔
45
        articleId,
1✔
46
        tags: [tagId],
1✔
47
      },
1✔
48
      sessionToken,
1✔
49
      type: 'mutation',
1✔
50
    })
1✔
51
    const tags = result.article.addTags
1✔
52
    mutate(
1✔
53
      {
1✔
54
        article: {
1✔
55
          tags,
1✔
56
        },
1✔
57
      },
1✔
58
      { revalidate: false }
1✔
59
    )
1✔
60
    return tags
1✔
61
  }
1✔
62
  const remove = async (tagId) => {
6✔
63
    const result = await executeQuery({
1✔
64
      query: removeTags,
1✔
65
      variables: {
1✔
66
        articleId,
1✔
67
        tags: [tagId],
1✔
68
      },
1✔
69
      sessionToken,
1✔
70
      type: 'mutation',
1✔
71
    })
1✔
72
    const tags = result.article.removeTags
1✔
73
    mutate(
1✔
74
      {
1✔
75
        article: {
1✔
76
          tags,
1✔
77
        },
1✔
78
      },
1✔
79
      { revalidate: false }
1✔
80
    )
1✔
81
    return tags
1✔
82
  }
1✔
83

84
  return {
6✔
85
    tags: data?.article?.tags || [],
6✔
86
    isLoading,
6✔
87
    error,
6✔
88
    add,
6✔
89
    remove,
6✔
90
  }
6✔
91
}
6✔
92

93
export function useArticleActions({ articleId }) {
1✔
94
  const sessionToken = useSelector((state) => state.sessionToken)
4✔
95
  const activeUser = useSelector((state) => state.activeUser)
4✔
96
  const copy = async (toUserId) => {
4✔
97
    return await executeQuery({
1✔
98
      query: duplicateArticle,
1✔
99
      variables: {
1✔
100
        user: null,
1✔
101
        to: toUserId,
1✔
102
        articleId,
1✔
103
      },
1✔
104
      sessionToken,
1✔
105
      type: 'mutation',
1✔
106
    })
1✔
107
  }
1✔
108
  const duplicate = async () => {
4✔
109
    return await executeQuery({
1✔
110
      query: duplicateArticle,
1✔
111
      variables: {
1✔
112
        user: activeUser._id,
1✔
113
        to: activeUser._id,
1✔
114
        articleId,
1✔
115
      },
1✔
116
      sessionToken,
1✔
117
      type: 'mutation',
1✔
118
    })
1✔
119
  }
1✔
120
  const rename = async (title) => {
4✔
121
    return await executeQuery({
1✔
122
      query: renameArticle,
1✔
123
      variables: { user: activeUser._id, articleId, title },
1✔
124
      sessionToken,
1✔
125
      type: 'mutation',
1✔
126
    })
1✔
127
  }
1✔
128
  const remove = async () => {
4✔
129
    return await executeQuery({
1✔
130
      query: deleteArticle,
1✔
131
      variables: { articleId },
1✔
132
      sessionToken,
1✔
133
      type: 'mutation',
1✔
134
    })
1✔
135
  }
1✔
136

137
  return {
4✔
138
    copy,
4✔
139
    duplicate,
4✔
140
    rename,
4✔
141
    remove,
4✔
142
  }
4✔
143
}
4✔
144

145
export function useArticleMetadata({ articleId, versionId }) {
1✔
146
  const sessionToken = useSelector((state) => state.sessionToken)
×
147
  const activeUser = useSelector((state) => state.activeUser)
×
148
  const hasVersion = typeof versionId === 'string'
×
149
  const { data, error, mutate, isLoading } = useFetchData(
×
150
    {
×
151
      query: getArticleMetadata,
×
152
      variables: { articleId, versionId: versionId ?? '', hasVersion },
×
153
    },
×
154
    {
×
155
      revalidateOnFocus: false,
×
156
      revalidateOnReconnect: false,
×
157
    }
×
158
  )
×
159

160
  const updateMetadataFormType = async (metadataFormType) => {
×
161
    if (versionId) {
×
162
      return
×
163
    }
×
164
    await executeQuery({
×
165
      sessionToken,
×
166
      query: updateWorkingVersion,
×
167
      variables: {
×
168
        userId: activeUser._id,
×
169
        articleId: articleId,
×
170
        content: { metadataFormType },
×
171
      },
×
172
      type: 'mutate',
×
173
    })
×
174
    await mutate(
×
175
      async (data) => {
×
176
        return {
×
177
          article: {
×
178
            ...data.article,
×
179
            workingVersion: {
×
180
              ...data.article.workingVersion,
×
181
              metadataFormType: metadataFormType,
×
182
            },
×
183
          },
×
184
        }
×
185
      },
×
186
      { revalidate: false }
×
187
    )
×
188
  }
×
189

190
  const updateMetadata = async (metadata) => {
×
191
    if (versionId) {
×
192
      return
×
193
    }
×
194
    await executeQuery({
×
195
      sessionToken,
×
196
      query: updateWorkingVersion,
×
197
      variables: {
×
198
        userId: activeUser._id,
×
199
        articleId: articleId,
×
200
        content: { metadata },
×
201
      },
×
202
      type: 'mutate',
×
203
    })
×
204
    await mutate(
×
205
      async (data) => {
×
206
        return {
×
207
          article: {
×
208
            ...data.article,
×
209
            workingVersion: {
×
210
              ...data.article.workingVersion,
×
211
              metadata: metadata,
×
212
            },
×
213
          },
×
214
        }
×
215
      },
×
216
      { revalidate: false }
×
217
    )
×
218
  }
×
219

220
  const metadata = hasVersion
×
221
    ? data?.version?.metadata
×
222
    : data?.article?.workingVersion?.metadata
×
223

224
  const options = data?.article?.workspaces
×
225
    ?.filter((w) => w.formMetadata.data !== null)
×
226
    .map((w) => {
×
227
      try {
×
228
        const data = merge(JSON.parse(w.formMetadata.data))
×
229
        const ui =
×
230
          w.formMetadata.ui !== null ? JSON.parse(w.formMetadata.ui) : {}
×
231
        return {
×
232
          name: data.title || 'untitled',
×
233
          data,
×
234
          ui,
×
235
        }
×
236
      } catch (e) {
×
237
        console.error(
×
238
          `Ignore form metadata configured on workspace id: ${w._id}`,
×
239
          e
×
240
        )
×
241
        return null
×
242
      }
×
243
    })
×
244
    ?.filter((o) => o !== null)
×
245

246
  const metadataFormType = hasVersion
×
247
    ? data?.version?.metadataFormType
×
248
    : data?.article?.workingVersion?.metadataFormType
×
249

250
  return {
×
251
    metadata,
×
252
    metadataFormType,
×
253
    metadataFormTypeOptions: options ?? [],
×
NEW
254
    metadataYaml: toYaml(clean(metadata)),
×
255
    updateMetadata,
×
256
    updateMetadataFormType,
×
257
    isLoading,
×
258
    error,
×
259
  }
×
260
}
×
261

262
export function useEditableArticle({ articleId, versionId }) {
1✔
263
  const sessionToken = useSelector((state) => state.sessionToken)
×
264
  const activeUser = useSelector((state) => state.activeUser)
×
265
  const hasVersion = typeof versionId === 'string'
×
266
  const { data, mutate, error, isLoading } = useFetchData(
×
267
    {
×
268
      query: getEditableArticle,
×
269
      variables: {
×
270
        article: articleId,
×
271
        hasVersion,
×
272
        version: versionId ?? '',
×
273
      },
×
274
    },
×
275
    {
×
276
      revalidateOnFocus: false,
×
277
      revalidateOnReconnect: false,
×
278
      fallbackData: {
×
279
        article: {},
×
280
      },
×
281
    }
×
282
  )
×
283

284
  const updateBibliography = async (bib) => {
×
285
    if (hasVersion) {
×
286
      // can only update the bibliography on the working copy
×
287
      return
×
288
    }
×
289
    await executeQuery({
×
290
      sessionToken,
×
291
      query: updateWorkingVersion,
×
292
      variables: {
×
293
        userId: activeUser._id,
×
294
        articleId: articleId,
×
295
        content: { bib },
×
296
      },
×
297
      type: 'mutate',
×
298
    })
×
299
    await mutate(
×
300
      async (data) => {
×
301
        return {
×
302
          article: {
×
303
            ...data.article,
×
304
            workingVersion: {
×
305
              ...data.article.workingVersion,
×
306
              bib,
×
307
            },
×
308
          },
×
309
        }
×
310
      },
×
311
      { revalidate: false }
×
312
    )
×
313
  }
×
314

315
  const updateZoteroLink = async (url) => {
×
316
    await executeQuery({
×
317
      sessionToken,
×
318
      query: updateZoteroLinkMutation,
×
319
      variables: {
×
320
        articleId: articleId,
×
321
        url,
×
322
      },
×
323
      type: 'mutate',
×
324
    })
×
325
    await mutate(
×
326
      async (data) => {
×
327
        return {
×
328
          article: {
×
329
            ...data.article,
×
330
            zoteroLink: url,
×
331
          },
×
332
        }
×
333
      },
×
334
      { revalidate: false }
×
335
    )
×
336
  }
×
337

338
  const bibtext = hasVersion
×
339
    ? (data?.article?.version?.bib ?? '')
×
340
    : (data?.article?.workingVersion?.bib ?? '')
×
341

342
  const entries = toEntries(bibtext)
×
343

344
  return {
×
345
    article: data?.article,
×
346
    updateBibliography,
×
347
    updateZoteroLink,
×
348
    bibliography: {
×
349
      bibtext,
×
350
      entries,
×
351
    },
×
352
    isLoading,
×
353
    error,
×
354
  }
×
355
}
×
356

357
export function useArticleVersions({ articleId }) {
1✔
358
  const { data, error, isLoading } = useFetchData(
×
359
    { query: getArticleVersions, variables: { article: articleId } },
×
360
    {
×
361
      revalidateOnFocus: false,
×
362
      revalidateOnReconnect: false,
×
363
    }
×
364
  )
×
365

366
  return {
×
367
    error,
×
368
    isLoading,
×
369
    article: data?.article,
×
370
  }
×
371
}
×
372

373
export function useArticleVersion({ versionId }) {
1✔
374
  const { data, error, isLoading } = useConditionalFetchData(
×
375
    versionId ? { query: getArticleVersion, variables: { versionId } } : null,
×
376
    {
×
377
      revalidateOnFocus: false,
×
378
      revalidateOnReconnect: false,
×
379
      fallbackData: {
×
380
        version: {},
×
381
      },
×
382
    }
×
383
  )
×
384

385
  return {
×
386
    error,
×
387
    isLoading,
×
388
    version: data?.version,
×
389
  }
×
390
}
×
391

392
export function useArticleVersionActions({ articleId }) {
1✔
393
  const sessionToken = useSelector((state) => state.sessionToken)
2✔
394
  const activeUser = useSelector((state) => state.activeUser)
2✔
395
  const { mutate } = useMutateData({
2✔
396
    query: getArticleVersions,
2✔
397
    variables: { article: articleId },
2✔
398
  })
2✔
399
  const create = async (version) => {
2✔
400
    const response = await executeQuery({
1✔
401
      query: createVersion,
1✔
402
      variables: {
1✔
403
        userId: activeUser._id,
1✔
404
        articleId,
1✔
405
        major: version.major,
1✔
406
        message: version.description,
1✔
407
      },
1✔
408
      sessionToken,
1✔
409
      type: 'mutation',
1✔
410
    })
1✔
411
    await mutate(async (data) => ({
1✔
412
      article: {
1✔
413
        ...data?.article,
1!
414
        versions: response.article.createVersion.versions,
1✔
415
      },
1✔
416
    }))
1✔
417
  }
1✔
418
  const updateDescription = async ({ versionId, description }) => {
2✔
419
    await executeQuery({
1✔
420
      query: renameVersion,
1✔
421
      variables: {
1✔
422
        version: versionId,
1✔
423
        name: description,
1✔
424
      },
1✔
425
      sessionToken,
1✔
426
      type: 'mutation',
1✔
427
    })
1✔
428
    await mutate(async (data) => ({
1✔
429
      article: {
1✔
430
        ...data?.article,
1✔
431
        versions: data.article.versions.map((v) => {
1✔
432
          if (v._id === versionId) {
2!
433
            return {
×
434
              ...v,
×
435
              message: description,
×
436
            }
×
437
          }
×
438
          return v
2✔
439
        }),
1✔
440
      },
1✔
441
    }))
1✔
442
  }
1✔
443

444
  return {
2✔
445
    create,
2✔
446
    updateDescription,
2✔
447
  }
2✔
448
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc