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

geonetwork / geonetwork-ui / 12471273633

23 Dec 2024 06:26PM UTC coverage: 84.204% (-0.4%) from 84.621%
12471273633

Pull #1051

github

web-flow
Merge 183a90b40 into 8c98ae01e
Pull Request #1051: [Editor]: Warn user if draft has been updated

3248 of 4338 branches covered (74.87%)

Branch coverage included in aggregate %.

69 of 73 new or added lines in 10 files covered. (94.52%)

11 existing lines in 2 files now uncovered.

9253 of 10508 relevant lines covered (88.06%)

199.09 hits per line

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

90.82
/apps/metadata-editor/src/app/edit/components/publish-button/publish-button.component.ts
1
import {
5✔
2
  ChangeDetectionStrategy,
3
  ChangeDetectorRef,
4
  Component,
5
  ElementRef,
6
  Input,
7
  OnDestroy,
8
  TemplateRef,
9
  ViewChild,
10
  ViewContainerRef,
11
} from '@angular/core'
12
import { CommonModule } from '@angular/common'
5✔
13
import { ButtonComponent } from '@geonetwork-ui/ui/inputs'
5✔
14
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'
5✔
15
import { EditorFacade } from '@geonetwork-ui/feature/editor'
5✔
16
import { MatTooltipModule } from '@angular/material/tooltip'
5✔
17
import { TranslateModule, TranslateService } from '@ngx-translate/core'
5✔
18
import { combineLatest, Observable, of, Subject, Subscription } from 'rxjs'
5✔
19
import { catchError, first, map, skip, switchMap, take } from 'rxjs/operators'
5✔
20
import { RecordsApiService } from '@geonetwork-ui/data-access/gn4'
5✔
21
import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface'
5✔
22
import {
5✔
23
  NgIconComponent,
24
  provideIcons,
25
  provideNgIconsConfig,
26
} from '@ng-icons/core'
27
import { iconoirCloudUpload } from '@ng-icons/iconoir'
5✔
28
import { matCheckCircleOutline } from '@ng-icons/material-icons/outline'
5✔
29
import { MatMenuTrigger } from '@angular/material/menu'
5✔
30
import {
5✔
31
  CdkOverlayOrigin,
32
  CdkConnectedOverlay,
33
  Overlay,
34
  OverlayRef,
35
} from '@angular/cdk/overlay'
36
import { TemplatePortal } from '@angular/cdk/portal'
5✔
37

38
export type RecordSaveStatus = 'saving' | 'upToDate' | 'hasChanges'
39
@Component({
40
  selector: 'md-editor-publish-button',
41
  standalone: true,
42
  imports: [
43
    CommonModule,
44
    ButtonComponent,
45
    MatProgressSpinnerModule,
46
    MatTooltipModule,
47
    TranslateModule,
48
    NgIconComponent,
49
    CdkOverlayOrigin,
50
    CdkConnectedOverlay,
51
  ],
52
  providers: [
53
    provideIcons({ iconoirCloudUpload, matCheckCircleOutline }),
54
    provideNgIconsConfig({
55
      size: '1.5rem',
56
    }),
57
  ],
58
  templateUrl: './publish-button.component.html',
59
  styleUrls: ['./publish-button.component.css'],
60
  changeDetection: ChangeDetectionStrategy.OnPush,
61
})
62
export class PublishButtonComponent implements OnDestroy {
5✔
63
  subscription = new Subscription()
9✔
64
  status$: Observable<RecordSaveStatus> = combineLatest([
9✔
65
    this.facade.changedSinceSave$,
66
    this.facade.saving$,
67
  ]).pipe(
68
    map(([changedSinceSave, saving]) => {
69
      if (saving) {
14✔
70
        return 'saving'
2✔
71
      }
72
      if (changedSinceSave) {
12✔
73
        return 'hasChanges'
2✔
74
      }
75
      return 'upToDate'
10✔
76
    })
77
  )
78

79
  record$ = this.facade.record$
9✔
80

81
  @ViewChild(MatMenuTrigger) trigger: MatMenuTrigger
82

83
  @ViewChild('actionMenuButton', { read: ElementRef })
84
  actionMenuButton!: ElementRef
85
  @ViewChild('template') template!: TemplateRef<HTMLElement>
86
  private overlayRef!: OverlayRef
87

88
  isActionMenuOpen = false
9✔
89
  publishWarning = null
9✔
90

91
  constructor(
92
    private facade: EditorFacade,
9!
93
    private recordsApiService: RecordsApiService,
9!
94
    private platformService: PlatformServiceInterface,
9!
95
    private overlay: Overlay,
9!
96
    private viewContainerRef: ViewContainerRef,
9!
97
    private cdr: ChangeDetectorRef,
9!
98
    private translateService: TranslateService
9✔
99
  ) {}
100

101
  ngOnDestroy() {
102
    this.subscription.unsubscribe()
8✔
103
  }
104

105
  confirmPublish() {
106
    this.saveRecord()
1✔
107
    this.closeMenu()
1✔
108
  }
109

110
  cancelPublish() {
111
    if (this.overlayRef) {
1✔
112
      this.closeMenu()
1✔
113
    }
114
  }
115

116
  closeMenu() {
117
    this.isActionMenuOpen = false
2✔
118
    this.overlayRef.dispose()
2✔
119
    this.cdr.markForCheck()
2✔
120
  }
121

122
  openConfirmationMenu() {
123
    this.isActionMenuOpen = true
1✔
124
    const positionStrategy = this.overlay
1✔
125
      .position()
126
      .flexibleConnectedTo(this.actionMenuButton)
127
      .withPositions([
128
        {
129
          originX: 'end',
130
          originY: 'bottom',
131
          overlayX: 'end',
132
          overlayY: 'top',
133
        },
134
      ])
135

136
    this.overlayRef = this.overlay.create({
1✔
137
      hasBackdrop: true,
138
      backdropClass: 'cdk-overlay-transparent-backdrop',
139
      positionStrategy: positionStrategy,
140
      scrollStrategy: this.overlay.scrollStrategies.reposition(),
141
    })
142

143
    const portal = new TemplatePortal(this.template, this.viewContainerRef)
1✔
144

145
    this.overlayRef.attach(portal)
1✔
146

147
    this.overlayRef.backdropClick().subscribe(() => {
1✔
NEW
148
      this.cancelPublish()
×
149
    })
150
  }
151

152
  verifyPublishConditions() {
153
    this.subscription.add(
2✔
154
      this.facade.record$
155
        .pipe(
156
          switchMap((record) => {
157
            this.facade.checkHasRecordChanged(record)
2✔
158
            return this.facade.hasRecordChanged$.pipe(
2✔
159
              skip(1),
160
              take(1),
NEW
161
              catchError(() => of(null))
×
162
            )
163
          }),
164
          first()
165
        )
166
        .subscribe((hasChanged) => {
167
          if (hasChanged !== null && hasChanged.date) {
2✔
168
            this.publishWarning = hasChanged
1✔
169
            this.openConfirmationMenu()
1✔
170
          } else {
171
            this.saveRecord()
1✔
172
          }
173
        })
174
    )
175
  }
176

177
  saveRecord() {
178
    this.facade.saveRecord()
3✔
179
    this.facade.saveSuccess$
3✔
180
      .pipe(
181
        take(1),
182
        switchMap(() =>
183
          combineLatest([this.platformService.getMe(), this.record$]).pipe(
3✔
184
            take(1)
185
          )
186
        ),
187
        switchMap(([userId, record]) =>
188
          this.recordsApiService.setRecordOwnership(
3✔
189
            record.uniqueIdentifier,
190
            0,
191
            Number(userId.id)
192
          )
193
        )
194
      )
195
      .subscribe()
196
  }
197

198
  formatDate(date: Date): string {
199
    return date.toLocaleDateString(this.translateService.currentLang, {
1✔
200
      year: 'numeric',
201
      month: 'long',
202
      day: 'numeric',
203
      hour: 'numeric',
204
      minute: 'numeric',
205
    })
206
  }
207
}
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